Frontend UX: object detail renders term/authority/localized values as raw JSON #45

Closed
opened 2026-06-06 18:50:53 +00:00 by logaritmisk · 2 comments
Owner

Severity: High. From a frontend UX audit. This is the worst readability defect in the detail view — and the machinery to fix it already exists.

Problems

  • Complex flexible-field values dumped as raw JSON. web/src/objects/object-detail.tsx:83-89 falls back to JSON.stringify(value) for object-typed values, so a term shows as {"id":"abc-123"}, an authority as a UUID blob, and a localized_text as {"sv":"…"}. The explicit goal — "term/authority values shown as human labels not ids" — is unmet. The form already resolves these correctly via useTerms/useAuthorities + labelText (lib/labels.ts); detail just doesn't reuse it.
  • Flexible fields are a flat, unordered block. object-detail.tsx:74-92 ignores FieldDefinitionView.group and renders in Object.entries(object.fields) (insertion) order rather than definition order. The form (object-form.tsx:165) has the same omission.
  • Detail polish: "Edit" is a bare indigo text <Link> crammed onto the <h2> line next to a delete button + badge (mixed affordance types) — object-detail.tsx:62-65; no export/copy action. Empty fields silently vanish (:17 returns null) so layout shifts per object. recording_date is shown verbatim though default_timezone/default_language are plumbed in ConfigView and unused.

Suggested fixes

  • Resolve term/authority values to labels (labelText + useTerms/useAuthorities), render localized_text by active language; switch on definitions.find(d=>d.key).data_type, not typeof value.
  • Iterate definitions in stable order, group flexible fields by def.group with subheadings (detail and form).
  • Make Edit a primary Button in a right-aligned actions toolbar (+ export); render known core fields with a muted "—" placeholder; format dates via Intl.DateTimeFormat(i18n.language).

Source: frontend UX/design audit, 2026-06-06.

**Severity: High.** _From a frontend UX audit. This is the worst readability defect in the detail view — and the machinery to fix it already exists._ ## Problems - **Complex flexible-field values dumped as raw JSON.** `web/src/objects/object-detail.tsx:83-89` falls back to `JSON.stringify(value)` for object-typed values, so a **term** shows as `{"id":"abc-123"}`, an **authority** as a UUID blob, and a **localized_text** as `{"sv":"…"}`. The explicit goal — "term/authority values shown as human labels not ids" — is unmet. The form already resolves these correctly via `useTerms`/`useAuthorities` + `labelText` (`lib/labels.ts`); detail just doesn't reuse it. - **Flexible fields are a flat, unordered block.** `object-detail.tsx:74-92` ignores `FieldDefinitionView.group` and renders in `Object.entries(object.fields)` (insertion) order rather than definition order. The form (`object-form.tsx:165`) has the same omission. - **Detail polish:** "Edit" is a bare indigo text `<Link>` crammed onto the `<h2>` line next to a delete button + badge (mixed affordance types) — `object-detail.tsx:62-65`; no export/copy action. Empty fields silently vanish (`:17` returns null) so layout shifts per object. `recording_date` is shown verbatim though `default_timezone`/`default_language` are plumbed in `ConfigView` and unused. ## Suggested fixes - Resolve term/authority values to labels (`labelText` + `useTerms`/`useAuthorities`), render `localized_text` by active language; switch on `definitions.find(d=>d.key).data_type`, not `typeof value`. - Iterate `definitions` in stable order, group flexible fields by `def.group` with subheadings (detail **and** form). - Make Edit a primary `Button` in a right-aligned actions toolbar (+ export); render known core fields with a muted "—" placeholder; format dates via `Intl.DateTimeFormat(i18n.language)`. _Source: frontend UX/design audit, 2026-06-06._
Author
Owner

Done in e2ae093 (merged to main). Object detail now resolves term/authority ids to their active-locale labels and localized_text to the active-language string (via a per-field FlexibleFieldValue reusing useTerms/useAuthorities + labelText), with a muted (unknown) fallback for deleted refs and raw-value muted rendering for orphaned keys. Flexible fields are grouped by def.group in definition order; core fields always render with a "—" placeholder; dates are locale-formatted; the header is an Edit/Delete actions toolbar. Out of scope (filed/known): export/PDF (#39), the object form's grouping, backend label resolution.

Done in `e2ae093` (merged to `main`). Object detail now resolves term/authority ids to their active-locale **labels** and `localized_text` to the active-language string (via a per-field `FlexibleFieldValue` reusing `useTerms`/`useAuthorities` + `labelText`), with a muted `(unknown)` fallback for deleted refs and raw-value muted rendering for orphaned keys. Flexible fields are **grouped by `def.group`** in definition order; core fields always render with a "—" placeholder; dates are locale-formatted; the header is an Edit/Delete actions toolbar. Out of scope (filed/known): export/PDF (#39), the object form's grouping, backend label resolution.
Author
Owner

Follow-up shipped: the deferred object-form flexible-field grouping is now done (merge 6e72f24).

  • Extracted a shared groupDefinitions helper (web/src/lib/group-fields.ts) so the detail view and the form group identically (one source of truth, unit-tested).
  • The object form now renders flexible-field inputs grouped by def.group (definition order, "Other" last) with label-caption subheadings; the <fieldset> keeps an sr-only legend for a11y.
  • Object-detail refactored onto the same helper (output-preserving — its tests stayed green).
  • No new dependency, no new i18n keys. 229 frontend tests pass; typecheck/lint/build clean; check:size 215.5 KB gz; check:colors clean.
Follow-up shipped: the deferred object-**form** flexible-field grouping is now done (merge `6e72f24`). - Extracted a shared `groupDefinitions` helper (`web/src/lib/group-fields.ts`) so the detail view and the form group identically (one source of truth, unit-tested). - The object form now renders flexible-field inputs grouped by `def.group` (definition order, "Other" last) with `label-caption` subheadings; the `<fieldset>` keeps an `sr-only` legend for a11y. - Object-detail refactored onto the same helper (output-preserving — its tests stayed green). - No new dependency, no new i18n keys. 229 frontend tests pass; typecheck/lint/build clean; check:size 215.5 KB gz; check:colors clean.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logaritmisk/biggus-dickus#45