Canvas: model-facing geometry + few-shot guidance, inline finding actions, fallback parity - #498
Merged
Conversation
The Canvas tool description never told the model how much space a page
gets or what a well-formed spec looks like, and its component list had
drifted behind the implemented set (textarea, editableTable, mediaPicker,
dependentSelect were missing). Add a GEOMETRY contract (chat column
~800px, grid{min} defaults, code-block scroll height), one complete
minimal example spec, and an INTERACTION section that tells the model
edits are saved automatically and it must never ask the user to re-type
Canvas values. Extend the catalog with the same geometry facts plus two
few-shot page templates (research dashboard, parameter intake), and note
which inputs degrade read-only in the HTML fallback.
Also retire the stale 'not yet wired' note in the code-review skill (the
action round-trip exists) and stop pointing at a repo path that does not
exist at runtime.
…urns The tool description promises that finding action buttons continue the conversation, and the HTML fallback delivers that via the daemon bridge — but the desktop inline renderer silently dropped `finding.actions`. Render them as buttons and route clicks through the existing onSubmitCanvasState channel with the finding's bundled context (canvasActions.actionMessage mirrors the fallback's wording so an action reads the same to the agent from either surface). Also format finding locations as path:line instead of dumping the raw location object as JSON, and add InlineCanvas stories covering the two catalog templates (review-with-actions, parameter intake).
The fallback renderer predates textarea/dependentSelect/editableTable/ mediaPicker/mediaModelSelect, so documented components rendered as '[unknown component: …]'. Add a real textarea input, degrade dependentSelect to a flat single-select with group-suffixed labels, and show editableTable/mediaPicker/mediaModelSelect read-only with an honest 'requires Puffer Desktop' note. Sync the interactive-node list with interactive_node_type() in canvas.rs and escape double quotes so item URLs cannot break out of attribute context.
Add a renderer regression test that requires the HTML fallback mediaPicker path to guard URL schemes before creating anchors. The fallback now only links http/https URLs and renders other model-provided values as escaped text.
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.
Why
An audit of the Canvas implementation against three questions — does the model get few-shot frontend templates, is it told the card/page geometry, and can it perceive canvas interactions — found:
grid{min}default 190px, code blocks scroll at 260px) but the model was told none of it, so it composes blind.textarea,editableTable,mediaPicker,dependentSelectwere undocumented).CanvasStatepull, Submit push, action round-trip via the daemon bridge) but the desktop inline renderer — the primary surface — silently droppedfinding.actions, contradicting the tool description's promise that action buttons continue the conversation. The HTML fallback also rendered five documented components as[unknown component: …].What
Model-facing guidance (
resources/tools/canvas.yaml,resources/canvas/components.md, code-review skill)grid{min}semantics and sane ranges, code-block scroll height, lead-with-summary rule.Desktop inline actions (
canvasActions.ts+ tests,InlineCanvas*.svelte, stories)finding.actionsnow render as buttons and route through the existingonSubmitCanvasStatechannel with the finding's bundled context;actionMessagemirrors the HTML fallback's wording so actions read the same to the agent from either surface.locationsrendered as raw JSON ({ "path": "src/pay.rs", "line": 210 }); nowsrc/pay.rs:210.HTML fallback parity (
canvas_template.html)textareainput;dependentSelectdegrades to a flat single-select with group-suffixed labels;editableTable/mediaPicker/mediaModelSelectdegrade read-only with an honest "requires Puffer Desktop" note.interactive_node_type()in canvas.rs;esc()now escapes"so item URLs can't break out of attribute context.Verification
vitestdesktop agent-screen suite: 102 passed (incl. newcanvasActionstests) ·svelte-check: 0 errors ·cargo test -p puffer-core canvas: 16 passed ·cargo test -p puffer-resources: all passed.Generated with Claude Code