Chat Experience
DynamicUI Widgets
The AI renders structured information as real interactive components (grids of recipe cards, macro tables, meal-plan timelines, togglable checklists, stat tiles, tabs, and forms) instead of long walls of markdown. 14 typed widgets, safe-by-construction schema validation, theme-aware styling, and graceful streaming.
Rich Responses, Not Walls of Markdown
Plexon lets the AI render structured information as real interactive components instead of long markdown. Ask for a three-day meal plan and you get tabs for each day, a grid of recipe cards with macros, a timeline for the meal schedule, and a tickable grocery list, every element styled to match the rest of the app. Ask for a project dashboard and you get stat tiles, a comparison table, and a progress checklist. Ask for a product catalogue and you get a responsive grid of cards with badges and images. Plain markdown remains the right choice for prose. Widgets are mixed inline with regular text in the same response.
- 14 typed widget types: card, grid, list, table, stat tiles, timeline, accordion, tabs, image gallery, single image, checklist, selector, slider, form
- Drop a single picture inline: from a link, or straight from a file the AI just downloaded, no HTML wrapper needed
- AI chooses the right widget for the shape of the data. The companion skill teaches it when to use each
- Widgets are theme-aware components. They automatically match your light / dark mode and accent colour
- Nested composition: a tabs widget can contain a grid of cards whose footer is a stat_tiles, up to 5 levels deep
Model Supplies Data. Plexon Draws the UI.
Widgets are trusted components shipped inside Plexon. The AI never supplies HTML, CSS, or JavaScript. It calls a single always-on MCP tool, plexon_render_ui, with a typed JSON payload like { widget: "card", props: { title, image, description, badges } }. A per-widget schema validator on the server rejects malformed payloads before they ever reach the client, and an error boundary wraps every widget instance so a single bad payload can never crash the chat. Together they make "let the AI build UI in chat" safe-by-construction, unlike the alternative of piping model-generated HTML through a sandbox.
- plexon_render_ui MCP tool: always on in both execution and planning modes, no configuration required
- Strict per-widget schema validation on the server: 100% of props are checked before render
- Image sources are allowlisted: https:// and data:image/* for remote pictures, plus local files kept inside Plexon’s own folders (like the file the AI just downloaded); javascript:, http:, and file: URLs are rejected
- Error boundary per widget: isolated failure mode, no cascading crash
- Allowlist sanitization on nested markdown bodies: defence in depth
Interactive When It Matters
Four of the fourteen widgets are interactive: checklist, selector, slider, and form. Tick off ingredients as you cook, adjust a serving-size slider and watch the macros recompute, pick between three workout tiers with a selector, or fill out a structured form. Interactive state is isolated per widget instance and scoped to the running app session. The AI does not see your progress, your choices stay on your machine. This is a deliberate v1 choice: no dual-channel message pathway, no surprise round-trips of private data, no session storage bloat. The scaffolding for wiring form submissions back to the chat is already in place for a future phase.
- Checklist: togglable items with a live progress bar (done / total)
- Selector: button group for picking between alternatives, persists your choice
- Slider: numeric slider with label, unit, and range; great for "scale this to N servings"
- Form: multi-field form (text / number / select) with local submit and an Edit action
- State scoped per widget instance, keyed by the message + tool call, in a standalone, isolated state store
Typed, Composable, Extensible
Every widget has a typed props interface mirrored from a server-side struct, so both ends of the pipeline agree on the shape. Any body field marked "Widget | string" can hold either markdown or a nested widget spec: compose freely until depth 5. The widget registry is a simple generic Map<name, component>. Adding a new widget is a few files (server schema case, typed interface, component, registration line, skill docs). Plugins can register additional widgets at runtime through the same public API that the built-ins use.
- Discriminated-union schema on the server side: add a case to the props validator and an enum entry to the struct tag
- Generic typed widget components: per-widget prop types preserved at every call site
- BodyRenderer primitive: nested bodies accept markdown or nested widgets transparently
- Plugins can register new widget types at runtime via the public registerWidget API
Streams Gracefully, Fails Loudly
Because plexon_render_ui is an MCP tool call, widgets ride the existing streaming pipeline: no new transport, no new content-block type, no changes to session storage. As the AI streams the tool_use input token-by-token, Plexon shows a pulsing "Rendering UI…" skeleton until the JSON closes, then snaps straight to the final widget without an intermediate half-rendered flash. If the model emits a widget type the current client does not know about (for example an older build vs a new plugin), Plexon falls back to a JSON dump with a clear "Unknown widget" warning instead of crashing. Version skew is a visual annoyance, never a broken chat.
- Partial-JSON tolerance: skeleton until the tool_use input closes, then a single atomic render
- No new content-block type: widgets travel through the existing tool_use pathway
- Zero changes to session round-trip: historical sessions replay widgets correctly
- Graceful unknown-widget fallback: JSON dump with a warning, never a crash
Safe by Construction
Three layers of defence protect the chat UI. (1) Schema validation rejects malformed payloads in the Go tool: invalid enum values, missing required fields, non-https image URLs, and nesting deeper than five all fail before reaching the client. (2) The UI layer escapes string props automatically: a title of "<script>alert(1)</script>" renders as literal text. No widget injects raw HTML. (3) Markdown bodies embedded inside widget fields flow through an allowlist sanitizer before rendering: the same allowlist that protects the main markdown pipeline. Arbitrary HTML / JS artifacts rendered in a sandboxed iframe are deferred as a future "tier C" feature and will require their own design.
- Server-side schema validation with strict image URL allowlist and nesting depth cap
- The UI layer escapes string props: no widget injects raw HTML
- An allowlist sanitizer cleans all nested markdown bodies, permitting code-block buttons and working-directory images
- Tier C (arbitrary sandboxed HTML / CSS / JS) is explicitly out of scope for v1 and will require its own iframe design
What Dynamic UI Widgets give you
14 Typed Widgets
Card, grid, list, table, stat tiles, timeline, accordion, tabs, image gallery, and single image for layout, plus checklist, selector, slider, and form for interactivity. Each widget has a typed props interface mirrored from a server-side schema.
Composable Nesting
Any body field accepts a markdown string or a nested widget spec. Tabs holding a grid of cards whose footer is a stat tiles, whose body is a list. Composition is the primitive, up to five levels deep.
Tables, Tiles, Timelines
Tabular macro breakdowns, KPI-style stat tiles with positive / negative deltas, and vertical timelines for schedules and itineraries. The AI picks the shape that fits the data, not the other way round.
Togglable Checklists
Checklists with a live progress bar. Tick items off as you cook, shop, or run through a workflow. State is scoped per widget instance and stays on your machine.
Sliders and Selectors
Adjust a serving-size slider, pick between three tiers with a button group, or fill in a structured form. Interactive widgets use a standalone, isolated state store and never touch the main chat state.
Structured Forms
Multi-field forms with text, number, and select inputs, an Edit button to revise, and a local-first submit model. Ready to wire into the existing follow-up question channel for AI-visible submissions.
Safe by Construction
The model supplies data, not markup. Strict per-widget schema validation on the server, output escaping at every prop, and an error boundary around every instance. A single malformed payload can never crash the chat.
Streaming-Aware
Widgets ride the existing tool_use streaming pipeline. Partial JSON shows a pulsing "Rendering UI…" skeleton; closed JSON snaps to the final widget in a single atomic render. No half-rendered flash.
Theme-Aware Styling
Every widget uses Plexon theme tokens and variables. Cards, tables, and tiles match your light / dark mode and accent colour automatically. Nothing clashes with the rest of the app.
Stat Tiles with Deltas
Big readable numbers with units and coloured deltas for comparisons. "Calories 2100 kcal −100" renders a red trend-down badge; "+50" renders a green trend-up. Built for dashboards and daily summaries.
Extensible Registry
A generic Map<name, component> backs the dispatch layer. Plugins can register new widget types at runtime via the same public API the built-ins use. Adding a widget is a few files end-to-end.
Companion Skill
A hidden dynamic-ui companion skill teaches the model when to use each widget, how to compose them tastefully, and hard rules like "never invent image URLs" and "one meal plan equals one tool call, not twenty".