feat(annotate): one-step submit with a quick note - #1436
Open
backnotprop wants to merge 1 commit into
Open
Conversation
Reading an agent's message and wanting to reply "that's fine, but watch the migration" took four interactions: open the global-comment composer, type, save, then Send. Every annotate surface now has a split Send control whose caret opens a one-line "Add a note..." field. Enter sends the note together with any annotations already queued, in one action. With nothing queued the primary Send button opens that field instead of staying hidden, which is what the header did before (submitting an empty review was never useful). With feedback present the primary button is the incumbent Send Feedback, unchanged. Escape closes the field without submitting and keeps the typed text for the rest of the session. The note is created as a GLOBAL_COMMENT at submit time and committed into the annotations state, so it rides exportAnnotations and the /api/feedback annotations array exactly like a composer-made global comment. Both runtimes' /api/feedback handlers take a pre-rendered feedback string plus an opaque unknown[], so there are no server changes. Committing into state rather than threading the note through the payload builders is what makes annotate-last's multi-message export pick it up, since those entries are rebuilt from the live linked-doc session snapshot; the submit therefore waits one render for the commit. The note is not recorded in the annotation undo/redo history: it exists for the duration of one submit. On HTML and live-app surfaces the comment-only clamp does not apply, because that clamp sits on the iframe's postMessage ingest and this note is created in the parent. Plan mode is untouched. The compact touch shell has no header Send control, so the field lives in its "Review and finish" surface. Covers: file, folder, annotate-last, URL and live-app sessions.
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
A user asked for a one-step "that's fine" submit on
/plannotator-last: they read the agent's message and want to send a quick overall note without opening the annotation machinery. Today that is four interactions (open the global-comment composer, type, save, Send Annotations), and with no annotations at all the Send button is not even rendered, so there is nothing to press.What ships
Every annotate surface now has a split Send control:
Add a note...field, and sending from there submits the note together with the queued annotations in one action.Surfaces covered:
annotate(file),annotate-last(message, single and multi-message),annotate-folder,annotate-app(live), and URL sessions. Plan mode is deliberately untouched.Zero server changes, verified
The note is created as a
GLOBAL_COMMENTat submit time, so it is just another annotation on the wire. Both/api/feedbackhandlers were read to confirm nothing else is needed:packages/server/annotate.tsreads the body as{ feedback: string; annotations: unknown[]; ... }, settles the decision with them, and passes the same pair topersistSubmittedDecision. The annotations array is opaque; no type or shape is inspected.apps/pi-extension/server/serverAnnotate.tsmirrors that exactly, including the same "does not type-validate its body" comment and the sameArray.isArray(annotations)degradation.The exported feedback string is produced client side by
exportAnnotations, which already rendersGLOBAL_COMMENTentries. So the note reaches the agent through the text and is persisted in the durable submission record, with no endpoint, schema or runtime change on either the Bun or the Pi side. No server file is touched by this PR.How it is wired
The note is committed into the
annotationsstate (commitSubmitNoteinApp.tsx) rather than threaded through the payload builders. That is deliberate:annotate-last's multi-message export rebuilds its per-message entries from the live linked-doc session snapshot, not fromallAnnotations, so a note spliced into the builder call would be silently dropped there. Committing into state makes every annotate export path pick it up for free. Because that commit is a state write, the submit waits one render for it, via an effect keyed on the pending note id.Other decisions:
useHtmlAnnotation), and this note is created in the parent App, never posted from the page. Verified in code and covered by a test on the raw-HTML surface.Footprint
App.tsxgrows by one memoised control object, one commit helper, one submit handler and one effect. The affordance itself is a newpackages/editor/components/AnnotateSendControl.tsx, following the auto-viewed and global-comments precedent. Nothing in@plannotator/uichanges behaviour: the only edit there is a new shortcut scope plus its barrel export, andAppHeader's newsubmitNoteprop is optional, so a header rendered without it keeps today's markup exactly.EnterandEscapeare handled locally on the input, and theannotate-notescope documents them in the annotate registry only. This follows thecomment-popoverprecedent, which is documentation only with local handlers.Enterrather thanMod+Enteris a deliberate choice for a one-line field where a bare Enter has no other job.Tests
New
packages/editor/App.submitNote.test.tsx, six tests, each named after the regression it guards:GLOBAL_COMMENT, present in both the/api/feedbackannotations array and the exported feedback text.Falsified: removing the header wiring fails 5 of the 6, and the plan-mode test correctly keeps passing.
Results:
DOM_TESTS=1 bun test packages/editor packages/ui: 1476 pass, 0 failbun test packages/editor packages/ui: 880 pass, 0 failbun run typecheck: cleanbun test(whole repo): 4104 pass, 12 fail, all pre-existing and unrelated (semantic diff, GitButler and workspace review-server tests, which fail identically on a clean tree)bun run --cwd apps/review build && bun run build:hook: both succeed, build order respectedAI-assisted (Claude) under maintainer direction.