* feat: extract checklist utilities to shared package
Move ChecklistItem, parseChecklist, extractDoneSteps, and
markCompletedSteps from Pi extension to @plannotator/shared
for reuse by the OpenCode plugin.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: Pi-style iterative planning for OpenCode plugin
Rewrite the OpenCode plugin to match Pi's planning methodology:
- Inject rich iterative planning prompt when agent is "plan"
(explore → update plan → ask user loop, structured plan format)
- File-based submit_plan: reads plan from disk instead of requiring
it as a string arg. Resolves path from OpenCode's system prompt,
falls back to PLAN.md
- Suppress plan_exit via tool.definition hook (directs to submit_plan)
- Add PLANNOTATOR_ALLOW_SUBAGENTS env var for #289
- Pass planFilePath to denial feedback template
- Keep existing subagent/build/title guards intact
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review findings in OpenCode plugin
- Cache agents list (static per session, was fetched every LLM call)
- Remove TOCTOU: drop redundant file.exists() before file.text()
- Eliminate duplicate system.join() by reusing joined string
- Resolve getSharingEnabled() once in submit_plan instead of twice
- Use path.join() instead of string concatenation for file paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: session-scoped plan files and tool-neutral prompt
Replace plugin-global resolvedPlanFilePath and prompt-path parsing with
per-session plan files at ~/.plannotator/session-plans/opencode/{id}/plan.md.
Remove resolvePlanFilePath() regex (fixes spaces-in-path and cross-session
race). Make planning prompt tool-neutral (no write/edit references) so it
works with apply_patch models. Strip OpenCode's native read-only and
experimental-mode prompt lines before injecting Plannotator's planning prompt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: directory-based plan storage with path validation
Replace session-scoped plan files with a shared plan directory at
~/.plannotator/session-plans/opencode/. Agent picks the filename,
submit_plan takes a path arg and validates it (absolute, inside plan
dir, exists, readable, non-empty) with canonical path checks to
defeat traversal and symlink escapes. Remove summary and plan string
args. Planning prompt is now tool-neutral and directs the agent to
reuse the same file on feedback, not create new ones.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use OpenCode's native plans directory for permission compatibility
OpenCode's plan mode permission ruleset only allows edits to
.opencode/plans/*.md and $XDG_DATA_HOME/opencode/plans/*.md.
Our custom ~/.plannotator/session-plans/opencode/ path was blocked
by PermissionDeniedError at the tool level regardless of prompt
stripping. Switch to the XDG path that OpenCode already allows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: exploration-first planning prompt
Restructure the planning prompt as a phased workflow:
Explore → Ask → Write → Submit. The agent now explores the
codebase before creating a plan file or asking questions,
producing better-researched plans for existing codebases.
Greenfield tasks can skip straight to questions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: fix stale path in JSDoc header comment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bun v1.1.43–1.1.45 throws EEXIST on mkdirSync with recursive:true
when the directory already exists. Add existsSync guard to prevent
save failures when the Obsidian folder already exists.
Fixes#315
The server-side inline SVG still had the original purple (#6B3FA0)
while the marketing site favicon was updated to dark navy (#070b14).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Git dumps its entire --help output on some failures (e.g. running
code review from a non-git directory). The error was passed raw to
the UI, rendering hundreds of lines of usage text. Extract only the
first meaningful line and cap at 200 chars. Also add line-clamp on
the UI error display as a safety net.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Unify review core across Bun and Pi
* Surface review diff failures
* fix: review core fixes from code review
- Handle unborn HEAD in runGitDiff("uncommitted") via rev-parse check
instead of assertGitSuccess, so fresh repos fall through to untracked diffs
- Bind Pi server to 0.0.0.0 for remote sessions (regression from e47eda0)
- Update CLAUDE.md: packages/shared/ description, PLANNOTATOR_SHARE env var,
/api/diff response shape
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update favicon background to dark navy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add favicon (option 2 — purple P with gold highlight)
Adds the "Purple P + Highlight" favicon across all three Plannotator
servers (plan, review, annotate). The icon is a rounded purple square
with a bold white "P" and a semi-transparent gold stripe through the
middle — typographic, on-brand, and legible at 16px.
Implementation:
- shared-handlers.ts: handleFavicon() serves the inline SVG with a
1-day cache header, shared by all servers
- index.ts, review.ts, annotate.ts: /favicon.svg route added before
the SPA catch-all
- apps/hook/index.html, apps/review/index.html: <link rel="icon">
added pointing to /favicon.svg
SVG favicons are the modern standard and render crisply at all sizes
without a separate .ico file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add favicon to marketing site
Use the same purple P + gold highlight SVG favicon on plannotator.ai.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
* refactor: extract useAnnotationHighlighter hook from Viewer
Move annotation plumbing (web-highlighter lifecycle, toolbar/popover
state, text-selection handlers, findTextInDOM, applyAnnotations) out
of Viewer.tsx into a dedicated hook. Pure refactor — zero behavior
change. Viewer consumes the hook and keeps its own code block, global
comment, and pinpoint-specific logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: block-level diff annotation with diffContext support
Add annotation support to plan diff view using block-level hover.
Hovering added/removed/modified sections shows the annotation toolbar.
No web-highlighter in diff mode — annotations live in React state only.
- diffContext field on Annotation type (added/removed/modified)
- PlanCleanDiffView: hover handlers, toolbar, comment/quicklabel flows
- Annotated blocks show persistent highlight ring via content matching
- View isolation: diff annotations filtered to diff view, normal to normal
- Share/draft restore filters diff annotations from Viewer DOM
- AnnotationPanel: neutral "diff" badge for diff annotations
- Export: [In diff content] label in feedback
- Toolstrip visible during diff mode for mode switching
- CLAUDE.md updated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: scroll-to-selected and visible highlight ring for diff annotations
Add scroll-to-selected when clicking a diff annotation in the panel —
scrolls to the block and briefly glows (same focused effect as Viewer).
Replace invisible ring-1 ring-primary/20 with ring-2 ring-accent for
annotated blocks so they're visually distinct.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* cleanup: memoize annotation filters, fix timer leaks, use blockId for highlight rings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve normal annotations across diff toggle
Replace ternary rendering with display:none so the Viewer
stays mounted and web-highlighter DOM marks survive the toggle.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: store full block content in diff annotations instead of truncating to 500 chars
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: tater sprite running behind plan document instead of in front
Move TaterSpriteRunning inside the z-0 content wrapper stacking context
so it layers correctly: above sidebars (z-auto) but below the plan
document (z-10). The mobile compat PR (#260) added relative z-0 to the
content wrapper, which trapped the plan at z-0 while the sprite at z-40
floated above everything.
* fix: move z-10 from main to inner content so sprite runs above grid
* feat: Octarine notes integration + auto-save for all integrations
Add Octarine as a third notes app integration alongside Obsidian and Bear.
Uses the octarine:// URI scheme to create notes via deep links, following
the same x-callback-url pattern as Bear.
- New file: packages/ui/utils/octarine.ts (cookie-backed settings)
- Server: saveToOctarine() in integrations.ts, wired into /api/approve
and /api/save-notes endpoints
- UI: Octarine tab in Settings, card in Export > Notes, dropdown button,
Cmd+S shortcut support
- Parallelize all integration saves with Promise.allSettled (was sequential)
- Add auto-save on plan arrival toggle to Bear and Octarine (Obsidian
already had this), consolidate into a single effect + single API call
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add YAML frontmatter properties to Octarine notes
Prepend Octarine-compatible YAML frontmatter with tags, Status, Author,
and Last Edited properties. Uses the same extractTags() as Obsidian for
auto-generated tags (project name, title words, code fence languages).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use fresh=true for Octarine saves to prevent content duplication
Octarine's create action appends by default. If auto-save fires on plan
arrival and the user then approves within the same minute, the same path
gets hit twice — doubling the content. Using fresh=true replaces instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: trim workspace and folder in saveToOctarine before building URI
The UI checks workspace.trim().length > 0 but the server used raw values.
Accidental whitespace in settings would cause Octarine saves to fail.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use isOctarineConfigured() for client-side Octarine guards
Auto-save and approve checks used raw workspace truthiness, so
whitespace-only workspace would trigger a save attempt that the server
rejects. Now uses isOctarineConfigured() which trims before checking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent Bear duplicate notes and align default-save dropdown gates
Bear creates a new note on every save, so skip it on approve when
arrival auto-save already succeeded. Gate the default-save dropdown
by actual configuration (vault path for Obsidian, workspace for
Octarine) instead of just the enabled toggle, matching the shortcut
behavior. Self-heal stale defaults back to "ask".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(pi-extension): add remote session support for SSH/devcontainer
- Detect remote sessions via PLANNOTATOR_REMOTE env var or SSH_TTY/SSH_CONNECTION
- Support PLANNOTATOR_PORT for fixed port (default 19432) in remote sessions
- openBrowser() returns { isRemote, url } to notify users to open manually
- Extract runBrowserReview helper to consolidate browser-open-wait-stop pattern
* fix: handle EADDRINUSE retry and honor browser overrides in remote sessions
- listenOnPort now retries up to 5 times on EADDRINUSE (matching
packages/server/ canonical pattern) instead of crashing on fixed-port
collisions in remote mode
- openBrowser honors PLANNOTATOR_BROWSER/BROWSER env vars in remote
sessions before early-returning (matching shared-handlers.ts pattern)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add async to server starters, rethrow non-EADDRINUSE errors, catch startup failures
- Add missing async keyword to startPlanReviewServer, startReviewServer,
startAnnotateServer
- Only wrap EADDRINUSE in retry message; rethrow other errors as-is
- Catch server startup failures in index.ts and surface via ctx.ui.notify
instead of crashing the extension
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds an agent-agnostic skill that reviews Renovate PRs updating GitHub
Actions — verifies pinned SHA integrity against upstream tags, checks
for breaking changes, and confirms workflow compatibility.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: shared feedback templates across all integrations
The deny/feedback prompts sent to LLM agents were duplicated as inline
string templates in hook, opencode-plugin, and pi-extension — each with
different tone and framing. The hook's directive style (from #224) was
the most effective at getting agents to address feedback. This extracts
all feedback text into @plannotator/shared/feedback-templates and has
every integration import from the single source of truth.
Closes#215 follow-up (propagates fix to OpenCode and Pi).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: rewrite feedback template tests around contracts not implementation
Tests now verify: cross-integration consistency, verbatim feedback
preservation, empty input handling, and that approved messages don't
contain directive language. Wording can change freely without breaking
tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: instruct agent to preserve plan title on resubmission (#296)
Version history slugs are derived from the plan's first # heading.
When the agent renames the heading after a deny, the version chain
breaks and the user loses diffs. The deny template now tells the
agent not to change the title unless explicitly asked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: improve plan deny preamble readability
Break the dense single-paragraph preamble into structured sections:
verdict, directive, and rules list. Easier for agents to parse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing @plannotator/shared workspace dependency
Hook and OpenCode plugin imported from @plannotator/shared/feedback-templates
without declaring it as a dependency. Worked locally but failed in CI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* revert: remove code review and annotate from shared templates
Scope-crept into code review/annotate feedback which introduced a double
heading regression and dropped integration-specific strings. Reverts those
paths to their original inline strings; shared module now only covers
plan deny feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore Pi plan file hint and vendor template for source installs
Add optional planFilePath to planDenyFeedback so Pi can tell the agent
to read the plan file before editing. Check in a vendored copy of the
template so Pi source installs work without running build:pi first.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(review): add file-scoped comments in code review (#302)
* refactor: reuse CommentPopover for file comment form
Replace the custom popover implementation in FileHeader (~88 lines of
hand-rolled state, click-outside, and escape handling) with the existing
CommentPopover component from packages/ui. This gives us expand-to-dialog
mode, image attachments, scroll-aware positioning, and keyboard shortcuts
for free, while saving ~62 net lines.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: custom theme system with 15 built-in themes
Consolidate CSS theming into a single source of truth (packages/ui/theme.css)
and introduce a multi-theme architecture where each theme defines both dark
and light mode variants. Users can pick a color palette (theme) and separately
toggle dark/light mode within it.
- Extract shared color tokens, Tailwind bridge, and base styles into packages/ui/theme.css
- Replace hardcoded oklch values with token references (oklch from var syntax)
- Fix 3 light-mode bugs in review-editor diff colors
- Create 15 built-in themes: Plannotator (default), Claude+, Soft Pop, Adwaita,
Caffeine, Cyberdyne, Cyberfunk, Doom 64, Dracula, Gruvbox, PaulMillr,
Quantum Rose, Solar Dusk, Terminal, Tinacious
- Expand ThemeProvider to manage colorTheme + mode independently
- Add Theme tab to Settings with mode toggle, search, and swatch grid
- Dark-only themes (Dracula, Terminal, etc.) suppress light class to prevent
broken styling; light-only themes (Tinacious) force it
- Cookie persistence: plannotator-color-theme for palette, existing key for mode
- Include theme conversion script for FinSitter theme adaptation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: refine themes, add Catppuccin/Rosé Pine/Monokai Pro/Synthwave 84/Tokyo Night
Theme curation:
- Replace Cyberdyne with Synthwave '84 (from robb0wen/synthwave-vscode)
- Replace Cyberfunk with Catppuccin (Mocha dark + Latte light), Rosé Pine
(dark + Dawn light), Monokai Pro (dark only), Tokyo Night (Storm + Day)
- Rewrite Gruvbox from canonical source (morhetz/gruvbox)
- Rewrite Adwaita from canonical VS Code theme (piousdeer/vscode-adwaita)
- Rewrite PaulMillr from Ghostty canonical palette
Theme fixes:
- Fix faded Send Feedback button on Solar Dusk, Quantum Rose, Caffeine
(accent colors were too dark/invisible at 15% opacity)
- Fix Dracula/Terminal/PaulMillr/Tinacious light mode breakage — dark-only
themes now suppress .light class via modeSupport in ThemeProvider
- Fix code block backgrounds — use --code-bg token instead of --muted
- Add faint green grid overlay for Terminal theme
- Alphabetize theme registry (Plannotator first)
Code review diff theming:
- Pass theme colors into @pierre/diffs shadow DOM via unsafeCSS prop
- Dynamic themeType based on resolved mode
18 built-in themes: Plannotator, Absolutely, Adwaita, Caffeine, Catppuccin,
Doom 64, Dracula, Gruvbox, Monokai Pro, PaulMillr, Quantum Rose, Rosé Pine,
Soft Pop, Solar Dusk, Synthwave '84, Terminal, Tinacious, Tokyo Night
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase settings modal height for theme grid visibility
Remove 340px cap on theme grid, bump content area from 70vh to 85vh
so all themes are visible without scrolling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: memoize ThemeProvider context, deduplicate Mode type, clean up DiffViewer
From /simplify review:
- Export Mode type from ThemeProvider, import in ThemeTab (was duplicated)
- Add resolvedMode to context — consumers no longer re-query matchMedia
- Memoize context value with useMemo, setters with useCallback (prevents
unnecessary re-renders of all useTheme consumers)
- Consolidate DiffViewer's two pierre state vars into single object
- Use resolvedMode from context in DiffViewer instead of classList check
- Format crammed single-line extended tokens in 4 theme CSS files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent FOUC, fix system mode stale read, restore marketing theme class
P1: Marketing site inline script now sets theme-{name} class on <html>
before first paint (reads plannotator-color-theme cookie). Without this,
CSS tokens under .theme-* selectors were never active.
P2: System mode effect now re-reads matchMedia.matches immediately when
entering system mode, not just on future changes. Fixes stale resolvedMode
when OS preference changed while pinned to explicit dark/light.
P3: ThemeProvider applies theme class synchronously during render (not in
a passive useEffect) to prevent flash of unstyled content on hard refresh.
Also extracted resolveThemeClasses to module scope to avoid useCallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add Quick Copy button, fix import icon, comment out agent badge
- Add Quick Copy button to annotation panel footer (splits horizontally
with existing Quick Share). Copies annotations wrapped with the deny
preamble so output is paste-ready for agent sessions.
- Export Modal annotations copy also wraps with deny preamble
- Extract wrapFeedbackForAgent() utility in parser.ts as single source
of truth for the preamble text
- Fix desktop import icon to match mobile (arrow-into-document, not download)
- Comment out code review agent badge — unreliable across multiple harnesses
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: don't ask agent to address feedback on LGTM approval (#284)
When the reviewer approves with no annotations, send a neutral
"Code review completed — no changes requested." message instead of
the contradictory "LGTM" + "Please address this feedback."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use explicit approved flag instead of annotations.length heuristic
The previous fix inferred LGTM from an empty annotations array, but
VS Code editor annotations are carried in feedbackMarkdown without
populating the annotations array — causing real review comments to be
misclassified as approvals. Thread an explicit `approved` boolean from
the UI through the review server to all three integrations.
Closes#284
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: append assertive instruction to review feedback output
When the reviewer submits actual feedback, append "The reviewer has
identified issues above. You must address all of them." so the agent
treats annotations with urgency rather than soft-acknowledging them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: annotate unused LGTM feedback string
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use non-blocking spawn for browser launch in Pi extension
execSync(xdg-open ...) blocks the Node.js event loop, preventing the
HTTP server from responding to the browser's initial request on Linux
desktops where xdg-open doesn't immediately exit.
Closes#288
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: handle async spawn errors in Pi openBrowser
spawn() reports missing-command failures asynchronously via an 'error'
event. Without a listener, a bad PLANNOTATOR_BROWSER or missing
xdg-open crashes the Pi process. Add child.once("error", () => {})
to swallow it silently, matching the original execSync try/catch
behavior.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clear /tmp/jiti on install to avoid stale cached Pi extension. Add a
PI USERS section to the post-install output with the install command.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pulsing dot on the Settings gear button and "new" pill badges on the
Display and Labels tabs signal newly added settings. Dismissed on first
open via cookie-based version gating.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove non-critical welcome dialogs
Remove UI Features Setup, Plan Diff Marketing, and What's New v0.11.0
dialogs from the first-run cascade. Only the Permission Mode Setup
dialog remains as it's the only one that affects agent behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract demo plan content to separate file
Moves the 334-line PLAN_CONTENT string from App.tsx to demoPlan.ts to reduce clutter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add quick annotation labels for one-click preset feedback
Add preset label chips (Needs tests, Security concern, Break this up, etc.)
that allow instant annotation without typing. Includes ⚡ toolbar button,
Alt+1..8 keyboard shortcuts, label customization in Settings, and label
summary in export output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add quick label selection mode for one-click annotations
* feat: redesign quick label picker UX and add label tips
- Redesign FloatingQuickLabelPicker as a vertical context-menu style list
with cursor-anchored positioning (appears at mouseup point, not selection center)
- Unify label dropdown: toolbar and quick-label mode now share the same
FloatingQuickLabelPicker component (removed duplicate InlineQuickLabelDropdown)
- Fix above/below flip positioning (follow CommentPopover pattern with
conditional translateY)
- Add label tips: optional instruction text on QuickLabel that gets injected
into agent feedback as a blockquote below the label
- Add tip editor in Settings with three visual states (empty/editing/filled)
- Add "Missing overview" default label with a tip for requesting narrative context
- Extend keyboard shortcuts from Alt+1-8 to Alt+1-9
- Suppress input method toggle (Alt) when label picker is open
- Reorder default labels: Clarify this, Needs tests, Consider edge cases,
Missing overview, Security concern, Break this up, Wrong order, Discuss first,
Nice approach
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: curate default labels, add cyan/amber colors, bare digit shortcuts
Finalize the 10 default quick labels based on user feedback data:
clarify, overview, verify, example, patterns, alternatives, regression,
out-of-scope, tests, nice-approach. Each label gets a unique color
(added cyan and amber to the palette). Bare digit keys (1-0) now apply
labels when the picker is open, Alt+N still works everywhere. Tip editor
cursor starts at beginning for readability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent duplicate annotation when digit key fires both toolbar and picker handlers
When the quick label picker is open from the toolbar's zap button,
let FloatingQuickLabelPicker own all keyboard input instead of both
components handling the same keypress.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
The annotate server was missing the /api/doc endpoint, so clicking
.md links (e.g., [see other](sibling.md)) in annotated files showed
"Failed to connect to server" — the request fell through to the SPA
catch-all and returned HTML instead of JSON.
Three changes:
1. annotate.ts — Add /api/doc route, auto-injecting the source
file's parent directory as ?base= so relative links resolve
from the correct location.
2. reference-handlers.ts — handleDoc() now accepts an optional
?base= query param and tries resolving relative paths against
it before falling back to the project-root glob search.
3. App.tsx — handleOpenLinkedDoc passes the current file's
directory as ?base= when navigating linked-doc → linked-doc,
so chained relative links resolve correctly.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds full inline image rendering to the Viewer for both annotate and
plan review modes. Previously,  syntax was treated as plain
text.
## What changed
### Viewer.tsx
- Added `` regex to InlineMarkdown before the link pattern.
Images starting with `!` were silently falling through to plain text
because `!` was not in the special-character scan set — added it.
- Added `imageBaseDir` prop (flows Viewer → BlockRenderer → InlineMarkdown)
so relative image paths in markdown resolve against the annotated file's
directory rather than the server's cwd.
- Added `onImageClick` callback (same prop chain) to open lightbox on click.
- Added `ImageLightbox` component: full-screen dark backdrop, image scaled
to 90vw/85vh with object-contain, alt text caption, closes on Escape or
backdrop click. Portaled to document.body.
- Images render with `cursor-zoom-in` to hint they're clickable.
### App.tsx
- Captures `filePath` from `/api/plan` response (already sent by annotate
server) and derives `imageBaseDir` (directory of the markdown file).
- Passes `imageBaseDir` to `<Viewer>`.
### ImageThumbnail.tsx
- `getImageSrc()` now accepts an optional `base` parameter. For relative
local paths, appends `&base=<dir>` to the `/api/image` query string.
### shared-handlers.ts
- `handleImage` falls back to resolving the path relative to a `base`
query param when the primary path doesn't exist and the path is relative.
Covers the common case of `` in a markdown
file opened from an arbitrary directory.
## Behavior
- Remote URLs (https://...) render directly, no proxy.
- Absolute local paths route through /api/image as before.
- Relative paths resolve against the markdown file's directory via ?base=.
- Click any image → lightbox. Escape or click outside → dismiss.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Add preset label chips (Needs tests, Security concern, Break this up, etc.)
that allow instant annotation without typing. Includes ⚡ toolbar button,
Alt+1..8 keyboard shortcuts, label customization in Settings, and label
summary in export output.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
* feat: add mobile compatibility
- Add responsive hamburger menu with all header actions (MobileMenu)
- Annotation panel renders as full-screen overlay on mobile with backdrop and close button
- Panel starts closed on mobile (<768px)
- Touch support for resize handles, pinpoint annotations, and toolstrip buttons
- Mobile text selection creates annotations via highlighter.fromRange() bridge
- Card action buttons always visible on touch devices (hover:none media query)
- Settings modal uses horizontal tab bar on mobile
- CommentPopover width capped to viewport on small screens
- Replace mousedown with pointerdown for touch-compatible click-outside handling
- Add useIsMobile reactive hook for breakpoint detection
- Desktop layout (>=768px) unchanged
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: mobile layout polish — panel gap, button overlap, responsive labels
- AnnotationPanel: fix bottom gap on mobile overlay (inset-y-12 → top-12 bottom-0)
- Viewer: push in-plan action buttons below badges on mobile (mt-6), add
clear-right before frontmatter, show short labels (Comment/Copy) on mobile
- App: reduce mobile horizontal padding to 8px (px-2)
- test-hook.sh: build review before hook to fix missing dist error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add plan width display setting with compact/default/wide options
Adds a configurable plan width preference (compact 832px, default 1040px,
wide 1280px) with an abstract layout preview in Settings. Dynamic max-width
flows through to Viewer, PlanDiffViewer, and the toolstrip. Default is
compact to preserve existing behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
* feat: add Graphviz rendering for plan mode
Support graphviz/dot/gv fenced blocks in the shared viewer and add a Graphviz sample diagram to PLAN_CONTENT for quick validation.
* fix: use imperative wheel listener with passive:false in GraphvizBlock
* fix(annotate): support Windows markdown paths in CLI annotate flow
* fix tmp path
* fix resolve-file.ts
* Update command to use EXE_PATH variable
* Update command path for plugin hooks in install.ps1
* test: add core test suite for path resolution, storage, remote detection, and install scripts
- resolve-file: absolute paths, relative paths, case-insensitive search, ignored dirs, extension filtering, ambiguity, Windows separators
- storage: slug generation, tilde expansion, version history, deduplication
- remote: env var detection (PLANNOTATOR_REMOTE, SSH_TTY), port config and validation
- image: tmpdir usage, extension validation
- install scripts: JSON structure validation, checksum verification, arch detection, full exe path in hooks
79 tests, 35ms
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add test workflow and gate release builds on tests
- New test.yml: runs `bun test` on PRs and pushes to main
- release.yml: tests must pass before build job runs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve pre-existing test failures for CI compatibility
- project.test.ts: make repo name assertion portable (works in CI where
checkout dir differs from local dev)
- vscode mock: add missing APIs needed by editor-annotations.ts
(comments, languages, Range, CodeActionKind, decorations)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: remove Viewer.test.tsx and happy-dom dependency
Tests didn't exercise any application code — they manually constructed
DOM elements inline and verified DOM API behavior, not Viewer.tsx logic.
The mock highlighter didn't simulate real hljs, and one test could never
fail due to its try/catch structure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use targeted markdown glob and restore clean lockfile
- Replace **/* glob with **/*.[mM][dD]{,[xX]} to only scan markdown
files during case-insensitive search (avoids iterating every file)
- Restore package.json key ordering from main, only removing happy-dom
- Regenerate bun.lock from main's base to eliminate version drift
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: drop redundant test and fix weak assertion
- Remove redundant PLANNOTATOR_REMOTE=TRUE test (already covered by true)
- Fix UPLOAD_DIR assertion that would pass even with hardcoded /tmp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Windows users were always shown the bash install command. Now uses
navigator.platform to show the PowerShell command on Windows.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Pi origin support to code review UI and update banner
Pi users were seeing "OpenCode" badge and getting the wrong update
command. Adds 'pi' to origin types in review/annotate servers, shows
violet "Pi" badge in code review header, and copies the correct
`pi install` command in the update banner.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: update Pi extension from install scripts when pi is available
The install scripts now check if `pi` is on PATH and run
`pi install npm:@plannotator/pi-extension` to update the extension.
Silent for users without Pi installed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add Codex support documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(codex): note plan mode not yet supported
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(codex): move plan mode notice to top
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: note plan mode not yet supported in Codex sections
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: remove redundant Codex description line
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(codex): lead with supported features
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(codex): use ! prefix for shell commands
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add per-file git add from code review UI (#254)
Adds the ability to `git add` individual files directly from the code
review interface. Users can stage approved files then switch to the new
"Unstaged changes" diff view to see only remaining work.
- POST /api/git-add endpoint with worktree support
- gitAddFile/gitResetFile utilities in packages/server/git.ts
- "Staged changes" and "Unstaged changes" added to diff type dropdown
- useGitAdd hook encapsulating all staging state and API logic
- "Git Add" / "Added" toggle button in FileHeader (next to Viewed)
- Visual indicator (left border) for staged files in sidebar FileTree
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: use green background tint for staged files instead of left border
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: support staged/unstaged diffs in worktrees and display stage errors
Adds "staged" and "unstaged" to worktree sub-type allowlists (server parser,
client parser, and runGitDiff switch), displays stageError in FileHeader,
and documents /api/git-add in CLAUDE.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: clean up git add feature — fix stale closure, timeout leak, dedup validation
- useGitAdd: use ref for stagedFiles to stabilize stageFile callback,
clear error timeout on reset, drop unnecessary useMemo
- App.tsx: wrap onFileViewed in useCallback
- FileTree: merge duplicate staged count conditionals
- review.ts: reuse exported validateFilePath for /api/file-content
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Persist the IPC port in workspaceState and try to rebind to it on
activation so restored terminals still have a valid PLANNOTATOR_VSCODE_PORT.
Falls back to a random port if the preferred one is taken.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add capture-phase paste listener to AttachmentsButton so users can
Cmd+V / Ctrl+V images directly into per-annotation attachments when
the popover is open. Uses stopPropagation to prevent the global paste
handler in App.tsx from also processing the event. Adds a platform-aware
keyboard shortcut hint in the drop zone.
Also fixes a regression from #237 where resolveMarkdownFile rejected
absolute paths outside the project root. The original behavior allowed
any absolute path the user explicitly provided; the security boundary
should only apply to relative path / bare filename search.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: bidirectional scroll navigation between annotations and highlights
When reviewing long plans with many annotations, it's hard to visually
connect which annotation card corresponds to which highlighted line.
- Click annotation card → scrolls content to the highlight + applies
a bright cyan "focused" color for visibility
- Click highlighted text → scrolls the right panel to bring the
corresponding annotation card into view
- Works with both web-highlighter and manually created (shared/imported)
annotations
- Handles edge cases: global comments (no highlight), multi-node
selections, already-visible elements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: scroll to center and skip focus on annotation creation
- Change scrollIntoView block from 'nearest' to 'center' so targets
appear prominently in the middle of the viewport
- Track just-created annotation IDs to skip scroll+focus effect when
a new annotation is added (user is already looking at it)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>