Skip to content

feat: collapsible grouped fields, blocks, and repeaters#635

Closed
bobbwal wants to merge 25 commits intoSonicJs-Org:mainfrom
bobbwal:fix/grouped-fields-ui
Closed

feat: collapsible grouped fields, blocks, and repeaters#635
bobbwal wants to merge 25 commits intoSonicJs-Org:mainfrom
bobbwal:fix/grouped-fields-ui

Conversation

@bobbwal
Copy link
Copy Markdown
Contributor

@bobbwal bobbwal commented Feb 4, 2026

Description

Adds collapsible UI for grouped object fields, blocks, and repeaters in admin forms, defaults them to collapsed, and aligns toggle chevrons. New items expand automatically.

Also adds explicit object field layout control via objectLayout ('nested' | 'flat') and clarifies collapsed behaviour in docs (collapsed applies to nested only).

This PR also adds session-level collapsed/expanded state persistence for grouped fields, repeaters, and blocks (scoped per document/collection), plus validation visibility improvements so hidden errors are revealed.

Fixes #634

Includes fixes from #649 and #645 (merged into this branch).

Changes

  • Add group headings for content blocks and collapsible object groups
  • Add objectLayout support for object fields (nested default, flat non-collapsible)
  • Add collapsed option for object group fields (default collapsed)
  • Make block and repeater items collapsible with toggle chevrons
  • Default items to collapsed; auto-expand newly added and duplicated items
  • Persist collapsed/expanded state for objects, repeaters, and blocks within session
  • Scope persisted UI state by collection/document to avoid cross-collection leakage
  • Keep persisted state stable through reorder flows
  • Improve validation visibility for collapsed sections (reveal invalid path and focus first invalid control)
  • Align chevron rotation classes with toggle logic
  • Update docs for objectLayout + collapsed behavior
  • Add/update E2E coverage:
  • tests/e2e/53-object-layout.spec.ts
  • tests/e2e/55-collapsible-state-persistence.spec.ts
  • tests/e2e/56-collapsible-validation-visibility.spec.ts
  • Update hardening in related E2E flows (54-hero-cta-style-persistence.spec.ts)

Testing

  • npm run type-check
  • tests/e2e/53-object-layout.spec.ts
  • tests/e2e/55-collapsible-state-persistence.spec.ts
  • tests/e2e/56-collapsible-validation-visibility.spec.ts
  • tests/e2e/54-hero-cta-style-persistence.spec.ts
  • Manual UI verification in admin content forms (objects + repeaters + blocks)

Unit Tests

  • Added/updated unit tests
  • All unit tests passing

E2E Tests

  • Added/updated E2E tests
  • All E2E tests passing

Screenshots/Videos

Before:
blocks-ui-before

After:
blocks-ui-after-02

Checklist

  • Code follows project conventions
  • Tests added/updated and passing
  • Type checking passes
  • No console errors or warnings
  • Documentation updated (if needed)

Follow-up planned: refactor collapsible-state persistence helpers to reduce duplication and simplify key/state handling (no functional change).


Generated with Claude Code in Conductor

@bobbwal bobbwal changed the title Fix/grouped fields UI feat: collapsible grouped fields, blocks, and repeaters Feb 4, 2026
@bobbwal bobbwal marked this pull request as draft February 24, 2026 13:11
@bobbwal
Copy link
Copy Markdown
Contributor Author

bobbwal commented Feb 24, 2026

Need to investigate further but it seems that in the grouped/blocks form setup, validation errors inside collapsed groups can be hidden, so Update / Update & Publish can appear unresponsive. This is a UX issue rather than a save failure. Follow-up could auto-expand groups with errors, show an error badge/count on collapsed headers, and focus/scroll to the first invalid field on submit.

…ields-ui

# Conflicts:
#	my-sonicjs-app/src/collections/page-blocks.collection.ts
#	packages/core/dist/chunk-2YRNPIU4.cjs
#	packages/core/dist/chunk-2YRNPIU4.cjs.map
#	packages/core/dist/chunk-6RABGLOO.cjs
#	packages/core/dist/chunk-6RABGLOO.cjs.map
#	packages/core/dist/chunk-7DU5PUKL.js
#	packages/core/dist/chunk-CC4JXOXD.js
#	packages/core/dist/chunk-D77GPCUP.cjs
#	packages/core/dist/chunk-D77GPCUP.cjs.map
#	packages/core/dist/chunk-DADFCDML.js
#	packages/core/dist/chunk-DADFCDML.js.map
#	packages/core/dist/chunk-FQAOOSEB.js
#	packages/core/dist/chunk-FQAOOSEB.js.map
#	packages/core/dist/chunk-GFKIWSGM.cjs
#	packages/core/dist/chunk-GFKIWSGM.cjs.map
#	packages/core/dist/chunk-IUARXOBE.cjs
#	packages/core/dist/chunk-IUARXOBE.cjs.map
#	packages/core/dist/chunk-K6TOTMAZ.js
#	packages/core/dist/chunk-K6TOTMAZ.js.map
#	packages/core/dist/chunk-PM35AAL5.js
#	packages/core/dist/chunk-PM35AAL5.js.map
#	packages/core/dist/chunk-QK5PFGDM.cjs
#	packages/core/dist/chunk-QKY6I4B7.js
#	packages/core/dist/chunk-RTXGSM7L.cjs
#	packages/core/dist/chunk-THCZHX25.cjs
#	packages/core/dist/chunk-VXTHM6MB.js
#	packages/core/dist/chunk-VXTHM6MB.js.map
#	packages/core/dist/chunk-YGWKSR7I.js
#	packages/core/dist/chunk-YGWKSR7I.js.map
#	packages/core/dist/chunk-ZKA4WKRO.cjs
#	packages/core/dist/chunk-ZKA4WKRO.cjs.map
#	packages/core/dist/index.cjs
#	packages/core/dist/index.js
#	packages/core/dist/middleware.cjs
#	packages/core/dist/middleware.js
#	packages/core/dist/migrations-AIIAB6XI.js.map
#	packages/core/dist/migrations-FKBLWET7.cjs.map
#	packages/core/dist/migrations-JXEISWW5.cjs.map
#	packages/core/dist/migrations-U57UHVWR.cjs.map
#	packages/core/dist/migrations-WJVCIKQO.js.map
#	packages/core/dist/migrations-ZI3P6YZN.js.map
#	packages/core/dist/routes.cjs
#	packages/core/dist/routes.js
#	packages/core/dist/services.cjs
#	packages/core/dist/services.js
#	packages/core/src/db/migrations-bundle.ts
#	packages/core/src/templates/components/dynamic-field.template.ts
# Conflicts:
#	my-sonicjs-app/src/collections/page-blocks.collection.ts
#	packages/core/dist/chunk-2YRNPIU4.cjs
#	packages/core/dist/chunk-2YRNPIU4.cjs.map
#	packages/core/dist/chunk-3ZUCKXWH.js
#	packages/core/dist/chunk-3ZUCKXWH.js.map
#	packages/core/dist/chunk-6RABGLOO.cjs
#	packages/core/dist/chunk-6RABGLOO.cjs.map
#	packages/core/dist/chunk-7DU5PUKL.js
#	packages/core/dist/chunk-CV3VJQSK.cjs
#	packages/core/dist/chunk-CV3VJQSK.cjs.map
#	packages/core/dist/chunk-DADFCDML.js
#	packages/core/dist/chunk-DADFCDML.js.map
#	packages/core/dist/chunk-ELKAJLYR.cjs
#	packages/core/dist/chunk-ELKAJLYR.cjs.map
#	packages/core/dist/chunk-F3B7Q2TS.js
#	packages/core/dist/chunk-F3B7Q2TS.js.map
#	packages/core/dist/chunk-FQAOOSEB.js
#	packages/core/dist/chunk-FQAOOSEB.js.map
#	packages/core/dist/chunk-H764CAXQ.cjs
#	packages/core/dist/chunk-H764CAXQ.cjs.map
#	packages/core/dist/chunk-NY3V264Z.js
#	packages/core/dist/chunk-OAV6BLZX.js
#	packages/core/dist/chunk-QK5PFGDM.cjs
#	packages/core/dist/chunk-W3CLHJUC.cjs
#	packages/core/dist/chunk-XFK5GC5H.js
#	packages/core/dist/chunk-XFK5GC5H.js.map
#	packages/core/dist/chunk-XI6ETTJM.js
#	packages/core/dist/chunk-XI6ETTJM.js.map
#	packages/core/dist/chunk-YKPHABCW.cjs
#	packages/core/dist/chunk-YKPHABCW.cjs.map
#	packages/core/dist/chunk-ZDH6MHUU.cjs
#	packages/core/dist/index.cjs
#	packages/core/dist/index.js
#	packages/core/dist/middleware.cjs
#	packages/core/dist/middleware.js
#	packages/core/dist/migrations-7RCSUPXP.cjs.map
#	packages/core/dist/migrations-I6PIUYDS.js.map
#	packages/core/dist/migrations-LCELTSFO.js.map
#	packages/core/dist/migrations-NXINNQAE.cjs.map
#	packages/core/dist/migrations-U57UHVWR.cjs.map
#	packages/core/dist/migrations-WJVCIKQO.js.map
#	packages/core/dist/routes.cjs
#	packages/core/dist/routes.js
#	packages/core/dist/services.cjs
#	packages/core/dist/services.js
#	packages/core/src/db/migrations-bundle.ts
@bobbwal
Copy link
Copy Markdown
Contributor Author

bobbwal commented Feb 27, 2026

Need to investigate further but it seems that in the grouped/blocks form setup, validation errors inside collapsed groups can be hidden, so Update / Update & Publish can appear unresponsive. This is a UX issue rather than a save failure. Follow-up could auto-expand groups with errors, show an error badge/count on collapsed headers, and focus/scroll to the first invalid field on submit.

This has been sorted, along with the following:

Update since original PR description:

  • Added per-document/collection session persistence for collapsed state across object groups, repeaters, and blocks
  • Added collapsed validation reveal behavior for hidden invalid fields
  • Added E2E coverage for state persistence and validation visibility
  • Addressed follow-up review issues around key scoping and nested object serialization host resolution

@bobbwal bobbwal marked this pull request as ready for review February 27, 2026 01:05
bobbwal added 3 commits March 6, 2026 19:59
…er add-item behavior

- Scope
  structured-array operations to direct child items to prevent parent arrays from capturing nested descendants
  - Harden structured-field initialization with template-skipping and init guards
  - Resolve list/template/hidden input references live during array actions to avoid stale refs after save/reload
  - Fix nested array empty-state visibility targeting to direct empty panel only
  - Add unit coverage for selector scoping in dynamic field template tests
  - Add E2E coverage for nested array serialization, deletion persistence, and add-after-return flow using page-blocks fixture updates
- Rebuild @sonicjs-cms/core distribution outputs
  - Update generated migrations bundle to match source changes
@bobbwal
Copy link
Copy Markdown
Contributor Author

bobbwal commented Mar 6, 2026

Update (Follow-up Fixes Added)

Added a follow-up fix for nested repeater serialization/regression uncovered after testing.

What was fixed

  • Fixed nested array serialization scope so parent arrays only read direct child .structured-array-item elements.
  • Fixed add-item behavior after save/reload by resolving list/template/hidden input references at action time (prevents
    stale refs).
  • Added safer structured init flow (skip template nodes + init guards) to avoid partial init states.
  • Fixed empty-state panel behavior to target only the array’s direct empty-state element.
  • Ensured nested children are preserved correctly without being duplicated at root level.

Test coverage added/updated

  • Added/updated unit coverage in:
    • packages/core/src/__tests__/templates/dynamic-field-extended.test.ts
  • Added E2E regression coverage in:
    • tests/e2e/57-nested-array-serialization-scope.spec.ts

Verification

  • npm test --workspace=@sonicjs-cms/core -- src/__tests__/templates/dynamic-field-extended.test.ts
  • BASE_URL=http://localhost:8787 npx playwright test tests/e2e/57-nested-array-serialization-scope.spec.ts --config tests/ playwright.config.ts

All passing locally.

@bobbwal bobbwal marked this pull request as draft March 9, 2026 16:03
- prevent structured field re-initialization while the document is still loading
- add nested object serialization regressions in unit and Playwright coverage
- update the sample page blocks schema fixture for the repro flow
@bobbwal
Copy link
Copy Markdown
Contributor Author

bobbwal commented Mar 9, 2026

Update (Follow-up Fixes Added)

What was happening

For nested object-of-objects fields, saves were succeeding, but reopening the edit form could rewrite the parent hidden JSON to only include the first parsed child object. In practice this meant cases like openingHoursWeek.monday/tuesday/wednesday could save correctly, then appear partially wiped on a later edit.

Root cause

Structured field initialization was being re-run from repeated inline scripts while the document was still loading. That
allowed a parent object container to initialize before all sibling child objects had been parsed, then mark itself
initialized too early.

Fix

  • Prevent structured field re-initialization while document.readyState === 'loading'
  • Keep the direct-child structured object host lookup improvements
  • Add regression coverage for nested object-of-objects persistence

Tests

  • npm test --workspace=packages/core -- dynamic-field-extended.test.ts
  • BASE_URL=http://localhost:8787 npx playwright test tests/e2e/58-nested-object-serialization.spec.ts --config tests/playwright.config.ts

@bobbwal bobbwal marked this pull request as ready for review March 9, 2026 17:11
@bobbwal
Copy link
Copy Markdown
Contributor Author

bobbwal commented Mar 11, 2026

Added a follow-up fix for media fields inside structured-array object rows.

What was happening

Media selection inside repeatable object rows could target the wrong field instance in admin forms. In practice, selecting/uploading media for a later row could do nothing at first, then eventually update an earlier row instead.

While fixing that path, I also found a related template-token issue in nested gallery-style blocks: block insertion was
replacing nested array __INDEX__ placeholders too early, which caused later rows to reuse row-0 media field names/ids.

Fix

  • Scope media picker selection to the active modal target instead of shared stale field state
  • Ensure single-media preview/remove actions update only the field that opened the picker
  • Preserve distinct placeholder tokens for block insertion vs structured-array row insertion so repeated media rows get unique field ids/names
  • Add regression coverage for gallery-style arrays of image objects inside blocks

Tests

  • npm test --workspace=packages/core -- dynamic-field.test.ts dynamic-field-extended.test.ts
  • BASE_URL=http://localhost:8787 npx playwright test tests/e2e/59-array-media-picker-targeting.spec.ts --config tests/playwright.config.ts

@lane711
Copy link
Copy Markdown
Collaborator

lane711 commented Apr 1, 2026

Thank you @bobbwal! This has been cherry-picked as PR #729 (currently in CI). Your collapsible fields feature is on its way to the next release! 🎉

@lane711 lane711 closed this Apr 1, 2026
@lane711
Copy link
Copy Markdown
Collaborator

lane711 commented Apr 1, 2026

More context — your collapsible fields feature was cherry-picked into PR #729, currently in CI. This is a big UX improvement and will ship in the next release. Amazing work on this one, @bobbwal — 25 commits of solid engineering!

Our process: we cherry-pick fork PRs into lane711 branches to run full CI. Your authorship is preserved in the git history.

lane711 pushed a commit that referenced this pull request Apr 1, 2026
…#635)

Add collapsible/expandable sections for grouped fields in the admin content
form, improving UX for complex nested collection schemas (objects, arrays,
blocks). Includes drag-and-drop reordering, validation visibility, and
proper serialization of nested structures.

Cherry-picked from bobbwal/sonicjs fix/grouped-fields-ui branch.
E2E tests 55-58 skipped pending CI fixture data.

Co-Authored-By: Rob Walton <bobbwal@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
lane711 added a commit that referenced this pull request Apr 1, 2026
…#635) (#739)

Add collapsible/expandable sections for grouped fields in the admin content
form, improving UX for complex nested collection schemas (objects, arrays,
blocks). Includes drag-and-drop reordering, validation visibility, and
proper serialization of nested structures.

Cherry-picked from bobbwal/sonicjs fix/grouped-fields-ui branch.
E2E tests 55-58 skipped pending CI fixture data.

Co-authored-by: Rob Walton <bobbwal@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

Enhancement: Collapsible UI for block and repeater items

2 participants