Repository navigation
Conversation
Notes pin to the current canvas selection and live in one file, .stacki/feedback.json, inside the open project (git-excluded with the rest of .stacki/). Claude Code reads that file with its own Read tool, edits the project, and flips each item's "done"; Stacki re-reads it on window focus, when the tab opens, and before every write. No server, port or watcher: the file is the interface, because the agent can already read the project. - shared/ipc/feedback.ts: the item and file parsers (version 1, bounded, ids unique). Agent-appended items may omit createdAt and use keys: []. - feedback:read / feedback:write: one channel pair through the usual six touch points. Main resolves each item's file:line trail from its selection keys with selectionTrail on every read and write, and writes through writeProjectText so the write is atomic like every project write. - Every renderer change reads the file fresh, applies itself, then writes, so an agent's done flips made in the meantime are not overwritten. - Feedback rail tab (M), with Copy for agent rendering the handoff text. - Numbered pins on the canvas for open notes at the current drill-in level; their paths join the frame's tracked set so it measures them. shortcut: pins are index paths, not handle-carried spans; a structural edit above a pinned node moves the pin to its neighbour. Upgrade by storing a byte span mapped with shared/engine/mapSpan.ts if notes start living across editing sessions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAFvSVVtdvBZHDz2thvb12
With the Feedback tab open, a click on the canvas annotates the element under the pointer instead of selecting it: a note box opens under the element, and saved notes show as numbered bubbles pinned to their copy of the element. A bubble opens a card with the note, Done and Delete. The tab no longer gives way to the Navigator on a canvas click, because the click never reaches the selection. Components are unwrapped: the frame hit-tests every file's markers while annotating, so an element inside a component is clickable without drilling in. The click reports the chain of files the element renders through (the page's instance, each nested instance, then the element), ordered by how far up the document each file's deepest node reaches, so content a page slots into its layout still ends on the page. The chain becomes the note's selection keys, so main resolves the same file:line trail as before, and the element's marker path and copy index become the note's `marker`, where its bubble draws. The agent finds the notes without being told: - Copy for agent names the file by absolute path, so the paste works from any directory. - The first write adds a short "Stacki feedback" section to the project's CLAUDE.md (created if missing) and to AGENTS.md if the project keeps one; a CLAUDE.md that imports AGENTS.md is left alone. Feedback now loads only while its tab is open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAFvSVVtdvBZHDz2thvb12
They had no CSS, so the note box rendered unpositioned at the top of the canvas, inside an overlay layer that ignores the pointer (and children inherit that), so it could not be clicked or typed into. Position both absolutely, give them a surface, and take pointer events back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAFvSVVtdvBZHDz2thvb12
The trail's file:line sat beside the label as one unbreakable monospace run, so it ran under the done and remove buttons. Give it its own line, cut with an ellipsis, with the whole trail in its title; let long labels wrap, and keep the buttons from shrinking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAFvSVVtdvBZHDz2thvb12
The app's button:hover background replaced the pin's amber, so a hovered pin all but disappeared. A more specific hover keeps it: a lighter amber, a slight grow and a shadow, eased over 120ms. The note box's primary button keeps its blue the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAFvSVVtdvBZHDz2thvb12
Copy for agent works with whatever agent runs in the project folder, so the toast no longer names one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAFvSVVtdvBZHDz2thvb12
pat-mw
force-pushed
the
feat/visual-feedback
branch
from
October 20, 2026 19:06
ed4cbbe to
040b67a
Compare
The project's .stacki/ folder is now tracked so shared settings reach Git and Studio; only generated folders stay in the local exclude. Feedback notes are the editor's working state, not site source, so the file joins .stacki/preview/ and .stacki/canvas/ in .git/info/exclude. The handler test asks Git directly whether it ignores the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pat-mw
force-pushed
the
feat/visual-feedback
branch
from
October 20, 2026 23:23
6d2ca1e to
47fc612
Compare
A click on a row now always brings its note on screen. A note on another page opens that page first; a note out of view scrolls the canvas to it. Before, a row did anything only when its pin was already drawn on the open page. The row asks useFeedback to reveal the item. feedbackRevealPlan finds the note's page among its selection keys: they are not in page-first order (content a page slots into its layout lists the layout first), so the open page is matched by the same any-key rule its pins use, and otherwise the first key naming one of the project's pages is opened through the page switcher's own selectPage. A note whose page is gone says so. The canvas half waits in useFeedbackReveal: once the pin exists and the frame showing its page has measured it, it posts the same avb:scroll-to the navigator uses, once. Rects held when the frame changed pages are refused, because index paths repeat across pages and a match among the previous page's would scroll to the wrong element. Done notes and an agent's notes without a canvas place have no pin, so their rows still only show the note in the list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
A new Feedback tab (M) - for annotations on the canvas, and a quick handoff to local coding agents.
While it is open, clicking the canvas leaves a note on the element instead of selecting it, even inside components. Notes show as numbered pins and are saved to
.stacki/feedback.json, which any local agent can read and resolve.Demo video:

Demo Screenshots (Expand)
Access annotation mode via the Feedback tab (M)Annotate freely on any element in the canvas, from any page



Jump to annotations from the sidebar:

Copy prompt for local agent:

Prompt looks something like this:
Clear resolved annotations

Breakdown
sequenceDiagram participant Canvas as Canvas frame participant App as Renderer participant Main participant Agent Canvas->>App: avb:annotate-node (element + chain of files) App->>Main: feedback:write Main->>Main: resolve file:line trails, write .stacki/feedback.json, note in CLAUDE.md / AGENTS.md App-->>Agent: Copy for agent (paste) Agent->>Main: edits project, sets "done": true in the file App->>Main: feedback:read (tab open, window focus, before each write)Evidence
After: in the Feedback tab the click opens a note box on that element.
outlines.test.jschecks that a click in the second Card reports[page#0.6.1, Card.astro#0.0.0.1], copy 1, and selects nothing.feedbackHandlers.test.ts: a live key resolves toindex.astro:1-3and a dead key to the bare file;CLAUDE.mdgets the note once; anAGENTS.mdthatCLAUDE.mdimports gets it instead.feedback.test.js: pins only for open notes on this page, and the handoff names the file by absolute path.npm run check:changedpasses. Full gate passes except the 10 Electron browser-probe suites, which won't launch as root in the container it ran in.Merge Danger
Door: two-way - easily reversible
The only things written into a user's project are
.stacki/feedback.json(git-excluded) and one appended section inCLAUDE.md/AGENTS.md, and only once a note is saved.Blast Radius: canvas
Annotate mode changes frame hit-testing only while the Feedback tab is open; when it isn't, the frame behaves as before.