- Remove the '⟵ render() →' chip on the preview card border; the blue
frame + 'Rendered output' label above already carry that meaning
clearly enough.
- Caption now says 'The controls on the left/top drive its props live.'
so it still makes sense at narrow widths where the split collapses
and the controls stack above the render column.
Label
- CodeLens now reads just '▶️ Preview Component' regardless of hook
kind / identity — the sidebar already carries the hook name and the
tooltip still has the full detail when you hover.
Fixtures
- ImportedAirQuality.tsx (V1, useCopilotAction) — render is an imported
reference to AirQualityBadge, defined in
test-workspace/hooks/shared/AirQualityBadge.tsx. Confirms the preview
bundler walks the import when the render prop is a component symbol
rather than an inline arrow.
- ImportedPollenReport.tsx (V2, useRenderTool) — render is an imported
PollenReport, which itself imports from ./shared/pollen-copy.ts.
Exercises a two-hop import graph rooted at the hook's render prop.
Scanner's prefilter skips shared/*.tsx automatically because neither
shared file calls a hook or references @copilotkit/*, so the sidebar
stays tidy — only the two new hook fixtures show up.
Integration test expectations updated: showAirQuality + pollenReport
added to the scan list, and both new files included in the bundle-smoke
loop.
Renders a '▶️ Preview <hook>(<name>)' CodeLens above every render-hook
call-site in .ts / .tsx files. Clicking the lens fires the existing
copilotkit.hooks.preview command with the resolved site — same flow as
the sidebar, just available inline where the hook is called.
- Only render-category hooks (useCopilotAction, useRenderTool,
useLangGraphInterrupt, useHumanInTheLoop, ...) get a lens; data hooks
have nothing to preview and are skipped.
- Provider parses on demand via the same oxc scanner as the sidebar, so
lens positions stay in sync without a separate cache.
- Debounced per-file save handler already rescans sites; we now also
call lensProvider.refresh() from there so lenses update on save.
The catalog sidebar was the last view still rendered via a native
TreeDataProvider while inspector + hooks had already moved to webviews.
Replace it with a matching webview so all three sidebars share the same
Tailwind-driven look and interaction model, and add a 'go to source' action
on every row (component and fixture).
Changes
- New CatalogListViewProvider mirrors HookListViewProvider: buffer
messages until the webview posts 'ready', then flush init +
components. Refresh / preview / openSource actions forwarded to
injected callbacks.
- New React webview at src/webview/catalog-list:
- Sticky 'A2UI Catalog' header with a refresh button
- One row per discovered component (name + relative path + 'auto'
badge when no fixture file exists)
- Expandable fixtures list (click the chevron / row to toggle)
- Hover-reveal actions: ▷ preview (component-only) + </> go-to-source
- Fixture rows preview via click, with a </> button that opens the
fixture file and reveals the named fixture key
- 'Go to source' for components opens the catalog file; for fixtures
opens the fixture file and selects the "<name>": key so the user
lands right on the edit point.
- New bridge types + new tsdown entry for the catalog-list bundle.
- activate.ts: drop the tree provider, own the scan results locally,
hand them to the webview, keep component registry seeding in sync on
file-change rescans.
- package.json: componentPreview view is now type: "webview", renamed
"A2UI Catalog". Removed the TreeView-only view/item/context binding.
- Deleted sidebar/view-provider.ts — no longer referenced.
All three sidebars (Inspector, Hooks, Catalog) now use the same webview
pattern, same go-to-source UX, and the same Tailwind styling.
UI chrome
- Replace the unstyled 'hook-preview' classes with Tailwind utilities layered
over the theme-aware VS Code CSS variables; Tailwind CDN is loaded from
panel.ts so user render props can use utilities too.
- Render section is now visually unmistakable: 'Rendered output' label,
sky-blue framed card with a 'render() →' chip, and a caption explaining
the frame. Caption + sidebar row follow the same visual language.
- Controls + form fields converted to Tailwind (ActionControls,
CoAgentStateControls, InterruptControls, RenderToolControls,
CustomMessageControls, RawJsonField, StringField, NumberField,
BooleanField, FormRenderer).
- JSON textarea styled to match regular inputs: same bg/border/padding,
minHeight floor, tab-size: 2, ligatures off, spellcheck off, 'JSON' pill.
Sidebar UX swap
- Clicking a render-row now previews the hook (the primary action); the
hover-button opens source (`</>` glyph). Data-row click still opens
source because they have no preview.
Load flow robustness
- Ready-handshake in panel.ts: buffer sends in `pendingMessages` until the
webview posts `{type:'ready'}`, then flush. Fixes 'Waiting for scan…'
that was caused by postMessage racing the webview's module load.
- Check fs.existsSync before bundleHookSite so a deleted-fixture path
surfaces a clean in-webview error instead of a rolldown UNRESOLVED_ENTRY
toast.
- Assign `window.__copilotkit_hookSite = undefined` between bundle loads
(rolldown's `var` declaration is non-configurable so `delete` throws).
- Gate Harness mount on a real hostRoot: render a 'Loading bundle…'
placeholder until the bundle finishes, otherwise the placeholder HostRoot
publishes an empty registry past the gate.
- Strip function values (onRespond / onResolve) before postMessage —
structured-clone can't serialize callbacks.
- Reset controls/respondValue/resolveValue in the load handler so a cross-
kind switch can't leave the previous hook's shape under a new kind's
control component.
- Top-level PreviewErrorBoundary with a resetKey-driven
componentDidUpdate recovery: any crash from the render prop, controls,
or descendants shows a readable message and auto-clears when the user
picks a different hook. Boundary does not remount descendants, so
AppInner's message listener stays attached and incoming 'load's aren't
lost on reset.
- FormRenderer filters undefined fields defensively and falls back to
an empty values object on first render tick.
Capture-only CopilotKit stubs
- Externalize @copilotkit/react-core and /v2, resolve at runtime to a
Proxy-backed stubs object that captures every hook call into
window.__copilotkit_captured and renders providers as Fragment
pass-throughs. Avoids dragging the chat/runtime-client/markdown graph
through rolldown IIFE (which was the __commonJSMin TDZ root cause).
- RegistryReader fans hook captures into the right slot (renderToolCalls,
tools, coAgentStateRenders) and keeps a `byHook` bucket so findConfig
can look up anonymous/nameless hook variants.
Hook registry coverage
- useComponent and useDefaultTool retagged from category:'data' to
category:'render' with correct renderProps. Both have render props in
real react-core; they're just thin wrappers over useFrontendTool and
useCopilotAction respectively.
- useCopilotAuthenticatedAction_c, useHumanInTheLoop, useDefaultRenderTool,
useComponent, useDefaultTool added to RENDER_TOOL_CALL_HOOKS in the
registry-reader so their captures route correctly.
Weather-themed fixtures (14 hook instances covering every V1 + V2 render hook)
- useCopilotAction: addLocation + removeLocation (WeatherActions), severeAlert
with imported CSS (SevereWeatherAlert + severe-alert.css)
- useCopilotAuthenticatedAction_c: publishAlert (AdminIssueAlert)
- useCoAgentStateRender: forecast_agent (ForecastAgent)
- useLangGraphInterrupt: LocationPermissionInterrupt
- useRenderTool: getWeather (WeatherTool)
- useRenderToolCall: viewRadar (WeatherRadar)
- useDefaultRenderTool: defaultWeatherFallback (DefaultWeatherRender)
- useLazyToolRenderer: historicalTemperatures (LazyHistoricalChart)
- useRenderCustomMessages: WeatherCustomMessage
- useRenderActivityMessage: WeatherActivityMessage
- useHumanInTheLoop: confirmEvacuation (ConfirmEvacuation)
- useInterrupt: UnitsPreferenceInterrupt
- useFrontendTool: precipitationGauge (PrecipGaugeFrontendTool)
- useComponent: sunTimes (WeatherComponent)
- useDefaultTool: DefaultWeatherCatchAll
Each fixture uses a distinct visual (gradient cards, severity-palette
alerts, forecast strip, radar grid, conic-gradient gauge, permission
dialogs) so the preview exercises a broad range of layouts.
Tests
- Integration test enumerates all 17 expected hook sites and bundles every
fixture file; capture test uses the new synthetic interrupt name.
ControlsDispatch (and its adapters — ActionControls, etc.) read fields
off `values` like `values.status`. `controls` is seeded via useEffect
after `config` lands, so the first render post-capture had
`controls === null` → 'Cannot read properties of null (reading status)'.
Add a short 'Preparing controls…' placeholder between the config arrival
and the next render tick when the seeding effect has run.
Reload race: after a 'load' message App called setHostRoot(null) + setPayload(new).
The first render showed Harness with a placeholder HostRoot (() => null).
RegistryReader's useEffect fired against an empty capture array, published
an empty registry, and the !hostRoot || !registry gate let the next render
past it before the real component ever ran — so the user saw 'was not
captured' even when the bundle was valid.
Split the gate: render a 'Loading bundle…' placeholder while hostRoot is
null, mount Harness only once it's set, then wait on registry. The real
component now always gets at least one render pass, populating
window.__copilotkit_captured before RegistryReader reads it.
Also remove the temporary diagnostic console.logs and drop the stray
excalidraw.log I accidentally staged earlier.
Temporary diagnostics to narrow down why the capture registry appears
empty in the real webview even though jsdom E2E tests (stub + Harness +
React render) pass. Will be removed once the flow is confirmed.
Rolldown's IIFE emits `var __copilotkit_hookSite = …` at the top level,
which binds a non-configurable property on `window`. The reload-cleanup
path tried to `delete` it, which throws in strict mode. Assigning
`undefined` has the same semantics for our read-below check (which
already treats `undefined` as "not set") without the throw.
The previous harness imported `@copilotkit/react-core` and mounted a real
`<CopilotKit>` provider inside the webview. That dragged the full
chat/runtime-client/markdown dep graph through rolldown's IIFE output,
which wraps CJS modules in `__commonJSMin`. Under the circular-import
graph this triangle produces, the wrappers hit a TDZ at runtime:
Cannot access 'require_clipboard' before initialization
Cannot access 'require_graphql' before initialization
Cannot access 'require_context_helpers' before initialization
(and every other chunk whose factory assignment lost its race with its
first caller). Every targeted fix only moved the failure to the next
chunk.
Pivot: the preview never needed a live CopilotKit runtime — we only ever
wanted the `render` prop the user passed to their hook, driven by a
form over its declared parameters. So:
- Externalize `@copilotkit/react-core` and `@copilotkit/react-core/v2`
in the hook bundler; map both globals to
`__copilotkit_deps.copilotkitStubs`.
- Add `copilotkit-stubs.ts`: a Proxy-backed object that captures every
hook call into `window.__copilotkit_captured`, renders CopilotKit/
CopilotKitProvider as Fragment pass-throughs, and returns null
components / noop functions for anything else — so the bundle never
fails on unknown exports.
- Harness drops the `<CopilotKit>` wrapper and the fetch interceptor;
RegistryReader reads the capture array in a useEffect and shapes it
into the existing `CapturedRegistry` layout.
- Tests rewritten against the stub path (no real react-core import).
Result: TodoActions.tsx bundle is 1.3KB with zero `__commonJSMin`
wrappers, down from 24MB with 1000+ `require_" identifiers. The
tradeoff is explicit in the stub file: hook-level state that needs a
real runtime (e.g. useCopilotChat returning live messages) isn't
available here — add a specific stub return value if a preview needs it
rather than leaning on the Proxy fallback.
The runtime IIFE bundler (used when previewing a user's hook site) had no
workspace-source aliasing — so @copilotkit/react-core → @copilotkit/shared
and → @copilotkit/runtime-client-gql both landed on the CJS dist, which
rolldown wraps in __commonJSMin. Under the circular-import graph that flows
through the chat/runtime-client/shared triangle that wrapping trips a TDZ:
Cannot access 'require_clipboard' before initialization
Cannot access 'require_graphql' before initialization
Mirrors the workspaceSourceAliases fix already applied in tsdown.config.ts
for the extension's own webview bundles: pre-compute a spec → src/index.ts
map by resolving each workspace package's package.json via extensionRequire,
then short-circuit resolveId before Node fallback. Only applied when the
src file exists on disk (monorepo checkout); for marketplace installs where
users pull these packages from npm the CJS dist is the only thing on disk,
so the alias silently falls through — that production path is a separate
concern tracked outside this change.
Bundled CJS libs (lucide-react, some radix internals) keep verbatim
require("react") calls inside __commonJSMin wrappers. Rolldown's
globals config rewrites ESM-style external refs but leaves those
require() calls alone, and the browser has no global require — so
the IIFE threw 'require is not defined' at first execution.
Install a window.require shim before appending the script that maps
the React externals to the already-imported modules and warns + returns
{} for unknown specifiers, so a stray transitive require doesn't take
the whole preview down.
Two-part fix that together make 'node_path is not defined' go away.
Problem 1: hook-bundler passed skipSpecifierPrefixes: ["node:", "vscode"].
The iife-bundler resolver's skipPrefixes check ran BEFORE the isBuiltin
stub, so "node:path" short-circuited to null. Rolldown then externalized
node:path as an IIFE parameter — the bundle opened with
(function(exports, react, ..., node_path, node_process, node_url) { ... })
and the webview call site only passed exports+react globals, leaving
node_path undefined. ReferenceError at first access.
Fix A: move the isBuiltin stub BEFORE the skipPrefixes check so both
"path" and "node:path" resolve to the virtual empty/shim module. Clean
up hook-bundler's skipSpecifierPrefixes to just ["vscode"] since "node:"
is no longer needed as a skip hint.
Problem 2: with Node builtins stubbed, rolldown's package-condition
resolver picks the BROWSER condition for subpath exports like
vfile's '#minurl'. The browser variant only exports { isUrl }, but
vfile/lib/index.js imports { urlToPath, isUrl }. MISSING_EXPORT errors
block the build — previously hidden because externalizing Node builtins
kept a node-ish resolution mode.
Fix B: stub the unreachable markdown chain (vfile, stringify-entities,
parse-entities, character-entities*, decode-named-character-reference,
hast-util-to-html) with a Record<string, string[]> that enumerates each
dep's named exports so rolldown's named-export analysis succeeds. Same
pattern as tsdown.config.ts's HOOK_PREVIEW_STUBBED_DEPS for the static
webview bundle.
Adds a regression test that bundles TodoActions.tsx and asserts no
`node_<builtin>` identifier is referenced without a declaration.
Prior behaviour marked transitive Node builtins ("path", "fs", etc.) as
external during per-hook source bundling. In IIFE format without a globals
map entry, rolldown emits:
var node_path = node_path;
…which throws 'node_path is not defined' the moment the IIFE initialises
inside the hook-preview webview. The user sees it as a Mount error.
Route builtins to virtual stub modules instead (same pattern as the
tsdown.config.ts stub for the static webview bundle):
- crypto → WebCrypto-backed shim (randomFillSync/randomBytes/randomUUID)
- anything else → empty module (export default {})
Browser-reachable code paths either never call the missing API or hit the
crypto shim and work. Code that does try to call e.g. path.join now fails
at the call site with a clearer error rather than an opaque ReferenceError
at module init.
Prior stub resolved 'crypto' to an empty module; that broke the uuid npm
package's v4 path (used by react-core's ThreadsProvider on first render):
Uncaught TypeError: (0, crypto_1.randomFillSync) is not a function
at rng → v4 → randomUUID → ThreadsProvider
Replace the empty stub with a tiny shim that forwards the three APIs we
actually see called at module-init / first-render time (randomFillSync,
randomBytes, randomUUID) to globalThis.crypto. The webview always has
WebCrypto available. Other crypto APIs (hashes, ciphers, key derivation)
still default to the empty stub since none of the transitive deps we
bundle hit them; extend the shim if that changes.
Second __commonJSMin TDZ hit:
Uncaught ReferenceError: Cannot access 'require_graphql' before initialization
Same shape as the clipboard fix in d551a2e77. runtime-client-gql's CJS
dist wraps the `graphql` npm lib (which is itself CJS) — with circular
imports through react-core's runtime client, the outer `require_graphql =
__commonJSMin(...)` declaration is accessed before its initializer runs.
TS source uses direct ESM imports that rolldown orders cleanly, so the
forward reference TDZ window doesn't open.
The button copied a made-up composite key (hook::name) that wasn't useful
for any realistic workflow — bug reports, codebase search, and Slack shares
all work better with the file path + line number already shown on the row.
Dropping it trims the hover area to just the ▷ preview button on render
hooks. Removes the copyIdentity case from the bridge type, view-provider
handler, and callback map. The copilotkit.hooks.copyIdentity palette
command stays registered (the helper still exists) — zero UI surface, so
it doesn't contribute to the clutter the user flagged.
Default interaction on a hook row was surprising — clicking a render hook
spawned the preview panel unprompted, while clicking a data hook opened
the source. Align both categories on the same default (open source on
click) and keep the preview action behind the dedicated ▷ button that
appears on hover for render hooks. Also drops the redundant hover
'open source' button since the row click already does that — removes
visual clutter the user flagged in the prior feedback.
The new HookListViewProvider replaces the TreeDataProvider entirely,
and the old view-provider.ts had no remaining imports after activate-
hook-explorer was migrated. Its tests covered buildTreeData /
findLeaf / statusKeyForSite, all of which now live in tree-model.test.ts
alongside the new groupSitesByHook coverage.
Replaces the native TreeView for copilotkit.hooks with a React webview
that mirrors the AG-UI Inspector sidebar. Registered hooks are shown
prominently as collapsible groups; HOOK_REGISTRY entries with zero call
sites are tucked behind a "Show available hooks" toggle so the main
view stays quiet. Uses VS Code CSS variables and Tailwind-via-CDN.
- HookListViewProvider implements vscode.WebviewViewProvider with a
typed bridge contract in hook-list-bridge-types.ts, replays sites on
the webview's `ready` signal, and forwards preview/openSource/
copyIdentity/refresh back to injected callbacks.
- New pure groupSitesByHook helper (in tree-model.ts) splits registry
entries into registered / available, covered by tree-model.test.ts.
- activate-hook-explorer keeps the five copilotkit.hooks.* commands for
the command palette; each now accepts an optional HookCallSite and
falls back to a QuickPick picker when invoked without args.
- tsdown.config.ts emits dist/webview/hook-list.js (no stubbing needed:
the bundle doesn't pull in @copilotkit/react-core).
@copilotkit/shared ships a CJS dist (utils/clipboard.cjs among others)
that rolldown wraps in __commonJSMin lazy-init closures. With circular
imports through react-core's markdown/chat chain, those closures get
accessed before their `let require_clipboard = ...` declaration has run,
producing a TDZ error at runtime:
Uncaught ReferenceError: Cannot access 'require_clipboard' before initialization
Symptom: the hook-preview webview never renders anything — the module
graph fails to initialize before React can mount.
Fix: add @copilotkit/shared to workspaceSourceAliases so rolldown reads the
TS source directly and skips the CJS wrapper, matching the existing
workaround for @copilotkit/a2ui-renderer.
Root cause: HostRoot was computed via useMemo([payload]) that read
window.__copilotkit_hookSite. That global is set by executeBundle inside a
separate useEffect([payload]). React runs useMemo during render and
useEffect after commit, so on the first render with a new payload:
1. setPayload(X) triggers re-render
2. useMemo runs, reads the stale global → returns null
3. Render commits showing 'Mounting host…'
4. useEffect fires, executeBundle writes the global — no state change
5. Harness mounts with () => null; RegistryReader fires setRegistry
6. Re-render: useMemo deps unchanged → returns CACHED null
7. UI stuck forever
mount-capture.test.tsx didn't catch this because it imports the fixture
directly and bypasses the bundle-then-resolve flow.
Fix:
- Extract the export-picking logic into resolve-host-root.ts (pure, unit
tested) so future readers see the precedence order (default → first
function) clearly.
- In App.tsx replace HostRoot useMemo with hostRoot useState populated
inside the executeBundle useEffect. The state write after executeBundle
triggers the re-render that was missing before. Clear window.__copilotkit_hookSite
before each execute so a failed bundle can't serve a stale module.
- Wrap the setter's argument in () => fn because useState treats a
function argument as an updater; we want hostRoot to BE the function,
not the result of calling it.
Root cause: HARDCODED_EXCLUDES omitted the standard Jest/Vitest directory
conventions. When running the extension against its own worktree (or any
repo that colocates test fixtures with source), we scanned fixture files
under src/**/__tests__/fixtures/*.tsx and listed them as real hook sites,
polluting the sidebar tree with entries the user perceives as 'outside' the
workspace.
Add __tests__, __fixtures__, __mocks__ to the excluded directory set so
the conventional boundary between test fixtures and shipped code is
respected.
Secondary fix: onDidSaveTextDocument fires for any .ts/.tsx the user saves,
including files opened via File > Open from outside the active workspace.
Those saves would push new sites into the tree via updateSitesForFile.
Gate the save handler on isInsideWorkspace(filePath, workspaceRoot) so a
stray editor tab doesn't leak entries.
I added it to the matrix in c3d105ae0 but the corresponding
examples/integrations/test-integration-tmp/ directory was never
created on this branch — the job fails on every PR run with
"No such file or directory".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auditableDemos filter (introduced for cli-start's informational
`command:` entry) was applied to the spec/qa SHOULD checks but not
the missing-demo-dir MUST check. Result: cli-start in langgraph-python's
manifest triggered a false missing-dir failure even though it has no
on-disk folder by design.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Post-merge CI caught two regressions:
1. shared-state-read-write demo directories were on disk but never
committed — 16 packages' manifests reference the demo but the
page.tsx scaffolds weren't tracked. This failed validate-parity,
bundle-demo-content tests, and drift-check transitively.
2. langgraph-python starter templates drifted after main added
docstring region markers to weather-tool-backend; regenerating
via `npx tsx generate-starters.ts` syncs the 17 starters.
Bundle (showcase/shell/src/data/demo-content.json) regenerated to
pick up the new demo content.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the aspirational 185-line prompt (read_me preflight, multi-call
iteration, palette discipline, zone backgrounds, label/fixedPoint rules,
opening+mid+final cameraUpdates) in favor of a ~35-line shape that biases
the agent toward exactly one create_view call with 3-5 elements and a
single framing cameraUpdate. Downgrades gpt-4.1 → gpt-4o-mini since the
element schema is simple JSON and model latency dominates end-to-end time.
Measured end-to-end (agent/run SSE to RUN_FINISHED) on the showcase
runtime: 5-6s per prompt across two prompts × two runs, all producing a
single create_view call with 8 elements (clean 3-step flowchart;
client/server/db diagram). Previously 3-5 minutes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restructure the headless-interrupt cell so the app surface is empty by
default and the popup appears there (not inside the chat or as a global
modal) when the backend calls `interrupt()`. The user triggers the run
from a pre-built suggestion chip in the right-hand `CopilotChat`, which
mirrors the other in-app-action demos (hitl-in-app, gen-ui-interrupt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Reimplements the SSE incremental reader workaround from the now-closed
PR #4093 on a fresh branch off `main`.
## Background
PR #4093 was closed under the assumption that PR #4095 (agno SDK upgrade
to 2.5.17) had fixed the underlying SSE stream termination bug at the
source. Follow-up diagnostics on 2026-04-19 revealed that the SDK
upgrade only addressed the **error path**; production `/api/smoke` with
real API keys still hangs because the agno SDK does not reliably emit
`TEXT_MESSAGE_END` / `RUN_FINISHED` on the **success path**.
Documented in Notion page `3473aa38-1852-815b-9765-fd28fa53275a`, item
#5.
## Change
`showcase/packages/agno/src/app/api/smoke/route.ts` — swap `await
res.text()` for an incremental reader:
- Read `res.body` via `getReader()` + `TextDecoder` + string buffer
accumulator.
- On each chunk, check whether the accumulated buffer contains
`"type":"TEXT_MESSAGE_CONTENT"` along with `"OK"` in the delta. Once
seen, cancel the reader and return a 200 success envelope — do not await
a terminal event that the SDK may never send on the success path.
- If the stream closes cleanly without ever producing the confirmation
payload, return a 502 with `stage:"response_incomplete"` so regressions
surface clearly in the smoke monitor.
- Keep `AbortSignal.timeout(45000)` as the final safety net.
- Response envelope shape preserved for the smoke monitor parser.
## Scope
Only the agno `/api/smoke/route.ts` is touched. The other 16 showcase
starters use the same `await res.text()` pattern but none have exhibited
the hang in production — no speculative fixes.
## Caveat — this is a mitigation, not a fix
The real fix belongs upstream in the agno SDK's AG-UI interface. **Do
not merge-and-forget.** Keep this PR open until upstream is addressed
and the fix is verified in production with real API keys, then revisit
whether this client-side workaround can be removed.
## Test plan
- [x] Visual inspection of the diff matches the pattern from PR #4093
- [x] Standalone `tsc --noEmit` against `route.ts` with DOM + ES2017
libs — clean (only missing `@types/node` for `process`, expected in
dry-run; code compiles cleanly otherwise)
- [ ] Production curl sanity check against
`https://showcase-agno-production.up.railway.app/api/smoke` once
deployed
- [ ] Monitor next 12h of `Showcase: Smoke Monitor` runs for agno
stability
Closes/replaces the approach from #4093.
Convert unselected/prebuilt-components.mdx single-file into a folder
matching the main /docs/prebuilt-components/ structure:
index.mdx, chat.mdx, sidebar.mdx, popup.mdx, meta.json
This way the nav builder creates a GROUP with CopilotChat /
CopilotSidebar / CopilotPopup children in the Built-in Agent sidebar
section, not just a single page link.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reimplements the approach from the now-closed PR #4093 (which I closed
after #4095 merged the agno 2.5.17 upgrade, on the belief that the SDK
bump had fixed the underlying SSE termination bug).
Follow-up evidence collected 2026-04-19 showed the SDK upgrade only
addressed the error path — production /api/smoke with real API keys
still hangs because the agno SDK does not reliably emit TEXT_MESSAGE_END
/ RUN_FINISHED on the success path. Documented in Notion page
3473aa381852815b9765fd28fa53275a, item #5.
Change: instead of `await res.text()` (which only resolves when the
server closes the stream), read res.body incrementally with
getReader() + TextDecoder. Short-circuit as soon as the buffer contains
`"type":"TEXT_MESSAGE_CONTENT"` with `"OK"` — the agent has produced
the expected output, so we cancel the reader and return a 200 success
envelope without awaiting a terminal event that may never arrive. If
the stream closes cleanly without confirmation, return a diagnostic
502 with stage="response_incomplete" so regressions surface clearly.
Keep the existing 45s AbortSignal timeout as a final safety net.
Response envelope shape is preserved for the smoke monitor parser.
This is a client-side mitigation, not a fix. The real fix belongs
upstream in the agno SDK's AG-UI interface. Keep this PR open until
upstream is addressed and verified in production with real keys.
Rewrites the mcp-apps agent system prompt from a minimal "call a tool and
populate the canvas" instruction into a detailed Excalidraw-aware
playbook. Adoptions:
- Workflow: instructs the agent to call `read_me` first (the Excalidraw
MCP server's own format reference) before `create_view`, and to use
`restoreCheckpoint` for follow-up edits.
- Layout principles: left-to-right vs. top-down vs. hub-and-spoke,
3-7 node target, consistent sizing (160x70 rectangles, 120x80
diamonds) and spacing (40-80px gutters, 60-120px viewport padding).
- Camera framing: opening `cameraUpdate` with a 4:3 size (400x300 →
1600x1200), optional mid-diagram pans, and a REQUIRED final
cameraUpdate that re-frames the whole diagram (otherwise the Excalidraw
animation camera ends zoomed on the last-drawn element).
- Labels: prefer `label: {text, fontSize}` on shapes/arrows (auto-centers,
auto-resizes) over bare `text:` fields (which Excalidraw silently
ignores on rectangles).
- Arrow bindings via `fixedPoint` edge anchors rather than hand-placed
coordinates; dashed stroke for async/optional paths; short label
annotations on decision transitions.
- Palette discipline: draws from the read_me palette (pastel fills +
primary strokes), caps at 3-4 background colors per diagram, assigns
them semantically (blue=frontend, green=success, yellow=decision,
red=error).
- Progressive emission order so the draw-on animation tells a story
(zone → shape → its label → its outgoing arrows → next shape), with
decorations last.
Also upgrades the model from gpt-4o-mini to gpt-4.1 because Excalidraw
element emission is structured/coordinate-heavy and a smarter model
produces visibly cleaner layouts (aligned nodes, edge-bound arrows,
labeled shapes) in practice.
Before: 3 colored boxes with no text (bare `text:` field silently
dropped), arrows ending on shape centers, no camera framing, no title.
After: 3 labeled boxes with consistent palette, edge-bound arrows with
transition labels, title text, cameraUpdate framing the diagram, and
the agent correctly consults `read_me` once per conversation for the
authoritative format reference.
Progressive streaming (Part 2 of the ask) was investigated and flagged
as shared-package-bound: `@ag-ui/mcp-apps-middleware` emits a single
`ACTIVITY_SNAPSHOT` only AFTER `RUN_FINISHED`, so there are no partial
tool-input events to consume in the cell. The Excalidraw MCP server does
animate elements in order once the full input arrives, which gives the
visual impression of progressive drawing without runtime changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the generic greeting-card / Pomodoro / revenue-chart suggestions
on the Fully Open-Ended Generative UI (minimal) cell with a set tuned for
intricate, educational visualisations: 3D airplane pitch/yaw/roll, a
feed-forward neural network's forward pass, quicksort, and a Fourier
series square-wave build-up.
Uses the provider's `openGenerativeUI.designSkill` prop (which injects
agent context for the built-in `generateSandboxedUi` tool) to swap in a
visualisation-tuned authoring prompt in place of the default shadcn
design language — covering SVG/canvas geometry, CSS keyframe motion,
mandatory labels + legends + callouts, a semantic palette (indigo /
emerald / amber / rose / slate), typography scale, and the self-running
constraint for the minimal cell. Agent system prompt updated in parallel
so the LLM knows the cell's focus and contract up-front.
Advanced variant untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The per-kickoff `agent.setMessages([])` call was racing with the
subsequent `addMessage` + `runAgent` pair, causing the second run
attempt to silently not dispatch (connection-refused on resume in some
timings). Drop the reset — rely on the fresh langgraph thread that each
`runAgent` implicitly produces, and on the existing `runId` marker to
gate ResultCard visibility so the stale result doesn't flash under the
button mid-run.
Validated: initial → modal → result → re-click → modal appears again,
all without a page refresh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`create_deep_agent` does not accept `state_schema=` as a kwarg (confirmed
against the installed signature; state schema is only pluggable via
middleware). The minimal state-extending middleware is kept but renamed
to `_GenUiStateMiddleware` with a comment pinning its one purpose, and
`CopilotKitMiddleware` is added so this cell matches every other
langgraph-python cell.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MDX wraps inline text inside HTML elements in <p> tags. Using <p> as
the outer wrapper for each card's description causes <p><p>text</p></p>
which breaks hydration. Switch to <div> as the outer wrapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sandboxed iframe runs with `sandbox="allow-scripts"` only, so a
`<form>` with `<button type="submit">` had its submit blocked by the
browser BEFORE the onsubmit handler could call `e.preventDefault()` —
which meant `Websandbox.connection.remote.evaluateExpression(...)`
never fired. The agent also read `res.result` while the host handler
returns `{ ok, value }`.
Tightened the system prompt and per-suggestion prompts to explicitly
forbid `<form>` / `type="submit"` and to steer the LLM toward the
handler's exact return shape (`res.value` / `res.error`). Validated
end-to-end via Playwright: both `evaluateExpression` and `notifyHost`
now round-trip with visible results in the sandbox UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The PieChart + BarChart renderers were added in 8be81821e as part of the
brand-polish pass, but the a2ui_dynamic agent's system prompt was still
only mentioning dashboards/status/KPIs. Expand the prompt to enumerate
the registered catalog (Card, StatusBadge, Metric, InfoRow, PrimaryButton,
PieChart, BarChart) and name when each chart fits — so the LLM reliably
reaches for PieChart on part-of-whole breakdowns and BarChart on
cross-category comparisons.
Validated on port 4100 with Playwright: "Show a pie chart of sales by
region" renders a brand-coloured donut with legend + percentages, and
"Render a bar chart of quarterly revenue" renders a polished Recharts bar
chart with tooltip. No "Unknown component" errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reorders gen-ui-agent (Agentic Generative UI / In-Chat State Rendering)
to appear AFTER Reasoning (Default Render) in the Operational
Generative UI category, per user preference.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Why
#oss-alerts currently fires red alerts on failure but never posts a
recovery message when `Showcase: Build & Deploy` or `Starter Deployed
Smoke Tests` flip back to passing. Team reported this gap — there's no
positive signal when the pipeline is healthy again after an outage. Per
the oss-alerts verbosity policy, we want red→green transitions (but not
green→green spam).
## What
Mirrors the per-service transition pattern already shipped in
`showcase_smoke-monitor.yml` (`✅ agno recovered (was
down since <iso8601>)`), applied at the workflow level for these two
pipelines.
### `showcase_deploy.yml` (notify job)
- Restore previous run's status from `actions/cache` (same
key/restore-keys pattern the smoke monitor uses:
`showcase-deploy-state-<run_id>` save key, prefix-based restore)
- Classify outcome as `failure | success | skip`
- `failure` → post existing red alert (unchanged wording) + set state to
`failure`
- `success` AND `PREV=failure` → post recovery message + clear state to
`ok`
- `success` AND `PREV=ok` → silent (green→green — matches prior behavior
for bulk drift rebuilds)
- `skip` (no-changes push, cancellation, indeterminate) → no state
change, no post
### `starter_deployed_smoke.yml`
- Restore state before tests run
- After the test step, classify via `steps.playwright.outcome`
- On red→green, post recovery message alongside the existing
failure-alert path
- On any terminal outcome (passed or failed tests), save state;
cancelled runs leave state untouched so streaks aren't broken
## Design choices
- **State storage: `actions/cache`** — not a committed JSON file. Avoids
commit-loop concerns entirely. TTL is ~7 days, far longer than the run
cadence of either workflow (showcase deploy fires on every main push +
manual dispatch; starter smoke runs every 6h + on every deploy). Same
pattern as the smoke monitor, which has been stable.
- **First-ever run**: initialize state to `ok` so we don't emit a false
recovery alert on the first green run after this ships.
- **Recovery wording** (matches smoke-monitor convention):
- `✅ *Showcase deploy*: recovered (was down since
<iso8601>) | <url|View run>`
- `✅ *Starter Deployed Smoke Tests*: recovered (was
down since <iso8601>) | <url|View run>`
- **Existing red-alert wording is unchanged.**
## Edge cases considered
- First-ever run with no prior state: initializes to `ok` — no spurious
recovery alert
- Cancelled / mid-matrix cancellation / no-changes push: no state
change, prior red state survives into next run
- Cache eviction beyond TTL: same as first-ever run (assume `ok`)
- `workflow_dispatch` with single service: doesn't post Slack today,
doesn't update state (unchanged behavior)
## Note for reviewer
This PR touches `.github/workflows/*.yml` so **please merge via the UI**
— my `gh` token can't admin-merge workflow-touching PRs.
## Test plan
- [ ] CI passes
- [ ] Merge → next failing deploy run writes `lastStatus=failure` to
cache
- [ ] Next green run after a failure posts `✅ *Showcase
deploy*: recovered ...` in #oss-alerts
- [ ] Subsequent green runs stay silent
- [ ] Same verification for starter smoke tests
Apply the verified CopilotKit brand palette (lilac #BEC2FF, mint #85ECCE,
near-black #010507, neutral greys) across every custom-defined component in
the langgraph-python cells. Replaces ad-hoc indigo/purple/amber/slate
Tailwind classes with brand tokens, softens corners (rounded-xl/2xl),
switches shadow-lg to shadow-sm, and normalises pill/label styling to
10-11px uppercase tracking-wider.
Preserves all component props, data-testid attributes, and @region markers.
Beautiful-chat (reference) is untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gen-ui-interrupt and hitl-in-chat both render an interactive time-picker
inline in the chat, but via different API layers:
- gen-ui-interrupt -> useInterrupt (low-level primitive)
- hitl-in-chat -> useHumanInTheLoop (ergonomic wrapper)
Rename to make the dashboard row label reflect the API distinction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Reorder meta.json so CSS is the first sub-page (easiest rung of the
customization ladder), then Slots, Headless UI, Reasoning Messages.
- Retitle slots.mdx frontmatter to "Slots (Subcomponents)" so the sidebar
nav reads the user-facing term people actually search for.
- css.mdx: demonstrate the new file+lines Snippet by pulling the
user/assistant bubble block straight from the cell's theme.css.
- headless-ui.mdx: expand from a bare IntegrationGrid stub into a full
page with minimal + complete examples, the three core hooks, and
Snippet pulls from both headless-simple and headless-complete cells.
- Mirror the css/slots changes into unselected/custom-look-and-feel/ so
the framework-agnostic tree stays in step (add css.mdx, reorder
meta.json, retitle slots nav).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rewrite overview to present the 4-level customization ladder (drop-in →
CSS → slots → headless), with a 2x2 card grid linking out to each rung.
- Add a new chat.mdx sub-page for CopilotChat with intro, InlineDemo,
the CopilotChat GIF, provider-setup snippet, and the standard code
example from docs.copilotkit.ai.
- Reorder meta.json to chat → sidebar → popup.
- Add gif embeds to sidebar.mdx and popup.mdx (matching docs.copilotkit.ai).
- Point stale /prebuilt-components cross-links at the new
/prebuilt-components/chat page.
- Align unselected/prebuilt-components.mdx with the ladder framing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
<Snippet> now supports a second lookup mode alongside the existing
region="..." marker: file="..." lines="A-B" pulls a line range (or
the whole file when lines is omitted) from any file in the cell's
bundled files[]. Region lookup wins when both are passed, preserving
all existing call sites.
Parses dash ("10-20"), en-dash, and single-line ("12") ranges.
Surfaces the same graceful WarningBox on missing files / bad ranges
as the region path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>