feat(landing): AI landing-page builder (json-render + Puck + streaming AI)#324
Draft
guillermoscript wants to merge 9 commits into
Draft
feat(landing): AI landing-page builder (json-render + Puck + streaming AI)#324guillermoscript wants to merge 9 commits into
guillermoscript wants to merge 9 commits into
Conversation
Add an AI landing-page generation layer (json-render) in front of the existing Puck visual editor. Creators describe a page in a sentence; an LLM generates it constrained to a catalog of our blocks; the result opens in Puck for hand editing. One component vocabulary, two flows. Registries & blocks: - Wire @tailark, @MagicUI, @shadcnblocks, @motion-primitives into components.json - Add 7 registry-sourced Puck blocks (LogoMarquee, AnimatedStats, ShinyEyebrow, StatsBand, FaqSplit, CtaBanner, ContentFeature) + installed source components json-render pipeline: - lib/json-render/from-puck-fields.ts: puckFieldsToZod() derives Zod props from Puck fields (text/textarea/number/radio/select/array incl. nested) - scripts/gen-puck-fields-manifest.ts -> puck-fields.generated.json: pure-data field manifest so the server-side catalog never bundles the client Puck tree - lib/json-render/catalog.ts: auto-derives catalog for all 30 generatable blocks - lib/json-render/to-puck.ts: normalizeSpec() + specToPuckData() bridge - app/api/landing/generate/route.ts: auth + tenant scoped; prompt -> generateObject -> catalog.validate (anti-hallucination) -> Puck Data Editor wiring: - components/admin/landing-page/generate-with-ai.tsx: button + dialog, injects the generated page via dispatch({type:'setData'}); i18n under puck.ai (en/es) Docs & scripts: - docs/adr/0001: architecture decision record - scripts/json-render-proof.ts, json-render-live-test.ts: pipeline verification Verified: npm run build passes, tsc 0 errors, proof scripts green. Live OpenAI call untested (invalid key in env, external). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…builder skill
Improve the json-render → Puck AI landing-page pipeline and harden it after
end-to-end testing with a real model call.
Generation quality:
- Add lib/json-render/authoring-guide.ts (LANDING_AUTHORING_GUIDE) — a shared
landing-specific prompt suffix that overrides json-render's generic JSONL
output format and coaches the model to build a complete 6–9 section page
(Hero → … → CtaBanner) with real copy and consistent accent theming.
Wired into both the route and the live-test script so they can't drift.
Turns a thin 5-block stub into a full 8-section page.
OpenAI structured-output fixes (route + live-test):
- elements is an ARRAY of {id,type,propsJson,children}; props are a JSON string
parsed server-side. Works around OpenAI rejecting z.record (propertyNames)
and .optional() (strict mode requires every key in `required`).
Crash-proofing the bridge:
- Manifest now carries each block's defaultProps; catalog exports
DEFAULT_PROPS_BY_TYPE; specToPuckData merges defaults UNDER the AI props so a
block never receives `undefined` for an array it maps over (Puck's Render does
not reliably backfill top-level defaults). Fixes the `items.length` render
crash on generated pages.
- StatsBand guards `items ?? []` as the reference defense-in-depth pattern.
Testing/infra:
- Seed Default School on the enterprise plan (persists across db reset).
Skill:
- Add .claude/skills/ai-landing-builder — SKILL.md (architecture, the 7
critical invariants, add-a-block steps, testing, debugging cheatsheet,
production backlog) + references/gotchas.md (the full why behind each gotcha
with exact code shapes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turn the fire-and-forget "Generate with AI" button into a multi-turn chat assistant inside the Puck editor, and make generation stream. Chat assistant (two-intent protocol): - New right-side chat panel (components/admin/landing-page/ai-chat-panel.tsx), persistent conversation, applies changes immediately (Puck undo reverts). - PAGE intent (nothing selected): build/rewrite the whole page → setData. - BLOCK intent (a block selected in Puck): edit ONLY that block's props → replace that one item by id, leaving every other block untouched. The context auto-targets the selected block; falls back to whole-page when none selected. - Both intents validate against the SAME catalog and get the defaultProps backfill, so anti-hallucination + crash-proofing apply to block edits too. Streaming: - Route uses streamObject and emits NDJSON: `progress` events per block while a page builds (first progress ~2.4s vs ~15s blank before), then a single `page`/`block` result. Block edits use a fast generateObject. Hardening (AI SDK v6 best practices, verified against bundled docs): - abortSignal wired from the client (closing the panel cancels the paid call), NoObjectGeneratedError handling (logs cause/text/usage), token usage logged, input length + message-count caps. - Replace generate-with-ai.tsx with the chat panel; export cleanProps from to-puck.ts; new chat i18n keys (en/es); add json-render:stream-test script. Verified: tsc clean; in-browser whole-page chat turn AND block-only hero edit both applied correctly with other blocks untouched; zero console errors on a clean build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the anthropics/skills skill-creator skill and register it in skills-lock.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
End-to-end browser recording (Claude Chrome extension): admin opens the Puck builder → "Generate with AI" → prompt → streamed 3-section page (hero + features + CTA) renders live in the canvas.
Owner
Author
✅ AI landing-page generation — verified end-to-endRecorded live against this branch (dev server on What was checked
Generated output was well-formed Puck data ( GIF recorded with the Claude Chrome extension; committed under |
The /api/landing/generate endpoint checked auth + tenant but had no plan or rate-limit guard, so any authenticated admin — including free-plan tenants — could call it directly and burn OpenAI tokens, bypassing the UI's PAID_PLANS gate. - Enforce the same PAID_PLANS gate server-side via get_plan_features → 403 for free-plan tenants (mirrors landing-pages-client canUseBuilder). - Add a per-user rate limit (20 / 5 min) using lib/rate-limit.ts, checked before the plan gate and the model call so it actually caps cost. - seed.sql: keep the Default tenant on the FREE plan (was bumped to enterprise) so plan-gating is exercised in local dev / E2E; use a paid tenant (Code Academy, pro) when gated features are needed. Verified: paid→200, free→403, unauth→401, >20/window→429; build passes.
…es (#324) - Add lib/json-render/to-puck.test.ts (node:test via tsx, new `test:unit` script): 15 cases covering arraySpecToSpec / normalizeSpec / cleanProps and specToPuckData — including the fallbacks that turn the valid-but-misstructured specs LLMs emit (missing root id, a real section used as root) into a full ordered page, plus defaults backfill/override. - Localize the AI assistant's replies/errors: the route's `reply`/`error` strings were English-only. The client now builds them from the structured stream events (page `blocks`, block `blockType`, error `status`) via next-intl, with new puck.ai keys (builtPage, updatedBlock, errorPlan, errorRateLimit, errorInvalid) in en + es. Build passes; test:unit 15/15 green.
The landing-ai-generation-324.gif (1.4MB) was committed to the branch only to embed QA evidence in the PR comment. That comment references the GIF via a permanent commit-SHA raw URL (commit e555275), so the embed survives this removal — but the binary doesn't need to ride into master via the feature diff. Co-Authored-By: Claude Opus 4.8 <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.

Summary
AI-assisted landing-page builder for school admins: conversational AI panel, streaming page generation, a json-render layer, and Puck editor refinements.
Changes (landing-builder)
app/api/landing/generate/route.ts— streaming AI generation endpointlib/json-render/*— json-render layerlib/puck/*,components/admin/landing-page/{ai-chat-panel,puck-editor}.tsx— Puck editor + AI chat panelcomponents/{call-to-action,faqs,stats}.tsx,components/ui/{animated-shiny-text,marquee,number-ticker}.tsx.claude/skills/ai-landing-builder/*— the landing-builder skill (distinct from skill-creator)messages/{en,es}.json— i18n stringssupabase/seed.sql,docs/adr/0001-json-render-puck-landing-builder.md,app/globals.css,components.json,package.json/ lockfile,scripts/*Test plan
npm run buildpasses🤖 Generated with Claude Code