Repository navigation
No UI for viewing or editing nested (object/array) schema properties #2009
Description
Overview
GenericSchemaForm's field renderer handles five field types (enum, date, number, boolean, string/text) and falls through to a literal "Unknown field type: {field.type}" placeholder for anything else — including object and array, both of which are real, already-defined field shapes in SchemaField (fields: Option<Vec<SchemaField>> for nested objects, item_type/item_fields for arrays). A property with nested structure is currently unviewable and uneditable in the UI; the only way to inspect or change it is direct DB/RPC access.
Problem Statement
packages/desktop-app/src/lib/components/schema/generic-schema-form.svelte:163-236 handles field types via an if/else chain:
{#if field.type === 'enum'} ...
{:else if field.type === 'date'} ...
{:else if field.type === 'number'} ...
{:else if field.type === 'boolean'} ...
{:else if field.type === 'string' || field.type === 'text'} ...
{:else}
<div class="text-sm text-muted-foreground">Unknown field type: {field.type}</div>
{/if}An object-typed field (or an array with item_type: 'object') renders this fallback text with no editor at all. task-schema-form.svelte and schema-property-form.svelte were not checked for the identical gap but are built on the same field-type-dispatch pattern and likely share it — confirm during implementation.
This blocks any schema field intentionally modeled with nesting (e.g. an address object with street/city sub-fields, or a list of structured line items) from having any UI at all, and blocks deeper-nested values in general from being viewed, expanded/collapsed, or deleted.
Proposed Solution
Decision: evaluate svelte-jsoneditor first, before committing to a hand-built recursive renderer.
A research pass surveyed the available options for editable nested-JSON UI components:
svelte-jsoneditor(josdejong) — the one realistic off-the-shelf candidate. Native Svelte 5 component, actively maintained, full CRUD (tree/text/table modes, add/edit/delete keys and values, arbitrary nesting depth), and — unlike most alternatives — exposes a genuine per-value/per-path custom renderer hook (onRenderValue), with built-in examples for enum-dropdown and timestamp-picker style leaf renderers. Caveat: this hook is explicitly marked experimental in the library's own docs, and the package carries real dependency weight (lodash-es, ajv, jmespath — potential 150-300KB+ added). It was not built around a schema system like NodeSpace'sSchemaField; making it render NodeSpace's own date-picker/enum-dropdown components (rather than generic inputs) means wiring NodeSpace components into its renderer-injection API, not just using them as-is.- Ruled out:
svelte-json-treeandsvelte-json-view-lite/@humanspeak/svelte-json-view-liteare Svelte-native but read-only (view only, no edit/delete).react-json-view(maintained fork) andjson-edit-reactare strong, well-designed, but React-only.jsoneditor(vanilla JS) is superseded byvanilla-jsoneditor, whichsvelte-jsoneditoralready wraps — no reason to use the legacy package directly.
Spike first: before implementation, time-box an evaluation of whether svelte-jsoneditor's onRenderValue hook can cleanly render NodeSpace's existing field components (date picker, enum dropdown, etc.) at each leaf, keyed off the corresponding SchemaField/EdgeField type at that path. If it integrates cleanly, prefer it over hand-building — it gets expand/collapse, delete, and tree/table view modes for free, matching the "modal view, expand/collapse, view, delete" ask directly. If the integration proves awkward (fighting the experimental hook's API, dependency weight not justified for what ends up being a thin wrapper), fall back to a hand-built recursive component: extend the existing five-case renderer in generic-schema-form.svelte to recurse into field.fields/item_fields, reusing NodeSpace's own field-type renderers at each leaf rather than introducing a new dependency. Record the spike's outcome and rationale in this issue before starting the full implementation, so the decision isn't silently made mid-PR.
Either path should support:
- Viewing a nested property's full structure (arbitrary depth)
- Expand/collapse per nesting level
- Editing leaf values using the same typed controls already used for top-level fields of that type (date picker for a
dateleaf, dropdown for anenumleaf, checkbox forboolean, etc.) — not generic text inputs for every value - Deleting a key/value at any nesting level
- A modal presentation, per the original ask, rather than inline in the already-cramped property panel
Acceptance Criteria
- Spike outcome recorded:
svelte-jsoneditorevaluated against NodeSpace's field-type renderers; decision (adopt it, or hand-build) documented with rationale in this issue before full implementation begins - Opening a node with an
object-typed orarray-of-object-typed property shows a working editor/viewer instead of "Unknown field type" - Nested values render using NodeSpace's existing typed controls per leaf (date picker, enum dropdown, boolean checkbox, etc.), not generic text/JSON-raw inputs
- Expand/collapse works at any nesting depth
- Deleting a key/value at any nesting level works and persists correctly
- Presented as a modal (matching the pattern already used for
RelationshipViewerModal,#1918) rather than crammed into the inline properties panel -
task-schema-form.svelte/schema-property-form.svelteaudited for the same "Unknown field type" fallback gap; fixed in the same PR if found, or filed separately if genuinely distinct scope - If
svelte-jsoneditoris adopted: bundle-size impact measured and considered acceptable; note any other apps/pages affected by the added dependency weight - No new test failures (
bun run test:all) - Code passes
bun run quality:fix
Technical Specifications
Reference Files
packages/desktop-app/src/lib/components/schema/generic-schema-form.svelte:163-236— the field-type dispatch chain; the fallback case (line 234-236) is the concrete symptompackages/nodespace-types/src/schema.rs(orpackages/core/src/models/schema_node.rs, per Store SchemaRelationship declarations as relationship-table edges instead of schema-node JSON #2008's finding on whichSchemaNode/SchemaFieldis actually live) —SchemaField.fields: Option<Vec<SchemaField>>(nested object shape),item_type/item_fields(array shape) — the existing data model this feature needs to render, already defined but never consumed by any frontend rendererpackages/desktop-app/src/lib/components/relationships/relationship-viewer-modal.svelte— existing modal pattern to follow for presentation, per Relationship viewer: view and manage typed relationships between nodes #1918packages/desktop-app/src/lib/components/property-forms/task-schema-form.svelte,schema-property-form.svelte— likely share the same fallback gap; audit as part of this work
Non-Goals
- Does not change the
SchemaField/EdgeFielddata model — nested object/array shapes already exist and are already stored correctly; this is purely a rendering/editing gap on the frontend. - Does not address isCustomSchemaType conflates "is core" with "has a dedicated frontend integration" across 4 call sites #1998's scope (the properties-form rendering gate itself, i.e. whether a form renders at all for a given node type) — this issue assumes the form renders and is about what happens once it does, for a field whose type is
object/array.
Activity
mstomar125 commentedon Aug 10, 2026
Spike outcome — hand-build a recursive schema-driven renderer (do NOT adopt
svelte-jsoneditor)Evaluated
svelte-jsoneditor(the one realistic off-the-shelf candidate) against NodeSpace's actual data model. Decision: hand-build, for concrete reasons rather than NIH:SchemaFieldalready models the nesting:fields: Option<Vec<SchemaField>>(nested object) anditem_type/item_fields(array + array-of-object). A recursive component that walksfield.fields/item_fieldsand reuses the existing five leaf renderers (enum → dropdown, date → picker, number, boolean → checkbox, string/text) maps 1:1 onto this model. That's exactly the AC's "render nested values using NodeSpace's existing typed controls per leaf."svelte-jsoneditoris JSON-generic. Getting it to render NodeSpace's typed leaf components means driving its experimentalonRenderValuehook, keyed off theSchemaFieldtype at each path — i.e. re-deriving the schema→component mapping we already have, through an injection API not built for it. That's fighting the tool to end up with a thin wrapper.RelationshipViewerModal(Relationship viewer: view and manage typed relationships between nodes #1918) with expand/collapse + delete — trivial to own in a hand-built component, versus theming/constraining a third-party editor.Implementation plan (hand-build)
SchemaField(packages/desktop-app/src/lib/types/schema-node.ts) currently exposes onlyitemType— it's missingfields?: SchemaField[](nested object) anditemFields?: SchemaField[](array item schema), which already arrive on the wire. Add them.generic-schema-form.svelte— replace theobject/arrayfall-through to "Unknown field type" with a recursiveNestedFieldEditorthat rendersfield.fields(object) or iteratesitem_fieldsper array element (array-of-object), and delegates each leaf back to the existing five-case renderer. Expand/collapse per level; add/delete key/element.RelationshipViewerModal, per the original ask, rather than inline in the cramped property panel.task-schema-form.svelte/schema-property-form.svelteshare the same five-case dispatch — fix the identicalobject/arrayfall-through there or file separately.This is a sizable feature (recursive component + modal + type extension + audit); logging the spike decision now per the issue's requirement so implementation can proceed against a settled direction.
mstomar125 commentedon Aug 11, 2026
Resolved in #2028 (squash-merged to
main).Hand-built a recursive, schema-driven editor for nested
object/arrayproperties (per the spike recorded above — no svelte-jsoneditor):schema-field-leaf.svelte(five leaf renderers extracted; one place for leaf rendering) + pure recursivenested-field-editor.svelte(value in, onChange out, never touches the store) + purenested-property-ops.ts+nested-property-modal.svelte(owns persistence via the samesharedNodeStore.updateNodepath, mirroring RelationshipViewerModal).GenericSchemaFormobject/array fields now open the modal ("N items"/"N fields" + Edit) instead of showing "Unknown field type".fields/itemFields/itemTypecamelCase, adapter passes through) — only the TS type needed extending; no data-model/Rust change.Audit → #2027:
task-schema-form.svelteandschema-property-form.svelteshare the fall-through but persist under different namespaces (properties.task.*/properties[nodeType].*), so the flat modal can't be dropped in as-is — filed #2027 for a namespace-aware read/persist strategy (plus schema-property-form's stubbed boolean).Two-pass adversarial review (no blockers/majors); addressed two minor Collapsible open-state findings (index-shift on delete + per-field remount), unit-tested. Full suite green (198 files / 4543 tests).