Restructure documentation and add index

This commit is contained in:
karilint
2026-04-22 13:39:42 +03:00
parent d6f718dcee
commit 1a5974c166
8 changed files with 129 additions and 622 deletions
+95
View File
@@ -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`
+46
View File
@@ -0,0 +1,46 @@
# Technical questions
This note answers `nowcommunity/nowdatabase#101`.
## 1) Calculated / derived fields
In this codebase, there are a few different “calculated field” patterns:
- **Pure display formatting (client-side):** Some table columns are derived from multiple persisted fields at render time.
- Example: developmental/functional crown type strings are formatted client-side via
`frontend/src/shared/types/util.ts` (`formatDevelopmentalCrownType`, `formatFunctionalCrownType`) and used as
`accessorFn` in table definitions (e.g. `frontend/src/components/Species/SpeciesTable.tsx`).
- **UI-only edit metadata (client-side):** Edit payload types wrap nested arrays as `Editable<T>` and add `rowState`
(e.g. `"new"`, `"removed"`) to drive add/remove UI behavior. This field is not a DB column.
- Type-level definition: `frontend/src/shared/types/util.ts` (`EditDataType`, `Editable`, `RowState`).
- Backend write-layer interprets `rowState` for join tables via `applyListChanges`:
`backend/src/services/write/writeOperations/writeHandler.ts`.
- **Client-side derived state / pagination metadata:** Some UI metadata is computed from server responses and cached in
Redux/context; it is not persisted to the DB.
- Example: `frontend/src/hooks/usePaginatedQuery.ts` computes table pagination metadata.
### Should derived values be written to the DB?
General guidance for this repository:
- Prefer **not** storing derived display-only values in the DB (avoid duplication and drift).
- Store (or materialize) a derived value only if you need it for **server-side filtering/sorting** or it is
**expensive** to compute and frequently used.
- In those cases, prefer computing it in SQL/query code first; only persist if you have a clear contract and
a single source of truth.
## 2) How does deletion of an entire entity show in tables?
For “entity delete” routes, the backend uses **hard deletes** (SQL `DELETE`) rather than a soft-delete flag:
- Low-level delete: `backend/src/services/write/writeOperations/databaseHandler.ts` (`DELETE FROM ...`).
- Write handler delete: `backend/src/services/write/writeOperations/writeHandler.ts` (`deleteObject`) also logs the
removed values before deleting (so update/audit views can still show a deletion event).
Practical UI behavior:
- In list tables (e.g. `/locality/all`), a deleted entity typically **disappears from results** after the next fetch.
- In edit-mode tables that represent **join rows** (e.g. child lists inside a DetailView), clicking remove generally
marks the row with a UI-only `rowState` (often rendered with a red background) until the user saves.
- Example: `frontend/src/components/DetailView/common/EditableTable.tsx`.
+67
View File
@@ -0,0 +1,67 @@
# Routing & Unsaved Changes
This frontend uses React Router v6 in **data router mode** (`createBrowserRouter` + `RouterProvider`). This enables `useBlocker` and related APIs for navigation interception.
## 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`.
- 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`).
## Enabling Unsaved-Changes Prompts
Unsaved-change blocking is provided by `UnsavedChangesProvider` and the `useUnsavedChangesPrompt` hook.
**Provider**
- Wrap any page containing a form or editable detail view with `UnsavedChangesProvider`.
- The provider listens to `useBlocker` and renders the shared confirmation dialog.
**Hook**
- 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.
- If submit fails, set dirty back to `true` so protection remains while the user fixes errors.
### Minimal Example
```tsx
import { useForm } from 'react-hook-form';
import { UnsavedChangesProvider } from '@/components/UnsavedChangesProvider';
import { useUnsavedChangesPrompt } from '@/hooks/useUnsavedChangesPrompt';
export const ExampleFormPage = () => {
const { handleSubmit, formState: { isDirty }, reset } = useForm();
const { setDirty } = useUnsavedChangesPrompt(isDirty);
const onSubmit = handleSubmit(async values => {
setDirty(false);
try {
await save(values);
reset(values);
} catch {
setDirty(true);
throw new Error('Save failed');
}
});
return (
<UnsavedChangesProvider>
<form onSubmit={onSubmit}>
{/* form fields */}
<button type="submit">Save</button>
</form>
</UnsavedChangesProvider>
);
};
```
### 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.
## Notes
- `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`.
@@ -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.
+24
View File
@@ -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.