Skip to content

Add field update history popover#1190

Draft
karilint wants to merge 1 commit into
mainfrom
codex/1183-single-field-update-log-ui
Draft

Add field update history popover#1190
karilint wants to merge 1 commit into
mainfrom
codex/1183-single-field-update-log-ui

Conversation

@karilint
Copy link
Copy Markdown
Collaborator

Refs #1183

Summary

  • Add a reusable read-mode field update history popover for detail labels when matching update log rows exist.
  • Show update metadata, before/after values, comments, and references from the existing nested update-log data.
  • Cover the new behavior and mocked-detail-context edge case with focused frontend tests.

Validation

  • cd frontend && npx jest src/tests/components/DetailView/FieldUpdateHistory.test.tsx src/tests/components/SpeciesTaxonomyTab.test.tsx --runInBand
  • cd frontend && npx tsc --noEmit
  • npm run lint:frontend
  • commit hook: npm run lint and npm run tsc

@karilint
Copy link
Copy Markdown
Collaborator Author

@copilot Please review this pull request.

Copy link
Copy Markdown
Contributor

Copilot AI commented May 29, 2026

@copilot Please review this pull request.

Here is my review of the PR:

Code Review: Add field update history popover

✅ What works well

  • The recursive collectUpdateContainers and collectReferences traversals correctly handle the polymorphic update data structures (now_lau, now_sau, OccurrenceUpdate, etc.) without knowing their concrete types.
  • The formatAction mapping (1→Delete, 3→Update, else→Add) is consistent with UpdateTab.tsx and TimeUnitUpdateTab.tsx.
  • useMemo for the update scan avoids re-traversing the data tree on every render.
  • The guard !detailContext?.mode.read || updates.length === 0 correctly suppresses the icon in edit/new modes and when there's no history.
  • Tests cover the main render path and the "no matching field" case.

🔴 Issues to address

1. SafeDetailContext fallback is dead code

DetailContext is typed as createContext<DetailContextType<unknown>>(null!) – it is always a fully initialised Context object, never undefined. The ?? createContext(null) branch can never execute.

// Current – fallback never runs
const SafeDetailContext =
  (DetailContext as unknown as Context<DetailContextType<Record<string, unknown>> | null> | undefined) ??
  createContext<DetailContextType<Record<string, unknown>> | null>(null)

// Simpler and equivalent
const SafeDetailContext = DetailContext as unknown as Context<DetailContextType<Record<string, unknown>> | null>

2. isUpdateLog type guard is too permissive

const isUpdateLog = (value: unknown): value is UpdateLog =>
  isObject(value) && ('column_name' in value || 'old_data' in value || 'new_data' in value || 'log_action' in value)

Any object with a property called old_data, new_data, or log_action will be mistaken for an UpdateLog. Since UpdateLog always has column_name (it's the field that's actually queried later), a stricter guard would reduce false positives:

const isUpdateLog = (value: unknown): value is UpdateLog =>
  isObject(value) && 'column_name' in value && 'log_action' in value

3. Non-unique key props in the update list

key={`${log.log_id ?? index}-${log.column_name ?? field}`}

When two different UpdateContainer objects each have an UpdateLog for the same field with the same log_id (possible across page loads / history merges), React will see duplicate keys. Including the outer index from the updates.map(…, index) call would make keys stable:

key={`${index}-${log.log_id ?? ''}-${log.column_name ?? field}`}

🟡 Suggestions (non-blocking)

4. No chronological ordering of updates

Updates are displayed in traversal order rather than by date. Showing the most-recent update first (sort descending by getDate(container)) would make the history easier to read.

5. References repeat when multiple updates share a container

Each update card calls collectReferences(container), so if one now_sau row has two field-level update logs, both cards show the same reference list. This is technically correct but may feel redundant. A comment or a visual "same references as above" collapse would improve UX.

6. formatDate references formatValue before its definition

Both are const arrow functions. formatDate calls formatValue via a closure, and since both are initialised before any render occurs, there is no actual runtime issue. Reordering so formatValue is defined first would improve readability and avoids the subtle forward-reference.

7. Test coverage gaps

The existing tests are a good start. Consider adding:

  • A case that verifies the icon is not shown in edit mode.
  • A case for multiple update entries on the same field (to catch key-collision issues).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants