Skip to content

Add scalar list field support (BND-5037) - #13

Merged
domano merged 2 commits into
mainfrom
bnd-5037-scalar-list-fields
Sep 1, 2026
Merged

domano merged 2 commits into
mainfrom
bnd-5037-scalar-list-fields

Conversation

@domano

@domano domano commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Why

Studio already sends field-update messages with isList: true for scalar list fields (String/ID/Enumeration/Int/Float/Boolean/Date/DateTime lists), and gates them on the SDK announcing a scalarListSync capability — failing closed, because an SDK without list support writes the array straight into the bound element (destroying a <ul>'s <li> children, rendering Invalid Date for a Date list).

No published SDK announces that capability, so scalar list fields currently never update live for anyone. The SDK half of the feature was written against the older copy of this SDK that still lives in the Hygraph monorepo (tools/preview-sdk, stuck at 0.2.0, not published), so it never shipped. This ports it here, translated onto the current API (Preview, createPreviewAttributes, no locale).

What

  • scalarListSync in the ready message. Not configurable — it states what this build can handle, not what the consumer wants.
  • Whole-list bindings render their items joined with , .
  • Per-item bindings: tag one node per item with data-hygraph-list-index (or listIndex on createPreviewAttributes) and each item is patched individually.
  • Container safety: writing a list into an element that has child elements is refused, reported through preview:update-failed, instead of destroying markup the SDK cannot rebuild.
  • Update events: preview:field-updated is now emitted once the built-in updater has applied a change (it previously emitted nothing), preview:update-failed when it could not. The detail carries componentChain, isList, fieldType and transformedValue, so consumers can re-render what the built-in updater deliberately leaves alone (component arrays, list add/remove).
  • Types: isList on the field-update base, a scalar-list variant of FieldUpdate with ScalarListValue, data-hygraph-list-index on ElementAttributes.
  • Version bumped to 1.1.0 with a CHANGELOG entry; the new attribute is documented in agents.md.

Verification

  • npm test — 51 passed (38 before, 13 added). Each new test was watched failing first.
  • npm run type-check, npm run lint, npm run build — clean.

Deliberately not ported

The monorepo copy also relaxed component-chain matching: it compares chains prefix-wise in both directions and always applies updates to untagged elements. That contradicts two behaviours this repo pins with tests (updates only elements with exact component chain match, excludes elements without component chain when other elements have it), so it is left out of this PR. Worth deciding separately which semantics we want — an element tagged with a chain deeper than the one Studio reports is currently skipped.

Follow-ups

  • Consumer docs for scalar lists live on hygraph.com (this repo's docs are pointers), so the Click to Edit / Advanced API pages need the data-hygraph-list-index section.
  • The monorepo's tools/preview-sdk should be deleted or stubbed with a pointer here, so the next SDK change does not land in the unpublished copy again.

Studio sends a `field-update` with `isList: true` for scalar list fields
(String/ID/Enumeration/Int/Float/Boolean/Date/DateTime lists), but withholds
those updates unless the SDK announces `scalarListSync` in its ready message.
No published SDK announces it, so list fields never update live. This ports the
SDK half of the change, which was implemented against the copy of this SDK that
still lives in the Hygraph monorepo and so was never released.

- Announce `scalarListSync` in the ready message. It is not configurable: it
  states what this build can handle, not what the consumer wants.
- Render a whole-list binding as its items joined with `, `, or per item when
  items are tagged `data-hygraph-list-index` (`listIndex` on
  `createPreviewAttributes`).
- Refuse to write a list into an element that has child elements, so a `<ul>` of
  `<li>` items reports `preview:update-failed` instead of losing markup the SDK
  cannot rebuild.
- Emit `preview:field-updated` once the built-in updater has applied a change,
  and `preview:update-failed` when it could not; the update detail now carries
  `componentChain`, `isList`, `fieldType` and `transformedValue`, so a consumer
  can re-render what the built-in updater deliberately leaves alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@domano
domano requested a review from a team as a code owner September 1, 2026 11:48
Found dogfooding this branch against local Studio: saving an entry makes Studio
send two `updatedAt` messages inside the debounce window, and the first one -
discarded in favour of the second - was reported to listeners as
`preview:field-updated`. Nothing had reached the DOM, so a consumer re-rendering
on that event re-renders from a value the page never received.

ContentUpdater now marks a superseded update as such instead of returning a bare
success, and Preview emits neither event for it; the update that does apply
still reports for itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@domano

domano commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Dogfooded this branch against a real local Studio before asking for review: UMD build from the branch served to a fixture page, driven through the Studio content form on a live entry (Bnd5037Page), with every postMessage and the resulting DOM state captured out of the preview iframe.

Handshake confirmed. The build announces {fieldFocusSync: true, fieldUpdateSync: true, scalarListSync: true}, Studio connects, and list updates start flowing — the gate that has kept scalar lists inert in production opens.

12 cases pass, including the ones that matter for existing consumers: single-field updates, component-chain disambiguation between sibling instances, the Click to Edit overlay round-trip (preview:field-click → field-focus), and save (content-saved → preview:content-saved). Date lists render as raw values rather than Invalid Date; a Float list bound to a <ul> with <li> children is refused with preview:update-failed and the markup survives.

One defect found, and it was mine — fixed in 13d0075:

Saving makes Studio send two updatedAt messages inside the debounce window. The first is discarded in favour of the second, but updateField returned a bare { success: true } for it, so this PR's new preview:field-updated fired for an update that never reached the DOM. UpdateResult.superseded now marks it and neither event is emitted; test added (watched failing at 2 events), and the fix re-verified in the live setup.

Two things for the reviewer, neither introduced here:

  • The exact-chain divergence this PR flags is real in the product: an element tagged with only the outer hop of a deeper chain ([sections/inst-a] when Studio reports [sections/inst-a, leaf/inst-b]) receives nothing, while the fully tagged element updates. Worth deciding before release.
  • Studio broadcasts field-update for the updatedAt system field on every save — twice, once with locale: "0" — so consumers get a preview:update-failed per save. That sender is in autobahn.

@domano
domano merged commit b259c04 into main Sep 1, 2026
2 checks passed
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