From cc757962a73a96823f212c4a341a1f6668b6eee5 Mon Sep 17 00:00:00 2001 From: Michael Bunsen Date: Mon, 7 Sep 2026 16:49:40 -0700 Subject: [PATCH 1/6] Add a reusable JSON metadata field for forms Introduce a form field that accepts a free-form JSON object and tells whoever is filling it in, next to the box, when what they have typed is not one. Nothing uses the field yet; the station and device-type forms adopt it in the following commit. The field holds the JSON text exactly as it was typed rather than a parsed object. That is what keeps a half-finished edit on screen while the inline error explains the problem, and it leaves the author's own key order and indentation alone. Three helpers in utils/fieldProcessors.ts handle the round trip, alongside the existing integer-list trio that solves the same "structured value edited as text" problem: formatMetadata renders a stored object for the field, parseMetadata turns the text back into the object the API stores, and validateMetadata is the rule that rejects text which does not parse or which parses into something other than an object. Syntax highlighting, which the ticket offers as optional, is left out. Every route to it means adding a code-editor dependency to a bundle that has none, so the field uses a monospace face and inline errors instead. Co-Authored-By: Claude Opus 5 (1M context) --- ui/src/components/form/metadata-field.tsx | 56 +++++++++++++++++++ ui/src/utils/fieldProcessors.test.ts | 67 ++++++++++++++++++++++ ui/src/utils/fieldProcessors.ts | 68 +++++++++++++++++++++++ ui/src/utils/language.ts | 11 ++++ 4 files changed, 202 insertions(+) create mode 100644 ui/src/components/form/metadata-field.tsx create mode 100644 ui/src/utils/fieldProcessors.test.ts diff --git a/ui/src/components/form/metadata-field.tsx b/ui/src/components/form/metadata-field.tsx new file mode 100644 index 000000000..7e964cdf2 --- /dev/null +++ b/ui/src/components/form/metadata-field.tsx @@ -0,0 +1,56 @@ +import { InputContent } from 'nova-ui-kit' +import { ControllerProps, FieldPath, FieldValues } from 'react-hook-form' +import { FormController } from './form-controller' +import { FormConfig } from './types' + +/* Tall enough to show a handful of metadata entries without taking over the form. */ +const VISIBLE_ROWS = 8 + +/** + * Free-form JSON entry for a record's metadata, so a project can keep fields + * the platform has no column of its own for. + * + * The form value is the text exactly as it was typed, not a parsed object. That + * keeps a half-finished edit on screen while the inline error explains what is + * wrong with it, and it preserves the key order and indentation the operator + * chose. Text that is not a JSON object is rejected by the `validate` rule in + * the field's config, and the form turns the text into an object on submit. + */ +export const MetadataField = < + TFieldValues extends FieldValues, + TName extends FieldPath +>({ + name, + control, + config, +}: Pick, 'name' | 'control'> & { + config: FormConfig +}) => { + const fieldConfig = config[name] + + return ( + ( + +