Skip to content

fix(annotate): use annotate feedback template for clipboard copy, not plan-deny#1109

Merged
backnotprop merged 1 commit into
mainfrom
fix/annotate-copy-wrapper
Jul 22, 2026
Merged

fix(annotate): use annotate feedback template for clipboard copy, not plan-deny#1109
backnotprop merged 1 commit into
mainfrom
fix/annotate-copy-wrapper

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

The bug

In annotate sessions, the clipboard Copy paths (the Export modal Copy button and the annotation panel quick copy) always wrapped the exported annotations with the plan-deny template. Copied text started with "YOUR PLAN WAS NOT APPROVED." even though the user was annotating a file, not reviewing a plan. That template is deliberately forceful directive framing (tuned in #224), so pasting the copied text into an agent falsely tells it a plan was rejected and pushes it into plan-revision behavior.

Meanwhile Send Feedback posts raw text and the server wraps it with the annotate template via getAnnotateFileFeedbackPrompt(), which also honors user-customized templates in ~/.plannotator/config.json (prompts.annotate.fileFeedback / messageFeedback). So Copy and Send Feedback produced different agent-facing payloads.

Root cause: wrapFeedbackForAgent() in packages/ui/utils/parser.ts unconditionally calls planDenyFeedback() with no mode awareness, and both copy call sites used it.

The fix

Copy is now mode-aware and config-faithful:

  1. Server ships the resolved template. Both annotate servers add a feedbackTemplates field to the /api/plan payload carrying the config-resolved fileFeedback and messageFeedback templates with placeholders intact ({{feedback}}, {{filePath}}, {{fileHeader}}). Resolution stays server-side (the browser cannot read config.json); new getters getAnnotateFileFeedbackTemplate / getAnnotateMessageFeedbackTemplate in packages/shared/prompts.ts back both the payload and the existing prompt functions, so Send Feedback output is byte-identical to before.
  2. Client wraps mode-aware. New browser-safe wrapFeedbackForClipboard() and applyFeedbackTemplate() in packages/core/feedback-templates.ts (no node: imports). The plan editor routes both copy paths through a single wrapper with this fallback chain:
    • annotate file/folder session: server-provided fileFeedback template, substituted with the current target; falls back to the built-in annotateFileFeedback default if the server field is absent
    • annotate-last (message) session: server-provided messageFeedback template, falling back to annotateMessageFeedback
    • plan review: planDenyFeedback, unchanged
    • shared/portal static views and archive mode never enter annotate mode and keep current behavior
  3. Both runtimes covered. The field is added in the Bun annotate server (packages/server/annotate.ts) and the Pi mirror (apps/pi-extension/server/serverAnnotate.ts). Pi consumes the new getters through its vendored generated modules; nothing in generated/ is hand-edited.
  4. Send Feedback is untouched everywhere. Only what Copy produces changes.

ExportModal takes an optional wrapCopiedAnnotations prop whose default reproduces today's behavior, so the review editor and any other consumers are unchanged.

Verification

  • New unit tests in packages/core/feedback-templates.test.ts cover the wrap selection: plan mode keeps the deny template, annotate mode with a server template applies it with substitution, annotate mode without one uses the default annotate template, unknown placeholders are left intact, and the message variant mirrors all of that. packages/shared/prompts.test.ts covers the new template getters including config and runtime overrides.
  • Full bun test: 2224 pass, 0 fail.
  • bun run typecheck passes; bun run build:review and bun run build:hook both build cleanly (artifacts not committed, maintainer rebuilds at release).
  • End-to-end: started both the Bun and Pi annotate servers against a sandboxed PLANNOTATOR_DATA_DIR and confirmed /api/plan ships the default templates, and the exact custom prompts.annotate.fileFeedback from config.json when configured.

Closes #1107

… plan-deny

Clipboard Copy paths (ExportModal Copy and the annotation panel quick
copy) unconditionally wrapped exported annotations with the plan-deny
template, so annotate sessions copied text starting with "YOUR PLAN WAS
NOT APPROVED." while Send Feedback used the annotate template (including
custom prompts.annotate.* config).

The annotate servers (Bun and Pi) now ship the resolved, unsubstituted
copy-wrapper templates in the /api/plan payload (feedbackTemplates), and
the plan editor wraps copied annotations mode-aware: server template when
present, browser-safe default annotate template otherwise, plan-deny only
in actual plan review. Send Feedback behavior is unchanged.

Closes #1107
@backnotprop
backnotprop merged commit 3f3b351 into main Jul 22, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Copying file annotations includes unexpected plan rejection message

1 participant