Commit Graph

228 Commits

Author SHA1 Message Date
Alem Tuzlak cc6dd5c4a9 Merge branch 'main' into fix/showcase-declarative-gen-ui-card-width 2026-05-11 16:03:32 +02:00
Alem Tuzlak ba5369c930 refactor(showcase/langgraph-python): make A2UI renderers fill their slot instead of widening the chat
Drop the inline <style> override that widened the chat's `cpk:max-w-3xl`
column and the outer max-w-6xl bump. The chat keeps its normal width;
the real bug was that the basic catalog's Row/Column primitives are
bare `display: flex` divs with no gap and no min-width control on
children, so when the agent dropped multiple Metrics or charts into a
Row they collapsed to content width and looked glued together.

Four targeted fixes inside our renderers:

- Metric gains `flex-1 min-w-[120px]` so a row of KPI tiles distributes
  the available width evenly inside the gap-less basic Row, instead of
  shrinking to content.
- PieChart and BarChart switch from a hardcoded max-w to `flex-1
  min-w-0` so two charts side-by-side each take half the card column
  with Recharts' ResponsiveContainer doing the rest, instead of one
  chart insisting on 640px and overflowing.
- Card's CardContent picks up a Tailwind arbitrary variant
  `[&_div[style*='flex-direction:_row']]:gap-4` (plus the column
  equivalent) that injects a gap into any nested basic Row/Column the
  agent drops in. Underscores in the arbitrary value compile to literal
  spaces, matching React's serialized inline `flex-direction: row`.
- Card itself drops the old `min-w-[260px]` floor in favour of
  `min-w-0` so it cooperates if the agent ever stacks Cards horizontally
  inside a Row.
2026-05-11 16:01:11 +02:00
Alem Tuzlak bedc53a652 fix(showcase/langgraph-python): widen declarative-gen-ui surface and polish InfoRow
The chat shell caps its scroll column at cpk:max-w-3xl (~768px), which
left A2UI-generated cards (KPI dashboards, charts, status reports)
feeling pinched on the declarative-gen-ui demo. Locally widen that
wrapper to 64rem via a scoped attribute selector on the demo and bump
the outer page wrapper from max-w-4xl to max-w-6xl so the card column
actually has room to grow.

While here, fix the InfoRow trailing-separator artifact: each row now
draws its own border-bottom with last:border-b-0 so the final row in a
Card (e.g. the Status Report demo) no longer leaves a dangling line,
regardless of whether the agent wraps the rows in a Column or drops
them directly into the Card child slot. Right-align the value with
tabular-nums for cleaner stacks. Card itself gains w-full
overflow-hidden so it stretches into the now-wider column instead of
sitting at its min-width.
2026-05-11 15:42:06 +02:00
Alem Tuzlak 25f1f921a8 fix(showcase): unbreak multimodal demo end-to-end (auto-send, dedupe, proxy) (#4761)
## Summary

Re-lands the multimodal-attachments fix from #4584 (May 1, never merged)
onto current `main`, ported to the post-refactor file layout where
`page.tsx` was split into `legacy-converter-shim.tsx`,
`multimodal-chat.tsx`, and `file-to-data-attachment.ts`.

Auto-send was the visible regression: clicking **Try with sample image /
Try with sample PDF** only queued the attachment chip instead of sending
the canned prompt. This PR restores the full end-to-end behavior plus
five regression tests so it can't silently break again.

## What was broken and what changed

1. **Random uploads crashed with `Failed to fetch`.** aimock returned
HTTP 404 on no-match, the LangGraph SDK surfaced `NotFoundError`, the
AG-UI stream surfaced a `RUN_ERROR`, the demo crashed. → Added
`--proxy-only` + `--provider-openai https://api.openai.com` to the local
aimock command so unmatched user prompts fall through to real OpenAI
(mirrors Railway).

2. **Bundled-sample fixtures keyed on user-visible canned prompts.**
Auto-prompts are now natural and specific ("can you tell me what is in
this demo image/pdf I just attached") so they render cleanly as the user
message bubble AND can't collide with arbitrary user prompts — random
uploads phrase questions differently and fall through to the proxy.

3. **Sample buttons now auto-send via `useAgent`.** The previous
DataTransfer path queued the attachment via the chat's hidden file input
but required clicking send while the attachment was still uploading —
`CopilotChat.onSubmitInput` rejects submits during upload AND clears the
input regardless, so the canned prompt was eaten. Rewrite calls
`agent.addMessage(...)` + `copilotkit.runAgent({ agent })` directly with
the base64'd content part.

4. **PDF flattened text bled into the rendered user message.**
`_PdfFlattenMiddleware` ran in `before_model` and persisted the rewrite
to agent state. Switched to `wrap_model_call` so the PDF→text rewrite is
scoped to the model request only.

5. **Attachments doubled (and PDFs rendered as broken `<img>`).** The
`@ag-ui/langgraph` round-trip mis-tags PDFs as `image` and re-injects
the user's original modern part, doubling chips. Added
`dedupeUserMessageMedia` subscriber on `onMessagesSnapshotEvent` +
`onRunFinalized` to dedupe by `source.value` and re-key type from
mimeType. Also flipped `onRunInitialized` from REPLACE to APPEND so the
modern part stays for the UI alongside a legacy `binary` sibling for the
converter.

6. **Regression suite (`tests/e2e/multimodal.spec.ts`).** Five focused
tests, all pass against live local stack (15.4s):
   - page loads with all expected affordances
- sample image: auto-sends, EXACTLY ONE `<img>`, assistant references
the logo
- sample PDF: auto-sends, EXACTLY ONE `DocumentAttachment` chip ("PDF"
label), NO `<img>`, no `[Attached document]` text bleed
- image then PDF in the same session: each message keeps its own single
chip
   - PDF then image in the same session: symmetric

## Test plan

- [x] `showcase up langgraph-python` — both sample buttons auto-send;
image renders as `<img>`, PDF renders as PDF chip; random paperclip
uploads go through proxy
- [x] `BASE_URL=http://localhost:3100 CI=1 npx playwright test
multimodal.spec.ts` — **5 / 5 passing**
- [ ] Post-merge: e2e-deep cycle for langgraph-python multimodal cell
stays green

## Closes

Closes #4584.
2026-05-11 15:19:34 +02:00
github-actions[bot] 8968007188 style: auto-fix formatting 2026-05-11 12:57:34 +00:00
Alem Tuzlak 7c3edca2b7 fix(showcase): unbreak multimodal demo end-to-end (sample buttons auto-send, dedupe, proxy)
The langgraph-python multimodal-attachments demo had a stack of bugs
that compounded each other. Fixing them required touching the local
docker-compose, the aimock fixtures, the LangChain middleware, the
client-side AG-UI shim, and the sample-attachment buttons. This
commit lands the full set together because they only make sense as
a unit — verified end-to-end against `showcase up langgraph-python`
in a headed browser. New e2e suite pins each regression.

Supersedes #4584 (the original fix from May 1 that never landed —
this is a fresh port onto the post-refactor file layout where
page.tsx is split into legacy-converter-shim.tsx, multimodal-chat.tsx,
file-to-data-attachment.ts).

What was broken and what changed:

1. Random uploads crashed with `Failed to fetch`. aimock returned
   HTTP 404 on no-match, the LangGraph SDK surfaced `NotFoundError`,
   the AG-UI stream surfaced a `RUN_ERROR`, the demo crashed.
   Added `--proxy-only` + `--provider-openai https://api.openai.com`
   to the local aimock command so unmatched user prompts fall through
   to real OpenAI (mirrors the Railway aimock setup).

2. Bundled-sample fixtures keyed on user-visible canned prompts.
   The auto-prompts are deliberately long, specific, and natural-
   reading ("can you tell me what is in this demo image/pdf I just
   attached") so they (a) render cleanly as the user message bubble,
   and (b) can't collide with arbitrary user prompts — random
   uploads phrase questions differently and fall through to the
   proxy.

3. Sample buttons now auto-send via `useAgent`. The previous
   DataTransfer-based path queued the attachment via the chat's
   hidden file input, then required clicking send while the
   attachment was still uploading — `CopilotChat.onSubmitInput`
   rejects submits during upload AND clears the input regardless,
   so the canned prompt was eaten. Rewrite to call
   `agent.addMessage(...)` + `copilotkit.runAgent({ agent })`
   directly with the base64'd content part, sidestepping the
   upload race entirely.

4. PDF flattened text bled into the rendered user message.
   `_PdfFlattenMiddleware` ran in `before_model` and returned
   `{"messages": rewritten}`, which persisted to agent state. The
   chat UI then rendered the `[Attached document]\n<pdf body>` text
   part inline with the user prompt. Switched to `wrap_model_call`
   so the PDF→text rewrite is scoped to the outgoing model request
   only and never pollutes state.

5. Attachments doubled (and PDFs rendered as broken `<img>`). The
   `@ag-ui/langgraph` round-trip translates outgoing `binary` parts
   to LangChain `image_url` and incoming `image_url` back to `image`
   AG-UI parts — regardless of mimeType, so PDFs came back as
   `type: "image"` with `mimeType: "application/pdf"` and were
   forced into `ImageAttachment`, where the load failed and the
   chat showed two "Failed to load image" boxes. Plus the user's
   original modern part survived alongside the round-tripped one,
   doubling visible chips.

   Added a `dedupeUserMessageMedia` subscriber on both
   `onMessagesSnapshotEvent` and `onRunFinalized` to:
   - dedupe media parts by `source.value` so the local + round-
     tripped copy collapse to one chip
   - re-key part `type` from `mimeType` so PDFs route to
     `DocumentAttachment` (icon + filename) and images to
     `ImageAttachment`.

   Also flipped the `onRunInitialized` shim from REPLACE to APPEND
   — keep the modern part for the UI AND emit a legacy `binary`
   sibling for the converter.

6. Regression suite (`tests/e2e/multimodal.spec.ts`). Replaces the
   pre-rewrite suite with five focused tests:
   - page loads with all expected affordances
   - sample image: auto-sends, EXACTLY ONE `<img>`, assistant
     references the logo
   - sample PDF: auto-sends, EXACTLY ONE `DocumentAttachment` chip
     ("PDF" label), NO `<img>`, no `[Attached document]` text bleed
   - image then PDF in the same session: each message keeps its own
     single chip, no cross-contamination
   - PDF then image in the same session: symmetric

   All 5 pass against the live local stack (15.4s).
2026-05-11 14:54:38 +02:00
Alem Tuzlak 985bebf39c fix(showcase/voice): use real Whisper for mic transcription
The voice route's OpenAI client previously fell through to OPENAI_BASE_URL,
which docker-compose.local.yml sets to http://aimock:4010/v1. Aimock has a
catchall transcription fixture that returns "What is the weather in Tokyo?"
for every audio file, so the mic button always produced that phrase no
matter what the user actually said.

Pin baseURL to real OpenAI (overridable via OPENAI_TRANSCRIPTION_BASE_URL).
The sample-audio button stays as synchronous text injection — that's the
documented design, and what the e2e + d5 probe rely on.

Also:
- Tidy the sample button label ("Try a sample question" -> "Try a sample
  audio") so the affordance matches what it does.
- Realign tests/e2e/voice.spec.ts with the shipped component (the
  voice-sample-audio container testid and Sample: "..." caption it asserted
  on never existed on HEAD) and add cold-start timeout headroom for the
  mic-button render and the agent-flow test.
- Add "env": ".env" to langgraph.json so langgraph_cli dev picks up
  OPENAI_API_KEY locally. Docker/Railway paths inject env vars directly so
  this is a no-op there.
2026-05-11 14:45:10 +02:00
Alem Tuzlak 32d0237cb5 fix(shell-docs): cutover-blocker fixes from Phase 4 validation (#4741)
## Summary

Phase 4 validation (run today against `docs.showcase.copilotkit.ai`)
surfaced four cutover-blocking issues. This PR fixes all of them in four
focused commits.

## Commits

1. **chore(showcase): close last 2 yellow Missing snippet boxes for
cutover** — adds in-place region markers to
`langgraph-python::frontend-tools` and a sibling
`slot-overrides.snippet.tsx` teaching file for
`langgraph-python::chat-slots`. Both pages now render zero `Missing
snippet` warnings.

2. **fix(shell-docs): correct feature-viewer slug + demo-id translation
for code tab** — adds `getFeatureViewerSlug()` and
`getFeatureViewerDemoId()` helpers in `registry.ts`, with explicit
override maps for the 8 framework-name mismatches (built-in-agent,
google-adk, claude-sdk-{python,typescript}, ms-agent-{python,dotnet},
crewai-crews, llamaindex) and 5 demo-id mismatches (gen_ui_tool_based,
gen_ui_agent, shared_state_streaming, shared_state_read_write,
hitl_in_chat). When the framework or demo has no feature-viewer
equivalent, the helper returns null and the Code tab is hidden on that
page (graceful degradation; Demo tab still renders).

3. **fix(shell-docs): redirect catalog hygiene (self-loops +
framework-scoped gap)** — removes 31 self-redirect entries from
`seo-redirects.ts` (where source === destination caused infinite 301
loops on canonical URLs like /frontend-tools, /faq, /human-in-the-loop).
Reorders middleware logic so the redirect catalog is consulted before
the framework-scoped short-circuit fires, fixing 97 framework-scoped
slug-rename URLs that were soft-404ing instead of redirecting. Adds
defense-in-depth skip-when-equal guard in middleware.

4. **fix(shell-docs): resolve 53 sitemap 500s before cutover** — three
independent root causes in MDX rendering:
- 4 missing \`<Component />\` registrations in \`mdx-registry.tsx\`
(CopilotCloudConfigureCopilotKit,
SelfHostingCopilotRuntimeConfigureCopilotKit, CloudCopilotKit, Content)
— affected ~37 pages.
- 3 langgraph tutorial pages had markdown lists immediately preceding
JSX closing tags, causing remark to bail. Fix = blank line between
bullet and close tag (~9 pages).
- 5 MDX files used escaped JSX comments that Acorn cannot parse —
replaced with proper unescaped form (~7 pages).

## Verification

- Production build clean: 27 static pages generated, 0 errors
- Local probes: all 53 sitemap-500 URLs return 200; all 31 former
self-loop URLs return 200; all 85 framework-scoped non-loop catalog
entries 301 to expected destinations
- Code tab: 47/60 (framework × demo) URLs resolve to real code panels
post-fix; remaining 13 are feature-viewer per-framework deploy-coverage
gaps in the \`ag-ui-protocol/ag-ui\` repo, not this one
- Both langgraph-python pages render 0 \`Missing snippet\` boxes after
re-bundling demo-content

## Test plan

- [ ] CI passes
- [ ] Sitemap probe: every URL in \`/sitemap.xml\` returns 200
- [ ] Redirect probe: spot-check 5 framework-scoped slug renames (e.g.
\`/agno/frontend-actions\`, \`/pydantic-ai/use-agent-hook\`)
- [ ] Self-loop probe: \`/frontend-tools\` returns 200, not infinite 301
- [ ] Code tab: visit \`/built-in-agent/frontend-tools\` (tab should be
hidden), \`/langgraph-python/agentic-chat\` (tab should render code),
\`/llamaindex/agentic-chat\` (tab should render after llama-index slug
rename)
- [ ] Yellow boxes: visit \`/langgraph-python/frontend-tools\` and
\`/langgraph-python/custom-look-and-feel/slots\` — zero Missing snippet
warnings
2026-05-11 13:26:19 +02:00
Alem Tuzlak 5c0a87e18b Merge branch 'main' into test/lgp-a2ui-regression-coverage 2026-05-11 13:12:38 +02:00
github-actions[bot] e831c72a8f style: auto-fix formatting 2026-05-10 22:19:25 +00:00
Tyler Slaton 59eb245a1c fix(showcase/langgraph-python): align e2e specs + QAs with current demos
The specs and QA markdowns had drifted from the demos they describe.
This commit brings every test contract into line with the actual demo
source — eliminating false-greens, false-fails, and stale assertions.

False-fail spec assertions (would fail every run):
- `agentic-chat.spec.ts` — rewrote from the old `change_background` /
  `weather-card` / `useAgentContext` flow that no longer exists. New
  spec exercises the vanilla `<CopilotChat>` + three suggestion pills
  contract the simplified demo actually exposes.
- `gen-ui-tool-based.spec.ts` — asserted on UI text ("Use the sidebar
  to generate charts", "Chart Generator") that doesn't exist; switched
  to suggestion-pill assertions and scoped the SVG check to inside the
  assistant-message bubble (was matching CopilotChat's send-button
  SVG).
- `agent-config.spec.ts` — asserted heading "Agent Config Object" but
  the demo has "Agent Config".
- `multimodal.spec.ts` — asserted a non-existent "Multimodal
  attachments" heading; switched to the `multimodal-demo-root` testid.
- `chat-slots.spec.ts` — asserted `[data-testid="custom-assistant-
  message"]` and the bare text "slot" — neither exists. The actual
  signal is `data-slot-label="MessageView.AssistantMessage"` from the
  SlotMarker wrapper.
- `reasoning-default.spec.ts` — asserted `[data-testid="copilot-
  reasoning-message"]` and `[data-message-role="reasoning"]`; neither
  is emitted by `CopilotChatReasoningMessage`. Switched to the text-
  based "Thinking…/Thought for…" header label.

False-green spec assertions (passed for the wrong reason):
- `shared-state-read.spec.ts` — was a complete false-green: asserted
  on "Sales Pipeline", "Total Pipeline", "Active Deals" but the demo
  has been a Recipe Editor for some time. Rewrote against the
  recipe-card / ingredients-container / instructions-container testids.
- 11 specs (agent-config, beautiful-chat, frontend-tools-async,
  gen-ui-tool-based, gen-ui-agent, gen-ui-interrupt, hitl-in-chat,
  hitl-in-app, multimodal, readonly-state-agent-context, voice) used
  `[data-role="assistant"]` to gate "agent responded" — but the v2
  react-core bundle never emits that attribute (it ships
  `data-testid="copilot-assistant-message"`). Mechanical sweep to the
  correct testid.
- Deleted `shared-state-write.spec.ts` (route consolidated into
  `shared-state-read-write` earlier on this branch — spec targeted a
  removed demo) and `renderer-selector.spec.ts` (asserted on a radio-
  pill UI that no longer exists; the four "Declarative UI" variants
  are now separate manifest demos).

QA drift:
- `qa/gen-ui-tool-based.md` documented a "Haiku Generator" demo with
  haiku-card / japanese-line / english-line / haiku-image testids — a
  demo that doesn't exist anywhere on this branch. Rewrote to match
  the chart-rendering demo's actual testids and pill prompts.
- `qa/chat-slots.md` referenced "Custom Slot" pill / "Welcome to the
  Slots demo" heading / "This welcome card is rendered via the
  welcomeScreen slot." body text — all of which the slot-wrappers
  refactor on this branch removed. Updated to match the
  `custom-welcome-message` sub-slot that's actually rendered. Also
  fixed max-w-4xl → max-w-5xl to match the page.
- `qa/shared-state-read.md` said default instruction is "Preheat oven
  to 350 F" but the source has "Preheat oven to 350°F (175°C)".
- `qa/agentic-chat.md` rewrote to match the simplified vanilla-chat
  demo (the previous QA documented `change_background` / `WeatherCard`
  flows that no longer exist).
- `qa/reasoning-default.md` cited `kind: "testing"` in feature-
  registry.json for the `reasoning-default` entry; the registry entry
  has no `kind` field. Rewrote without the false cross-file claim.
- Deleted 4 orphan QA files for demos that don't exist:
  `agentic-chat-reasoning.md`, `hitl.md`, `hitl-in-chat-booking.md`,
  `shared-state-write.md`.
- Renamed `qa/reasoning-default-render.md` → `qa/reasoning-default.md`
  to match the manifest cell name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:16:42 -07:00
Tyler Slaton 70e2fb13c8 refactor(showcase): rename byoc-* slugs to declarative-* + sort index by manifest features
User-facing renames so the showcase reads the way a cold visitor would
expect:

- `byoc-hashbrown` → `declarative-hashbrown` (and `byoc-json-render` →
  `declarative-json-render`). The display titles already said
  "Declarative UI: …"; only the URL slugs and folder paths still
  leaked the internal BYOC ("Bring Your Own Components") jargon.
  Renamed:
    /demos/byoc-hashbrown          → /demos/declarative-hashbrown
    /demos/byoc-json-render        → /demos/declarative-json-render
    /api/copilotkit-byoc-*         → /api/copilotkit-declarative-*
    src/app/demos/byoc-*           → src/app/demos/declarative-*
    qa/byoc-*.md                   → qa/declarative-*.md
    tests/e2e/byoc-*.spec.ts       → tests/e2e/declarative-*.spec.ts
  Internal Python module names + langgraph graph IDs stay legacy
  (`byoc_hashbrown_agent.py`, `byoc_hashbrown`) — those are not
  user-facing and renaming them is a separate cross-codebase pass.
- `a2ui-fixed-schema` slug intentionally unchanged.
- Tool Rendering trio parenthetical rename (Default → Catch-all →
  Custom progression reads clearly as "how much do I customize?"):
    Tool Rendering (Default)        — unchanged
    Tool Rendering (Custom default) → Tool Rendering (Catch-all)
    Tool Rendering (Specific)       → Tool Rendering (Custom)
- `tool-rendering-reasoning-chain` cell renamed from
  "Generative UI: Rendering multiple tools" to
  "Generative UI: Tool calls + reasoning" (the demo is about combining
  reasoning + tool rendering, not about quantity of tools).
- `Open Generative UI: Default` / `Open Generative UI: Custom`
  descriptions expanded so a visitor understands how Open Generative UI
  differs from Tool Rendering (agent composes UI from a registered
  library vs. attaching a renderer to a *named* backend tool).
- Showcase index now sorts demos within each tag by `manifest.features`
  order. Previously demos appeared in manifest declaration order, which
  ignored the team's curated "polished flagship → simplest start →
  variants" arc.

Cross-cutting registry / harness / dashboard updates that fall out of
the rename:

- `shared/feature-registry.json` adds the two new IDs alongside the
  legacy `byoc-*` (so the catalog stays valid; the other 17
  integrations still declare `byoc-*` in their manifests).
- `shared/constraints.yaml` adds the new IDs to the
  generative-ui-approach allow-list.
- `scripts/__tests__/generate-catalog.test.ts` updates the cell-count
  expectations (45 features × 18 integrations = 810; 792 after docs-
  only exclusion; 45 LGP cells = 38 wired + 1 stub + 6 unshipped).
- Harness probe `d5-byoc.ts` + `d5-byoc.test.ts` now route both slug
  families through `preNavigateRoute` and exercise the new branches.
- `d5-feature-mapping.ts` and `shell-dashboard/live-status.ts` mirror
  the dual-ID mapping so both legacy and renamed slugs roll up under
  the same `byoc` D5 featureType.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:16:02 -07:00
Tyler Slaton 08ba26590e refactor(showcase/langgraph-python): clean up agents + demos for didactic clarity
Extracts duplicated logic into shared modules so each demo file reads
as the feature it teaches, not the boilerplate around it.

Python agents:
- `_a2ui_utils.py` (new) — `sanitize_a2ui_components` and
  `has_root_component`, consumed by both `a2ui_dynamic.py` and
  `beautiful_chat.py` (these previously duplicated the same defensive
  validator inline).
- `byoc_hashbrown_prompt.py` (new) — the 56-line system prompt extracted
  from `byoc_hashbrown_agent.py` so the agent file stays focused on the
  `create_agent(...)` wiring.
- `beautiful_chat.py` secondary `ChatOpenAI` now passes
  `streaming=True` (matching `a2ui_dynamic.py`) so aimock's SSE-only
  fixture matcher sees the call in replay mode — without it the demo
  surfaced "An internal error occurred" on every load.
- `multimodal_agent.py` — top-level `from pypdf import PdfReader` (was
  lazy with three layers of `# pragma: no cover` exception handling
  for stages that never failed independently); kept a single log line
  at the outer except so Railway logs stay triageable.
- `gen_ui_agent.py` — switched from `deepagents.create_deep_agent` (whose
  planner+sub-agent middleware ate enough supersteps to trip LangGraph's
  default recursion limit on this single-tool ReAct loop) to plain
  `langchain.agents.create_agent`. Comment explains the math.
- `tool_rendering_agent.py` — docstring no longer claims to back the
  `tool-rendering-reasoning-chain` cell (it has its own agent file).

TypeScript / TSX demos:
- `_shared/parse-json-result.ts` (new) — extracted from
  `tool-rendering/parse-json-result.ts`; now consumed by three demos.
- `_shared/slot-override.ts` (new) — `makeSlotOverride<T>` centralizes
  the 11 `as unknown as` casts that `chat-slots/page.tsx` previously
  scattered across the slot-override block.
- `shared-state-read` — extracted `RecipeCard` component + `types.ts`,
  dropped the dual-state-sync pattern that had the read-only demo
  locally mutating recipe state. Page is now a thin shell that publishes
  edits via `agent.setState` and reads back via `agent.state.recipe`.
  Also surfaces `runAgent` rejections via `console.error` instead of
  the previous silent `.catch(() => {})`.
- `headless-complete/hooks/use-auto-scroll.ts`,
  `headless-complete/hooks/use-typing-indicator.ts` (new) — extracted
  from `chat.tsx`. The chat file shrinks by ~40 lines. Same silent-
  rejection fix on `runAgent` as shared-state-read.
- `frontend-tools-async/fake-notes-db.ts` (new) — extracted from
  `page.tsx`; the demo file no longer leads with 60 lines of fake-DB
  scaffolding before `useFrontendTool` (the actual feature) appears.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:15:27 -07:00
Tyler Slaton a3d93266e9 fix(showcase/langgraph-python): correctness fixes for demos
Real demo-time bugs in the langgraph-python integration:

- `interrupt-headless` was rendering hardcoded stale slot dates from a
  `DEFAULT_SLOTS` constant instead of reading `payload.slots` that the
  backend `interrupt(...)` already supplies. Sibling `gen-ui-interrupt`
  does this correctly. Headless variant now matches.
- `_shared/interrupt-fallback-slots.ts` (new) — the JS fallback for
  when the backend returns no `slots` array. Generates relative to
  Date.now() so the picker never shows past dates. Used by both
  `interrupt-headless` and `gen-ui-interrupt`. Deletes the old stale-
  literal `gen-ui-interrupt/fallback-slots.ts` (dates from April that
  had already decayed).
- `interrupt_agent.py` — replaced hardcoded `timezone(timedelta(-7))`
  PDT with `zoneinfo.ZoneInfo("America/Los_Angeles")` so the demo
  doesn't lie about offsets in winter. Also fixed a Sunday edge case
  where `next_monday` collapsed to the same date as `tomorrow` (both
  Python and the JS fallback) — added a `<= 1` skip-a-week guard.
- `package.json#dev` ran `uvicorn agent_server:app --port 8000` but
  `src/agent_server.py` was a 3-line stub with no `app` symbol AND the
  API route pointed at port 8123. Replaced with the canonical
  `langgraph_cli dev --port 8123` invocation; deleted the dead stub.
- `entrypoint.sh:47` smoke-checked `src/agents/tools.py` (a file that
  never existed) so every Railway boot logged a phantom ERROR. Dropped.
- `reasoning_agent.py` and `tool_rendering_reasoning_chain_agent.py`
  intentionally omit `CopilotKitMiddleware` (they exercise only
  reasoning-token streaming, no frontend tools / app context). Added a
  one-line comment so a future maintainer doesn't cargo-cult it back in.
- `subagents._invoke_sub_agent` text-block walker had a regression
  where a `{"type":"text","text":null}` payload (a known provider
  quirk) would crash `"".join(parts)` with `TypeError: sequence item
  N: expected str instance, NoneType found`. Restored the
  `isinstance(block.get("text"), str)` guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:14:43 -07:00
Tyler Slaton 2f6816fe3f feat(showcase/langgraph-python): align chat surfaces + shadcn recipe overhaul (#4753)
## Summary

Four LangGraph Python showcase demos updated:

- **HITL In-app**: `CopilotChat` → `CopilotPopup`
(`defaultOpen={true}`). Tickets panel fills the viewport; chat is a
popup in the corner. Approval dialog still portaled to `<body>`.
- **Shared State: Streaming**: `CopilotChat` (custom aside) →
`CopilotSidebar`. Document view fills the page; sidebar opens by
default.
- **Shared State: Read + Write**: `CopilotPopup` → `CopilotSidebar`.
Card grid breakpoint bumped from `lg:` to `xl:` so cards stack instead
of pinching when the sidebar consumes ~480px on mid-size laptops;
`overflow-y-auto` on the wrapper keeps the page scrollable.
- **Shared State: Read (Recipe)**: visual overhaul with shadcn
primitives (`Card`, `Input`, `Select`, `Textarea`, `Badge`, `Button`,
`Spinner`, `Separator`). All `data-testid` attributes and
QA-doc-asserted strings preserved verbatim ("Make Your Recipe", "AI
Recipe Assistant", "+ Add Ingredient", "+ Add Step", "Improve with AI",
default ingredients/instructions, all 7 dietary preference labels, all 5
cooking-time labels, all 3 suggestions, etc.). React state-sync logic
preserved verbatim (no behavior change).

## Why

The chat surfaces were inconsistent across demos — HITL used a full-pane
chat where the demo's premise is "approval modals appear *outside* the
chat surface", and the shared-state demos mixed `CopilotChat` and
`CopilotPopup` instead of using the prebuilt `CopilotSidebar`. The
Recipe demo was raw Tailwind while the rest of the suite uses shadcn
primitives.

## Plumbing fixes that fell out of the work

- **`clsx` + `tailwind-merge` added to `package.json`** as direct deps.
They were imported by `src/lib/utils.ts` (the `cn` helper used by all
shadcn primitives) but only transitively resolvable, which broke `next
dev --turbopack`'s stricter module resolution.
- **`globals.css` rule**: `body[data-scroll-locked] { padding-right: 0
!important }`. Radix overlays (Select, Dialog, Popover) use
`react-remove-scroll-bar`, which measures the gap between viewport and
body inner width to detect scrollbar size — and our body uses
`margin-inline-end: 480px` to make room for `<CopilotSidebar />`. The
library mis-reads that 480px as scrollbar width and "compensates" with a
matching `padding-right`, which shrinks the content area and shifts the
centered card every time a dropdown opens. We have no real scrollbar
(`body { overflow: hidden }`), so the compensation is unnecessary —
neutralize it. Comment in the diff explains the rationale.

## Test plan

- [x] `next build` clean across all 54 routes
- [x] `oxlint` reports zero new warnings (only pre-existing patterns the
diff preserved verbatim)
- [x] Manually verified `/demos/hitl-in-app`,
`/demos/shared-state-streaming`, `/demos/shared-state-read-write`,
`/demos/shared-state-read` in browser (Chrome, dev server)
- [x] Verified the Recipe page's Select dropdowns no longer shift the
card when opened (the original motivation for the `globals.css` rule)
- [ ] HITL e2e suite (`tests/e2e/hitl-in-app.spec.ts`) — selectors
preserved (`getByPlaceholder("Type a message")`, suggestion-pill
testids, approval-dialog-* testids); CopilotPopup's `defaultOpen={true}`
mounts the chat input on first paint
- [ ] Streaming and Read e2e suites are stale pre-PR (reference content
that doesn't exist in source — "AI Document Editor", "Sales Pipeline")
and remain in the same state

## CR loop

Ran `cr-loop` (Round 1, 7 agents). Reclassified one finding to bucket
(d) per user direction (npm/pnpm lockfile-convention concern is a
different PR's subject — pre-existing across all
`showcase/integrations/*` and out of scope here). Procedure 3 audit
promoted no items to bucket (a); 24 bucket (c) items confirmed as
pre-existing and routed to follow-up backlog.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-10 14:46:57 -07:00
Tyler Slaton 3d47397957 feat(showcase/langgraph-python): align chat surfaces + shadcn recipe overhaul
Switch HITL In-app to CopilotPopup; Shared State Streaming and Read+Write
to CopilotSidebar; widen card-grid breakpoint and add overflow scroll on
Read+Write so content stays usable when the sidebar consumes ~480px of
viewport. Re-skin the read-only Recipe demo with shadcn primitives,
preserving every data-testid and QA-doc-asserted string.

Add clsx and tailwind-merge as direct deps (previously only transitively
resolvable, breaking turbopack dev) and a globals.css rule to neutralize
the Radix scroll-lock padding-right that react-remove-scroll-bar injects
when it mis-detects body's margin-inline-end (CopilotSidebar) as
scrollbar width.

QA docs updated to reference the new chat surfaces.
2026-05-08 22:26:14 -07:00
Tyler Slaton 46c5b56881 fix(showcase/langgraph-python): force reasoning emission in reasoning-chain agent
The tool-rendering-reasoning-chain D5 probe has been red since the
probe was added (PR #4743) — the reasoningMessage slot's
<ReasoningBlock> never mounts because the agent doesn't emit
reasoning summaries.

Compared to the working reasoning_agent.py (which powers
reasoning-default + reasoning-custom and is green), the only
meaningful difference was the reasoning config:

  reasoning_agent.py (works):
    reasoning={"effort": "medium", "summary": "detailed"}
    tools=[]

  tool_rendering_reasoning_chain_agent.py (was broken):
    reasoning={"effort": "low", "summary": "auto"}
    tools=[get_weather, search_flights, ...]

`summary: "auto"` lets the model decide whether to emit a reasoning
summary. With tools present the model often skips it (chain-of-thought
goes straight into the tool call without a summary). `summary:
"detailed"` forces emission on every response, which lights up the
ReasoningBlock slot. Bumping effort medium so the surface visible to
the user reads as a real chain-of-thought, matching reasoning-display.

Probe diagnostics from prod confirmed the toolCalls were firing fine
(WeatherCard mounted, assistant text landed) — only the reasoning
emission was missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:06:04 -07:00
Tyler Slaton 89e4c4e52c chore(showcase/langgraph-python): bump model to gpt-5.4 across all agents
Swap every ChatOpenAI / init_chat_model call across the LGP agents
from the prior mix (gpt-4o-mini, gpt-4o, gpt-4.1, gpt-5-mini) to the
unified `gpt-5.4` model. Also updates the OPENAI_REASONING_MODEL env
default in reasoning_agent.py and tool_rendering_reasoning_chain_agent.py
so reasoning demos pick up the new model unless explicitly overridden.

Verified locally with the user's gpt-5.4 OpenAI access — all 39 LGP
demos render and respond correctly. The probe sweep on prod will
confirm the model name resolves end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 20:59:36 -07:00
Tyler Slaton 80a7f9af0e feat(showcase): align demo names + add Show Deprecated toggle
Two related changes that bring the dashboard's gold-standard view in
line with the desired naming convention and surface deprecated rows
behind a toggle (instead of hiding them at catalog generation).

## Naming alignment

Applied 28 renames in feature-registry.json + 20 in LGP manifest per
the user-provided mapping. Highlights:

- "Pre-Built CopilotChat" -> "Pre-Built: CopilotChat"
- "Headless Chat (Simple/Complete)" -> "Headless UI: Simple/Complete"
- "Multi-modal / File Uploads" -> "Attachements" (intentional spelling)
- "Controlled Gen-UI (Display)" -> "Generative UI: useComponent"
- "In-Chat HITL (use*)" -> "Human In/in the Loop: In-chat / Interrupts"
- "Headless Interrupt" -> "Human in the Loop: Headless Interrupts"
- "Declarative Generative UI (A2UI - *)" -> "Declarative UI: */* A2UI"
- "Fully Open-Ended Generative UI" -> "Open Generative UI: Default"
- "Tool Rendering ..." -> "Generative UI: Tool Rendering (...)"
- "Tool Rendering + Reasoning Chain" -> "Generative UI: Rendering multiple tools"
- "Agentic Generative UI ..." -> "Generative UI: Agent State"
- "Frontend Tools (...)" -> "Frontend Tools: ..."
- "Shared State (...)" -> "Shared State: ..."
- "State Streaming" -> "Shared State: Streaming"
- "Readonly State (Agent Context)" -> "Shared State: Frontend Context"
- "BYOC Hashbrown <-> json-render" -- labels intentionally swapped per
  user instruction (demos were historically reversed; new labels
  reflect what they actually do).

LGP manifest demos[].name updated to match feature-registry names so
the dojo and dashboard surface the same human-readable label.

## Show Deprecated toggle (feature-grid.tsx)

Added a checkbox in the matrix header -- default OFF -- that filters
feature rows where `feature.deprecated === true`. Toggle ON shows all
deprecated features across all integrations (audit trail); toggle OFF
hides those rows entirely so the gold-standard view stays clean.

Reverted the catalog-side filter from PR #4744 (which dropped LGP
cells for deprecated features at catalog-generation time). Now the
catalog emits cells uniformly for all (integration x feature) pairs,
and visibility is controlled at the dashboard layer. Toggling on
shows complete cross-integration data without missing-cell artifacts.

Affects 4 features marked deprecated:true in feature-registry.json:
agentic-chat-reasoning, hitl, hitl-in-chat-booking,
reasoning-default-render.

LGP cell count: back to 43 (38 wired + 1 stub + 4 unshipped). The 4
unshipped rows are hidden by default; toggle to surface them.

Tests: 18/18 catalog tests + 1588/1588 harness vitest passing.
validate-fixture-tool-surface clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 20:06:39 -07:00
Tyler Slaton be94bd7a6f feat(showcase): add 3 LGP D5 probes + driver retry-once
Closes the demo↔probe coverage gap for /demos/{interrupt-headless,
shared-state-read, tool-rendering-reasoning-chain} so every demo
under langgraph-python (the north-star integration) now has a D5
probe writing to its own PocketBase cell — not relying on cross-
demo umbrella records.

New probes (multi-turn, mirroring the agentic-chat structure):
  - d5-interrupt-headless: exercises useHeadlessInterrupt — chip
    prompt → backend interrupt(...) → app-surface popup → slot pick
    → resume → assistant confirmation. Distinct from gen-ui-interrupt
    (which uses inline useInterrupt).
  - d5-tool-rendering-reasoning-chain: combines reasoning-block slot
    + per-tool renderer (WeatherCard, FlightListCard) on the same
    chat surface. Catches a regression in either side.
  - d5-shared-state-read: recipe-editor demo (neutral default agent,
    no tools) — verifies recipe-card form mounts AND agent reads
    shared state across turns. Drops the dual-claim that
    d5-shared-state.ts had on `shared-state-read` (now write-only).

Driver retry-once (e2e-deep.ts):
  Probes that fail with a transient class (`goto-error` /
  `conversation-error`) AND took ≥2s on the first attempt now retry
  once before recording red. Persistent assertion-style failures
  (sub-2s) and intentional aborts/feature-timeouts skip retry —
  retrying a deterministic mismatch just burns clock and obscures
  the signal. Cuts ~10× the dashboard flap rate.

Plumbing:
  - D5FeatureType enum: +interrupt-headless, +tool-rendering-reasoning-chain.
  - REGISTRY_TO_D5 (harness) + CATALOG_TO_D5_KEY (dashboard) mirror
    the new mappings; d5-mapping-drift test enforces this.
  - LGP manifest features + demos entries + constraints allowlist.
  - feature-registry.json: +shared-state-read.
  - aimock d5-all.json: +2 shared-state-read fixtures (interrupt-
    headless + tool-rendering-reasoning-chain reuse existing fixtures
    that already match their chip prompts).

Tests: 1588/1588 harness vitest green. validate-fixture-tool-surface
clean (282 fixtures × 627 demos, no drift). Two pre-existing test
fixes folded in — d5-gen-ui-interrupt assertion mock updated to
match the current evaluate-poll resume signal; conversation-runner
preFill ordering test now asserts the actual deferred-cascade
contract instead of a stricter pre-preFill ban that the runner
never enforced.

Known follow-up (not in this PR): auth.spec.ts test #5 ("signing
back in re-mounts a fresh chat surface") fails on Railway — second
sign-in's "Hello again" never produces an assistant response. Looks
like a react-core/v2 ref-handling regression on <CopilotKit>
unmount/remount; deserves its own focused investigation.

Other integrations may flip red on the new probes — that's
expected. We're treating LGP as the template; cross-integration
parity follows in a separate wave.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:22:57 -07:00
Tyler Slaton 5d1a55e921 fix(showcase/langgraph-python): close remaining D5 cells (framework + fixtures)
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.
2026-05-08 15:08:00 -07:00
Sam Julien 938b8d8dd3 chore(showcase): close last 2 yellow Missing snippet boxes for cutover
/langgraph-python/frontend-tools referenced regions
frontend-tool-registration and frontend-tool-handler which were missing
markers on the production demo. Added in-place @region markers on the
existing useFrontendTool block in
showcase/integrations/langgraph-python/src/app/demos/frontend-tools/page.tsx
since the demo is a clean teaching example.

/langgraph-python/custom-look-and-feel/slots referenced regions
register-welcome-slot, register-assistant-message-slot, and
register-disclaimer-slot. The chat-slots production demo registers ~12
slot overrides at once with `as unknown as typeof X` casts that obscure
the per-pattern teaching shape, so added a sibling
slot-overrides.snippet.tsx file (mirrors the llamaindex chat-slots
sibling pattern) with the three minimal teaching regions.

Verified by re-bundling demo-content.json, running shell-docs production
build, and curling both pages on a local server: zero Missing snippet
boxes, and the region code (change_background, CustomWelcomeScreen,
CustomDisclaimer, CustomAssistantMessage) renders on the pages.
2026-05-08 13:03:50 -07:00
Alem Tuzlak ccc2eabfc2 test(showcase/langgraph-python): regression coverage for A2UI middleware-intercept + display_flight loop
Three classes of regression are now pinned:

1. Secondary-LLM tool name doesn't collide with the A2UI middleware's
   default intercept list (`render_a2ui`). New
   `src/agents/test_a2ui_internal_tools.py` parametrises over
   `beautiful_chat._design_a2ui_surface`, `a2ui_dynamic._design_a2ui_surface`,
   and `a2ui_fixed.display_flight` and asserts none match the
   middleware's `a2uiToolNames` default. Catches accidental rename
   reverts that would re-enable the bypass.

2. `generate_a2ui` force-pins the canonical `catalog_id` even when the
   secondary LLM hallucinates a wrong one. The new test stubs
   `ChatOpenAI` with a fake response carrying a bogus catalogId and
   asserts the surface op carries the module's `CUSTOM_CATALOG_ID`.

3. `generate_a2ui` short-circuits with a clean error string when the
   LLM emits a root component without a `component` field — never
   feeds the renderer the partial tree that surfaced as the "Cannot
   create component root without a type" infinite-loop.

7 unit tests, all green locally (`pytest src/agents/test_a2ui_internal_tools.py`).

E2E tests on the same fixes now also assert:
- No `A2UI render error: Catalog not found` banner on the page after
  Beautiful Chat → Sales Dashboard, Declarative Gen UI → BarChart, and
  A2UI Fixed Schema → Find SFO → JFK round-trips.
- No `Cannot create component … without a type` banner on the same
  three pills.
- Exactly ONE flight card on A2UI Fixed Schema (was 6+ on deploy
  pre-fix from the `display_flight` loop) — `Flight Details` count
  pinned to 1, `Book flight` count pinned to 1.
- At most one ResponsiveContainer on the BarChart pill (loops would
  stack multiple).
- At most two ResponsiveContainers on Beautiful Chat → Sales Dashboard
  (one pie + one bar = single dashboard render).
2026-05-08 19:07:52 +02:00
Alem Tuzlak c91a9c9aff fix(showcase/langgraph-python): A2UI fixed-schema loop + propagate rename to shared tools and aimock
Two follow-up fixes layered on the previous internal-tool rename:

(1) `a2ui_fixed.py` — fixed-schema demo infinite loop on deploy. The
`display_flight` tool returns the raw `a2ui.render(...)` JSON descriptor
as its tool result. gpt-4o-mini reads that opaque blob, can't tell the
flight was rendered, and re-calls `display_flight` indefinitely (visible
on the deployed showcase as 6+ duplicate flight cards stacked under
repeated assistant text). Local was just lucky.

Hardened the docstring + system prompt to spell out: the JSON return
value is the surface descriptor, the card is already rendered, do NOT
call again, reply with one short confirmation and stop.

(2) Rename `render_a2ui` → `_design_a2ui_surface` in shared and
langgraph-python parity copies of `tools/generate_a2ui.py` (+
`tools/__init__.py` re-export `RENDER_A2UI_TOOL_SCHEMA` →
`DESIGN_A2UI_SURFACE_TOOL_SCHEMA`), and in `showcase/shared/typescript/
tools/generate-a2ui.ts`. These shared helpers were the source-of-truth
for the secondary-LLM tool name across integrations; renaming here keeps
parity with the langgraph-python agents already renamed in
`beautiful_chat.py` / `a2ui_dynamic.py`. Other framework integrations
keep their own `render_a2ui` for now (separate parity sweep).

(3) `showcase/aimock/feature-parity.json` — added a sibling fixture
matching `toolName: "_design_a2ui_surface"` for the beautiful-chat Sales
Dashboard pill so the langgraph-python e2e suite still hits a
deterministic mock on Railway. The original `render_a2ui` fixture is
kept above it so other integrations whose secondary LLM still requests
`render_a2ui` continue to match.

(4) Comment update in `beautiful-chat.spec.ts` to name the new internal
tool.
2026-05-08 18:49:53 +02:00
Alem Tuzlak 5d3abc6680 fix(showcase/langgraph-python): rename internal A2UI tool so middleware doesn't intercept
The A2UI middleware (`@ag-ui/a2ui-middleware`) defaults `a2uiToolNames` to
`["render_a2ui"]` and synthesises ACTIVITY_SNAPSHOT events from the
streaming tool-call args of any matching call — using the LLM's RAW
catalogId and components verbatim, before the Python tool body has a
chance to validate or normalise.

Both `beautiful_chat.py` and `a2ui_dynamic.py` use a `generate_a2ui` tool
that internally invokes a secondary LLM bound to a structured-output
`render_a2ui` helper. Because the helper's name matched the middleware's
default intercept list, the secondary LLM's hallucinated catalogId
(`declarative-gen-ui-catalog` leaking into the beautiful-chat dashboard)
and malformed root components (no `component` field on KPI dashboard)
were emitted to the frontend bypass, surfacing as:

- "A2UI render error: Catalog not found: declarative-gen-ui-catalog" on
  the beautiful-chat Sales Dashboard pill
- "A2UI render error: Cannot create component root without a type"
  infinite-loop on the declarative-gen-ui KPI Dashboard pill

The earlier force-pin of `catalog_id` and the defensive component sweep
in `generate_a2ui` were correct but ran too late — they execute on the
tool-result, after the middleware has already fired surface events from
the streaming args.

Fix: rename the internal helper to `_design_a2ui_surface` (and update
`tool_choice`, prompt header, and module docstring) so it falls outside
the middleware's intercept list. The explicit `a2ui.render(...)` ops the
outer `generate_a2ui` returns are then the only path to the frontend,
and our Python validation layer is authoritative.

Verified locally: Beautiful Chat → Sales Dashboard pill renders Total
Revenue / New Customers / etc. with no errors.
2026-05-08 18:30:25 +02:00
Alem Tuzlak 3b3908e353 fix(showcase/langgraph-python): restore A2UI rendering for beautiful-chat + declarative-gen-ui (#4733)
## Summary

- Beautiful Chat **Search Flights** pill: restored
`_build_flight_components` so the agent emits a flat literal-children
FlightCard tree (the structural-children template form via
`flight_schema.json` + `update_data_model` doesn't expand correctly
through GenericBinder for our custom catalog — it was working until the
recent flagship-cell pass swapped it back). FlightCards render again
instead of falling through to the default tool card.
- Beautiful Chat **Sales Dashboard** pill: hardened `generate_a2ui` so
the secondary LLM gets explicit catalog-id + component-shape rules and
the resulting `catalog_id` is force-pinned to the registered catalog.
Kills the "Catalog not found: declarative-gen-ui-catalog" sibling-demo
hallucination.
- **Declarative Gen UI** (`a2ui_dynamic.py`): same `generate_a2ui`
hardening, plus a defensive sweep that drops malformed components and
bails clean when the LLM omits a typed root. Kills the "Cannot create
component root without a type" infinite-loop renderer error reported on
the KPI Dashboard pill.

Two files touched (`src/agents/beautiful_chat.py`,
`src/agents/a2ui_dynamic.py`); no frontend / runtime / package changes.
Existing aimock fixtures in `feature-parity.json` already use the
canonical catalogId and well-formed components, so e2e specs should pass
without fixture changes.

## Test plan

- [x] Verified locally: `langgraph_cli dev` + `next dev`, clicked
**Search Flights** on `/demos/beautiful-chat` — Delta DL 405 ($329) and
United UA 120 ($289) FlightCards rendered live with airline, route,
times, status, Select.
- [x] Verified locally: clicked **Show a KPI dashboard** on
`/demos/declarative-gen-ui` — REVENUE / SIGNUPS / CHURN metric tree
rendered cleanly, no "Cannot create component root without a type"
error.
- [ ] CI / `nx run @copilotkit/showcase-langgraph-python:e2e`
(beautiful-chat spec already authored against the literal-children
form).
2026-05-08 17:46:30 +02:00
Alem Tuzlak 3ac103b3bf fix(showcase/langgraph-python): restore A2UI rendering for beautiful-chat + declarative-gen-ui
Three regressions, two files. Without these the Search Flights pill falls
through to the default tool card instead of rendering FlightCards, the
Sales Dashboard pill errors with "Catalog not found:
declarative-gen-ui-catalog", and the Declarative Gen UI cell loops on
"Cannot create component root without a type."

beautiful_chat.py — search_flights:
- Restore _build_flight_components and have search_flights emit the flat
  literal-children component tree it produces, instead of the
  flight_schema.json structural-children template (Row.children =
  { componentId, path: "/flights" }) plus update_data_model. The
  GenericBinder doesn't reliably expand the structural form for our
  custom FlightCard catalog — sibling demos avoid the form for the same
  reason. The literal form has been the working shape since it was
  introduced; the recent flagship-cell pass swapped it back to the
  schema/data-model form and broke fixed-schema rendering.
- Make Flight TypedDict permissive (total=False) and drop the required
  id / statusIcon fields. langchain rejected calls when the LLM (or
  aimock fixture) omitted those, surfacing the validation string as the
  tool result and never producing a surface.

beautiful_chat.py + a2ui_dynamic.py — generate_a2ui:
- Prepend a hard-requirements header to the secondary LLM's prompt
  pinning the canonical catalogId and the component-shape contract
  (every entry — including root — must carry both `id` AND `component`).
  With injectA2UITool: false the runtime context alone leaves the LLM
  enough room to hallucinate sibling-demo catalog IDs (e.g.
  declarative-gen-ui-catalog leaking into the beautiful-chat dashboard)
  and to omit `component` on the root entry (the source of the
  "Cannot create component root without a type" infinite loop).
- Force catalog_id to the module-level CUSTOM_CATALOG_ID after the tool
  call so any residual hallucination still routes to the registered
  frontend catalog.
- Drop malformed component entries before constructing the operations
  list, and bail with a clean error string if no valid root survives —
  fail-soft instead of looping the renderer.

The renderer-side definitions.ts/renderers.tsx changes from the same
flagship pass are kept as-is; their Row/Column children union
(string-array OR { componentId, path }) is forward-compatible and
doesn't hurt the literal-children path.
2026-05-08 17:17:54 +02:00
Alem Tuzlak 247f061456 fix(showcase/langgraph-python): disable inspector on voice demo for D5 parity
The voice demo's <CopilotKit> didn't pass enableInspector, so
shouldShowDevConsole(undefined) defaulted to isLocalhost(), which
auto-mounts <cpk-web-inspector> on any local Docker host
(localhost:3100 in showcase compose). The inspector overlay
intercepts pointer events on top of the voice sample-audio button,
so dev/D5 probe runs can't click it through Playwright.

Production isn't localhost, so the inspector never mounts there —
voice is D5 in prod and D4 locally for this reason alone. Set
enableInspector={false} explicitly so the demo behaves the same in
both environments.

Probe result: d5:langgraph-python/voice flips green.
2026-05-08 13:54:05 +02:00
Tyler Slaton 197c588c32 fix(showcase/langgraph-python): auth e2e spec uses v2 testid not v1 data-message-role
CR Round 3 final: my auth.spec.ts e2e was asserting on
[data-message-role="assistant"] which is the v1 react-ui RenderMessage
attribute. The auth demo uses v2 CopilotChat — its
CopilotChatAssistantMessage only emits data-testid="copilot-assistant-message".
The selector would never have matched and both tests would have
timed out at 30s when actually run.

Verified via grep:
- packages/react-core/src/v2/components/chat/CopilotChatAssistantMessage.tsx:192
  emits data-testid="copilot-assistant-message" (no data-message-role)
- packages/react-ui/src/components/chat/messages/RenderMessage.tsx:32,41
  emits data-message-role="user"/"assistant" (v1 path)
- All sibling specs in langgraph-python/tests/e2e/ correctly use
  data-testid="copilot-assistant-message"

One-character switch from data-message-role to data-testid with the
canonical v2 testid value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:47:22 -07:00
github-actions[bot] deb0b7394d style: auto-fix formatting 2026-05-08 00:52:19 +00:00
Tyler Slaton 8d39b53f6e fix(showcase): rework auth probe and e2e for unmount-based sign-out flow
The auth demo refactor flipped its lifecycle: unauthenticated is now
the default state, <CopilotKit> only mounts after sign-in, and
sign-out unmounts the entire chat tree (instead of leaving stale
auth headers in a still-mounted chat). The old probe + e2e spec
chased a 401-error-banner surface that no longer exists in the new
demo, plus a brittle 500ms hardcoded `useEffect` flush wait.

Probe rewrite (`d5-auth.ts` + tests):
- Add `buildAuthPreFill` that clicks the SignInCard's sign-in button
  before turn 1, then waits for the chat textarea to mount (proves
  <CopilotKit> handshook with the runtime).
- `buildAuthAssertion` now clicks sign-out, then waits for SignInCard
  to re-mount. The unmount marker IS the proof — no chat-send-and-401
  dance is needed (or possible — there's no chat to send into).
- Drop the hardcoded 500ms setTimeout, the unauth-banner wait, and
  the error-surface poll. None apply to the new flow.

E2E rewrite (`tests/e2e/auth.spec.ts`):
- "page loads unauthenticated with SignInCard visible"
- "signing in mounts the chat surface with AuthBanner"
- "authenticated send produces an assistant response"
- "signing out unmounts the chat tree and re-renders SignInCard"
- "signing back in re-mounts a fresh chat surface"

Fixture comment updated to reflect the new flow. The user message
("auth check turn 1") and content response are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:55:35 -07:00
Tyler Slaton 4f26734b4d fix(showcase): rewrite d5-gen-ui-headless-complete probe + fixture for new chip set
The headless-complete refactor replaced the old 5-chip set
(Weather/AAPL/Highlight/Sketch/Largest continent) with a new 4-chip
set wired through `useConfigureSuggestions`/SuggestionBar:

  Weather       → "What's the weather in Tokyo?"      → WeatherCard
  Stock price   → "What's the price of AAPL right now?" → StockCard
  Highlight a note → "Highlight this note for me: 'ship the demo on Friday'."
                                                       → HighlightNote
  Revenue chart → "Show me a chart of revenue over the last six months."
                                                       → ChartCard

Probe rewrite:
- Drop the wrapper-scoped chip selector (`[data-testid=
  "headless-suggestions"] >> text=...`) — the new SuggestionBar
  doesn't have that container testid. Click via plain `button >>
  text="<chip title>"` instead.
- Per-turn assert the matching tool card testid mounts
  (headless-weather-card, headless-stock-card, headless-highlight-card,
  headless-revenue-chart) plus a distinguishing text token. Driven
  off a TURN_EXPECTATIONS table so adding a chip is one entry.
- Drop the readMessagesText helper that scoped to a missing
  `[data-testid="headless-complete-messages"]` container; collect
  text by concatenating all `headless-message-assistant` bubbles.

Fixture rewrite:
- Drop the Excalidraw + Largest-continent entries (no longer in the
  chip set). Weather is already covered by feature-parity.json.
- Add a Revenue chart fixture for `get_revenue_chart` with inline
  data so ChartCard renders without backend.
- Stock + Highlight fixtures preserved (still match the new chip
  messages via substring).

Also restore `data-message-role` attributes on the headless-complete
UserBubble + AssistantBubble so the runner's settle plateau cascade
can resolve them — matches the runner's documented headless-template
contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:52:04 -07:00
Tyler Slaton 2d78a0f409 fix(showcase): rename gen-ui-headless D5 type to headless-simple and rewrite probe
The headless-simple demo was refactored to a deliberately minimal
"two hooks, one shadcn shell" template — text-in/text-out only, no
gen-UI. The D5 type literal `gen-ui-headless` no longer described
what the probe tests, and the old probe (Profile-card useComponent
+ continent fallback) was asserting against UI that no longer exists.

Three coordinated changes:

1. Rename `gen-ui-headless` D5FeatureType to `headless-simple` so the
   slug matches the demo. Updated d5-registry, REGISTRY_TO_D5,
   CATALOG_TO_D5_KEY (dashboard), and dependent tests/comments.
   `headless-complete` keeps its existing literal because that demo
   still drives the full gen-UI surface.

2. Replace d5-gen-ui-headless.{ts,test.ts,fixture} with
   d5-headless-simple.ts + headless-simple.json fixture. New probe
   clicks the "Say hello in one short sentence." chip and asserts the
   `[data-testid="headless-message-assistant"]` bubble mounts with
   non-empty content.

3. Restore `data-message-role` attributes on the headless-simple
   UserBubble + AssistantBubble. The runner's chat-input cascade
   documents these as the headless-template contract; the refactor
   dropped them, breaking the runner's settle plateau detection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:48:43 -07:00
Tyler Slaton 4e03a3f105 fix(showcase): expose data-slot-label on SlotMarker and use it in d5-chat-slots
The chat-slots refactor replaced the explicit `data-testid="custom-assistant-message"`
attribute with a SlotMarker wrapper component. The probe still asserted
the old testid which no longer exists, so the cell stuck at D4.

Add `data-slot-label={label}` to SlotMarker's outer span — idiomatic
data attribute that mirrors the existing `label` prop and gives the
probe a stable contract without restoring the legacy testid pattern.
The marker was already passing label="MessageView.AssistantMessage";
this just surfaces it in the DOM.

Update d5-chat-slots probe + test to assert
`[data-slot-label="MessageView.AssistantMessage"]`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:43:53 -07:00
Tyler Slaton c7be72308e fix(showcase): harden beautiful-chat toggleTheme against page-close races
Two related changes targeting the fc=97 'Target page closed' D5 failure
on `beautiful-chat-toggle-theme`:

1. Probe (`_beautiful-chat-shared.ts:assertToggleTheme`) — replace the
   manual 200ms `page.evaluate` poll over 30s with Playwright's
   `waitForFunction`. The native polling is event-driven inside the
   browser context, disconnects cleanly on page-close, and avoids
   ~150 round-trip evaluates per probe. The catch branch now checks
   `page.isClosed()` first and surfaces a diagnostic message naming
   the renderer-crash case explicitly, so operators don't have to
   guess what 'Target page closed' meant.

2. Demo (`use-generative-ui-examples.tsx`) — drop `[theme, setTheme]`
   from the `useFrontendTool` deps array. The handler reads `document`
   directly and the setter is stable across renders, so the deps
   array forced a re-registration after every theme flip. That race
   could collide with an in-flight tool result and surface as a
   renderer error during multi-turn sequences. Removing deps keeps
   the tool registration stable for the duration of the conversation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:33:27 -07:00
Alem Tuzlak 5d95e8e102 Merge branch 'main' into blitz/lgp-genuine-pass/integration 2026-05-07 21:17:15 +02:00
github-actions[bot] b13129b319 style: auto-fix formatting 2026-05-07 19:08:48 +00:00
Alem Tuzlak b7ac944315 fix(showcase/langgraph-python): surface explicit sentinel for empty sub-agent results
_invoke_sub_agent's last-resort branch silently returned '' or a
Python repr like \"[{'type': 'text', ...}]\" for block-list content,
which the UI would render as a blank/garbled card. Return a stable
SUB_AGENT_EMPTY_SENTINEL ('<sub-agent produced no output>') instead
so the d5-subagents probe can match it against its boilerplate-marker
list and fail the genuine-pass test loudly when a sub-agent produces
no usable output.
2026-05-07 20:55:13 +02:00
Alem Tuzlak 447c3d415f fix(showcase/langgraph-python): make get_stock_price deterministic via optional args
The get_stock_price tool returned randint-based price/change every
call, so e2e specs asserting $338.37 / -2.96% would only pass when
aimock short-circuited the entire call. The Python tool body still
runs server-side under aimock — only the LLM call is mocked.

Mirror the deterministic-`value` pattern on roll_d20: accept optional
price_usd / change_pct arguments and echo them back when present.
Defaults to random mock data when the args are omitted, preserving
the legacy live-LLM behaviour.
2026-05-07 20:54:49 +02:00
Alem Tuzlak abfbb803b8 fix(showcase): assert non-boilerplate hello in headless-simple spec
The HELLO_LEADING phrase was the showcase-assistant catch-all
boilerplate ('I can help you with weather lookups...') that other
tests in this PR explicitly guard AGAINST. The dedicated d5-all.json
fixture for 'Say hello in one short sentence' now returns a distinct
non-boilerplate greeting; the spec asserts that distinct phrase, so a
fixture-priority misroute fails loudly instead of passing by accident.
2026-05-07 20:54:36 +02:00
Alem Tuzlak ddb248034d fix(showcase/langgraph-python): restore useDefaultRenderTool() in default catchall cell
The blitz removed the explicit useDefaultRenderTool() call expecting a
framework-level fallback to handle zero-hook registrations, but the
integration uses the published @copilotkit/react-core@1.56.5 which does
not yet ship that fallback. Without the call, useRenderToolCall has no
'*' renderer and tool calls render invisibly — the user only sees the
agent's final text summary instead of the OOTB tool card.

Restore the explicit useDefaultRenderTool() invocation. The framework
fallback (committed in this PR but inert until react-core publishes a
release that includes it) becomes a no-op once that ships.
2026-05-07 19:57:48 +02:00
Alem Tuzlak 6ad6dfbbd1 Merge slot B8 into blitz/lgp-genuine-pass/integration
Resolved conflicts:
- d5-all.json: appended B8's 26 new fixture entries to the existing 131
- gen-ui-open.json: kept ours (B3's verbatim pill messages match the suggestions.ts pills); dropped B8's paraphrased fixtures
- d5-gen-ui-open.ts: kept B7's basic-only routing + B8's iframe-presence assertion logic; updated PILL_PROMPT_PREFIX to '3D axis visualization (model airplane)' so it matches the actual pill message
2026-05-07 18:22:52 +02:00
Alem Tuzlak 06989b977b Merge slot B6 into blitz/lgp-genuine-pass/integration
Resolved d5-all.json conflict by appending B6's headless-simple/complete fixtures (Say hello, joke, fun fact, Highlight, chart) before the tool-rendering and open-gen-ui entries already on integration.
2026-05-07 18:20:15 +02:00
Alem Tuzlak e8eaacee14 Merge slot B5 into blitz/lgp-genuine-pass/integration 2026-05-07 18:18:23 +02:00
Alem Tuzlak 4097b166eb Merge slot B4 into blitz/lgp-genuine-pass/integration 2026-05-07 18:18:22 +02:00
Alem Tuzlak d5615fed90 Merge slot B3 into blitz/lgp-genuine-pass/integration 2026-05-07 18:17:58 +02:00
Alem Tuzlak 92e405946d Merge slot B2 into blitz/lgp-genuine-pass/integration
Resolved d5-all.json conflict by appending B2's readonly-state-agent-context fixtures after B1's hitl-in-app and frontend-tools-async fixtures.
2026-05-07 18:17:42 +02:00
Alem Tuzlak 63aef191ff feat(showcase/langgraph-python): add testids and pills required by d5 probes
Adds production-code testids needed by the Phase-2B genuine D5 probes:

- frontend-tools/background.tsx: data-testid='frontend-tools-background'
  + data-background-value mirror so the probe can read the live gradient
  off the DOM without computing styles.
- declarative-gen-ui/a2ui/renderers.tsx: data-testid for Card,
  StatusBadge, Metric, PieChart, BarChart so per-pill probes can assert
  the expected catalog component painted.
- a2ui-fixed-schema/a2ui/renderers.tsx: data-testid='a2ui-fixed-card'
  on the Card override so the fixed-schema component-tree mount is
  observable without keyword-matching the transcript.

Each cell's Layer 1 spec is already green; this is purely additive — no
spec rewrites.
2026-05-07 18:14:13 +02:00
Alem Tuzlak 05395937fe test(showcase/langgraph-python): rewrite tool-rendering trio to pill-driven 6-test plans
All three tool-rendering cells now drive the suggestion pills directly
instead of typing free-form prompts that race fixture matchers:

- tool-rendering: 6 tests (page loads + 5 pills) — Weather in SF
  asserts SF city + deterministic temp/humidity/wind; Find flights
  asserts >=2 flight rows from the dedicated fixture; Stock price
  asserts AAPL $338.37 / -2.96%; Roll a d20 asserts exactly 5 d20
  cards with the 5th=20; Chain tools asserts weather+flights+d20
  cards mount from a single pill click.
- tool-rendering-default-catchall: 6 tests asserting the OOTB default
  tool-call renderer paints every tool call with data-testid=
  copilot-tool-render plus data-tool-name. Branded sibling-cell
  testids stay at zero. Test 6 asserts every card matches the
  built-in renderer DOM signature.
- tool-rendering-custom-catchall: 6 tests asserting the same
  custom-wildcard-card testid renders for every tool. Test 6 is the
  cross-tool snapshot — every tool kind paints via the same shell.
2026-05-07 17:55:20 +02:00
Alem Tuzlak ba60df5d33 feat(showcase/langgraph-python): add per-tool testids
Add stable testids and rendering surfaces for the three tool-rendering
cells so the e2e suite can distinguish each cell's strategy:

- tool-rendering: register useRenderTool for get_stock_price and
  roll_d20; new StockCard / D20Card components with testids
  stock-card / d20-card / stock-price / stock-change / d20-value.
  Rename FlightListCard testid flight-list-card -> flights-card.
- tool-rendering-default-catchall: drop the custom shadcn
  useDefaultRenderTool registration so the cell is truly zero
  custom-render-hooks. The framework's built-in
  DefaultToolCallRenderer now paints every tool call, with stable
  data-testid='copilot-tool-render' wrapper plus data-tool-name,
  data-args, and data-result attributes for inspection without
  expanding the card.
- tool-rendering-custom-catchall: rename the wildcard renderer's
  testids from custom-catchall-* to custom-wildcard-* so the cell
  is distinguishable from the (now-OOTB) default-catchall demo.
- packages/react-core: when no per-tool / wildcard renderer is
  registered, useRenderToolCall now falls back to the built-in
  DefaultToolCallRenderer instead of returning null.
2026-05-07 17:55:02 +02:00