Components: Generation & Regenerate Prompt Context
Purpose: Exact prompt text and request context for (1) first generation (seed) and (2) Netlify regenerate, as implemented in code.
Priority: Rigid, reviewable prompting on both paths.
Click-only regenerate quality: shipped in PR #32. Plan status: Click-Only Regenerate Quality (Closed).
Export-oriented regenerate prompts (Aug 2026): Reimagine / Fix UI bugs now ask for named primitive + Preview harness, Preview-scoped onboarding, stronger a11y, @deps, optional tag recipes, and CSS-only motion (no framer-motion). Lucide remains required. See Component Generation Quality PRD.
Shared XML prompts (Aug 2026): Seed + regenerate + local debug share src/lib/component-generation/prompt-shared.mjs. Both paths use the same XML envelope, full Notion tags list, and tag-driven <InteractionRecipe> when matched.
Related: Components API CDN PRD · Components Cache Workflow · Generation & Normalization Pipeline
Request envelopes (what Gemini receives)
A. First generation (seed) — offline cache
| Code | scripts/build-components-cache.mjs → buildGeminiPrompt → shared buildSeedPrompt |
| Model | gemini-2.5-pro (default), then gemini-2.5-flash fallback |
| Text part | XML envelope (same family as regenerate) |
| Image parts | Yes — coverImage inlined. GIF → up to 5 visually distinct PNG frames (even samples, loop-end dropped) |
| generationConfig | temperature: 0.1, topP: 0.4, maxOutputTokens: 8192, thinkingBudget: 4096 |
contents[0].parts = [
{ text: <PROMPT_A> },
...referenceImages[].inlinePart // 1..N image parts; GIF expands to multiple frames
]
B. Regenerate — Netlify Function (production /debug)
| Code | netlify/functions/lib/components-generate.mts → shared buildRegeneratePrompt + regenerateFromSeed |
| Model | gemini-2.5-flash only |
| Text part | XML envelope (shared module) |
| Image parts | None by default. Opt-in: includeImage: true or ENABLE_COMPONENTS_REGENERATE_IMAGE=true (single still; no GIF frames). Current UI does not opt in. |
| generationConfig | temperature: 0.3, topP: 0.7, maxOutputTokens: 8192, thinkingBudget: 1024, random seed |
| Baseline code | Active version TSX when present, else immutable seed (truncated ~14 000 chars) |
| Meta enrich | Server merges body → Blobs version meta → search-index.json (description/tags not required from UI) |
| Near-dup retry | If output ≈ baseline (Dice ≥ ~0.94) and ≥7s remain, one stronger retry (same userPrompt + distinctness prefix, or legacy mode retry brief) |
| UI | Single Regenerate action with optional free-text userPrompt. Empty = mode: "reimagine" (default brief). Non-empty = mode: "custom" + userPrompt. Pattern <description> is always included. |
contents[0].parts = [
{ text: <PROMPT_B> }
// optional: + { inlineData: cover still } only if includeImage
]
Direct answers
| Question | Answer |
|---|---|
| Do regenerates send the original GIF/image? | No on production Netlify regenerate (default). First gen does. |
| Do regenerates send pattern metadata? | Yes, server-enriched: id, title, slug, description, and all tags. Description stays in PatternContext for both empty (reimagine) and custom prompts. |
Are uxFlows / uiCategories sent? | No separate fields. Those Notion taxonomies are already in flat tags (alongside Devices / System / Language). Both paths send the full tag list. |
| Are interaction recipes on seed? | Yes — same interactionRecipeForTags() when tags match carousel/modal/tabs/popover. |
| Is there Gemini response caching? | No on the Function. Mild temp/topP + random seed + near-dup retry reduce near-copies. |
| Extra UI params needed? | Optional userPrompt. Empty regenerates with the default reimagine brief; typed instructions become ModificationRequest. Viewport / icon library still deferred. |
PROMPT A — First generation (exact text template)
Source: shared buildSeedPrompt() in src/lib/component-generation/prompt-shared.mjs (called from buildGeminiPrompt() in scripts/build-components-cache.mjs).
<DesignSystemRules>
</DesignSystemRules>
<PatternContext>
<id></id>
<title></title>
<slug></slug>
<description></description>
<tags>
<tag>…</tag> <!-- full Notion tag set; devices/system/language/UX/UI -->
</tags>
</PatternContext>
<!-- Optional when tags match (carousel/modal/tabs/popover) -->
<InteractionRecipe>
</InteractionRecipe>
<ReferenceImageHints>
<hint>…</hint>
</ReferenceImageHints>
<GenerationRequest>
Generate one production-ready React + Tailwind component from PatternContext,
InteractionRecipe (when present), and reference images. Honor Title, Description,
and all Tags… Return complete TSX only…
</GenerationRequest>
Pattern context (1st gen)
descriptionpreferscomponents-metadata.json, else search-indextags= full flat Notion tag list (nouxFlows/uiCategoriessplit)
`` (1st gen script)
Keys currently interpolated from the cache script’s local BASE_COMPONENT_THEME_TOKENS map (may omit --ui-text-scale / --ui-corners that exist on the Function regenerate default list).
Multimodal context (1st gen only)
Alongside PROMPT_A, Gemini also receives binary image parts built from entry.coverImage / entry.images[imageIndex]:
- Still image → one
inlineDatapart - GIF → even timeline samples (default 12) → keep visually distinct PNG stills, up to 5 (
src/lib/component-generation/reference-images.mjs). Last frame is not forced — looping GIFs that return to the start are dropped as duplicates. Example#412(394 frames) samples include0, 98, 197, 295, 393at N=5; unique-filter may omit393if it matches frame0.
Shared by build:metadata, build:components, and regenerate when includeImage is on.
There is no prior TSX in the 1st-gen prompt. The cover/GIF stills are the visual ground truth.
search-index stores ordered Notion body images in images[] (coverImage = images[0]). Notion page Cover is unused. Future: --image-index=1|2 for alternate flow/brand GIFs.
PROMPT B — Netlify regenerate (exact text template)
Source: shared buildRegeneratePrompt() in src/lib/component-generation/prompt-shared.mjs (wired through netlify/functions/lib/components-generate.mts).
XML envelope
<DesignSystemRules>
</DesignSystemRules>
<PatternContext>
<id></id>
<title></title>
<slug></slug>
<description></description>
<tags>
<tag>…</tag>
</tags>
<baselineVersionId></baselineVersionId>
</PatternContext>
<!-- Optional when tags match (carousel/modal/tabs/popover) -->
<InteractionRecipe>
</InteractionRecipe>
<CurrentComponent>
<!-- Active version when present; otherwise immutable seed. Use as baseline. -->
</CurrentComponent>
<ModificationRequest>
</ModificationRequest>
`` (exact shared text)
You are an expert design engineer making the most polished user interface.
Technical Constraints & Dependencies:
- FRAMEWORK: MUST use React and Tailwind CSS utilities.
- ICONS: MUST use 'lucide-react' icon components only (Search, List, X, …). Never import LucideIcon, LucideProps, or IconNode as runtime values (use import type or omit). Never invent names (no ListText). Never use heroicons, react-icons, react-feather, or raw <svg> paths.
- MOTION: Do NOT import or use 'framer-motion'. Use CSS transitions/animations, Tailwind animate-*, or light React state for motion. Honor prefers-reduced-motion.
- INTERACTIVITY: Keep components reactive with animation/transitions to improve their function.
- ONBOARDING: Highlight the main function with a floating marker and/or tooltip in the Preview/default demo harness only. Do not require onboarding tips inside the reusable named primitive.
- STATES: Use lightweight local React state ('useState') for interactive elements.
- IMAGES: If media placeholders are needed, MUST use one placeholder from our CDN:
- https://raw.githubusercontent.com/pabliqe/patttterns-cdn/refs/heads/main/placeholder-01.png
- https://raw.githubusercontent.com/pabliqe/patttterns-cdn/refs/heads/main/placeholder-02.png
- https://raw.githubusercontent.com/pabliqe/patttterns-cdn/refs/heads/main/placeholder-03.png
- DEPS COMMENT: When lucide-react (or other peers) are imported, include `// @deps lucide-react` near the top. Never depend on framer-motion.
- Keep DOM nesting shallow (<5 levels deep) and mock data minimal (maximum 6 items) to prevent token exhaustion.
Aesthetic & Theme Rules:
- STYLE: Keep clean shadcn/ui style but color using only existing CSS tokens:
- --ui-bg, --ui-surface, --ui-text, --ui-text-muted, --ui-border, --ui-ring, --ui-accent, --ui-accent-contrast, --ui-success, --ui-success-contrast, --ui-warning, --ui-warning-contrast, --ui-error, --ui-error-contrast, --ui-info, --ui-info-contrast, --ui-font-headings, --ui-font-body, --ui-font-code, --ui-text-scale, --ui-spacing, --ui-corners
- CSS TOKENS: Do NOT override these tokens. Do not hard-code brand hex colors in UI chrome.
- LANGUAGE: All visible UI copy must be English (US) — labels, buttons, headings, body, placeholders, empty states, toasts, tooltips, aria-labels, and alt text. Translate demo strings to English even when the reference image, title, or tags are not. Keep real brand/product names unchanged.
- NO MOCKUPS: Do NOT mock devices or extra frames.
- RESPONSIVE: Layout MUST adapt to width sizes from 1440px to 320px. Always hide menus and lateral sidebars.
- ACCESSIBILITY (mandatory): meaningful alt text; aria-labels on icon-only buttons; keyboard support for the primary interaction; visible focus rings; honor prefers-reduced-motion for autoplay/heavy motion.
Default ModificationRequest — empty prompt (mode: "reimagine")
/debug Regenerate with an empty box. ModificationRequest = REIMAGINE_MODIFICATION_REQUEST. Pattern <description> is included. Sampling: reimagine profile.
Custom ModificationRequest — mode: "custom" (Regenerate + userPrompt)
Typed instructions. ModificationRequest = normalized userPrompt + shared output-constraints footer (CUSTOM_REGENERATE_OUTPUT_CONSTRAINTS). Pattern <description> is still included (intent + change request). Sampling: mid profile (~temperature 0.25, topP 0.6).
Legacy ModificationRequest — mode: "fix"
Still available if a caller sends mode: "fix" without userPrompt.
Retry ModificationRequest (near-duplicate only)
Custom retry re-sends the same user prompt with a “more distinct change” prefix. Legacy reimagine/fix retries keep their prior briefs.
Baseline + meta (server)
Handled in netlify/functions/components-api.mts (enrichPatternMeta, resolveBaselineCode):
| Input | Source |
|---|---|
| Baseline TSX | activeVersionId code if readable and ≠ seed, else seed (Blobs path; local debug uses clicked row baselineVersionId) |
| Title / slug / cover | POST body → Blobs version meta → search-index.json |
| Description / tags | POST body → search-index.json; description always included in PatternContext |
What /debug POSTs today (generateComponentVersion / local regenerate):
{
"id": "<patternId>",
"title": "<row.title>",
"slug": "<row.slug>",
"coverImage": "<row.coverImage URL>",
"imageRole": "primary",
"userPrompt": "<optional free-text instruction>",
"mode": "custom"
}
Local debug also sends baselineVersionId (row version). Empty prompt stores mode: "reimagine" and userPrompt: null; typed prompt stores mode: "custom" + userPrompt.
Generate response extras (in addition to version meta):
{
"baselineVersionId": "v1",
"mode": "custom",
"userPrompt": "…",
"retriedNearDuplicate": false,
"similarityToBaseline": 0.81
}
coverImage is stored on the version meta but not attached as an image part unless includeImage: true.
Env knobs (regenerate)
| Var | Default | Purpose |
|---|---|---|
PATTERN_REGENERATE_THINKING_BUDGET | 1024 | Thinking tokens |
PATTERN_REGENERATE_GEMINI_TIMEOUT_MS | 22000 | Per Gemini call abort (ignores shared cache timeout if < 15s) |
PATTERN_REGENERATE_TEMPERATURE | 0.3 | Legacy reimagine first-pass sampling |
PATTERN_REGENERATE_TOP_P | 0.7 | Legacy reimagine topP |
PATTERN_REGENERATE_CUSTOM_TEMPERATURE | 0.25 | Custom userPrompt sampling |
PATTERN_REGENERATE_CUSTOM_TOP_P | 0.6 | Custom topP |
PATTERN_REGENERATE_RETRY_TEMPERATURE | 0.45 | Near-dup retry |
PATTERN_REGENERATE_NEAR_DUP_SIMILARITY | 0.94 | Dice threshold |
PATTERN_REGENERATE_NEAR_DUP_RETRY | 1 | Set 0 to disable retry |
PATTERN_REGENERATE_SEED_MAX_CHARS | 14000 | Baseline truncation |
PATTERN_COMPONENTS_CACHE_GEMINI_TIMEOUT_MS | 18000 | Offline cache script Gemini timeout (not used for Function regen if < 15s) |
ENABLE_COMPONENTS_REGENERATE_IMAGE | off | Attach cover stills (same GIF unique-frame prep as seed) |
PATTERN_GEMINI_GIF_FRAME_COUNT | 5 | Max distinct GIF PNG parts to send |
PATTERN_GEMINI_GIF_SAMPLE_COUNT | 12 | Even timeline candidates to search before unique-filter |
PATTERN_GEMINI_GIF_DIFF_THRESHOLD | 8 | Mean |Δ| on a 32×32 grey thumb; used with a ~1% changed-pixel floor so small tooltips stay |
Diff vs earlier slim PROMPT B
| Restored / added | Notes |
|---|---|
| Full rigid rules (onboarding, CDN images, a11y, hide sidebars, interactivity) | Shared with PROMPT A via prompt-shared.mjs |
| XML structure on seed + regenerate | DesignSystemRules / PatternContext / InteractionRecipe / … |
| Full Notion tags (no uxFlows split) | Devices / System / Language / UX / UI in one <tags> list |
| Stronger mode briefs | Legacy reimagine/fix retained; custom userPrompt is primary |
| Active baseline | Improves from latest vN, not only seed (Blobs); local debug uses clicked row |
| Server meta enrich | Description/tags without UI changes; description always in PatternContext |
| Mild temp/topP + random seed | Reduces identical copies |
| Near-dup retry | One stronger pass when similarity is high |
Shipped: optional free-text userPrompt on /debug Regenerate (empty = reimagine; typed = custom). Description always included.
Deferred (later): end-user customize UI on pattern pages / export modal; icon library; viewport params; multimodal GIF frames on Function.
Local debug regenerate
src/lib/component-generation/regenerate.ts uses the same shared XML buildRegeneratePrompt. Local POST /api/debug/components/[id]/regenerate accepts optional userPrompt (empty → mode: "reimagine") and persists mode + prompt on disk history. Production /debug may still prefer the Netlify Function when not on localhost.
Locked intent
- Keep one shared rigid design-system contract in
prompt-shared.mjs. - Seed and regenerate both use XML envelopes + full Notion tags + optional InteractionRecipe.
- Regenerate prompt is optional: empty uses default reimagine; typed text is
mode: "custom". - Pattern
<description>always stays inPatternContext(title/tags/recipe too), including whenuserPromptis set. - Same optional
userPromptcontract is intended for later end-user “customize component” surfaces. - Document intentional compromises (still image vs GIF frames, sync timeout; Blobs baseline ≠ local row baseline).
Code map
| Prompt / context | File |
|---|---|
| Shared XML prompts / recipes / custom prompt helpers | src/lib/component-generation/prompt-shared.mjs |
| PROMPT A + GIF/cover parts | scripts/build-components-cache.mjs |
| PROMPT B + Function knobs | netlify/functions/lib/components-generate.mts |
| Meta enrich + active baseline | netlify/functions/components-api.mts |
/debug Regenerate dialog + client payload | src/components/debug/ComponentsGenerationsTable.tsx, RegeneratePromptDialog.tsx, src/lib/components-api-client.ts |
/debug Rebuild (normalize existing TSX, no model) | src/app/api/debug/components/[id]/rebuild/route.ts |
| Post-Gemini normalize (fences, React types, lucide, default export) | src/lib/component-generation/normalize-generated-code.mjs, fix-icon-imports.mjs — pipeline spec |
| Local debug regenerate | src/lib/component-generation/regenerate.ts, src/app/api/debug/components/[id]/regenerate/route.ts |
| Shared theme tokens (app) | src/lib/component-theme-tokens.ts |
| Plan status (closed click-only) | docs/product-roadmaps/specs/click-only-regenerate-quality-plan.md |