mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Restructure documentation and add index
This commit is contained in:
@@ -48,24 +48,6 @@ The new version is not deployed yet.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Reviewer notes: Locality climate pollen validation
|
### Documentation index
|
||||||
|
|
||||||
- Locality pollen fields (`pers_pollen_ap`, `pers_pollen_nap`, `pers_pollen_other`) now require integer values in the range `0...100`.
|
|
||||||
- Shared locality validation enforces combined pollen constraint `AP + NAP + OP <= 100` and surfaces the same message in form validation feedback.
|
|
||||||
- Backend locality write validation now evaluates pollen totals with full update context so partial updates cannot bypass the combined-value rule.
|
|
||||||
- API tests include invalid create/update pollen payload coverage for non-integer, out-of-range, and total-above-100 cases.
|
|
||||||
|
|
||||||
### Reviewer notes: Locality age method persistence
|
|
||||||
|
|
||||||
- In Locality edit mode, the Age tab now preserves method-specific values when switching Dating method between `Time unit`, `Absolute`, and `Composite`.
|
|
||||||
- Switching methods restores previously entered age values (e.g., `min_age`, `max_age`, basis/fraction selections) instead of clearing them.
|
|
||||||
- Locality age validation now evaluates required basis fields against the currently active dating method, while allowing preserved values from other methods to remain in draft state.
|
|
||||||
- See [documentation/CHANGELOG.md](documentation/CHANGELOG.md) for release-level tracking.
|
|
||||||
|
|
||||||
|
|
||||||
### Reviewer notes: Occurrence terminology update
|
|
||||||
|
|
||||||
- User-facing UI captions now use **Occurrence/Occurrences** in navigation, cross-search, and Species detail surfaces.
|
|
||||||
- This is a presentation-only rename: backend/database identifiers are unchanged, including the `now_ls` table and related API payload fields.
|
|
||||||
- See `frontend/src/constants/occurrenceLabels.ts` for centralized frontend label constants and `frontend/tests/locality-species-ui-label-inventory.md` for inventory coverage.
|
|
||||||
|
|
||||||
|
See `documentation/README.md` for the full documentation tree (includes reviewer notes, FAQ, and a complete list of docs).
|
||||||
|
|||||||
@@ -1,240 +0,0 @@
|
|||||||
# Feature Plan: Occurrence Detail Page (now_ls)
|
|
||||||
|
|
||||||
## 1️⃣ Assumptions & Scope
|
|
||||||
- The current `/occurrence` experience is backed by `CrossSearchTable` and reuses `LocalityDetails` for detail routing (`/occurrence/:id`), so this feature introduces a dedicated Occurrence detail view instead of locality detail reuse.
|
|
||||||
- Occurrence identity must be **composite** (`lid` + `species_id`) because `now_ls` has no primary key; we will not change DB schema or add migrations.
|
|
||||||
- Backend and frontend contracts should remain additive and backward compatible; existing Locality/Species detail pages and list/table flows must continue to work.
|
|
||||||
- Required tabs should map to meaningful `now_ls` columns/groups, and an **Updates** tab should be present as a placeholder shell only (no update-log implementation yet), matching existing tab UX patterns.
|
|
||||||
- Linking behavior from occurrence listings should open the new Occurrence detail route using `lid` + `species_id` key, while preserving existing table filter/sort/pagination query params for return navigation.
|
|
||||||
- Authorization model remains unchanged: read access follows current authenticated behavior; edit affordances follow existing role checks (Admin/EditUnrestricted/EditRestricted) already used around detail pages.
|
|
||||||
- No database structure changes, no Prisma migration files, and no table/model renames.
|
|
||||||
|
|
||||||
## 2️⃣ High-Level Plan
|
|
||||||
1. **Occurrence identity and routing design**
|
|
||||||
- Define a stable URL shape for composite keys (e.g. `/occurrence/:lid/:speciesId`), and retain list URL compatibility (`/occurrence?...`).
|
|
||||||
- Update route wiring so occurrence detail resolves to a new Occurrence detail component instead of `LocalityDetails`.
|
|
||||||
|
|
||||||
2. **Backend read endpoint for one occurrence (composite key)**
|
|
||||||
- Add/extend an endpoint to fetch one `now_ls` row by `lid` + `species_id` with required joins (species/locality context fields needed for header + tabs).
|
|
||||||
- Reuse existing services/query utilities where possible to stay DRY and avoid duplicated SQL mapping.
|
|
||||||
|
|
||||||
3. **Frontend data model + API client wiring**
|
|
||||||
- Add shared/frontend type(s) for occurrence detail payload shaped around `now_ls` + related display fields.
|
|
||||||
- Add query hook/client call for fetching one occurrence by composite key.
|
|
||||||
|
|
||||||
4. **Build Occurrence detail feature component (DRY with existing DetailView patterns)**
|
|
||||||
- Create `OccurrenceDetails` component mirroring Locality/Species detail architecture (`DetailView`, tab config array, validator wiring).
|
|
||||||
- Group tabs by `now_ls` domain columns (e.g., core occurrence info, microwear/mesowear, measurements/notes as available in schema).
|
|
||||||
- Add **Updates** tab placeholder using standard tab container/panel only, no data implementation yet.
|
|
||||||
|
|
||||||
5. **Linking from occurrence list to detail**
|
|
||||||
- Ensure row actions/links in occurrence table navigate with composite key (`lid`, `species_id`) rather than locality-only id.
|
|
||||||
- Preserve table query state (`columnfilters`, `sorting`, `pagination`) in return navigation state.
|
|
||||||
|
|
||||||
6. **Permissions + UX parity**
|
|
||||||
- Keep edit/delete capability checks consistent with existing role policy; if detail is read-only initially, explicitly disable write actions.
|
|
||||||
- Keep tab deep-linking behavior (`?tab=n`) consistent with `useSyncTabSearch` and existing detail pages.
|
|
||||||
|
|
||||||
7. **Validation, testing, docs**
|
|
||||||
- Add/adjust backend integration tests for composite-key fetch and error paths.
|
|
||||||
- Add frontend tests for route parsing, tab rendering (including Updates placeholder), and occurrence row-to-detail navigation.
|
|
||||||
- Update docs/changelog notes for new occurrence detail page and composite key route contract.
|
|
||||||
|
|
||||||
## 3️⃣ Tasks (JSON)
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "T1",
|
|
||||||
"title": "Define composite-key occurrence route and page wiring",
|
|
||||||
"summary": "Introduce occurrence detail routing that accepts lid + species_id and maps to a dedicated OccurrenceDetails component instead of LocalityDetails.",
|
|
||||||
"app": "frontend",
|
|
||||||
"files_touched": [
|
|
||||||
"frontend/src/router/index.tsx",
|
|
||||||
"frontend/src/components/pages.tsx",
|
|
||||||
"frontend/src/components/Page.tsx"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Accessible to logged-in users who can view occurrence list",
|
|
||||||
"No role escalation; existing page-level role checks remain intact"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Navigating to /occurrence/<lid>/<species_id> opens Occurrence detail page",
|
|
||||||
"Navigating to /occurrence?columnfilters=[]&sorting=[]&pagination={...} still opens occurrence list",
|
|
||||||
"Legacy /occurrence/<id> behavior is either redirected safely or handled explicitly"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add router-level test for occurrence composite route",
|
|
||||||
"Add regression test for occurrence list route query parsing"
|
|
||||||
],
|
|
||||||
"estimate_hours": 2.0,
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T2",
|
|
||||||
"title": "Add backend occurrence-detail read endpoint by lid + species_id",
|
|
||||||
"summary": "Implement composite-key lookup for a single now_ls occurrence with required joins/shape for detail tabs and header metadata.",
|
|
||||||
"app": "backend",
|
|
||||||
"files_touched": [
|
|
||||||
"backend/src/routes/occurrence.ts",
|
|
||||||
"backend/src/controllers/occurrenceController.ts",
|
|
||||||
"backend/src/services/occurrenceService.ts",
|
|
||||||
"backend/src/services/queries/crossSearchQuery.ts"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Available to authorized readers under existing auth middleware",
|
|
||||||
"Write permissions unchanged (no new mutation endpoint)"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"GET endpoint returns one occurrence by exact lid + species_id",
|
|
||||||
"Missing pair returns 404 with structured error payload",
|
|
||||||
"Invalid keys return 400 with validation message"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add backend integration tests for success/404/400 cases",
|
|
||||||
"Add unit tests for service query parameter handling"
|
|
||||||
],
|
|
||||||
"estimate_hours": 3.5,
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T3",
|
|
||||||
"title": "Create occurrence detail types and data hook",
|
|
||||||
"summary": "Add shared/frontend types and query hook/API client for composite-key occurrence detail retrieval.",
|
|
||||||
"app": "frontend",
|
|
||||||
"files_touched": [
|
|
||||||
"frontend/src/shared/types/data.ts",
|
|
||||||
"frontend/src/redux/services/api.ts",
|
|
||||||
"frontend/src/hooks/**"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Read-only data retrieval inherits existing auth token handling"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Hook accepts lid + species_id and returns typed detail payload",
|
|
||||||
"Loading/error states align with existing detail pages"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add hook/API tests for correct URL and response mapping",
|
|
||||||
"Type-check verifies no implicit any in detail payload usage"
|
|
||||||
],
|
|
||||||
"estimate_hours": 2.0,
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T4",
|
|
||||||
"title": "Implement OccurrenceDetails with now_ls-based tabs and Updates placeholder",
|
|
||||||
"summary": "Build dedicated detail UI using shared DetailView primitives; include required tabs from now_ls columns and a standard placeholder Updates tab (no updates logic yet).",
|
|
||||||
"app": "frontend",
|
|
||||||
"files_touched": [
|
|
||||||
"frontend/src/components/Occurrence/OccurrenceDetails.tsx",
|
|
||||||
"frontend/src/components/Occurrence/Tabs/*.tsx",
|
|
||||||
"frontend/src/components/DetailView/common/UpdateTab.tsx"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Edit controls shown only for roles already allowed by policy",
|
|
||||||
"If mutations are not implemented, controls remain disabled/hidden"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Detail page renders tab set derived from now_ls fields",
|
|
||||||
"Updates tab is visible and clearly marked placeholder",
|
|
||||||
"Tab deep-linking works via ?tab=<index>"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add component tests for tab labels/content and placeholder tab",
|
|
||||||
"Add test for out-of-range tab param fallback behavior"
|
|
||||||
],
|
|
||||||
"estimate_hours": 4.0,
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T5",
|
|
||||||
"title": "Wire occurrence table row navigation to composite-key detail",
|
|
||||||
"summary": "Update occurrence list row action/linking so each row opens its specific occurrence detail using lid + species_id key.",
|
|
||||||
"app": "frontend",
|
|
||||||
"files_touched": [
|
|
||||||
"frontend/src/components/CrossSearch/CrossSearchTable.tsx",
|
|
||||||
"frontend/src/components/TableView/TableView.tsx",
|
|
||||||
"frontend/src/hooks/useReturnNavigation.ts"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Row visibility restrictions remain unchanged",
|
|
||||||
"Navigation does not bypass protected routes"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Clicking an occurrence row opens /occurrence/<lid>/<species_id>",
|
|
||||||
"Return button restores prior list URL including filter/sort/pagination query",
|
|
||||||
"No regression in locality/species table row navigation"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add frontend tests asserting navigation target path composition",
|
|
||||||
"Regression tests for return navigation stack behavior"
|
|
||||||
],
|
|
||||||
"estimate_hours": 2.5,
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T6",
|
|
||||||
"title": "Quality gate, docs, and rollout safeguards",
|
|
||||||
"summary": "Run lint/type/tests, document route contract and placeholder status, and ensure feature is safely reviewable/deployable.",
|
|
||||||
"app": "fullstack",
|
|
||||||
"files_touched": [
|
|
||||||
"README.md",
|
|
||||||
"frontend/docs/routing.md",
|
|
||||||
"frontend/tests/**",
|
|
||||||
"backend/src/api-tests/**"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Verify role-based UI states for read/edit users"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Lint + type-check pass in affected scopes",
|
|
||||||
"New/updated tests pass",
|
|
||||||
"Docs mention composite key route and Updates tab placeholder"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"npm run lint:frontend && npm run lint:backend",
|
|
||||||
"npm run tsc:frontend && npm run tsc:backend",
|
|
||||||
"Run targeted frontend/backend test suites for occurrence detail"
|
|
||||||
],
|
|
||||||
"estimate_hours": 2.0,
|
|
||||||
"priority": "medium"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4️⃣ Risks & Mitigations
|
|
||||||
- **Auth Failures**: New detail route could miss existing guard expectations.
|
|
||||||
**Mitigation**: Keep route under existing authenticated shell and add explicit unauthorized tests for restricted edit actions.
|
|
||||||
- **Composite Key Ambiguity**: Using only `lid` could open wrong record when multiple species share locality.
|
|
||||||
**Mitigation**: Enforce `lid + species_id` in route, API contract, and row navigation helpers.
|
|
||||||
- **Performance**: Occurrence detail joins may fetch unnecessary fields.
|
|
||||||
**Mitigation**: Select only tab-needed columns; defer heavy/related datasets to lazy queries if needed.
|
|
||||||
- **Error Handling**: Bad URL params may produce opaque failures.
|
|
||||||
**Mitigation**: Add input validation and consistent 400/404 JSON errors; render friendly UI fallback.
|
|
||||||
- **Security**: Query construction risk if custom SQL is touched.
|
|
||||||
**Mitigation**: Keep Prisma/parameterized query patterns and avoid string interpolation for user params.
|
|
||||||
- **Rollback**: Route change could disrupt existing deep links.
|
|
||||||
**Mitigation**: Add compatibility redirect/fallback for legacy paths and keep change isolated behind clean commits.
|
|
||||||
|
|
||||||
## 5️⃣ Out of Scope
|
|
||||||
- Any database schema/table/index/primary-key changes for `now_ls`.
|
|
||||||
- Implementing actual Updates tab data loading/editing/audit timeline logic.
|
|
||||||
- Broad UI redesign beyond adding occurrence detail page and required tabs.
|
|
||||||
- Refactoring unrelated list/detail pages (Locality, Species, etc.) outside minimal DRY extraction.
|
|
||||||
- Non-feature performance tuning unrelated to occurrence detail fetch/render.
|
|
||||||
|
|
||||||
## 6️⃣ Definition of Done ✅
|
|
||||||
- [ ] All acceptance criteria in tasks T1–T6 are satisfied.
|
|
||||||
- [ ] Occurrence detail is accessible via composite key route (`lid` + `species_id`).
|
|
||||||
- [ ] Required `now_ls`-based tabs are implemented and Updates tab placeholder is present.
|
|
||||||
- [ ] No DB migrations/schema changes are introduced.
|
|
||||||
- [ ] Unit/integration/frontend route tests for new behavior pass.
|
|
||||||
- [ ] Linting and TypeScript checks pass in affected scopes.
|
|
||||||
- [ ] Role-based read/edit behavior is verified unchanged.
|
|
||||||
- [ ] Documentation is updated with new route contract and placeholder note.
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
# Pollen Record Validation Plan (Locality → Climate)
|
|
||||||
|
|
||||||
## 1) Assumptions & Scope
|
|
||||||
|
|
||||||
- The feature applies to existing Locality climate fields `pers_pollen_ap`, `pers_pollen_nap`, and `pers_pollen_other`, which are already present in Prisma and shared frontend/backend locality types.
|
|
||||||
- No database schema change is required for this feature because all required columns already exist and are typed as nullable integers.
|
|
||||||
- Validation must be enforced in both layers:
|
|
||||||
- Shared field-level validation (used by frontend forms and backend locality service validation flow).
|
|
||||||
- Backend request-time validation to guarantee data integrity regardless of client behavior.
|
|
||||||
- UI impact is limited to Locality Climate tab input behavior and validation feedback; no new routes/pages are needed.
|
|
||||||
- Existing role-based access control for locality create/update remains unchanged; validation should run after permission checks and before persistence.
|
|
||||||
- Error responses must stay compatible with current structured validation error patterns used by locality endpoints.
|
|
||||||
|
|
||||||
## 2) High-Level Plan
|
|
||||||
|
|
||||||
1. **Validation rules definition (shared contract)**
|
|
||||||
- Define/extend locality validator behavior so each pollen field accepts only integers in `[0, 100]` when provided.
|
|
||||||
- Add a cross-field rule: `AP + NAP + OP <= 100` (ignoring `null` values as zero or validating only when value exists, consistent with existing optional-field behavior).
|
|
||||||
|
|
||||||
2. **Backend enforcement**
|
|
||||||
- Ensure locality create/update flows surface the shared validator errors and reject invalid payloads with `400` + structured field errors.
|
|
||||||
- Confirm validation is applied for partial updates and full payload updates.
|
|
||||||
|
|
||||||
3. **Frontend UX validation**
|
|
||||||
- Surface immediate field-level errors in the Locality Climate tab for non-integer/out-of-range input.
|
|
||||||
- Surface cross-field combined-percentage error in a predictable location (field error or form-level message) before submit.
|
|
||||||
|
|
||||||
4. **Testing strategy**
|
|
||||||
- Add/extend backend API tests for accepted and rejected cases.
|
|
||||||
- Add/extend shared validator tests (or frontend validation tests) to cover integer/range/sum constraints.
|
|
||||||
- Verify no regressions for locality create/update and no auth behavior changes.
|
|
||||||
|
|
||||||
5. **Docs/ops**
|
|
||||||
- No migration, env, or deployment config changes expected.
|
|
||||||
- Update any relevant validation documentation/changelog entry if maintained.
|
|
||||||
|
|
||||||
## 3) Tasks (JSON)
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "T1",
|
|
||||||
"title": "Add pollen field constraints to shared locality validator",
|
|
||||||
"summary": "Implement validation for pers_pollen_ap, pers_pollen_nap, and pers_pollen_other as optional integers between 0 and 100, plus a cross-field sum constraint AP+NAP+OP<=100.",
|
|
||||||
"app": "shared",
|
|
||||||
"files_touched": [
|
|
||||||
"frontend/src/shared/validators/locality.ts"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"No RBAC change",
|
|
||||||
"Validation applies to any caller already authorized to create/update localities"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Each pollen field rejects non-integer values",
|
|
||||||
"Each pollen field rejects values <0 or >100",
|
|
||||||
"Combined AP/NAP/OP values above 100 return a clear validation error",
|
|
||||||
"Null/empty pollen fields remain accepted where currently optional"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add unit tests for per-field integer/range validation",
|
|
||||||
"Add unit tests for cross-field sum rule with partial-null and full-value combinations"
|
|
||||||
],
|
|
||||||
"estimate_hours": 2.5,
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T2",
|
|
||||||
"title": "Enforce pollen validation in locality backend write flows",
|
|
||||||
"summary": "Ensure locality create/update service paths reject invalid pollen payloads with structured 400 responses using existing locality validation pipeline.",
|
|
||||||
"app": "backend",
|
|
||||||
"files_touched": [
|
|
||||||
"backend/src/services/locality.ts",
|
|
||||||
"backend/src/api-tests/locality/create.test.ts",
|
|
||||||
"backend/src/api-tests/locality/update.test.ts",
|
|
||||||
"backend/src/api-tests/locality/data.ts"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Existing locality write permissions unchanged",
|
|
||||||
"Unauthorized users remain blocked before data validation/persistence"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Create endpoint returns 400 when pollen values violate integer/range/sum rules",
|
|
||||||
"Update endpoint returns 400 for invalid pollen changes",
|
|
||||||
"Valid pollen payloads continue to persist successfully"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add integration tests for invalid create payloads",
|
|
||||||
"Add integration tests for invalid update payloads",
|
|
||||||
"Assert response contains expected validation error keys/messages"
|
|
||||||
],
|
|
||||||
"estimate_hours": 3.0,
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T3",
|
|
||||||
"title": "Improve Locality Climate tab validation feedback",
|
|
||||||
"summary": "Ensure Locality Climate tab shows actionable validation messages for pollen fields and cross-field total constraint before submit.",
|
|
||||||
"app": "frontend",
|
|
||||||
"files_touched": [
|
|
||||||
"frontend/src/components/Locality/Tabs/ClimateTab.tsx",
|
|
||||||
"frontend/src/shared/validators/locality.ts"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"No RBAC change",
|
|
||||||
"UI controls remain enabled/disabled according to existing locality edit permissions"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"User sees immediate validation feedback for invalid pollen inputs",
|
|
||||||
"User sees clear message when AP+NAP+OP exceeds 100",
|
|
||||||
"Form submission is blocked client-side when pollen validation fails"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add/update frontend validation tests for pollen input edge cases",
|
|
||||||
"Add component-level tests for error rendering in climate tab"
|
|
||||||
],
|
|
||||||
"estimate_hours": 2.5,
|
|
||||||
"priority": "medium"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T4",
|
|
||||||
"title": "Quality gates and release readiness",
|
|
||||||
"summary": "Run lint, type-check, and impacted test suites; record feature notes for reviewers.",
|
|
||||||
"app": "fullstack",
|
|
||||||
"files_touched": [
|
|
||||||
"README.md"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"N/A"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Lint/type-check pass in affected scopes",
|
|
||||||
"All new/updated tests pass",
|
|
||||||
"Reviewer notes describe validation behavior and edge cases"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Run npm run lint",
|
|
||||||
"Run npm run tsc",
|
|
||||||
"Run locality backend API tests and relevant frontend tests"
|
|
||||||
],
|
|
||||||
"estimate_hours": 1.5,
|
|
||||||
"priority": "medium"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4) Risks & Mitigations
|
|
||||||
|
|
||||||
- **Auth failures**: Validation changes might unintentionally alter create/update control flow.
|
|
||||||
- *Mitigation*: Keep permission middleware/service checks unchanged and add unauthorized regression tests.
|
|
||||||
- **Behavior ambiguity for null/partial fields**: Optional fields can conflict with sum validation semantics.
|
|
||||||
- *Mitigation*: Document rule as "sum of provided values" and codify with tests for null/empty combinations.
|
|
||||||
- **Inconsistent frontend/backend messaging**: Users may see different messages client vs server.
|
|
||||||
- *Mitigation*: Reuse shared validator constants/messages where possible and assert text in tests.
|
|
||||||
- **Regression risk on locality updates**: Existing locality test fixtures might fail due to stricter constraints.
|
|
||||||
- *Mitigation*: Update fixtures explicitly and add targeted test cases to make constraints clear.
|
|
||||||
- **Rollback complexity**: Stricter validation can block data entry workflows.
|
|
||||||
- *Mitigation*: Keep change isolated to validators and endpoint checks; revertable in one release if needed.
|
|
||||||
|
|
||||||
## 5) Out of Scope
|
|
||||||
|
|
||||||
- Backfilling or correcting historical pollen data already stored in the database.
|
|
||||||
- Redesigning Locality Climate UI layout beyond validation feedback.
|
|
||||||
- Introducing i18n/localized validation messages.
|
|
||||||
- Unrelated locality performance optimizations.
|
|
||||||
|
|
||||||
## 6) Definition of Done ✅
|
|
||||||
|
|
||||||
- [ ] Pollen fields accept only integers from 0 to 100.
|
|
||||||
- [ ] Combined AP/NAP/OP rule (`<=100`) enforced client and server side.
|
|
||||||
- [ ] Locality create/update API responses return structured validation errors for violations.
|
|
||||||
- [ ] Frontend displays clear validation messages and blocks invalid submit.
|
|
||||||
- [ ] Added/updated backend + frontend tests for all new rules.
|
|
||||||
- [ ] Lint and TypeScript checks pass in CI.
|
|
||||||
- [ ] Documentation/changelog entry updated for validation behavior.
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Documentation Index
|
||||||
|
|
||||||
|
This folder is the canonical home for repository documentation.
|
||||||
|
|
||||||
|
## Quick Links
|
||||||
|
|
||||||
|
- Getting started: `README.md` (repo root)
|
||||||
|
- Contributing: `documentation/contributing.md`
|
||||||
|
- DevOps / running locally: `documentation/devops/setup.md`, `documentation/devops/commands.md`
|
||||||
|
- Testing overview: `documentation/testing.md`
|
||||||
|
- Frontend overview: `documentation/frontend/frontend.md`
|
||||||
|
- Backend notes: `documentation/backend/`
|
||||||
|
- Entity docs: `documentation/entities/`
|
||||||
|
|
||||||
|
## Reviewer Notes
|
||||||
|
|
||||||
|
- `documentation/reviewer-notes.md`
|
||||||
|
|
||||||
|
## FAQ / Troubleshooting
|
||||||
|
|
||||||
|
- `documentation/computer_specific_troubleshooting.md`
|
||||||
|
- `documentation/lint-tsc-failure-analysis.md`
|
||||||
|
- `documentation/faq/technical-questions.md`
|
||||||
|
|
||||||
|
## Full Document List (no orphans)
|
||||||
|
|
||||||
|
### Root-level docs
|
||||||
|
|
||||||
|
- `README.md`
|
||||||
|
- `OCCURRENCE_UI_RENAME_PLAN.md`
|
||||||
|
|
||||||
|
### documentation/
|
||||||
|
|
||||||
|
- `documentation/README.md`
|
||||||
|
- `documentation/CHANGELOG.md`
|
||||||
|
- `documentation/api/time-units.md`
|
||||||
|
- `documentation/architecture.md`
|
||||||
|
- `documentation/backend/cross_search_export.md`
|
||||||
|
- `documentation/backend/rate_limiting_for_emails.md`
|
||||||
|
- `documentation/backend/server_side_pagination.md`
|
||||||
|
- `documentation/backend/species_synonym_creation_flow.md`
|
||||||
|
- `documentation/class_diagram.md`
|
||||||
|
- `documentation/codex_planning_prompt.md`
|
||||||
|
- `documentation/coding_prompt_template_fullstack.md`
|
||||||
|
- `documentation/computer_specific_troubleshooting.md`
|
||||||
|
- `documentation/contributing.md`
|
||||||
|
- `documentation/cypress-performance-optimization.md`
|
||||||
|
- `documentation/definition_of_done.md`
|
||||||
|
- `documentation/devops/commands.md`
|
||||||
|
- `documentation/devops/database.md`
|
||||||
|
- `documentation/devops/docker.md`
|
||||||
|
- `documentation/devops/github_actions.md`
|
||||||
|
- `documentation/devops/openshift.md`
|
||||||
|
- `documentation/devops/setup.md`
|
||||||
|
- `documentation/devops/write-guard.md`
|
||||||
|
- `documentation/entities/locality.md`
|
||||||
|
- `documentation/entities/occurrence.md`
|
||||||
|
- `documentation/entities/project.md`
|
||||||
|
- `documentation/entities/reference.md`
|
||||||
|
- `documentation/entities/reference_types.md`
|
||||||
|
- `documentation/entities/region.md`
|
||||||
|
- `documentation/entities/species.md`
|
||||||
|
- `documentation/entities/time_bound.md`
|
||||||
|
- `documentation/entities/time_unit.md`
|
||||||
|
- `documentation/faq/technical-questions.md`
|
||||||
|
- `documentation/frontend/components/EditableTable.md`
|
||||||
|
- `documentation/frontend/components/SimpleTable.md`
|
||||||
|
- `documentation/frontend/components/TableView.md`
|
||||||
|
- `documentation/frontend/components/editingComponents/BasisForAgeSelection.md`
|
||||||
|
- `documentation/frontend/components/editingComponents/DropdownSelector.md`
|
||||||
|
- `documentation/frontend/components/editingComponents/DropdownSelectorWithSearch.md`
|
||||||
|
- `documentation/frontend/components/editingComponents/EditableTextField.md`
|
||||||
|
- `documentation/frontend/components/editingComponents/FieldWithTableSelection.md`
|
||||||
|
- `documentation/frontend/components/editingComponents/FormTextField.md`
|
||||||
|
- `documentation/frontend/components/editingComponents/RadioSelector.md`
|
||||||
|
- `documentation/frontend/components/editingComponents/TimeBoundSelection.md`
|
||||||
|
- `documentation/frontend/components/maps/CoordinateSelectionMap.md`
|
||||||
|
- `documentation/frontend/components/maps/LocalitiesMap.md`
|
||||||
|
- `documentation/frontend/components/maps/README.md`
|
||||||
|
- `documentation/frontend/components/maps/SingleLocalityMap.md`
|
||||||
|
- `documentation/frontend/frontend.md`
|
||||||
|
- `documentation/frontend/routing.md`
|
||||||
|
- `documentation/frontend/tables.md`
|
||||||
|
- `documentation/functionality/cascading_effects.md`
|
||||||
|
- `documentation/functionality/changelog.md`
|
||||||
|
- `documentation/functionality/put_requests_for_db_entries.md`
|
||||||
|
- `documentation/functionality/species_merge.md`
|
||||||
|
- `documentation/functionality/species_taxonomy_checks.md`
|
||||||
|
- `documentation/functionality/table_column_visibility.md`
|
||||||
|
- `documentation/lint-tsc-failure-analysis.md`
|
||||||
|
- `documentation/reference-validation-messages.md`
|
||||||
|
- `documentation/reviewer-notes.md`
|
||||||
|
- `documentation/testing.md`
|
||||||
|
- `documentation/update-log-navigation-flow.md`
|
||||||
|
- `documentation/user_rights.md`
|
||||||
@@ -3,23 +3,28 @@
|
|||||||
This frontend uses React Router v6 in **data router mode** (`createBrowserRouter` + `RouterProvider`). This enables `useBlocker` and related APIs for navigation interception.
|
This frontend uses React Router v6 in **data router mode** (`createBrowserRouter` + `RouterProvider`). This enables `useBlocker` and related APIs for navigation interception.
|
||||||
|
|
||||||
## Data Router Basics
|
## Data Router Basics
|
||||||
|
|
||||||
- Entry point: `frontend/src/router/index.tsx` exports the router created with `createBrowserRouter` and is consumed by `RouterProvider` in `frontend/src/main.tsx`.
|
- Entry point: `frontend/src/router/index.tsx` exports the router created with `createBrowserRouter` and is consumed by `RouterProvider` in `frontend/src/main.tsx`.
|
||||||
- Global layout: `App` renders global chrome and an `Outlet` for child routes.
|
- Global layout: `App` renders global chrome and an `Outlet` for child routes.
|
||||||
- When adding routes, define them in `router/index.tsx` using the data-router configuration (array or `createRoutesFromElements`).
|
- When adding routes, define them in `router/index.tsx` using the data-router configuration (array or `createRoutesFromElements`).
|
||||||
|
|
||||||
## Enabling Unsaved-Changes Prompts
|
## Enabling Unsaved-Changes Prompts
|
||||||
|
|
||||||
Unsaved-change blocking is provided by `UnsavedChangesProvider` and the `useUnsavedChangesPrompt` hook.
|
Unsaved-change blocking is provided by `UnsavedChangesProvider` and the `useUnsavedChangesPrompt` hook.
|
||||||
|
|
||||||
**Provider**
|
**Provider**
|
||||||
|
|
||||||
- Wrap any page containing a form or editable detail view with `UnsavedChangesProvider`.
|
- Wrap any page containing a form or editable detail view with `UnsavedChangesProvider`.
|
||||||
- The provider listens to `useBlocker` and renders the shared confirmation dialog.
|
- The provider listens to `useBlocker` and renders the shared confirmation dialog.
|
||||||
|
|
||||||
**Hook**
|
**Hook**
|
||||||
|
|
||||||
- In a form component, call `const { setDirty } = useUnsavedChangesPrompt(isDirty)` where `isDirty` comes from your form state (`react-hook-form`’s `formState.isDirty`, etc.).
|
- In a form component, call `const { setDirty } = useUnsavedChangesPrompt(isDirty)` where `isDirty` comes from your form state (`react-hook-form`’s `formState.isDirty`, etc.).
|
||||||
- Set dirty to `false` immediately before/after a successful submit so navigation to the next page isn’t blocked.
|
- Set dirty to `false` immediately before/after a successful submit so navigation to the next page isn’t blocked.
|
||||||
- If submit fails, set dirty back to `true` so protection remains while the user fixes errors.
|
- If submit fails, set dirty back to `true` so protection remains while the user fixes errors.
|
||||||
|
|
||||||
### Minimal Example
|
### Minimal Example
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { UnsavedChangesProvider } from '@/components/UnsavedChangesProvider';
|
import { UnsavedChangesProvider } from '@/components/UnsavedChangesProvider';
|
||||||
@@ -52,8 +57,11 @@ export const ExampleFormPage = () => {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Detail Views
|
### Detail Views
|
||||||
|
|
||||||
- For detail pages that use shared context (e.g., `DetailView`), place the provider at the page level and wire `useUnsavedChangesPrompt` to the context’s `isDirty` flag inside a small tracker component.
|
- For detail pages that use shared context (e.g., `DetailView`), place the provider at the page level and wire `useUnsavedChangesPrompt` to the context’s `isDirty` flag inside a small tracker component.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- `useBlocker` requires the data router; ensure new routes continue to use `createBrowserRouter`.
|
- `useBlocker` requires the data router; ensure new routes continue to use `createBrowserRouter`.
|
||||||
- The dialog text can be customized via `UnsavedChangesProvider` props or by passing a `message` option to `useUnsavedChangesPrompt`.
|
- The dialog text can be customized via `UnsavedChangesProvider` props or by passing a `message` option to `useUnsavedChangesPrompt`.
|
||||||
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
# Feature Plan: Handle invalid Time Bound IDs in Time Unit PUT requests
|
|
||||||
|
|
||||||
## 1) Assumptions & Scope
|
|
||||||
- The issue is in `PUT /time-unit` backend handling where non-existing `up_bnd` or `low_bnd` can still reach persistence and surface as an unhandled internal error.
|
|
||||||
- Existing role restrictions for time-unit writes (`Admin`, `EditUnrestricted`) remain unchanged.
|
|
||||||
- No schema change is required; this is a validation and error-mapping hardening task.
|
|
||||||
- Impacted areas:
|
|
||||||
- Backend request validation and error handling in `backend/src/routes/timeUnit.ts`
|
|
||||||
- Shared validation behavior used by backend (`backend/src/services/timeUnit.ts`, shared validator if needed)
|
|
||||||
- Backend API tests for time-unit update/create invalid bounds flows
|
|
||||||
- Frontend error messaging path for time-unit save failures only if backend response shape changes
|
|
||||||
- Goal behavior:
|
|
||||||
- Invalid/non-existent `up_bnd`/`low_bnd` should return a client error (`400` or existing project-standard `403`) with actionable payload, never `500`.
|
|
||||||
- Existing valid flows and cascade checks must remain intact.
|
|
||||||
|
|
||||||
## 2) High-Level Plan
|
|
||||||
1. Reproduce and characterize failure path
|
|
||||||
- Add/adjust API test case for `PUT /time-unit` with non-existing `up_bnd` and/or `low_bnd` to confirm current `500` behavior.
|
|
||||||
2. Harden backend validation before write
|
|
||||||
- In time-unit route/service layer, explicitly detect when bound IDs are provided but corresponding time bound lookup returns no record.
|
|
||||||
- Return structured validation error response immediately.
|
|
||||||
3. Normalize database/Prisma error mapping
|
|
||||||
- Add defensive handling for Prisma foreign-key violations as a final guardrail, mapping to deterministic client error.
|
|
||||||
4. Align frontend handling (if necessary)
|
|
||||||
- Ensure existing UI save flow shows validation message from backend instead of generic failure when invalid IDs slip through.
|
|
||||||
5. Validate and regress
|
|
||||||
- Extend backend tests for both single-invalid and dual-invalid bound IDs.
|
|
||||||
- Run lint/typecheck/tests in impacted scope.
|
|
||||||
|
|
||||||
## 3) Tasks (JSON)
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "T1",
|
|
||||||
"title": "Add failing regression coverage for invalid Time Bound references",
|
|
||||||
"summary": "Create backend API tests for PUT /time-unit where up_bnd and/or low_bnd reference non-existent IDs and assert non-500 behavior with structured error payload.",
|
|
||||||
"app": "backend",
|
|
||||||
"files_touched": [
|
|
||||||
"backend/src/api-tests/timeUnit/update.test.ts",
|
|
||||||
"backend/src/api-tests/timeUnit/create.test.ts"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Requires authenticated users with Admin or EditUnrestricted role"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Test demonstrates invalid bound IDs are rejected with client error status",
|
|
||||||
"Response includes consumable validation message and does not return 500"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Run targeted timeUnit API tests",
|
|
||||||
"Verify existing valid update/create tests still pass"
|
|
||||||
],
|
|
||||||
"estimate_hours": 1.5,
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T2",
|
|
||||||
"title": "Enforce explicit bound existence validation in PUT /time-unit",
|
|
||||||
"summary": "Update backend route/service logic to treat provided up_bnd/low_bnd IDs as invalid when lookup misses, and short-circuit with structured validation response before writeTimeUnit.",
|
|
||||||
"app": "backend",
|
|
||||||
"files_touched": [
|
|
||||||
"backend/src/routes/timeUnit.ts",
|
|
||||||
"backend/src/services/timeUnit.ts",
|
|
||||||
"frontend/src/shared/validators/timeUnit.ts"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Preserve existing role enforcement via requireOneOf([Role.Admin, Role.EditUnrestricted])"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"PUT with non-existing up_bnd is rejected consistently",
|
|
||||||
"PUT with non-existing low_bnd is rejected consistently",
|
|
||||||
"PUT with both non-existing IDs is rejected consistently",
|
|
||||||
"No invalid ID request reaches DB write path"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Re-run timeUnit API tests",
|
|
||||||
"Add validator unit test(s) if validation contract changes"
|
|
||||||
],
|
|
||||||
"estimate_hours": 2.0,
|
|
||||||
"priority": "high"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T3",
|
|
||||||
"title": "Map Prisma FK constraint errors to deterministic client response",
|
|
||||||
"summary": "Add catch-path handling for Prisma known request FK errors as a safety net to return project-standard validation/conflict response instead of 500.",
|
|
||||||
"app": "backend",
|
|
||||||
"files_touched": [
|
|
||||||
"backend/src/routes/timeUnit.ts",
|
|
||||||
"backend/src/services/write/timeUnit.ts"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"Same write permissions as existing endpoint"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Unexpected FK violations in time-unit write return client error payload",
|
|
||||||
"Unhandled 500 for invalid bound references is eliminated"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add integration assertion for mapped FK error path (if reachable)",
|
|
||||||
"Smoke-test PUT /time-unit valid update path"
|
|
||||||
],
|
|
||||||
"estimate_hours": 1.0,
|
|
||||||
"priority": "medium"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T4",
|
|
||||||
"title": "Verify frontend error surfacing for time-unit save failures",
|
|
||||||
"summary": "Confirm frontend Time Unit edit/create views display backend validation errors for invalid bounds and avoid generic crash-like messaging.",
|
|
||||||
"app": "frontend",
|
|
||||||
"files_touched": [
|
|
||||||
"frontend/src/components/TimeUnit/TimeUnitDetails.tsx",
|
|
||||||
"frontend/src/components/DetailView/common/UpdateTab.tsx",
|
|
||||||
"frontend/src/redux/timeUnitReducer.ts"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"UI remains editable only for authorized roles"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"User sees clear validation message when save includes invalid bound IDs",
|
|
||||||
"No UI regression for normal time-unit save"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Add/adjust component or reducer tests for error state display",
|
|
||||||
"Manual smoke check in Time Unit edit form"
|
|
||||||
],
|
|
||||||
"estimate_hours": 1.5,
|
|
||||||
"priority": "medium"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "T5",
|
|
||||||
"title": "Document API error contract for invalid bound references",
|
|
||||||
"summary": "Update API and entity docs to specify expected status code and payload when Time Unit references missing Time Bound IDs.",
|
|
||||||
"app": "docs",
|
|
||||||
"files_touched": [
|
|
||||||
"documentation/api/time-units.md",
|
|
||||||
"documentation/entities/time_unit.md",
|
|
||||||
"documentation/CHANGELOG.md"
|
|
||||||
],
|
|
||||||
"migrations": false,
|
|
||||||
"permissions": [
|
|
||||||
"N/A"
|
|
||||||
],
|
|
||||||
"acceptance_criteria": [
|
|
||||||
"Docs reflect non-500 behavior and example error payload",
|
|
||||||
"Changelog records bugfix behavior change"
|
|
||||||
],
|
|
||||||
"test_plan": [
|
|
||||||
"Documentation review for accuracy against implemented response"
|
|
||||||
],
|
|
||||||
"estimate_hours": 0.5,
|
|
||||||
"priority": "low"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4) Risks & Mitigations
|
|
||||||
- Auth failures: endpoint hardening could accidentally bypass/alter auth flow.
|
|
||||||
- Mitigation: keep `requireOneOf` unchanged and include unauthorized test coverage.
|
|
||||||
- Behavior drift in status codes (`403` vs `400`).
|
|
||||||
- Mitigation: align with existing NOW validation conventions and update docs/tests to one canonical outcome.
|
|
||||||
- Hidden FK paths still causing 500.
|
|
||||||
- Mitigation: explicit pre-write validation + Prisma error mapping guardrail.
|
|
||||||
- Frontend mismatch with new error payload shape.
|
|
||||||
- Mitigation: preserve existing validation error format where possible; add adapter logic only if needed.
|
|
||||||
- Regression in create/update cascade checks.
|
|
||||||
- Mitigation: run full time-unit create/update test suite and targeted locality-cascade tests.
|
|
||||||
|
|
||||||
## 5) Out of Scope
|
|
||||||
- Legacy data cleanup/migration for already persisted inconsistent records.
|
|
||||||
- UI redesign or theming changes beyond existing MUI patterns.
|
|
||||||
- Broad refactor of shared validation framework unrelated to Time Unit bounds.
|
|
||||||
- General performance tuning not specific to this bug.
|
|
||||||
|
|
||||||
## 6) Definition of Done
|
|
||||||
- [ ] Invalid `up_bnd`/`low_bnd` PUT payloads return structured non-500 client errors.
|
|
||||||
- [ ] Authorized/unauthorized behavior remains correct.
|
|
||||||
- [ ] Backend tests added/updated and passing for regression scenarios.
|
|
||||||
- [ ] Linting and type checks pass in touched scopes.
|
|
||||||
- [ ] API/entity docs and changelog updated with new behavior.
|
|
||||||
- [ ] CI passes for backend/frontend checks relevant to touched files.
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Reviewer Notes
|
||||||
|
|
||||||
|
This file collects high-signal review notes that previously lived in the root `README.md`.
|
||||||
|
|
||||||
|
## Locality climate pollen validation
|
||||||
|
|
||||||
|
- Locality pollen fields (`pers_pollen_ap`, `pers_pollen_nap`, `pers_pollen_other`) now require integer values in the range `0...100`.
|
||||||
|
- Shared locality validation enforces combined pollen constraint `AP + NAP + OP <= 100` and surfaces the same message in form validation feedback.
|
||||||
|
- Backend locality write validation now evaluates pollen totals with full update context so partial updates cannot bypass the combined-value rule.
|
||||||
|
- API tests include invalid create/update pollen payload coverage for non-integer, out-of-range, and total-above-100 cases.
|
||||||
|
|
||||||
|
## Locality age method persistence
|
||||||
|
|
||||||
|
- In Locality edit mode, the Age tab now preserves method-specific values when switching Dating method between `Time unit`, `Absolute`, and `Composite`.
|
||||||
|
- Switching methods restores previously entered age values (e.g., `min_age`, `max_age`, basis/fraction selections) instead of clearing them.
|
||||||
|
- Locality age validation now evaluates required basis fields against the currently active dating method, while allowing preserved values from other methods to remain in draft state.
|
||||||
|
- See `documentation/CHANGELOG.md` for release-level tracking.
|
||||||
|
|
||||||
|
## Occurrence terminology update
|
||||||
|
|
||||||
|
- User-facing UI captions now use **Occurrence/Occurrences** in navigation, cross-search, and Species detail surfaces.
|
||||||
|
- This is a presentation-only rename: backend/database identifiers are unchanged, including the `now_ls` table and related API payload fields.
|
||||||
|
- See `frontend/src/constants/occurrenceLabels.ts` for centralized frontend label constants and `frontend/tests/locality-species-ui-label-inventory.md` for inventory coverage.
|
||||||
|
|
||||||
Reference in New Issue
Block a user