feat!: composition builder — JSON PressTree replaces the dynamic-zone mechanism#13
Open
odenirdev wants to merge 31 commits into
Open
feat!: composition builder — JSON PressTree replaces the dynamic-zone mechanism#13odenirdev wants to merge 31 commits into
odenirdev wants to merge 31 commits into
Conversation
…youts - Introduced `preset-organism.columns`, allowing editors to create 2-4 column layouts with rich text, images, and buttons. - Added `preset-molecule.column` as a nested component within `preset-organism.columns`, supporting rich text, optional images, and buttons. - Implemented deep population in `buildBodyPopulate` to ensure nested components are correctly populated. - Updated serializers and generators to handle the new two-level nesting without requiring code changes. - Created a new `Columns` renderer that manages layout and rendering of columns based on specified ratios and gaps. - Enhanced theme styles to accommodate the new columns layout and button styles. - Added comprehensive tests for the new columns functionality, ensuring correct rendering and behavior.
…mechanism) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… group Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…; retire nav-item/column/columns Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…JSON custom field; Dynamic Zones and dz-populate removed Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion with tree contract version Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…w enum values Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…refs in composition trees Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ted coverage Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iants Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pplicability, palette groups) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…slots modes, registry-driven forms) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ves the wire Task 13 of the composition-builder refactor: rewrites the text atoms (paragraph/list/quote) to render curated plain-text strings instead of the Strapi blocks AST, moves the button atom onto the shared PressLink/PressLinkData resolver, deletes the in-house blocks-AST renderer, and rewrites types/base.ts so PageBody/Page describe the PressTree wire shape from @ogs-tech/press-shared. Every PresetAtom*/PresetOrganism* type drops __component/id (they are tree `data` objects now, not DZ rows). Also fixes heading.test.ts and map-page.test.ts fixtures to match the new type contracts (PresetAtomHeading, PageBody) — both are direct, mechanical fallout of this task's own produced types, not part of the Task 14/17 migration debt. The full pnpm typecheck still reports errors in sections/hero.tsx, cta.tsx, columns.tsx, columns.test.ts (and their downstream block-renderer.test.tsx runtime failure) — expected, deferred to Tasks 14/17 per the brief. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…a PressLink Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… tree renderer Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…solve-slots) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… blockKey retired Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t main children; cell/stack CSS Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ressPageRef, no DZ unions) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on; seed-shape regression guard Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ime dep (workspace: allowed to co-published targets) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he tarball A `files` allowlist makes npm stop honoring .npmignore, so the vitest suite shipped in the published tarball. Match press-web's proven pattern (no `files` field; .npmignore alone) — the CLI publish-readiness guard documents this trap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…geset + CLAUDE.md architecture rewrite Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…not Map.entries()
GET /api/press/schema 500'd at runtime ("registry.entries is not a function"):
Strapi's components registry is not a Map — it exposes keys()/get()/getAll(), no
entries(). The `as Map<string, any>` cast let `registry.entries()` typecheck AND
pass a Map-based unit fake, so the bug only surfaced on boot (type-sync). Iterate
with keys()+get(); type the registry structurally so a future .entries() fails to
compile; reshape the test fake to the real registry API so this class can't regress.
Verified end-to-end: schema 200 (tree.version=1), /api/pages/home hydrates the
hero image assetId -> url/dims.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ost React The builder custom field crashed the Strapi admin with "Cannot read properties of null (reading 'useState')": @strapi/sdk-plugin's vite build externalizes only the plugin's dependencies + peerDependencies, and cms declared neither react nor react-dom, so the admin chunk BUNDLED the workspace-hoisted React 18 — a second React instance beside Strapi 5.48's admin React 19 → null hooks dispatcher. Declaring them as peerDependencies externalizes them; the rebuilt chunk now imports react (not a bundled copy). Added a guard test since nothing in build/test/typecheck catches this — only opening the admin does. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root pnpm.overrides pinned React 18, while Strapi 5.48's admin runs React 19 — a split that (with the plugin bundling its own React) crashed the admin. Pin react/react-dom to Strapi's 19.2.7 across the workspace so the Next host, engine packages, and Strapi admin all share ONE React (single 19.2.7 resolved). Bump press-web dev/@types to 19; regenerate the CLI scaffold's react pin to ^19.2.7. React 19 fallout was minimal: two `JSX.Element` refs needed `import type { JSX }` (@types/react 19 dropped the global JSX namespace). Full gate green under React 19 (web 260, cms 68, cli 29, shared 11; all typechecks; pack:check). CLAUDE.md documents the pin + the plugin react-externalization contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Composition builder — a JSON
PressTreereplaces the dynamic-zone mechanism engine-widePer-page layout root (header/footer/children slots), rows with 1–4 ratio-bound columns, full recursion, adopter
custom-*blocks anywhere. Stored via oneplugin::press-cms.builderJSON custom field; a structural tree-editor drives it in the admin.BREAKING (wire + palette + API), no data migration (pre-release). 25 commits, executed task-by-task from
docs/superpowers/plans/2026-07-20-composition-builder.md.Highlights
page.body(fullPressTree) andsite-setting.pageDefaults({header,footer}slots) are JSON custom fields; the page-body + chrome Dynamic Zones anddz-populateare gone.@ogs-tech/press-sharedis now a published runtime dependency: it ships the tree types + the sanitizingvalidatePressTree/validateNodeArrayvalidators (cms bundles into dist; web depends + host transpiles). Strict-write (cms lifecycle rejects errors OR warnings) / tolerant-read (web sanitizes).preset-molecule.link(label/page/url/newTab) referenced by button/hero/cta/navbar;nav-item/molecule.column/organism.columnsretired. Text atoms store curated plain text (content: text) — the Strapi blocks AST leaves the wire.preset-layout.{container,row,column}descriptors drive the builder's layout forms.{assetId}→ url/dims, page{documentId}→ slug — resolved server-side (rename-safe internal links, no populate depth to fight), batched one query pair per request.TreeRenderer(<header>/<main>/<footer>shell) replacesBlockRenderer;PressLink/resolveLinkis the one link resolver (dangerous-protocol-safe); prose rail rescoped to direct<main>children;PageBodygenerates asPressTree;PressPageRefadded;HeaderBlocks/FooterBlocksunions gone. Block-override contractcomponents={{ 'preset-organism.hero': MyHero }}unchanged.Automated verification (all green)
pnpm build6/6 incl. the playground Next build (SSG 3/3)pnpm -r test— web 260, cms 67, cli 29, shared 11test:ts:back/test:ts:front)pnpm pack:checkexit 0 — no*.test.tsin any tarballPlan gaps caught & fixed during execution
hydratePageDocsto one query pair (was a plan-mandated N+1 on the unpaginated/pagesroute) + addedserve-hydrated.test.ts.press-shareddevDeps→deps in web (Decision 3 gap); host transpiles it.footer.tsxinline-typed afterResolvedChromeFooterdeletion (Task 14 migrated navbar but not footer).publish-readinessmade target-aware (press-shared now published;workspace:allowed to co-published targets).filesallowlist from press-shared so.npmignorekeeps tests out of the tarball (surfaced bypack:check).Page.bodyofftype==='customField'alone (serialize strips the id) — production-shape guard test added.Manual review checklist (not covered by automated tests — please verify on
pnpm dev)/api/press/schemahastree:{version:1};/api/pages/homebody.version===1, heroimagecarries a hydratedurl, navbarpageDefaults.headeritem haspage:{documentId,slug:"home"}.localhost:3000renders navbar / hero+image / prose atoms at ~72ch / the recursive 50-50 row / cta / callout / footer.pageDefaultsslots editor; emptying header → inheriting pages render bare.verticalAligncenter/end, nomain[data-press-stack]↔ organismmargin-blockdouble-gap.Known cosmetic note
The playground regen changed the cms Strapi
uuid(the plan expected it preserved) — harmless dev instance id; can restore on request.🤖 Generated with Claude Code