Repository navigation
feat(website-builder): Content Entry input type - #5576
SvenAlHamad wants to merge 29 commits into
Conversation
Approved design for the createContentEntryInput WB primitive: manual + query modes, autoLoad server-side data loading, bridge-component reuse, and phased plan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sort is Record<fieldId,'asc'/'desc'> (single field, via transformSortToArray); search is a native sdk.cms.listEntries param (fullTextSearch-enabled fields only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mode)
Adds a new Content Entry editor input that lets a page editor hand-pick one or more CMS entries from an author-fixed model, stored as references ({ id, modelId }).
- website-builder-sdk: ContentEntryInput type + ContentEntryReference, createContentEntryInput factory (renderer 'Webiny/ContentEntry'), wired into ComponentInput and InputFactory unions.
- website-builder-react: re-export the factory + types for app developers.
- app-website-builder: 'Webiny/ContentEntry' editor renderer reusing the CMS ref autocomplete presenters via a scoped DI container (single vs. list on input.list); registered in ElementInputRenderers; added @webiny/app-headless-cms dependency.
Verified: tsc --build across the changed packages' closure (0 errors), adio, format, lint (exit 0). Query mode and autoLoad are later phases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds an editor-configured query mode alongside manual entry-picking, on the same author-fixed model.
- website-builder-sdk: ContentEntryInput gains mode ('manual' | 'query') and a query config (ContentEntryQueryConfig); new ContentEntryQueryValue captures the editor's chosen sort/limit/search. Types re-exported via website-builder-react.
- app-website-builder: the 'Webiny/ContentEntry' renderer branches on mode. Manual DI wiring moved into a ManualRenderer sub-component; new QueryInner renders a query builder — sort field (from dev-declared options) + direction, limit (bounded by max), and a search box — writing a ContentEntryQueryValue. Single sort field only (per Phase 0).
Query execution (sdk.cms.listEntries) and loadMore land with Phase 4 (autoLoad) + the app-side bridge.
Verified: tsc --build closure (0 errors), adio, format, lint (exit 0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 4 (increment 1): adds ContentEntryInput.autoLoad (default true) and a decoupled resolveContentEntryInput(input, value, loader) that turns references (getEntry) or a query spec (listEntries) into CMS entries. Loader is injected so website-builder-sdk stays decoupled from cms-sdk. Wiring into the render pipeline follows in the next increments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…live/SSR)
Phase 4 (increment 2): live/SSR path for autoLoad, mirroring the CMS pattern (resolve on the server, hand resolved data to the client render loop — no useEffect).
- website-builder-react: new resolveAutoLoad(document, components) RSC helper walks the document, resolves every contentEntry input with autoLoad !== false via the CMS contentSdk (references -> getEntry, query -> listEntries), keyed by 'elementId:inputName'. A ContentEntryResolutionProvider/context carries the resolved map (plain object, RSC-serializable); DocumentRenderer wraps the tree with it. LiveElementRenderer.onResolved gains a contentEntry branch that swaps the raw value for the resolved data (falls back to raw for autoLoad:false / unresolved). Adds @webiny/cms-sdk dependency.
- website-builder-nextjs: DocumentRenderer forwards the resolvedContentEntries prop.
- cms-sdk: ListEntriesParams gains search; LiveSdk forwards it (query-mode search).
Usage: await resolveAutoLoad(document, components) in the RSC page, pass to <DocumentRenderer resolvedContentEntries={...}>. Editor-preview reactive resolution is increment 3.
Verified: tsc --build closure (0 errors), adio (no cycle), format, lint (exit 0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 4 (increment 3): the editor iframe re-resolves autoLoad content-entry inputs on the client as the editor changes the selection/query, mirroring the CMS refCache pattern. - website-builder-react: new contentEntryEditorCache — a mobx-observable cache with a lazy, deduped resolve() (async write inside runInAction), keyed by the raw value so an edit yields a fresh key and a re-resolve. LiveElementRenderer.onResolved now branches: in the editor (contentSdk.isEditing()) it reads/populates the cache and falls back to the server-resolved value until it lands (no flash); live/SSR keeps the server pre-pass path. No useEffect for the data. Verified: tsc --build closure (0 errors), adio, format, lint (exit 0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ements) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ontent-entry types The @webiny/sdk-nextjs barrel re-exports WB factories via an explicit list, so the new content-entry APIs weren't reaching consumers (the Next.js starter imports from @webiny/sdk-nextjs). Add createContentEntryInput, resolveAutoLoad, and the ContentEntry* types. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…guard non-array
resolveAutoLoad previously required Component[], but component modules are 'use client' — importing them into a server component (the Next.js page) yields a non-iterable client reference. Relax the param to { manifest }[] (only .manifest is read) so a server-safe manifest module can be passed, and guard against a non-array.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ilt-ins The built-in editor components (Box, Grid, Root, …) come from '.manifest.ts' files marked 'use client', so on the server their exports are client-reference proxies and blueprint.manifest is undefined. They never contain contentEntry inputs, so resolveAutoLoad now iterates only the caller-provided manifests, with a guard for malformed carriers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A selected tag with a long label grew past the field in the flex-wrap row: the tag had no max-width, so its inner truncate never engaged. Cap each selected tag at max-w-full and add min-w-0 to the Tag content span so truncation works when the tag is width-constrained. Fixes overflow in the content-entry picker (and the CMS ref field), long titles now ellipsize. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The selected-tags row is a flex item (flex-1) with default min-width:auto, so it grew to its content and the tags' max-w-full resolved against an overgrown row (no effect). Add min-w-0 so the row shrinks to the field width; combined with max-w-full + the truncating content span, long tags now ellipsize instead of overflowing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… tag list The MultiAutoComplete rendered selected entries as in-field tags, which overflowed with long titles and read poorly. Rework the list-mode picker to a single AutoComplete (search + add) plus a separate block of dismissible tags below. The tag block has a definite width, so long labels truncate cleanly instead of overflowing. Reuses the existing multi autocomplete presenter for search/resolution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…h scoped) We no longer use MultiAutoComplete in the content-entry picker, so revert the MultiAutoCompleteInput row/tag changes. Keep only the one-line Tag content-span min-w-0, which the content-entry tag list relies on for truncation. (The CMS reference field's MultiAutoComplete overflow remains a separate, pre-existing issue.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-entry list Replace the tag list with reorderable rows (reusing ObjectRow + ObjectRowActions from the object-list input): each selected entry shows a truncating title with hover actions to move up/down and remove. The autocomplete now clears after each pick (remounted via a key). Order is persisted by writing the references in the chosen order. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… Tag The multi picker switched to ObjectRow-based rows, so the Tag content-span tweak is no longer needed. Revert it so admin-ui is untouched and the branch is fully scoped to content-entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The read API sorts meta fields by bare name but value fields as values_<fieldId>_DIR. The resolver passed the field id verbatim, so sorting by a value field (e.g. title) failed. Map field ids to sort keys (meta stay bare, others get a values_ prefix), so query mode can sort by value fields using plain field ids in query.sort.fields (e.g. ["title"]). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…_ magic) Pass query.sort.field straight to the read API instead of auto-prefixing. Sort keys now match the API exactly: value fields as values_<fieldId>, meta fields bare. Documented on ContentEntryQueryConfig.sort. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…when single
query.sort.fields now accepts { field, label } (or a bare string), so the editor can show a friendly label (e.g. 'Title') instead of the raw sort key. When only one sortable field is configured, the 'Sort by' field picker is hidden and only the direction is shown.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the asc/desc direction dropdown with a SegmentedControl (fullWidth) in both the single- and multi-field sort layouts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rop autoLoad flag
- Query resolver embeds the continuation query (modelId/sort/limit/search) in the result when pagination is enabled, so the client can fetch further pages.
- New useContentEntryList client hook: seeds from the server-rendered first page (SSR-safe, no mount fetch), exposes { items, hasMore, loading, loadMore }; loadMore fetches the next page via cursor and appends; re-seeds when the query changes (editor preview).
- Removed the autoLoad flag — the framework always resolves content-entry inputs (autoLoad:false is redundant with plain inputs). Exported the hook + ResolvedContentEntryQuery/ContentEntryList via sdk-nextjs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…amping Typing a limit above max now keeps the entered value and shows 'Maximum is N.' (also 'Must be at least 1.' / 'Enter a whole number.'), rather than rewriting the input to the cap. Only valid values are committed; a helper description shows the cap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Manual list: selected entries use a non-interactive row (no misleading click affordance) with hover move/remove; empty state ('No entries selected yet.').
- Query mode: section header (input label + description) above the controls; search placeholder.
- Single sort field now sorts by default (ascending) so the editor's shown direction matches what renders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Covers manual single/list resolution, empty values, broken-ref filtering; query params, values_ sort key pass-through, default limit, single-field default sort, pagination continuation embed, and the no-model empty result. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gination, sort keys, UX) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🚓 Slop Cop Coherent, well-scoped PR implementing the Content Entry input feature as described; no integrity red flags found, only a couple of minor style nits. 📏 Code-style rule checks 🟡 Low — Multi-named import on one line packages/app-website-builder/src/inputRenderers/ContentEntryInput.tsx imports 🟡 Low — Multi-named import in resolveContentEntry.ts packages/website-builder-sdk/src/contentEntry/resolveContentEntry.ts imports 🟡 Low — Multi-named import in useContentEntryList re-export packages/website-builder-react/src/contentEntry/useContentEntryList.ts re-exports 🟡 Low — String concatenation-like conditional spread for query result packages/website-builder-sdk/src/contentEntry/resolveContentEntry.ts uses an inline conditional spread Automated, non-blocking heads-up from an LLM. It can be wrong — use your judgment. Regenerates on every push. |
…nto SDK Move CMS content-entry resolution out of React components and into the SDK-level BindingsResolver, following the CMS RefCache pattern. This replaces the React context + props threading with a singleton mobx cache (ContentEntryCache) that both the server pre-pass and editor preview read from directly. - Add ContentEntryCache (SDK singleton) combining server-seeded values and editor-preview lazy resolution in one mobx store - Move resolution logic into BindingsResolver.resolveContentEntryValue() - Delete ContentEntryResolutionContext and contentEntryEditorCache - Remove resolvedContentEntries prop from DocumentRenderer - Change resolveAutoLoad to seed the cache directly (void return) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Content Entry Input for the Website Builder
Adds a new Content Entry input type (
createContentEntryInput) that lets a page editor either hand-pick CMS entries from an author-fixed model, or configure a dynamic query (sort / limit / search / pagination) against that model. The framework resolves the input into CMS entries server-side for SEO, and query mode adds cursor-basedloadMorevia a client hook.Full design + as-built notes:
plans/wb-content-entry-input.md.What's in this PR
SDK primitive & resolver (
website-builder-sdk)createContentEntryInput({ name, models, list, mode, query })→{ type: "contentEntry", renderer: "Webiny/ContentEntry", ... }.resolveContentEntryInput(input, value, loader)— resolves manual single/list references and query specs into entries; injectableContentEntryLoaderkeeps it decoupled from@webiny/cms-sdk.ContentEntryInput,ContentEntryReference,ContentEntryQueryConfig,ContentEntryQueryValue,ResolvedContentEntryQuery.Server-side resolution & pagination (
website-builder-react,-nextjs)resolveAutoLoad(document, manifests)— RSC pre-pass that resolves everycontentEntryinput against the CMS SDK; threaded throughDocumentRenderer→ context →LiveElementRenderer.onResolved. NouseEffectfor SSR data.useContentEntryList— client hook seeded from the server's first page;loadMorefetches subsequent cursor pages.contentEntryEditorCache) mirroring the CMS ref cache.Editor renderer (
app-website-builder)Webiny/ContentEntrysidebar renderer: manual mode (search-and-add autocomplete + reorderable rows, reusing the Headless CMS reference presenters via DI); query mode (sort field + segmented direction, limit with validation, search). One sort field → picker hidden, sorts by default.Plumbing
sdk-nextjsbarrel exports the factory,resolveAutoLoad,useContentEntryList+ types.cms-sdk:searchparam onlistEntries.Notes / decisions
autoLoad:false— the framework always resolves server-side; a component that wants to fetch for itself just uses plain inputs. (Flag was prototyped then removed.)values_<fieldId>, meta fields bare (novalues.dot-path magic). The read-API dot/underscore inconsistency is a known core item, parked.loadMorepages aren't in server HTML (not crawlable); a single declared sort field is mandatory to sort by default.Testing
resolveContentEntryInputunit-tested (15 cases: manual single/list, deleted-ref filtering, query params,values_sort keys, default limit, single-field default sort, pagination embed). 145/145website-builder-sdktests pass.adio✅,lint✅,generateTsConfigsInPackages(no changes),format✅.next-theming-content-entrysample app; full live multi-mode smoke test still recommended.🤖 Generated with Claude Code