generate-registry.ts imports the catalog cross-join/flatten fold from
../harness/src/shared/catalog/catalog-flatten.ts, which does
`import yaml from "js-yaml"`. The generator's build/test environments did
not stage that file (or its module-resolution scope), so the fold could
not resolve.
- Dockerfiles (shell, shell-dashboard, shell-docs, shell-dojo): COPY the
shared catalog source + harness/package.json (its `"type":"module"` is
required so catalog-flatten resolves as ESM and its named exports bind)
and provide a node_modules for js-yaml resolution.
- generate-registry-pattern.test.ts (makeHarness): stage catalog-flatten.ts
and harness/package.json at the exact relative path the generator
resolves, and symlink the scripts node_modules onto the harness tree so
the ESM `import yaml from "js-yaml"` resolves.
- js-yaml + @types/js-yaml added to showcase/scripts (package.json and the
npm package-lock.json), and the root pnpm-lock.yaml regenerated to add
the matching importer entries for showcase/scripts (js-yaml >=4.1.1 via
the root override, @types/js-yaml ^4.0.9) so `pnpm install
--frozen-lockfile` stays in sync.
The dojo's preview iframe built its src from `integration.backend_url`,
which generate-registry.ts bakes into registry.json at Docker BUILD time
(default `showcase-{slug}-production.up.railway.app`). So the staging
dojo iframed PROD integration backends — the exact staging->prod leakage
the shell's SU-13 runtime-derivation fix already prevents, but which was
never ported to shell-dojo.
Port the `backendHostPattern` slice of SU-13:
- copy shell's backend-url.ts verbatim (resolveBackendUrl + the
NEXT_PUBLIC_LOCAL_BACKENDS local-dev override); a scripts drift-guard
test keeps it byte-identical to the shell's and pins the default
pattern across backend-url.ts and generate-registry.ts.
- add `backendHostPattern` to shell-dojo's RuntimeConfig (server reads
SHOWCASE_BACKEND_HOST_PATTERN at request time; client carries the SSR
sentinel) — the existing layout injection picks it up automatically.
- page.tsx derives previewUrl via resolveBackendUrl at request time,
gated on a `mounted` flag so the SSR-phase sentinel host never reaches
an iframe src (shell-dojo loads the registry synchronously, so unlike
the shell it has no data-loading guard to defer the read past
hydration).
Staging dojo's SHOWCASE_BACKEND_HOST_PATTERN is set to
`showcase-{slug}-staging.up.railway.app`; prod stays unset (= default
prod pattern), so prod behavior is byte-identical.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What & why
[OSS-137](https://linear.app/copilotkit/issue/OSS-137/controlled-gen-ui-demo-optimize-2nd-suggestion-prompt-rename-sidebar)
— the **Controlled Generative UI** demo (`gen-ui-tool-based`) had two
issues, scoped here to **LangGraph-Python** and **Google ADK** (per the
ticket; other 16 integrations roll out later).
### 1. 2nd suggestion didn't reliably render UI
The "Traffic pie chart" chip (`"Show me a pie chart of website traffic
by source."`) names a subject but supplies no numbers, so the agent
**asked the user for data** instead of rendering a chart.
**Fix:** a system-prompt directive (both LGP + ADK agents) instructing
the agent to invent plausible illustrative sample values, call
`render_*` immediately, and **never** reply with a clarifying question.
The suggestion copy stays clean — behavior is carried by the system
prompt, not by leaking "(use sample data)" hints into the UI.
### 2. Sidebar tag → product language
Retagged the demo from `generative-ui` → `controlled-generative-ui` (LGP
+ ADK), so the dojo sidebar pill reads **"Controlled Generative UI"** —
the established taxonomy already used in `shared/feature-registry.json`
and the dashboard catalog.
## Tests
Added D5 aimock fixture entries mirroring all three suggestion chips
(bar / traffic-pie / market-share) so the suggestion-click path has
deterministic coverage. The existing `"revenue by category"` probe
message is **preserved**, so the
[dashboard](https://dashboard.showcase.copilotkit.ai/#matrix:links,health)
D5 row for the edited row stays green.
## Acceptance check
- [x] 2nd suggestion renders UI without asking for data (system-prompt
directive; verified locally against the live agent)
- [x] Sidebar entry tagged "Controlled Generative UI"
- [x] Sample/hallucinated data supplied via system prompt
- [x] Scoped to LGP + ADK
- [x] Tests augmented (D5 fixtures for every chip)
- [x] D5 still shown for the edited row (probe message unchanged)
## Out of scope (left out deliberately)
`package-lock.json` churn from a local reinstall (un-pins `latest`) was
**not** committed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
runner-stage ENV NEXT_PUBLIC_COMMIT_SHA/BRANCH expanded empty because Docker ARGs are
per-stage; re-declaring them in the runner stage (mirroring shell-docs) restores build-arg
values at runtime. Verified via local buildx.
Six fixes addressing CR findings on the Option-B runtime URL-injection migration:
1. SSR_PLACEHOLDER must be parseable URL sentinels — `new URL("")` throws on
SSR causing 500s for any consumer that constructs URLs from runtime-config
fields. Use `.invalid`-TLD sentinels (RFC 2606) for URL fields; analytics
keys stay empty string. Add `suppressHydrationWarning` on consumers that
render the placeholder server-side and the real value post-hydration
(integration-grid, page-actions popover).
2. Hook-order: move `usePathname()`/`useEffect` ABOVE the early-return in
use-google-analytics. Gate the effect bodies on `GA_ID` instead so React
sees a stable hook order across renders.
3. `readUrl`/`readKey` accept either bare or `NEXT_PUBLIC_*`-prefixed env
names via a fallback chain — covers both server-only and inlined-public
variable conventions without forcing a rename across deploy targets.
4. Extract `serializeRuntimeConfig` to `lib/runtime-config-serialize.ts` so
the OWASP-escape behavior (XSS via </script>, U+2028/U+2029 line-terminator
injection) can be unit-tested without importing the layout into vitest.
5. Reclassify `intelligenceSignupUrl`/`posthogHost` from FATAL-CONFIG to
info-level in shell-docs — these are optional integrations, not hard
wiring failures, so absence should not poison the error stream.
6. Comment-rot cleanup: drop "Option B", B12, "the bug we are fixing", fix
"four substrings"→"three substrings" miscounts, and refresh shell-docs
.env.example to describe the runtime-injection contract instead of a
stale next.config throw claim.
V1: shell + shell-docs `next build` succeeds (no Edge-runtime crash on
`unstable_noStore`).
V2: `OPS_BASE_URL=` shell-dashboard `next build` no longer throws —
`next.config.ts` is now a phase-aware function that emits a sentinel
destination at build time and throws only at start (PHASE_PRODUCTION_BUILD
from next/constants).
Tests: shell-docs 72/72, shell 12/12, shell-dashboard runtime-config 16/16
(pre-existing baseline-partner-count failure unchanged).
getRuntimeConfig() in each shell's runtime-config.client.ts threw when
typeof window === 'undefined'. But Next.js App Router executes 'use
client' component bodies on the SERVER during initial SSR, so any client
component that called getRuntimeConfig() in its render body 500'd the
page. shell-dashboard already had the fix.
Mirror shell-dashboard's pattern: return a typed SSR_PLACEHOLDER (empty
strings for URL/key fields; {} for shell-dojo whose RuntimeConfig is
empty) when window is undefined. Keep the loud throw when window IS
present but window.__SHOWCASE_CONFIG__ is missing — that's a genuine
wiring bug and should not be masked.
Updated shell-docs and shell client tests: replace 'throws on server'
case with 'returns SSR sentinel placeholder' assertion matching each
shell's RuntimeConfig shape. shell-dojo has no client test so verified
via tsc only.
Adds the inline <script id="__showcase_config__"> tag as the FIRST
child of <head> in shell-dojo's root layout (B6). Calls
getRuntimeConfig() server-side and serializes the result via the OWASP
recommended escape (< / U+2028 / U+2029) before injecting it as
window.__SHOWCASE_CONFIG__.
shell-dojo's RuntimeConfig is currently {}, so the injected value is
`window.__SHOWCASE_CONFIG__={};` — harmless but symmetric with the
other shells. The <script> sits ahead of the fonts <link> so it runs
before any other head-level script (including future next/script
beforeInteractive blocks).
Regex sources for U+2028 / U+2029 use ECMAScript escapes
(/\\u2028/g, /\\u2029/g) so the source compiles in TypeScript — a
literal codepoint in the regex source breaks tsc with TS1161
(unterminated regex literal). The regex engine resolves the escape at
runtime, so the substitution still targets the actual codepoint.
Mirror of the runtime-config pattern from shell-dashboard for shell-dojo
(B7). shell-dojo has no URL consumers today (B0 audit reported zero
process.env.NEXT_PUBLIC_* reads), so RuntimeConfig is an empty object
literal. Module exists to keep the runtime-config / layout-injection
pattern symmetric across all four shells; adding a URL later is a
single field addition.
- src/lib/runtime-config.ts: server reader with unstable_noStore()
opt-out (Node) and noStore-skip option (Edge wrapper not needed yet).
- src/lib/runtime-config.client.ts: client reader from
window.__SHOWCASE_CONFIG__ injected by root layout.
No tests included for shell-dojo: matches B7 file list (no test files
listed for shell-dojo) and reflects that there is no behavior to assert
on an empty config beyond the type contract.
gen-ui-tool-based was in the global FEATURED_DEMO_IDS, so it was promoted
into Featured for all 18 integrations. The Controlled Generative UI product
treatment (reliable sample-data rendering + the "Controlled Generative UI"
pill) is only polished for LangGraph Python and Google ADK so far, so move it
out of the global list and into EXTRA_FEATURED_BY_INTEGRATION for those two,
mirroring the per-integration scoping pattern from #4980 (HITL demos).
The other 16 integrations still expose the demo in its category section; it
just isn't promoted into Featured until the treatment scales to them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add EXTRA_FEATURED_BY_INTEGRATION map so hitl-in-chat, hitl-in-app, and
gen-ui-interrupt only appear in Featured for langgraph-python,
langgraph-typescript, langgraph-fastapi, and google-adk. All other
integrations see the original 9-item Featured list unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Surface hitl-in-chat, hitl-in-app, and gen-ui-interrupt in the dojo's
hand-curated Featured list so they are visible at the top of the sidebar
for every integration that supports them (LGP, ADK, etc.). Unsupported
demos are silently skipped per the existing filter logic.
Resolves OSS-138
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Findings from a self-review pass on this PR:
- code-block.tsx: hoist the seven per-row inline-style objects to module
scope so the file-render hot path stops re-allocating ~3 fresh objects
per line per render. ~500-line demos previously allocated ~1.5k style
objects on every parent re-render.
- page.tsx URL-sync effect: add a same-value guard before
history.replaceState so unrelated re-renders don't write the same
query string back to the address bar.
- page.tsx FileTreeRow: collapse the color ternary chain — both selected
and highlighted leaves resolve to the same primary color, so
`isSelected || isHighlighted ? primary : disabled` reads cleaner than
the nested `?:?:`.
- page.tsx: trim the restated WHAT-comment above the reset-on-demo
effect (the function body already says what the comment said).
- vitest.setup.ts MemoryStorage: coerce keys in getItem/removeItem to
match setItem and the real Storage spec (all key args coerce to string).
Deferred to a follow-up PR (would also touch showcase/shell/* and
showcase/shell-docs/*): hoisting `escapeHtml` (5 copies in the repo
today), the hljs `try { highlight } catch { escapeHtml }` pattern (also
copied), and the file-tree build/sort utilities the dojo duplicates from
showcase/shell/.../code/page.tsx.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace Multimodal Attachments with Agentic Chat as the top Featured
entry (chat is a better landing demo than attachments). Append three
more entries to round out the curated set:
Featured
• Agentic Chat (was Multimodal Attachments)
• Tool-Based Generative UI
• Declarative Gen UI (Dynamic)
• MCP Apps
• Fully Open-Ended Gen UI
• Chat Customization (CSS) ← new
• Headless Chat (Simple) ← new
• Frontend Tools ← new (in-app actions)
All eight still appear in their real category groups below.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Demo source files use inline region markers like
// @region[provider-setup]
...lines belonging to the region...
// @endregion[provider-setup]
(see `showcase/scripts/bundle-demo-content.ts` for the spec). The bundler
strips the markers and emits each region as
`{ file, startLine, endLine }` on the demo's `regions` map. 537 of the
697 bundled demos have at least one region today.
The dojo's code viewer was ignoring this. Now: for the active file, any
1-based line numbers that fall inside one of its regions render with a
yellow per-line background, so the author-marked "this is the interesting
bit" sections jump out visually without any data-format change.
To make per-line backgrounds work, `CodeBlock` was rewritten from a
single `<pre>` + `hljs.highlightElement()` to a per-line render: highlight
once with `hljs.highlight()`, split by `\n`, render each line as its own
row with optional background. Tradeoff: multi-line constructs (block
comments, template literals) can lose their wrapping span at the line
break — minor cosmetic glitch for cheap per-line styling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hand-curated entry-point demos that should be the first thing a visitor
sees, regardless of where they happen to live in the feature taxonomy:
Featured
• Multimodal Attachments (platform → multimodal)
• Tool-Based Generative UI (controlled-gen-ui → gen-ui-tool-based)
• Declarative Gen UI (Dynamic) (declarative-gen-ui → declarative-gen-ui)
• MCP Apps (open-gen-ui → mcp-apps)
• Fully Open-Ended Gen UI (open-gen-ui → open-gen-ui)
This list lives in the dojo page itself rather than in the registry so
the editorial pick can evolve without touching the underlying feature
taxonomy or each integration's manifest. Each demo also still appears
in its real category group below, since the goal is "look here first,"
not "remove from elsewhere."
Featured demos are pulled from the *current* integration's demos, in
the curated order; any IDs the active integration hasn't implemented
are silently skipped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adopt the same highlighted-file pattern the standalone shell already
uses at `showcase/shell/src/app/integrations/[slug]/[demo]/code/page.tsx`:
- Each demo's `highlight: […]` list in the integration manifest already
flows into the bundled `demo-content.json` as `file.highlighted: true`.
The dojo file tree now consumes that flag instead of ignoring it.
- Core files render bold + ★ in amber and float to the top within their
parent directory. Non-core files render dimmer (muted color, normal
weight) so the eye lands on the demo's "real" code first.
- A "show all" toggle in the tree header switches between the curated
core view (highlights only) and the full tree. Default is "core" when
any file is highlighted, otherwise "all". The mode resets per demo.
- Selection switched from index to filename so it survives the
core⇄all toggle without aliasing to the wrong file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related dojo polish items so the page is more navigable:
1. Sync integration + demo selection to the URL as
?integration=<slug>&demo=<id>. On mount the page hydrates state from
the query string (with fallback to the first deployed integration's
first demo when params are missing or invalid). On every selection
change the URL is rewritten via history.replaceState, so refreshing
lands on the same selection and links are now shareable.
2. Replace the flat horizontal file-tab strip above the code panel
with a vertical folder tree on the right side of the code view.
Filenames like `src/app/api/copilotkit/route.ts` now nest under
their `src/ → app/ → api/ → copilotkit/` ancestors instead of
competing for horizontal space, which scales much better as demos
accumulate backend + frontend files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two small dojo-only design tweaks:
1. Replace the single static "Demos" label in the dojo sidebar with the
feature category names already present in the registry, so the
navigation reflects how demos are grouped instead of presenting them
as one flat list. Section titles bumped to 11px / weight 600 /
primary text color for a touch more prominence — applies uniformly
to the existing "Integrations" and "View" headers as well.
2. Drop the "(TanStack AI)" qualifier from the built-in agent's
display name and description. The integration is now surfaced as
"CopilotKit Built-in Agent" so users aren't pushed to think about
the LLM backend in the integration name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands the Bucket A framework fix and the fixture-correctness changes
needed to flip the remaining D4/D2 cells in the langgraph-python column
to D5. Full local D5 sweep is green (1 passed, 0 failed).
Framework: every Python `ToolMessage` constructed via `Command(update=...)`
now sets `name=` and `id=str(uuid.uuid4())`. Without these, @ag-ui/langgraph
synthesises TOOL_CALL_START events with `toolCallName: null` and
`parentMessageId: null`, which @ag-ui/client@0.0.53's Zod schema rejects;
the rejection is silently swallowed by `withAbortErrorHandling -> EMPTY`,
completing the SSE observable mid-stream so post-tool state never reaches
the consumer. Fix is applied across shared_state_streaming, shared_state_read_write,
gen_ui_agent, beautiful_chat, subagents (2 sites). Single-flag change in
a2ui_dynamic flips the secondary `_design_a2ui_surface` LLM call to
`streaming=True` so aimock's record/replay (SSE-only) sees it.
Fixtures (d5-all.json):
- toolCallId follow-ups for set_steps (3), display_flight, generate_a2ui (4),
schedule_meeting (2), generateSandboxedUi (7), and revenue chart so
multi-turn probes don't recurse into recursion-limit loops
- four hand-crafted secondary `_design_a2ui_surface` fixtures so A2UI
dynamic renders without a real LLM
- mcp-apps fixture rewritten to emit `create_view` tool call with a
minimal Excalidraw element payload; runtime middleware fetches the UI
resource and the iframe mounts
- AAPL and revenue `hasToolResult: true` follow-ups tightened to
`toolCallId` so they don't match cross-turn after prior turns' tool
results
- voice fast-path content-only fixture
- beautiful-chat-schedule-meeting first-turn fixture gains content so
the conversation runner sees an assistant message before the picker
click assertion
Probes: bumped per-card waitForSelector in d5-gen-ui-headless-complete from
15s to 60s — recharts ResponsiveContainer can be slow under 4 sequential
turns.
Shell-dojo: hide CLI Start Command from the dojo navigation via
`HIDDEN_DOJO_FEATURE_IDS`. Registry/manifests untouched so
harness/parity/dashboard still see it.
Now that generated JSON is gitignored, every path that consumes these
files must run generators first. Fixes:
- shell: add bundle-demo-content to dev preamble (eliminates race
between watcher and Next.js on fresh clone); add
bundle-starter-content to Dockerfile RUN chain
- shell-dojo: add predev hook (generate-registry + bundle-demo-content)
- shell-docs: add predev hook (generate-registry + bundle-demo-content
+ generate-search-index)
- ops: replace direct COPY of gitignored registry.json with
generate-registry.ts at build time (copy scripts+shared+packages,
npm ci, run generator)
Add */src/data/*.json patterns to showcase/.gitignore for all 4 shell
apps. Remove 11 tracked JSON blobs (~28K lines of generated content)
that were causing constant git noise from embedded timestamps and
leaking into PRs on every build/dev run.
Every build path (Docker, CI, npm run build, npm run dev) regenerates
these files — they never needed to be committed.
Every generator embedded `generated_at: new Date().toISOString()` in its
output, causing constant git noise on every build/dev run even when
actual content was unchanged. Remove the field from all 4 generator
scripts, all consumer interfaces (Registry, BundledContent,
BundledStarters, DocsStatusBundle), inline type casts, and test
assertions.
Also: add shell-dashboard as a generate-registry output directory (it
was cross-importing from shell); move probe-docs output to
shell-dashboard/src/data/ (sole consumer); update test beforeAll to
generate files instead of restoring from git HEAD (prep for gitignore).
Declare open-gen-ui and open-gen-ui-advanced in langgraph-python
manifest (code existed, was never registered). Add both to
constrained-explicit allowlist, fill shell_docs_path for 5 demos,
add hitl-in-app override, drop stale chat-customization-css fallback.
Regenerate registry.json, demo-content.json, constraints.json,
and docs-status.json across shell / shell-dojo / shell-docs.
Bump feature/demo count assertion 30→32 in generate-registry test.
Extend check-binaries.sh whitelist for sister-shell demo-content.
- Update registry.json, demo-content.json, status.json, constraints.json,
docs-status.json across shell/shell-docs/shell-dojo
- Add integration="langgraph-python" default to quickstart InlineDemo so
the base unscoped page shows a demo instead of being empty
The scrub and #4084 touched the same surface: #4084 re-added an `open:`
generative_ui profile listing `open-gen-ui`/`open-gen-ui-advanced`, and
re-added both features to `constrained-explicit.allowed`. Extending the
branch's scrub to both re-additions keeps the semantic consistent with
the schema (which already dropped `open` from the approaches enum).
- `showcase/shared/constraints.yaml`: drop `open-gen-ui` +
`open-gen-ui-advanced` from `constrained-explicit.allowed`; drop main's
re-added `open:` profile entirely.
- `showcase/packages/langgraph-python/manifest.yaml`: drop the now-orphan
`open-gen-ui` + `open-gen-ui-advanced` feature and demo entries
(validator confirmed they had no allowed approach left).
- Regenerated `showcase/shell/src/data/registry.json` + sibling
`shell-docs`/`shell-dojo` registries and `constraints.json` via
`pnpm --dir showcase/scripts generate-registry`. All 17 integrations
validate.
`feature-registry.json` intentionally still defines both features — the
original scrub commits (2b996c54d, 27f886e59) left it untouched, so the
demo source files on disk also stay. Follow-up deletion if desired is
out of scope for this merge.
The dojo app was missing items under the langgraph column because
shell-dojo shipped a stale committed registry.json. The generator
only wrote to shell/, the dojo Dockerfile didn't run the generator
at build, and the CI path filter didn't rebuild the dojo when
manifest files changed.
Fix: emit from generate-registry.ts to shell, shell-dojo, and
shell-docs; add the generator step to shell-dojo's Dockerfile;
expand the deploy workflow's path filter to include packages/**
and shared/**; and refresh the committed registry/demo-content
JSON so files on disk match what the generator produces today.