feat(trigger-chat-agent): AI chat agent that teaches Trigger.dev by drawing - #126
feat(trigger-chat-agent): AI chat agent that teaches Trigger.dev by drawing#126D-K-P wants to merge 37 commits into
Conversation
Fork of clickhouse-chat-agent, reworked from data analytics to learning Trigger.dev. Strips ClickHouse (client, listTables/describeTable/runQuery tools, PointMap/maplibre) and the chart cards; swaps in an interactive React Flow + dagre FlowGraph plus code/diagram/prompt/stat cards rendered from json-render specs. The chat.agent() keeps the versioned AI Prompt and renderVisualization validate/retry loop, and merges a docs MCP server (default Context7, DOCS_MCP_URL) into its tool set each turn to ground answers on the live docs without inventing API surface.
Replace the parked shadcn-neutral styling with the Trigger.dev Launch Week look: charcoal surfaces, apple-green accent, Satoshi titles. Adds the brand palette as real CSS variables (so the React Flow SVG edge layer can read them) and remaps the shadcn semantic tokens so the chat shell and the json-render primitives inherit the look with no per-component overrides. Satoshi loads from the Fontshare CDN via a <link> in the layout.
Turn the agent into a tutor, not just a diagram renderer. Adds a sandboxed HTML `Lesson` catalog component (model-authored Tufte-style lesson with an interactive quiz, rendered in an allow-scripts-only iframe so its JS can't reach the app origin or session token) and a `suggestNext` tool that ends every turn with clickable deeper / sideways / practice / topic chips to keep the learning flowing. Rewrites the versioned prompt around a teaching method adapted from Matt Pocock's "teach" skill: mission-first, one tangible win per turn, knowledge then a retrieval quiz, everything grounded in the docs MCP. The empty state seeds start-here / go-deeper goals plus a docs-grounded "suggest more topics".
…cted HTML Adversarial review confirmed the sandbox (allow-scripts, no allow-same-origin) already blocks app-origin XSS and reading the session token, but a lesson could still beacon out or render a token-phishing form. Inject a strict CSP into the lesson document — connect-src/form-action 'none', img-src data: only — closing the exfiltration and phishing channels while keeping the quiz scripts, inline styles, and Fontshare fonts working. Also harden the resize postMessage handler against non-finite heights.
…ender Add a pre-render screening layer for model-authored lesson HTML, on top of the sandbox + CSP. In the renderVisualization tool (server-side, before anything reaches the browser) every Lesson is vetted by a deterministic red-flag scan (lesson-screen.ts — network calls, forms, credential inputs, cookie/storage access, navigation/redirect, nested frames, eval) AND a fan-out of cheap parallel LLM screeners with distinct adversarial lenses (exfiltration, social-engineering). Any hit fails the tool, so the model regenerates a clean lesson through the existing validate-and-retry loop. The static scan is unit tested (attacks blocked, benign quizzes pass). LESSON_SCREENING=off disables the LLM fan-out.
… source The docs a lesson is grounded on are the upstream prompt-injection vector: a poisoned page could steer the agent. Wrap every docs-MCP tool's output before the model sees it (quarantine.ts) — coerce it to text and delimit it as "untrusted reference material: data, not instructions", with an inline flag when injection markers are present. Reinforce the same rule in the system prompt. Unlike lessons, docs can't be regenerated, so this neutralizes rather than blocks; the lesson screen remains the backstop for any malicious output an injection might still induce. Quarantine + flag logic unit tested.
…t execute A live run surfaced a TypeError: the docs-MCP tool's own toModelOutput does `'content' in output`, which throws when the wrapper had replaced execute's result with a plain string. Quarantine in toModelOutput instead (the layer that decides what text the model sees), leaving the raw MCP result shape intact for the SDK. Verified end to end against a real project: grounding, quarantine, renderVisualization (FlowGraph + Lesson), lesson screening, and suggestNext all complete cleanly.
…esign Replace the generic shadcn shell with the Launch Week chat language: lavender user bubbles and translucent charcoal assistant bubbles that grow from their tail corner (bubbleIn), the chat.agent phosphor wordmark, a pill composer with the masked gradient-border focus glow and apple send button, a breathing-dot thinking indicator, word-by-word blur-in on streaming text, and newest-turn-to- top scrolling with edge-fade masks. Adds the lavender token and a deeper charcoal-1000 page background. Cards still render full-width (no bubble).
Bring in the real Launch Week card designs and a set of cheap, data-fill components the model populates instead of authoring HTML. Ports HeroCard (icon badge + kicker + display title) and the rich StatCard (count-up value, delta badge, mini bars) from the marketing catalog, and adds Quiz (interactive multiple-choice with immediate feedback), Callout (tip/warn/note), Steps, Glossary, and Compare. Replaces the plain Stat. Each is a handful of tokens for the model and renders identically every time.
Remove the model-authored HTML `Lesson` and everything it required — the sandboxed iframe (lesson.tsx), the injected CSP, the deterministic scan (lesson-screen.ts) and the LLM screening fan-out in the agent — now that the teaching kit is all data-driven components. Nothing generates markup, so the whole attack surface is gone; the docs-MCP quarantine stays (docs are still untrusted input). Rewrites the prompt to keep responses short and valuable (components carry the density) and to steer the model across the new kit.
… often Drop the per-word blur-in reveal (it read laggy) — assistant text now just renders markdown as tokens stream in, the standard AI SDK approach, which feels snappier. Steer the agent to frequently offer a "paste-ready prompt to scaffold this in my repo" next-step chip and answer it with a PromptCard.
…unclickable cards
Persist conversations to Postgres, gated on DATABASE_URL — unset, the app runs exactly as before with no sidebar; set, you get history, resume-on-reload and delete. Showcases where the chat.agent lifecycle hooks belong: onChatStart creates the row (once per chat), onTurnStart awaits the message write so the question is durable before streaming AND sets the prompt (it fires on continuation runs, where onChatStart doesn't), and onTurnComplete writes the turn plus the resume cursor in one transaction so a refresh can't replay it. Messages live in a single JSON column, so AI SDK upgrades need no migration. Chats are owned by an anonymous cookie id minted in proxy.ts and every query is scoped by it. Drizzle + pg against Supabase; pg avoids the prepared-statement limitation of the transaction pooler.
Dagre was laying out every node as 128px wide — a constant carried over from a design that assumed one-word labels — while the nodes actually rendered 150-300px. Dagre reserved too little room, so siblings on a rank drew on top of each other. Measure each node's width from its content and use that same value for both the layout and the rendered node, so the two can't diverge; the serpentine grid gets one column pitch sized to the widest node. Labels and sublabels are capped in the catalog (22/24 chars) and truncate, and the schema now asks for at most ~10 nodes and 3 branches per node so graphs stay readable in a chat column.
…architecture Nothing was shown when a turn failed — the commonest case being `pnpm dev:trigger` not running, which failed silently. Add an inline error banner with a retry (a banner, not a toast: these are usually setup problems worth leaving on screen) that maps failures to actionable text, including the dev-server case. Sanitise errors server-side via uiMessageStreamOptions.onError so the browser gets a useful message and the full error stays in the run log. Also adds "Explain how this app is built" as a starting question, plus a factual section in the prompt describing this app's architecture — the docs it grounds on don't cover it, so without this the showcase question got a vague answer.
…duplicate prose Restores auto-follow while an answer streams — I'd removed it when the entrance felt jerky, but the blur was the culprit, not the scrolling. It's driven by a ResizeObserver (message count doesn't change while text streams, so a messages-keyed effect never fired) and disarms the moment the reader scrolls up, re-arming at the bottom. The composer now floats over the thread: content scrolls underneath and the last inch masks into the background instead of stopping at a hard edge. Side rules on the column so the overflowing Next chips read as clipped by a boundary, plus a fade on that row. Prompt: say each thing once (prose must not restate a card's contents) and call suggestNext exactly once at the end of a turn — calling it repeatedly was producing duplicated paragraphs and orphaned chip sets. Card text no longer carries its own max-width: inside a card the card is the measure, so a 55ch cap just left half the card empty.
Trigger.dev already persists the conversation: each chat.agent chat is backed by a durable Session that outlives its runs, and the agent rebuilds full history from a snapshot after an idle timeout, crash or redeploy. So the Postgres tables were storing a second copy of what the platform already holds. Removes Drizzle, pg and the schema/migrations, and rebuilds the sidebar on sessions.list(). Owner and title are stamped into session metadata when the session is created, and the list is filtered by owner server-side so a browser only ever receives its own chats. Two API limitations shaped this, both verified against 4.5.9: sessions.update() returns Unauthorized even with a secret key, and triggerConfig.tags is dropped at create time — so tags (which list can filter on server-side) aren't usable, and metadata has to be set up front. Opening an old chat resumes it and the agent remembers everything, but earlier messages aren't redrawn: session.out is trimmed to ~one turn and there's no public API to read the stored transcript. The UI says so rather than looking broken. Verified live: turn runs, metadata persists, owner-scoped list returns only that user's chat.
…er-side
Session tags go in the top-level `tags` field on session create, not
`triggerConfig.tags` (which tags each run the session schedules). That lets
sessions.list({ tag }) do the owner filtering server-side instead of listing
everything and narrowing in memory. Verified against 4.5.9: tags persist on the
session row and the tag filter returns just that owner's chats.
Back to a single-conversation demo: no sidebar, no chat list, no anonymous-user cookie, no /chat/[id] route, no clientData. The example is one static page again and needs nothing but a Trigger project and an Anthropic key. The conversation is still durable — that's the platform's job, not the app's — but nothing in the UI depends on enumerating or reopening past chats, which is what all the removed machinery existed for.
…cost observability
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughAdds a complete Estimated code review effort: 5 (Critical) | ~120 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (4)
trigger-chat-agent/trigger.config.ts (1)
4-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFail fast when
TRIGGER_PROJECT_REFis missing.The non-null assertion hides a missing environment variable. The config then resolves
projecttoundefined, and the CLI error does not point at the cause. Add an explicit check so the setup step in the README is enforced.♻️ Proposed refactor
import { defineConfig } from "`@trigger.dev/sdk`"; +const projectRef = process.env.TRIGGER_PROJECT_REF; +if (!projectRef) { + throw new Error("TRIGGER_PROJECT_REF is not set. Copy .env.example to .env and set it."); +} + export default defineConfig({ - project: process.env.TRIGGER_PROJECT_REF!, + project: projectRef, runtime: "node-22",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@trigger-chat-agent/trigger.config.ts` at line 4, Replace the non-null assertion on project in the Trigger configuration with an explicit validation of TRIGGER_PROJECT_REF that fails immediately with a clear missing-environment-variable error, ensuring setup cannot continue with an undefined project value.trigger-chat-agent/package.json (1)
5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd a
lintscript and ESLint dependencies, or remove the ESLint directive.
src/components/chat.tsxline 172 contains// eslint-disable-next-line react-hooks/exhaustive-deps, but this package declares no ESLint dependency and nolintscript. Users who copy the example get no lint feedback.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@trigger-chat-agent/package.json` around lines 5 - 11, Add a package-level lint script and the required ESLint dependencies/configuration in package.json so the existing directive in Chat component remains effective, or remove that directive if linting is intentionally unsupported; ensure the chosen approach gives copied examples consistent lint behavior.trigger-chat-agent/src/components/chat.tsx (1)
610-619: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThis branch is unreachable; remove it or document why it stays.
groupMessagePartsroutes every part that is not text,tool-renderVisualization, orsuggestNextinto adocsgroup, andMessagerenders those groups withDocsToolChain.MessageParttherefore never receives a generictool-ordynamic-toolpart. The block duplicates the documentation-status presentation and will drift fromDocsToolChain.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@trigger-chat-agent/src/components/chat.tsx` around lines 610 - 619, The generic tool/dynamic-tool branch in MessagePart is unreachable because groupMessageParts routes those parts into the docs group. Remove the startsWith("tool-")/dynamic-tool handling and rely on DocsToolChain for documentation-status rendering, unless there is a demonstrated routing exception that must be documented.trigger-chat-agent/src/lib/quarantine.ts (1)
1-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the stale
lesson-screen.tsreference.No
lesson-screen.tsfile exists in the repository. Update the header comment so it does not reference removed code.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@trigger-chat-agent/src/lib/quarantine.ts` around lines 1 - 12, Update the header comment in the quarantine module to remove the stale lesson-screen.ts reference, while preserving the remaining description of the downstream mitigation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@trigger-chat-agent/.gitignore`:
- Line 2: Update the ignore rules in .gitignore to cover all local environment
variants, including .env.local, .env.development.local, and
.env.production.local, while retaining the existing .env rule.
In `@trigger-chat-agent/src/app/actions.ts`:
- Around line 12-20: Update mintChatAccessToken to require an authenticated
caller and verify that the caller owns or may access the supplied chatId before
invoking auth.createPublicToken; reject unauthenticated or unauthorized
requests. If anonymous sessions are intentionally supported, document that
security requirement explicitly in the action instead of leaving the endpoint
unprotected.
In `@trigger-chat-agent/src/components/chat.tsx`:
- Around line 496-556: Update DocsToolChain to treat both output-available and
output-error states as terminal when computing complete, so failed lookups show
“Complete” and stop the searching animation. Preserve a distinct output-error
row indicator rather than rendering the successful Check icon for failed parts.
In `@trigger-chat-agent/src/components/error-notice.tsx`:
- Around line 53-58: Add role="alert" to the root div returned by the error
notice component so asynchronously rendered failures are announced to assistive
technology, leaving the existing styling and content unchanged.
In `@trigger-chat-agent/src/components/flow-graph.tsx`:
- Around line 450-472: Stabilize the nodes and sequence inputs used by the
status animation useEffect so replaceMessage’s deep-cloned but unchanged data
does not reset statuses or recreate timers. Derive content-based keys or
memoized array values from nodes and sequence, and use those stable dependencies
while preserving updates when their actual contents change.
In `@trigger-chat-agent/src/components/quiz.tsx`:
- Around line 36-62: Update the option buttons rendered in the options.map
callback to remain focusable after answered: replace the disabled attribute with
aria-disabled and guard the onClick handler so setPicked only runs before
answering. Mark the conditional explanation paragraph as an assertive live
region so its appearance is announced to assistive technologies.
In `@trigger-chat-agent/src/components/stat-card.tsx`:
- Around line 73-93: Update the onUpdate formatting in AnimatedValue so animated
numbers retain digit grouping when numText contains commas. Format the
interpolated value with grouping separators before combining it with prefix and
suffix, while preserving decimal precision and existing ungrouped-number
behavior.
In `@trigger-chat-agent/src/components/visualization.tsx`:
- Around line 11-40: Update VisualizationErrorBoundary usage in Visualization to
key the boundary by spec, ensuring repaired specifications mount a fresh
boundary and reset failed state. Add componentDidCatch to
VisualizationErrorBoundary to log rendering errors during development without
changing the existing fallback UI.
In `@trigger-chat-agent/src/lib/catalog.ts`:
- Around line 304-311: Update validateSpec to perform a reachability walk from
the root element using a visited set, following each element’s children and
rejecting any node encountered more than once as a cyclic or repeated reference.
Preserve the existing missing-child-key validation and return the accumulated
validation errors through the current result shape.
In `@trigger-chat-agent/src/trigger/trigger-chat-agent.ts`:
- Around line 48-67: Update loadDocsTools to initialize createMCPClient with
initializationOptions.timeout set to 10,000 ms, and replace client.tools() with
listTools({ options: { timeout: 10_000 } }) followed by
toolsFromDefinitions(...). Clear docsToolsPromise only when loading throws so
failed loads can retry, while preserving a valid empty tool set as a successful
cached result.
---
Nitpick comments:
In `@trigger-chat-agent/package.json`:
- Around line 5-11: Add a package-level lint script and the required ESLint
dependencies/configuration in package.json so the existing directive in Chat
component remains effective, or remove that directive if linting is
intentionally unsupported; ensure the chosen approach gives copied examples
consistent lint behavior.
In `@trigger-chat-agent/src/components/chat.tsx`:
- Around line 610-619: The generic tool/dynamic-tool branch in MessagePart is
unreachable because groupMessageParts routes those parts into the docs group.
Remove the startsWith("tool-")/dynamic-tool handling and rely on DocsToolChain
for documentation-status rendering, unless there is a demonstrated routing
exception that must be documented.
In `@trigger-chat-agent/src/lib/quarantine.ts`:
- Around line 1-12: Update the header comment in the quarantine module to remove
the stale lesson-screen.ts reference, while preserving the remaining description
of the downstream mitigation.
In `@trigger-chat-agent/trigger.config.ts`:
- Line 4: Replace the non-null assertion on project in the Trigger configuration
with an explicit validation of TRIGGER_PROJECT_REF that fails immediately with a
clear missing-environment-variable error, ensuring setup cannot continue with an
undefined project value.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a2df0c9a-246a-4da6-a147-033700484457
⛔ Files ignored due to path filters (2)
trigger-chat-agent/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamltrigger-chat-agent/src/app/icon.svgis excluded by!**/*.svg
📒 Files selected for processing (34)
README.mdtrigger-chat-agent/.env.exampletrigger-chat-agent/.gitignoretrigger-chat-agent/README.mdtrigger-chat-agent/components.jsontrigger-chat-agent/next.config.tstrigger-chat-agent/package.jsontrigger-chat-agent/postcss.config.mjstrigger-chat-agent/src/app/actions.tstrigger-chat-agent/src/app/globals.csstrigger-chat-agent/src/app/layout.tsxtrigger-chat-agent/src/app/page.tsxtrigger-chat-agent/src/components/chat.tsxtrigger-chat-agent/src/components/code-card.tsxtrigger-chat-agent/src/components/diagram-card.tsxtrigger-chat-agent/src/components/error-notice.tsxtrigger-chat-agent/src/components/flow-graph.tsxtrigger-chat-agent/src/components/hero-card.tsxtrigger-chat-agent/src/components/prompt-card.tsxtrigger-chat-agent/src/components/quiz.tsxtrigger-chat-agent/src/components/stat-card.tsxtrigger-chat-agent/src/components/streaming-text.tsxtrigger-chat-agent/src/components/teaching-cards.tsxtrigger-chat-agent/src/components/visualization.tsxtrigger-chat-agent/src/components/wordmark.tsxtrigger-chat-agent/src/lib/catalog.tstrigger-chat-agent/src/lib/code-theme.tstrigger-chat-agent/src/lib/motion.tstrigger-chat-agent/src/lib/quarantine.tstrigger-chat-agent/src/lib/registry.tsxtrigger-chat-agent/src/lib/utils.tstrigger-chat-agent/src/trigger/trigger-chat-agent.tstrigger-chat-agent/trigger.config.tstrigger-chat-agent/tsconfig.json
… quarantine nonce, auth-gap note)
…hability, leaf children, unknown props)
…l/chips, drop unsafe casts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
trigger-chat-agent/src/lib/quarantine.ts (1)
63-65: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winUse the full UUID as the quarantine nonce.
crypto.randomUUID().slice(0, 8)keeps only 32 bits of entropy. The code uses this value as a second defense against forged reference fences. Keep the full UUID so future matcher changes do not reduce this defense to a guessable token.Proposed fix
- const nonce = crypto.randomUUID().slice(0, 8); + const nonce = crypto.randomUUID();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@trigger-chat-agent/src/lib/quarantine.ts` around lines 63 - 65, Update the nonce initialization in the quarantine fence construction to retain the full value returned by crypto.randomUUID(), removing the .slice(0, 8) truncation while preserving the existing per-call nonce behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@trigger-chat-agent/src/lib/quarantine.ts`:
- Around line 63-65: Update the nonce initialization in the quarantine fence
construction to retain the full value returned by crypto.randomUUID(), removing
the .slice(0, 8) truncation while preserving the existing per-call nonce
behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6ff5fdfc-34ae-4917-b3cc-87164a9e1f3f
📒 Files selected for processing (12)
trigger-chat-agent/.gitignoretrigger-chat-agent/src/app/actions.tstrigger-chat-agent/src/components/chat.tsxtrigger-chat-agent/src/components/error-notice.tsxtrigger-chat-agent/src/components/flow-graph.tsxtrigger-chat-agent/src/components/prompt-card.tsxtrigger-chat-agent/src/components/quiz.tsxtrigger-chat-agent/src/components/stat-card.tsxtrigger-chat-agent/src/components/visualization.tsxtrigger-chat-agent/src/lib/catalog.tstrigger-chat-agent/src/lib/quarantine.tstrigger-chat-agent/src/trigger/trigger-chat-agent.ts
🚧 Files skipped from review as they are similar to previous changes (11)
- trigger-chat-agent/.gitignore
- trigger-chat-agent/src/components/quiz.tsx
- trigger-chat-agent/src/components/stat-card.tsx
- trigger-chat-agent/src/app/actions.ts
- trigger-chat-agent/src/components/visualization.tsx
- trigger-chat-agent/src/components/error-notice.tsx
- trigger-chat-agent/src/components/chat.tsx
- trigger-chat-agent/src/lib/catalog.ts
- trigger-chat-agent/src/components/flow-graph.tsx
- trigger-chat-agent/src/trigger/trigger-chat-agent.ts
- trigger-chat-agent/src/components/prompt-card.tsx
The session server actions are unauthenticated public POSTs, so a public deployment can be driven to start unbounded agent runs. Cap parallel sessions with a queue concurrencyLimit — this throttles throughput; pair it with an org spend limit in the dashboard for the real ceiling. Also correct the actions.ts security comment: chatId is an unguessable client id, so the practical risk is cost/abuse, not session theft.
Summary
A new example: a Trigger.dev AI Chat agent that teaches you Trigger.dev by drawing instead of dumping paragraphs. Ask how a fan-out with retries works and you get an interactive React Flow node-graph; ask about retries and you get a short explainer, a quiz, and a gotcha callout. Every turn ends with clickable next-step chips so the learning keeps going.
How it's built
The agent is a single
chat.agent()task, so the conversation is a durable run that survives redeploys and crashes with no database. It answers by calling arenderVisualizationtool with a json-render spec built from a fixed component catalog (FlowGraph, Quiz, Callout, CodeCard, and more), which the Next.js frontend renders live with React Flow and shadcn/ui. The model supplies data, not markup, so every card is cheap and always well-formed.Facts are grounded on the live docs through a documentation MCP server, and retrieved docs are quarantined as untrusted input before the model ever sees them. The system prompt is a versioned AI Prompt editable from the dashboard, and model calls emit standard GenAI telemetry spans, so you get token, cost and latency observability for free.
Also updates the root examples table with the new entry.
Summary by CodeRabbit