feat: decouple example apps from test space + add setup script [AIS-246]#77
Open
Lisa White (whitelisab) wants to merge 19 commits into
Open
feat: decouple example apps from test space + add setup script [AIS-246]#77Lisa White (whitelisab) wants to merge 19 commits into
Lisa White (whitelisab) wants to merge 19 commits into
Conversation
…es/ [AIS-246]
Copy examples/{nextjs,sveltekit} to test-apps/{nextjs,sveltekit} so we have
an internal place to iterate without mutating the customer-facing example
apps. Test-app package names renamed to @contentful/experiences-testapp-*
to avoid workspace collisions. examples/ untouched.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fresh minimal fixture that a bootstrap script will provision into a caller's space. Shape: 1 promotion ContentType, 3 promotion entries, 3 image assets, 8 ComponentTypes (Section/Heading/RichText/Text/Button/Image + hero-plain + card), 1 page template, 2 DataAssemblies (hero/card), and 1 Experience composed of 1 hero + 2 cards. Uses tempId placeholders so the fixture is self-referential without baking target-space ids in. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…AIS-246] examples/scripts/bootstrap-example.ts provisions the fixture into a caller- specified space + env in 9 steps: ContentTypes, Assets, Entries, Design tokens, ComponentTypes, Template, DataAssemblies, DA→ComponentType links, Experience. Uses contentful-management@12.6.0-dev.4 for the ExO surface (componentType / template / dataAssembly / experience plain clients) and raw HTTP for design_tokens (not yet in the plain client). Idempotent per resource. Verified end-to-end against f7tvosi12w6q / exo-sdk — Experience "landing" publishes cleanly. RichText body is temporarily dropped from the DataAssembly return mapping — the DA validator rejects our body→document GraphQL alias with "Field path _node.body not found." Symbol fields (title, ctaLabel, ctaUrl, image.url) work. Follow-up needed to figure out the RichText mapping shape. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Updates the Next.js + SvelteKit example READMEs, the top-level README's Examples section, and AGENTS.md "Run the example app" so the customer flow is clear: cd examples/scripts && npm run bootstrap; then run the app with a CDA token. Adds a scripts/README.md that explains what the bootstrap provisions and calls out known limitations (RichText body omission, CMA dev-build pin, design_tokens via raw HTTP). Adds scripts/.env.example and updates both example .env.example files with inline pointers to the bootstrap flow. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-246]
The page template was being seeded without a componentTree, so the CDA
wrapped the whole Experience in a Template-variant node whose slots.content
never surfaced. The SDK's resolver skips Template-variant nodes (v1 scope),
so the entire tree rendered empty.
Two changes:
1. Fixture: the page template now declares slots: [{ id: 'content' }] plus
a componentTree with a single Slot node pointing at that slot. That's
what tells the resolver where in the template to drop the Experience's
content-slot nodes.
2. Bootstrap: templates carrying a componentTree need the
Contentful:ComposedImplementation annotation — the server-assigned
default (Contentful:CodedImplementation) requires an empty tree and
publish 422s otherwise. seedTemplate now sets that annotation on both
create and update, and update-detection compares annotations so
fixture edits actually re-publish. seedExperience always upserts on
existing records so template/DA changes further up the run don't leave
the Experience with a stale defining-entity version at publish time.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds Next.js implementations for the two composed ComponentTypes the bootstrap fixture provisions: - components/HeroPlain.tsx — title + CTA + hero image, 2-column grid - components/Card.tsx — image + title + teaser + CTA in a shadowed panel Both read design values via useDesignValues() and consume tempate-bound content properties as regular React props. Wired into experience-config.tsx as `'hero-plain'` and `card` alongside the existing primitives. Also adds color.primary and color.primaryText to design-tokens.ts (the hero uses them as background + foreground). Verified: `npm run dev` against f7tvosi12w6q/exo-sdk renders /landing with the hero + 2 cards, all content resolved from Promotion entries via DataAssembly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hero CTA now points at /contact/sales/ (not /request-demo/), and the On card CTA at /case-studies/on/ (not /customers/on/). Live entries in f7tvosi12w6q/exo-sdk were also patched + republished so the running demo reflects the fix. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The /advanced/[slug] route was crashing because its config only knew
about the six primitives — hero-plain and card weren't registered, so the
SDK's MissingComponent kicked in and hit an SSR bug in useExperience().
Registers hero-plain (bare) and card (with async resolveData) on the
advanced config, adds resolveToken so design tokens flow the same way as
the simple route, and drops the old Button-level resolveData demo (Button
is an internal render detail of the composed CTs now).
The card's resolveData does two things worth showing:
1. A stand-in async enrichment fetch — prepends "Featured:" to the title.
2. A metadata-aware CTA rewrite — non-http URLs get prefixed with
locale + slug from experience.metadata.
Verified: /advanced/landing renders end-to-end.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…AIS-246] The Next.js and SvelteKit example indexes had /demo hardcoded from before the fixture existed, which 404s against a freshly bootstrapped space (the seeded id is `landing`). Also updated the "replace demo with a real id" note on the Next.js index to reflect the new default. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Preview mode reads from preview.xdn.contentful.com, which requires a Content Preview token — the CDA token is rejected there. Both examples were hardcoded to always use CDA_TOKEN, so /advanced/[slug]?preview=true (and the "Advanced demo (preview)" button on the Next.js index) crashed with UnauthorizedError on any real space. Both routes now pick CPA_TOKEN when previewMode is on (falls back to CDA if CPA is unset — the request will 401 in that case, which is still an improvement over silently sending the wrong token to the wrong host). Adds CPA_TOKEN to both .env.example files with a note on when it's needed, and updates both READMEs to document the three tokens (CMA / CDA / CPA) and which route uses which. Also switches sveltekit to $env/dynamic/private so new env vars don't need a code change to be readable. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… [AIS-246] The old "Run it" section only walked through the basic /landing path; readers who then clicked the "Advanced demo (preview)" button on the index page got a 500 with no docs pointing at CPA_TOKEN. Restructures both example READMEs (and touches the top-level README) so: - Steps are numbered and labeled (1 = seed, 2a = basic, 2b = preview) and each step tells you exactly which token env var it needs. - The Next.js "Two routes, same data" table gets a "Try it locally" column with clickable URLs instead of just source-file links (source links moved into a caption below). - Tokens moved into a compact three-row summary table so it's clear which token each step needs, without repeating the guidance inline. - SvelteKit README gets the same shape — one main route + a "preview mode" subsection instead of burying it in a bullet. Verified preview mode end-to-end against f7tvosi12w6q/exo-sdk with a CPA token — /advanced/landing?preview=true returns 200 and renders the hero + 2 cards with async resolveData firing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-246] Node's strict ESM loader rejects the extensionless relative imports in @contentful/experiences-client's dist (e.g. `from "./types"`), which comes in transitively via @contentful/experiences-svelte. The other three workspace packages were already listed in ssr.noExternal — this one was missed. Underlying dist-shape issue is a separate SDK-wide bug worth tracking (the source uses extensionless relative imports and tsup bundle:false emits them verbatim, which breaks any strict-ESM consumer). Contained here to unblock the sveltekit example. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…[AIS-246] The SvelteKit example was authored against a placeholder content model (button/text/header + hi/hero templates), so nothing rendered against the seeded landing Experience even though the SSR pipeline worked. Now it mirrors the Next.js example 1:1 in component vocabulary. Adds: - Section.svelte, Heading.svelte, RichText.svelte, Image.svelte - HeroPlain.svelte, Card.svelte (composed CTs) - design-tokens.ts (same 15 tokens as Next.js) Rewrites (props changed to match the seeded content model): - Text.svelte value → text - Button.svelte text → label, adds target from design values - experience-config.ts fully rewritten to register the 8 CTs and page Small tweak to Page.svelte so the max-width doesn't crush the multi-column card layout. Removes Header.svelte (superseded by Heading.svelte with the shape our seeded ComponentType expects). Verified: http://localhost:5173/landing renders the seeded Experience end-to-end against f7tvosi12w6q/exo-sdk — same content as the Next.js example, same three CTA URLs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rt [AIS-246] The Page template was boxing everything into a 1024px max-width column, which crushed the hero (its background never reached the edge) and left awkward margins on the two-column card grid. SvelteKit's Page component was already a passthrough, so the two examples looked different for no good reason. Strip Page down to a bare flex-column, matching the SvelteKit shape. Each top-level node now handles its own containment: the hero renders edge-to-edge, the cards Section applies horizontal padding via its own design values. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The existing `.nxignore` already lists `examples` to keep them out of `nx run-many`, `nx graph`, `nx affected`, and `nx release`. test-apps serve the same role (internal scratchpads, not part of the SDK release surface) but weren't in the ignore list, so they got picked up by generic Nx detection and CI tried to `next build`/`vite build` them without the required env — the sveltekit one 401'd fetching from the seeded space and failed the build. Adding `test-apps` to `.nxignore` puts them back on the same footing as `examples`: npm workspaces still link them, but Nx skips them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI's format:check step flagged 13 files added on this branch that weren't prettier-formatted. `npm run format` applied the rewrites; behavior unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rce CDN [AIS-246] The bootstrap used to fetch three images from the shared demo test space's public CDN at seed time. That worked, but tied the demo to a URL we don't own — if those assets moved or the space got cleaned up, first-time bootstrap runs would 404. Ships the source bytes in-repo under examples/scripts/fixture/assets/ (hero-bg.png ~53 KB, card-on.png ~35 KB, card-guide.webp ~15 KB, ~103 KB total). AssetFixture.sourceUrl → sourcePath; seedAsset reads the bytes with readFileSync resolved relative to the script's own directory so the demo works from any CWD. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lisa White (whitelisab)
marked this pull request as ready for review
July 21, 2026 16:58
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.
AIS-246
Summary
examples/scripts/bootstrap-example.ts— a one-time setup script that provisions the demo Experience into a caller-specified space + environment viacontentful-management@12.6.0-dev.4. Idempotent in 9 phases: ContentTypes → Assets → Entries → DesignTokens (raw HTTP; not yet in the plain client) → ComponentTypes → Template → DataAssemblies → DA↔ComponentType links → Experience. Prints the seededexperienceIdat the end.examples/(stable, customer-facing) fromtest-apps/(internal scratchpad). Both example apps + the SvelteKit port are wired up to render the seeded Experience end-to-end; test-apps carry the original placeholder content so we can iterate without touching the customer surface.CMA_TOKEN/CDA_TOKEN/CPA_TOKEN), and clickable "Try it locally" URLs.Test plan
npm run bootstrapagainst a clean space — provisions everything, publishes clean, printsexperienceId: landing. Re-running skips existing resources per phase./landingrenders the seeded hero + 2 cards against that space (HTTP 200, content resolved through DataAssembly, assets served fromimages.ctfassets.net)./advanced/landing?preview=true&locale=en-USrenders identically to the basic route withCPA_TOKENset — asyncresolveDataon card, User-Agent → viewport seeding, preview host swap all firing./landingrenders 1:1 parity output against the same seeded space.npx tsc --noEmitclean inexamples/scripts; example apps build and dev-serve without errors.Known limitations (worth follow-ups)
packages/core/dist/index.jsemits extensionless relative imports (export * from "./types"), which strict-ESM consumers (Vite/SvelteKit SSR) reject. Worked around inexamples/sveltekit/vite.config.tsviassr.noExternal; real fix is.jssuffixes at the source. Not scoped to this ticket.🤖 Generated with Claude Code