Repository navigation
feat(studio,ui,api): RFC-0045 step 6a, reference and repeater fields in the entry editor - #219
Merged
Merged
Conversation
…in the entry editor New @cloudhal/ui blocks, built from Kumo: FieldControl (one typed control, extracted from EntryFieldsForm), RepeaterField (items as fieldsets with ICU legends, add/move/duplicate/remove through buttons, focus after add and remove, undo, a polite live region, errors opening their items), ReferenceList, EntryPicker (labelled search, listbox with arrow keys and Enter, polite result count, cursor and truncated reads, focus back to the trigger; no API calls of its own) and UsedByList. Studio's entry editor renders references through FieldReference (around GET .../entries/pick and the new GET .../entries/groups), repeaters with FieldImage and FieldReference sub-fields, item errors by path, translation fallbacks, an undo toast, and a "Used by" card. Everything loads with the lazy fields chunks; the initial bundle is unchanged. Words live in the lazy fields namespace; every other language is an AI draft through i18n:import. Both types stay refused on every production write path until step 7. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
- Repeater undo writes through the latest onChange and items; EntryFieldsForm writes over its latest value and the editor updates its draft functionally, so an undo keeps later edits and the saved rev. The toast is scoped to its entry (closed on leave/unmount); undo returns false when the list is gone or full, and Studio says so. - Item errors are re-keyed to stable item keys when they arrive, so they follow their items through moves, removals and undo. - Live regions empty before each message (repeated messages re-announce). - ReferenceList keeps focus on a moved row, moves it to the next row or the picker trigger after a remove, and announces both. - EntryPicker: a full field keeps its trigger focusable (aria-disabled); a failed search is the polite status; the spinner is hidden from AT. - Groups read sends only well-formed group ids; others show "Not found"; a failed read shows an error banner with retry. - During running field operations: renamed keys (top level and sub-field) are read and written through the marker; unconverted values show read-only as stored with "Being changed". - Saves leave out empty repeater items. - i18n: six new fields keys, AI drafts for 18 languages. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
- Item issues after a save that left empty items out are mapped back onto the draft's items (sentIndexes, draftPath) before the error map is built, so a problem never lands on an empty item. - A read-only repeater shows every item open (its toggles sit in the editor's disabled fieldset). - "Used by" moved to the reach cards (EntryUsedByCard, beside "In menus"), outside the read-only fieldset, so "Show more" works when the person can't edit the article. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
- Duplicate leaves empty repeater items out, like a save (duplicateOf runs withoutEmptyItems; the page function shrinks, baseline tightened). - Item issues map back through the entry the failed save sent (the mutation's variables, passed as `save`), not the draft at error time. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RFC-0045 rollout step 6, first of two PRs: the blocks and the entry editor. Step 6b (the builder, the delete dialog and the publish warnings) follows once this merges.
Both types stay refused on every production write path until step 7, so no production collection can define them and Studio looks unchanged there.
Blocks (
packages/ui/src/blocks, all words as props)FieldControlEntryFieldsForm(444 → ~240 lines);EntryFieldsFormand repeater items share it. A reference is afieldsetaround the host'srenderReference.RepeaterField(+repeater-state.ts)fieldsets with an ICU legend ("Item 2: Ada Lovelace"), folded inCollapsibles; "Add item" + "3 of 20"; up/down icon buttons fromsmand a ⋯ menu (move, duplicate, remove). Focus goes to the new item's first control after add, to the next item's toggle (else "Add item") after remove;onRemovedhands the host an undo. Moves and removals are announced in a polite live region. A new error map opens the items it names; collapsed items with problems are listed above the items.ReferenceListInsetPanelrows: the title linked, collection · language, "Draft" / "In review" / "Archived" with "Will not show until published", "Not found" with the id in mono; up/down fromsmand ⋯ (open, move, remove).EntryPickerLayerDialogopened by itstrigger, with focus returning to it. A labelled search over a combobox listbox (aria-activedescendant, arrows, Home, End, Enter) and a polite result count. Collection and status badges. One pick closes the dialog; several are ticked up to the remaining count. "Show more" or "Keep searching" follows the cursor after atruncatedread. It takes asearch(q, cursor)prop and makes no API calls itself.UsedByListTable(the entry, where the value sits, who else shows it; the status), so it fits 390 px.Studio
FieldReferencecomposesReferenceListandEntryPickeraroundGET …/entries/pick. The entry's own group and targets already chosen can't be picked.FieldImageandFieldReferencefor their sub-fields. Removing an item shows an info toast with "Undo" for 10 s.fields/speakers/2/personshows on that control.UsedByCard) is a flushSectionCardfromGET …/entries/:id/references, published referrers first, with "Show more". It shows only when some field of the site can reference the collection, so no production site shows it yet.EntryFieldCardslazy-loads both cards;ArticleEditorPagelost its inlinelazy(446 → 442 lines in the baseline).LAZY_ONLY. The initial load is unchanged at 391.9 kB gzip.API (one deviation)
New read-only
GET /api/sites/:siteId/entries/groups?ids=a,b(content:read; scopes table and playground allow-list). It answers each group's members in the site, any status, as the picker's cards, with[]for a group with none left. It takes 1 to 100 well-formed ids.The picker finds new targets only, and
referenceslists referrers, so the editor otherwise had no way to name a stored id or say "Not found". A shared-suite case runs on SQLite and D1.Deviations (also in the RFC, "Amended in 6a")
itemLabel, else the first text, select or reference sub-field.pickMember; the locale-fallback cases are left to the publish warnings in 6b.EntryPicker's several-choice marks are check icons inside each option, not KumoCheckboxes, so the listbox nests no controls. A group found in several languages is listed once.i18n
New words go in the lazy
fieldsnamespace (fields.repeater.*,fields.reference.*,fields.usedBy.*, threeeditorkeys and two issue codes).enis hand-written. The other 18 languages are AI drafts awaiting review, imported withpnpm i18n:import. No language is marked reviewed.Checks
pnpm design:lint(rtl-logical and rtl-icon included) andpnpm size:checkpass.pnpm design:shotsat 1440 and 390 px passes: no sideways scroll, no console errors. It covered the event editor, a referenced place ("Used by") and the open picker, on a local scratch site seeded through a store withfieldTypes.packages/ui/test/repeater-field.test.tsx: add focus, move focus and announcement, remove focus and undo, last-item removal to "Add item", duplicate, errors opening items and the collapsed list;entry-picker.test.tsx: search through the prop, polite count, arrows and Enter, focus back to the trigger, several up to the remaining count, keep searching from the cursor, plusReferenceListandUsedByList;entry-fields-form.test.tsx: slots and paths, errors by path, translation fallbacks, undo hand-off;apps/studio/test/fields-references.test.tsx: picker over the pick route, Not found, the undo toast, item issues, Used by.pnpm checklocally. Lint, size, generated-file checks and typecheck pass. The test stage has three caveats:turbo run test --concurrency=1 --continue, 18 of 19 packages pass, including Studio (84 files) and UI (20). The default--concurrency=3overloads this machine: unrelated Studio tests time out at 20 s.i18n-messages.test.ts"every catalog file is valid ICU" now has 60 s instead of the 20 s default; it compiles every catalog synchronously and takes about 5 s alone.store.test.tsandlist-values-query.test.tsfail, all withEADDRNOTAVAIL. That is ephemeral-port exhaustion, with another project'sworkerdprocesses running. core-ts's change here is type-only, and CI covers those variants.🤖 Generated with Claude Code