feat(channels): render arbitrary JSX as images via Takumi + static chart components#6146
Open
AlemTuzlak wants to merge 35 commits into
mainCopilotKit/CopilotKit:mainfrom
feat/channels-jsx-imageCopilotKit/CopilotKit:feat/channels-jsx-imageCopy head branch name to clipboard
Open
feat(channels): render arbitrary JSX as images via Takumi + static chart components#6146AlemTuzlak wants to merge 35 commits intomainCopilotKit/CopilotKit:mainfrom feat/channels-jsx-imageCopilotKit/CopilotKit:feat/channels-jsx-imageCopy head branch name to clipboard
AlemTuzlak wants to merge 35 commits into
mainCopilotKit/CopilotKit:mainfrom
feat/channels-jsx-imageCopilotKit/CopilotKit:feat/channels-jsx-imageCopy head branch name to clipboard
Conversation
added 28 commits
July 23, 2026 12:18
…dd Takumi render config
Adds render_mrr, replacing the removed Playwright render_chart tool: posts an MrrCard (plain createElement-authored React component) and, when a signups series is passed, a BarChart from @copilotkit/channels/charts -- both routed to the Takumi image path automatically via thread.post's arbitrary-JSX detection, no wrapper needed. Also widens channels-ui's JSX.ElementType (TS 5.1 tag/element decoupling) so a function component that returns a real ReactElement (e.g. BarChart) can be used directly as a JSX tag under the channels pragma without tripping TS2786 -- the type checker previously required every tag's return type to be assignable to JSX.Element (ChannelNode), which broke exactly the "arbitrary app JSX" pattern this demo exercises. Adds @types/react to examples/slack so React types actually resolve there.
…ata/negative guards, palette fallback
…ve; test fidelity + example error logging
…rrect stale comments + docs
… render-smoke color assertion Takumi does not resolve CSS var()/theme tokens for SVG stroke/fill, so DEFAULT_CHART_COLORS is now a fixed hex palette instead of shadcn --chart-N custom properties. PieChart returns an empty <svg> for all-non-positive data instead of a misleading full disk, and colors a single positive slice by its original index in `data`. Meter clamps NaN input to 0. render-smoke.test.ts decodes rendered PNGs and asserts a palette color is actually visible (not just a non-empty file), guarding against the invisible-var() failure mode. Docs corrected to match: charts theme via the `colors` prop, not automatic shadcn token adoption, and the per-chart styling-prop support matrix is documented.
…waitChoice vs arbitrary JSX
…rds; correct Meter height docs; guard-branch tests
…harts; readonly colors; wider render-smoke
…le stylesheet; runtime PORT guard
…generate centering; coverage + empty-id warn
…reduce-based extent; chart guard tests
… peer-install prereq; stacked-bar extent
# Conflicts: # pnpm-lock.yaml
Contributor
Production Digest-Pinning AuditAll 41 services digest-pinned. Run 2026-07-24 09:09:17 PDT — 0 finding(s). |
… standup
Three "run it in our own Slack" showcase features under app/showcase/, each a
shadcn-styled card + charts rendered to images via Takumi, and each triggerable
BOTH by a slash command and by prompt (a defineChannelTool the agent calls) via
one shared render* fn:
- /prs render_pr_radar - open PRs oldest-first, age-coloured badges +
PRs-by-age bar chart (public GitHub, no token)
- /pulse render_weekly_pulse - KPI card (stars/downloads/open issues) +
downloads line chart + issues bar chart
(public GitHub + npm)
- /standup render_standup - progress card + status pie (with legend) +
load-per-assignee bar (Linear, LINEAR_API_KEY)
The shadcn look is one token stylesheet fed via render.stylesheets; text is
Geist (Takumi's built-in font). Every feature reads live data and falls back to
labelled sample data (never throws), with per-request timeouts so a stalled
upstream still degrades. Wires showcaseTools/showcaseCommands into the channel,
declares the commands in both Slack manifests, and documents them in the README
and the posting-jsx-as-images guide.
… per-team standup Addresses testing feedback on the Slack showcase features. charts (@copilotkit/channels/charts): - Takumi does not rasterize SVG <text>, so LineChart's title/axis labels never rendered. Rework LineChart to keep the line/grid/points in SVG but draw the title, a 5-tick y-axis scale, and per-point x labels as HTML around it. - BarChart/StackedBar now print each bar's value (compacted, e.g. "1.2k") above it, via a shared formatCompact helper. examples/slack: - Stop the agent re-posting earlier turns' images: history is rebuilt from Slack message text each turn and image posts leave none, so the model re-satisfied every past request. Each render now leads with a one-line text summary (which survives reconstruction), and the runtime prompt gains a "latest request only" rule plus the three render_* tools. - Size the SVG charts (LineChart) to the post canvas so they fill the image instead of sitting in a corner. - Redesign the standup: query all Linear teams, compute per-team cycle completion, and render a per-team progress card (a meter per team) plus a done-vs-remaining stacked bar. Docs + tests updated to match.
…createElement
Let apps author image cards as plain JSX (`<div className="card">…`) instead of
`createElement`, while native channel components keep working unchanged.
channels-ui:
- The JSX runtime now compiles host/intrinsic tags (<div>, <span>, <svg>) to
real React elements, and component tags (<Message>, <BarChart>, <Meter>, app
cards) to ChannelNodes. A React element's $$typeof distinguishes host markup
(image path) from the string-typed channel vocabulary (`{type:"section"}`,
native) — no ambiguity. `react` is an OPTIONAL peer, lazily required only when
a host tag is used, so channel-only/react-free bots are unaffected.
- JSX.IntrinsicElements now types host tags (loose CSS map so channels-ui stays
type-level react-free).
channels-core:
- render/takumi.ts gains a `toReact` converter (image path only, where react is
present): it materializes the tree — rebuilding React elements with converted
children and INVOKING nested component nodes — so a component nested inside a
host card (`<div><Meter/></div>`) renders. detect.ts routing is unchanged.
examples/slack: rewrite the image cards (mrr, pr-radar, weekly-pulse,
cycle-standup) from createElement to JSX. Docs note the authoring model.
…d cards Adds ready-made data-viz tools and a CopilotKit-branded look. channels-core/charts: - chartTool (render_chart) + diagramTool (render_diagram): ready-made channel tools any bot registers. render_chart turns structured data (e.g. a parsed CSV) into a bar/line/pie/stacked/scatter image; render_diagram turns nodes + edges into a layered flow diagram. Both validate input and return a helpful string instead of throwing. - FlowDiagram: layered longest-path layout (cycle-safe), HTML boxes + SVG arrows. - DEFAULT_CHART_COLORS is now the CopilotKit brand data-viz palette. examples/slack: - Style cards with Tailwind (brand tokens in styles/tailwind.css → compiled to styles/brand.css via `pnpm build:css`), fed to createChannel with the Plus Jakarta Sans brand font (assets/fonts) via app/render/brand.ts. Takumi resolves the Tailwind classes at render time. Cards rewritten to the light OG brand look; the old shadcn theme is removed. - Register chartTool + diagramTool so you can ask for an ad-hoc chart/diagram. Docs (guide + README) cover the tools, branding, and build:css workflow.
…rner reveal) - FlowDiagram root fills the post canvas (width/height 100% + white bg), and render_diagram sizes the canvas to the flow direction (portrait for down, landscape for right) — so the diagram spans the image instead of sitting in the top-left with a large blank/gray area. - Drop the outer rounded corners on the showcase cards (mrr, pr-radar, pulse, standup): the rounded edge exposed the canvas behind the card. Cards are now full-bleed; inner badges/tiles keep their rounding. Recompiled styles/brand.css.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Channels' JSX vocabulary (
<Message>,<Button>, …) renders to native platform components (Slack Block Kit, Teams Adaptive Cards). But arbitrary app JSX — a shadcn-styled card, a dashboard tile — had no path to a channel. This adds one:thread.post(<AnyStaticJSX/>)now auto-renders arbitrary JSX to a PNG via Takumi (browser-free, in-process) and uploads it through the existingpostFile, while channel-UI components keep rendering natively. No browser anywhere.How it works
render/detect.ts→resolveArbitraryElement): a React element, or an unbranded component that returns a React element → image; channels-ui components (branded viaCHANNEL_COMPONENT) and user components that return channel nodes → native. This peek-at-output approach is what lets a developer's own native channel components and arbitrary app JSX coexist with zero call-site ceremony.channels-ui's emptyIntrinsicElements+ aJSX.ElementTypedeclaration make React-returning components valid JSX tags in channels-pragma files.Thread.postroutes;update/postEphemeral/awaitChoicereject arbitrary JSX with a clear error (an uploaded image can't be edited in place / can't dispatch a click).createChannel({ render: { fonts, stylesheets, width, height } }), overridable per-post. Takumi resolvesvar()/oklch in compiled CSS for arbitrary cards (shadcn theming works).react+takumi-jsare optional peer deps ofchannels-core/channels, imported only from the lazyrender/takumi.ts— a consumer who never posts an image never loads them.Built-in charts —
@copilotkit/channels/chartsBrowser-free static chart components (geometry computed in TS, no Recharts/Chart.js/Mermaid):
BarChart,StackedBar,Sparkline,Meter(box-model) andLineChart,PieChart,Scatter(inline SVG). Styleable viacolors/className/style/gridColor/labelClassName; fixed default palette. A real-Takumi render-smoke test asserts each chart renders its data in a visible palette color.examples/slack migration
Removes the Playwright/headless-Chromium render path (
render/{browser,chart,diagram}.ts, the Chart.js/Mermaid tools).render_mrrnow posts an arbitrary-JSXMrrCard+ a<BarChart>as images via Takumi.playwrightis kept only as a devDependency for the e2e Slack-login harness. (Mermaid diagram rendering is dropped — no browser-free equivalent.)Docs
New guide
showcase/shell-docs/.../channels/posting-jsx-as-images.mdx(auto-routing,renderconfig, per-chart styling/behavior table, peer-install prerequisite, and the plain limitations: static SSR only — no Recharts/Chart.js/Mermaid).Notes
var()fallbacks inbackgroundColorbut not in SVGfill/stroke— so the chart palette is concrete hex, not CSS vars.@copilotkit/vueSSR test had its timeout raised (it times out under the pre-commit full-suite load, unrelated to this change).createBot→createChanneldocs rename, anawaitChoicewaiter-leak on handler-throw, an MCP client leak in the example'sconnectMcp) are intentionally left for separate PRs.🤖 Generated with Claude Code