feat: agents builder template, AI kit workspace primitives and a shared live preview - #43
Conversation
… kit Tool trace, thinking status, ask card, connect prompt, agent draft card, usage notice, prompt tray and attachments, and a model selector with reasoning levels, pricing and recommendations. Kbd now derives its fill from the surrounding text colour so it reads on any surface.
An analytics agent workspace: chat with scripted replies, an agentic inbox, schedules as cards or a table, sources, skills, and an agent page with a live run and canvas. Responsive through container queries and safe for server rendering.
…shell Adds the Agents Builder page and preview route, AI kit docs and prompt input compositions, and replaces the per-showcase preview shells with a single LivePreview.
|
Review complete. 🟡 4 medium 💬 Inline comments (4)
🧹 Nitpicks (2) — 🟢 2 low
This PR introduces the
Reviewed commit: 4de8dcc |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Adds the Agents Builder template block with new AI kit primitives, registry and docs wiring, and React 19.3/motion 13 dependency bumps.
Key findings
- 🟡 Resize handle mixes displayed and logical widths — live-preview.tsx:441
- 🟡 Updated column inverts
aria-sortsemantics — skills-view.tsx:74 - 🟡
useAgentRunnever resets whenscriptchanges — use-agent-run.ts:155 - 🟡 Stale
readyStatecheck defeats the loading veil on reload — live-preview.tsx:245
| <ResizeHandle | ||
| max={Math.max(MIN_CUSTOM_WIDTH, Math.floor(available))} | ||
| onReset={() => onCustomWidth(null)} | ||
| onResize={onCustomWidth} | ||
| onResizingChange={setResizing} | ||
| width={Math.round(width * scale)} | ||
| /> |
There was a problem hiding this comment.
🟡 bug · medium
Resize handle mixes displayed and logical widths
The resize handle receives the frame's displayed width, Math.round(width * scale), but the values it emits are stored as the logical customWidth (apps/blocks/src/components/docs/live-preview.tsx:446). When the stage is narrower than the frame (scale < 1), dragging computes the next size from the scaled starting width and stores it unscaled, so a rightward drag shrinks the frame (e.g. at scale 0.8 a displayed 312 stores 332, which renders as 266).
The keyboard bounds are also in the wrong space: Home/MIN_CUSTOM_WIDTH = 320 is a logical minimum compared against a displayed value, and aria-valuemax/aria-valuenow mix the two units, so reported values are wrong whenever scale ≠ 1.
📋 Prompt for AI Agents
In apps/blocks/src/components/docs/live-preview.tsx around lines 441-447, <ResizeHandle> is given width={Math.round(width * scale)} and max={Math.floor(available)}, but the values it emits via onResize are stored as the logical customWidth. Fix the unit mismatch: pass the logical width (and a logical max of roughly available / scale) into ResizeHandle, keep the MIN_CUSTOM_WIDTH clamp in logical units, and only convert to display space (multiply by scale) for aria-valuenow and aria-valuetext. This prevents rightward drags from shrinking the frame and wrong aria values when the preview is scaled down (scale < 1).
| return ( | ||
| <th | ||
| scope="col" | ||
| aria-sort={active ? sort?.direction : undefined} |
There was a problem hiding this comment.
🟡 bug · medium
Updated column inverts aria-sort semantics
The skills table sets aria-sort={active ? sort?.direction : undefined} (packages/ui/src/components/agents-builder/skills-view.tsx:74), but the updated comparator is newest-first (packages/ui/src/components/agents-builder/skills-view.tsx:202) while name/type/author are oldest-modelled ascending. Clicking the Updated column starts at direction ascending yet renders newest-first, so assistive tech is told the column is sorted ascending when the visible order is descending by date. Screen-reader users get the wrong model of the table ordering.
📋 Prompt for AI Agents
In packages/ui/src/components/agents-builder/skills-view.tsx, the 'updated' case in compareSkills (line 202) sorts newest-first while the other columns sort ascending-first, so for the Updated column the direction state (and the aria-sort emitted at line 74) is inverted relative to the actual row order. Change the 'updated' comparator to Date.parse(left.updatedAt) - Date.parse(right.updatedAt) so all columns share the same ascending-first convention, keeping the existing reverse-on-descending logic at line 231.
| export function useAgentRun(script: AgentRunScript, { autoplay = true }: { autoplay?: boolean } = {}) { | ||
| const timeline = React.useMemo(() => buildRunTimeline(script), [script]); | ||
| const [run, setRun] = React.useState(() => | ||
| autoplay ? { state: INITIAL, next: 0 } : { state: settledState(script), next: timeline.length }, | ||
| ); | ||
|
|
||
| usePlaybackClock(timeline[run.next], false, () => | ||
| setRun((current) => { | ||
| const frame = timeline[current.next]; | ||
| return frame ? { state: frame.apply(current.state), next: current.next + 1 } : current; | ||
| }), | ||
| ); | ||
|
|
||
| const stop = React.useCallback(() => setRun({ state: settledState(script), next: timeline.length }), [script, timeline.length]); |
There was a problem hiding this comment.
🟡 bug · medium
useAgentRun never resets when script changes
useAgentRun rebuilds the timeline whenever the script identity changes (packages/ui/src/components/agents-builder/use-agent-run.ts:156) but never resets run.next or the run state, which are seeded only once at mount (packages/ui/src/components/agents-builder/use-agent-run.ts:157-159). If a host passes a new script (e.g. after a data update), playback resumes at the old index into the new timeline: frames can be skipped, misapplied, or — when the new timeline is shorter — next lands past the end and the run freezes on stale state. A host-driven data change mid-run leaves the run panel showing a broken, half-played state.
📋 Prompt for AI Agents
In packages/ui/src/components/agents-builder/use-agent-run.ts, useAgentRun (lines 155-171) seeds run only in the useState initializer, so a change to the script prop rebuilds timeline (line 156) while run.next keeps its old value, causing skipped or frozen playback. Add a React.useEffect(() => setRun(autoplay ? { state: INITIAL, next: 0 } : { state: settledState(script), next: timeline.length }), [script]) that resets the run whenever script identity changes.
| useEffect(() => { | ||
| setLoaded(false); | ||
| const frame = innerRef.current; | ||
| if (frame?.contentDocument?.readyState === 'complete' && frame.contentDocument.URL !== 'about:blank') setLoaded(true); |
There was a problem hiding this comment.
🟡 bug · medium
Stale readyState check defeats the loading veil on reload
In PreviewFrame, the effect keyed on src calls setLoaded(false) and then immediately reads frame.contentDocument.readyState (apps/blocks/src/components/docs/live-preview.tsx:245); on a src change the iframe still holds the previous, complete document, so setLoaded(true) overrides the reset in the same batch. The loading veil therefore never renders for the Reload button or any src change — users see stale frame content with no loading indication until the new document's load event fires. Only the first mount behaves correctly, because the initial document is about:blank.
📋 Prompt for AI Agents
In apps/blocks/src/components/docs/live-preview.tsx, the effect in PreviewFrame (lines 242-248) resets loaded on src change but then synchronously reads the still-old document via frame.contentDocument?.readyState === 'complete', immediately setting loaded back to true so the loading veil never shows on reload. Keep the synchronous pre-check only for the first mount (e.g. compare against a ref of the previously loaded src) so src changes rely solely on the load event and the LOAD_FALLBACK_MS timer.
Summary
@constructive/ai): new agent workspace primitives —ToolTrace,ThinkingStatus,AskCard,ConnectPrompt,AgentDraftCard,UsageNotice,PromptInputTray,PromptInputAttachment(s),MarkTile, and aModelSelectorwith reasoning levels, optional pricing per 1M tokens, cost tiers and recommendations.Kbdnow derives its fill from the surrounding text colour, so keys read on any surface.@constructive/agents-builder): an analytics agent workspace composed from the AI kit.dataand callbacks; responsive through container queries; renders on the server without browser globals.LivePreviewshell (viewport switch, custom width, reload, open in new tab, full screen, loading state) replacing the per-showcase shells.AnimateViewis used only for in-block show/hide swaps and reorders, never for route transitions).Test plan
pnpm check,pnpm build:pages,pnpm build:storybookin CIaiandagents-builderGenerated with Devin