* feat: add shared EditorAnnotation type
Single source of truth for the editor annotation interface,
imported by both @plannotator/server and @plannotator/ui.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: editor annotation server endpoints
In-memory store with POST/GET/DELETE endpoints for editor annotations.
The array lives in the handler closure and dies with the server session.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: editor annotation UI — polling hook, card, panel, and export
- useEditorAnnotations hook with auto-disable polling (500ms interval)
- EditorAnnotationCard with file path, code preview, and comment
- AnnotationPanel conditionally renders editor annotation section
- exportEditorAnnotations formats annotations for Claude feedback
- App.tsx wires hook, includes in output memo and send feedback gate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: VS Code extension editor annotation command
Cmd+Shift+. or right-click to capture selected text as an annotation.
POSTs through the cookie proxy to the plannotator server. Adds amber
left-border decorations on annotated lines, cleared on panel close.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: inline comment threads, lightbulb menu, and stronger decorations
Replace showInputBox with VS Code CommentController for inline annotation
threads anchored to selected code. Add CodeActionProvider for lightbulb
discoverability. Stronger decoration styling with gutter icon.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: VS Code theme integration for webview
Bridge VS Code CSS variables to Plannotator's CSS variable system via
postMessage between wrapper page and proxied iframe. Automatically adopts
the active VS Code color theme (dark/light/custom) without touching any
UI components or CSS files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: gate editor annotation polling behind VS Code detection
Only poll /api/editor-annotations when running inside a VS Code webview
(window.__PLANNOTATOR_VSCODE). Browser and shared URL users now have
zero network cost from the editor annotations feature. Also fixes poll
interval from 500ms to 2000ms.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: faster polling, prevent unnecessary re-renders, unify HTTP helper
- Poll interval 2s → 500ms for snappier annotation pickup
- Shallow equality check on annotation IDs prevents React re-renders
when poll data is unchanged
- Merge postToProxy/deleteFromProxy into single requestProxy function
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: editor annotations support in code review
Wire existing editor annotation infrastructure into the review flow
so VS Code users can annotate files outside the diff during code review.
- Add editor annotation endpoints to review server (same 3-line pattern)
- Call useEditorAnnotations hook in review App.tsx
- Display editor annotations in ReviewPanel with "Editor" divider
- Include editor annotations in feedback export and gating logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
feat: support git worktrees in code review (#196)
Adds full worktree support to the code review flow:
- Detects worktrees via `git worktree list --porcelain` and surfaces them in the diff dropdown
- Selecting a worktree enters "worktree mode" with scoped diff options (uncommitted, last commit, vs main)
- "Back to main repo" restores the original dropdown
- Handles initial commits gracefully (no HEAD~1 crash)
Closes#196
* feat: add --browser CLI flag and session discovery (#135)
- Add `--browser <name>` global flag to override which browser opens
(sets PLANNOTATOR_BROWSER, works with existing openBrowser() logic)
- Add `plannotator sessions` subcommand to list active server sessions
with `--open [N]` to reopen in browser and `--clean` to prune stale entries
- Track active sessions in ~/.plannotator/sessions/<pid>.json with
automatic stale cleanup via PID liveness checks
- Register/unregister sessions in all three server modes (plan, review, annotate)
- Export ./sessions and ./project from @plannotator/server package
Closes#135
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add integration test for session discovery
Non-interactive test script that validates:
- Session file creation/cleanup lifecycle
- Session file content (all fields)
- `plannotator sessions` listing, --open, --clean
- Stale PID auto-cleanup
Also syncs bun.lock versions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add --browser flag and session discovery to configuration page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add troubleshooting page
Covers lost tabs (sessions --open), data storage locations,
browser issues, and hook not firing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: harden title sanitization for Obsidian filenames
Strip parentheses, brackets, curly braces, hash, tilde, and backtick
from plan titles when generating Obsidian filenames. Re-trim after
stripping to handle edge cases where stripped chars leave leading/trailing
whitespace. Closes part of #235.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: filename separator + auto-save on arrival for Obsidian
Filename separator: adds a dropdown in Obsidian settings to replace
spaces with dashes or underscores in generated filenames. Fixes issues
with CLI tools fumbling space-escaped paths inside Obsidian vaults.
The setting flows through the save request to generateFilename() as a
post-processing step. Live preview reflects the choice.
Auto-save on arrival: adds an opt-in toggle that automatically saves
plans to Obsidian the moment they load in the browser, before the user
approves or denies. This fixes the workflow where approving from the
Claude Code CLI kills the hook server before the user can Cmd+S.
Guarded by a ref to fire only once per session.
Closes#235, closes#228.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pass filenameSeparator in ExportModal Obsidian save path
The ExportModal's save-to-Obsidian callsite was missing the
filenameSeparator config, causing the separator preference to be
silently ignored when saving via the Export Modal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The pinpoint click effect read `highlighterRef.current` at effect setup
time and bailed early if it was null. After exiting plan diff, the
Viewer fully remounts — usePinpoint's click effect ran before the
highlighter init effect, so `highlighterRef.current` was still null.
The effect returned early without installing the click handler, and
nothing triggered it to re-run since the dependencies (stable refs,
unchanged `isActive`) didn't change.
Move the highlighter read into the click handler body so it's resolved
lazily at click time, when the highlighter is guaranteed to be ready.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add case-insensitive file resolution so users can open markdown files
without typing exact paths. Applies to both the CLI (`plannotator
annotate`) and the `/api/doc` endpoint for linked documents.
Resolution strategies (tried in order):
1. Exact path — absolute or relative to cwd (existing behavior)
2. Case-insensitive relative path — `docs/setup.md` matches `docs/SETUP.md`
3. Bare filename search — `setup.md` searches the entire project tree
Ambiguity handling:
- 1 match → opens the file, prints "Resolved: /full/path"
- 0 matches → "File not found: <input>"
- 2+ matches → "Ambiguous filename: found N matches" with full paths
Skips node_modules, .git, dist, build, .next, __pycache__, .obsidian,
.trash during search. Restricts results to .md/.mdx/.markdown files
and enforces project root boundary (no path traversal).
Extracted shared resolveMarkdownFile() into packages/server/resolve-file.ts
and refactored /api/doc handler to use it, removing ~40 lines of inline
resolution logic.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
* fix(annotate): resolve file paths correctly on Windows
- Use PLANNOTATOR_CWD env var to get original working directory
- Strip @ prefix from file paths (Claude Code reference syntax)
- Add debug logging (PLANNOTATOR_DEBUG=1)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: add missing path import for annotate subcommand
The dynamic `import("path")` was removed but `path.isAbsolute()` and
`path.join()` are still used, causing a ReferenceError at runtime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Julien Fernandez <theflysurfer@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Bold-wrapped links like **[text](url)** were rendered as plain bold text
because the bold regex matched first and treated inner content as a string.
Fix: recurse into InlineMarkdown for bold/italic inner content so nested
markdown (links, code, etc.) is properly parsed.
Affected:
- Viewer.tsx (main plan view)
- PlanCleanDiffView.tsx (diff view duplicate)
* fix: unreadable text in markdown code blocks (light mode)
highlight.js's markdown grammar tokenizes underscores, asterisks, and
backticks as emphasis/strong/code spans. The github-dark theme assigns
these tokens colors (#c9d1d9, #8b949e) that are nearly invisible against
light backgrounds, making large sections of markdown code blocks appear
"washed out."
The existing light-mode overrides in index.css cover keywords, strings,
comments, and numbers — but not .hljs-emphasis, .hljs-strong, or
.hljs-code. This patch forces those three token types to inherit the
base code color, which is already correctly overridden for light mode.
Tokens fixed:
- .hljs-emphasis: underscores in variable names (e.g. data_quality_good)
were parsed as italic emphasis — now inherits color, removes italic
- .hljs-strong: **bold** markers used #c9d1d9 — now inherits color
- .hljs-code: `backtick` inline code markers used #8b949e — now inherits
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add markdown file that reproduces light-mode highlighting bug
Contains a ```markdown code block with underscores in variable names,
**bold** markers, and `backtick` code — all patterns that trigger the
washed-out text rendering this PR fixes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds tabs to the help dialog: "Selection Modes" (new video) and
"How Plannotator Works" (existing video). Defaults to selection modes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add pinpoint mode for point-and-click annotations
Adds a new input method toggle (Drag / Pinpoint) orthogonal to the
existing annotation action modes (Selection / Comment / Redline).
In pinpoint mode, hovering over blocks and inline elements shows a
dashed outline with a label tooltip. Clicking auto-selects the
element's text via highlighter.fromRange(), firing the same CREATE
event as drag-selection — all downstream annotation flow is unchanged.
New files:
- blockTargeting.ts: resolves hover targets (inlines, cells, list items, blocks)
- usePinpoint.ts: hover/click state machine with text-range bridge
- PinpointOverlay.tsx: dashed outline + label badge
- InputMethodToggle.tsx: Drag/Pinpoint segmented toggle
- inputMethod.ts: cookie-based persistence
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: unified AnnotationToolstrip with smooth animations
Merge InputMethodToggle + ModeSwitcher into a single compact toolbar
with two pill groups: input method (Select/Pinpoint) and action mode
(Markup/Comment/Redline). Buttons expand to show labels on hover/active
with explicit pixel-width transitions. Suppress transitions on mount
to prevent initial flash.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: keyboard shortcuts tab and settings reorganization
Add Shortcuts tab to settings with OS-aware key cap styling and
organized shortcut groups. Move Obsidian and Bear integrations
to dedicated tabs under an Integrations sidebar group, leaving
compact navigation links on the Saving tab.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: Alt/Option key to switch between Select and Pinpoint
Hold Alt to temporarily switch input method (reverts on release).
Double-tap Alt to permanently toggle. Added to keyboard shortcuts
panel in settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove md:py-8 to use consistent py-3 padding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: toolbar polish — icon swap, full opacity active states, styling
Move text-bracket icon to Select, pencil to Markup. Active buttons
use full opacity backgrounds with contrasting foreground. Help link
lowercase with updated styling. Revert md:py-8 padding. Add gap
between buttons in pill groups.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: match toolbar button sizing to main branch
Use text-xs (12px) and px-2.5 padding to match main branch button
rendering. Active state uses bg-background with no color tint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: smarter pinpoint hover — edge-zone tables, row targeting, list groups
Table edge-zone detection: top/bottom edges target whole table, left/right
edges target the specific row, interior targets individual cells. List group
wrappers create hover zones in gaps between consecutive list items. Demo
plan now includes sample tables for testing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove unused InputMethodToggle component
Superseded by AnnotationToolstrip which unified input method + mode switching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove unimplemented S3 paste store stub
Never used — paste service runs on Cloudflare KV and local FS.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove dead ModeSwitcher component
Replaced by AnnotationToolstrip which unified mode switching + input method toggle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate plugin hooks.json on install to ensure correct timeout
The Claude Code plugin marketplace can leave stale hooks.json with
incorrect timeout values (e.g. 1800 instead of 345600). Install scripts
now check for and overwrite the plugin's hooks.json if it exists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: warn about duplicate hook when plugin and settings.json both configure plannotator
Detects "command".*plannotator in settings.json (only matches hook configs, not
enabledPlugins) and only warns when the plugin is also installed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: define CLAUDE_SETTINGS variable in install.cmd
The duplicate hook warning referenced !CLAUDE_SETTINGS! but the variable
was never set, causing the warning to silently never fire for CMD users.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: integrate VS Code extension from community (7tg/plannotator-vscode)
Opens Plannotator plan reviews, code reviews, and annotations inside VS Code
tabs instead of an external browser. Intercepts PLANNOTATOR_BROWSER via env
var injection in integrated terminals, with cookie persistence and auto-close.
Original repository: https://github.com/7tg/plannotator-vscodeCloses#91
Co-Authored-By: Barbaros Gören <tayyipgoren@gmail.com>
* chore: adapt VS Code extension for monorepo
- Update package.json: publisher → backnotprop, repo → plannotator, add private
- Replace node -e URL encoding with curl --data-urlencode in router script
- Simplify panel-manager tests to keep only behavioral tests
- Add dev:vscode, build:vscode, package:vscode scripts to root
- Add *.vsix to .gitignore
- Update bun.lock with new workspace member
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address code review and duplicate panel issue
- Fix macOS browser: detect script paths in PLANNOTATOR_BROWSER and
invoke directly instead of via 'open -a' (which only accepts app
names/.app bundles)
- Update CLAUDE.md: add vscode-extension to project structure,
development, and build sections
- Fix command title: 'Simple Browser' -> 'Open URL in Editor'
- Add 1s debounce to panel opens: Claude Code fires ExitPlanMode hook
twice, spawning two plannotator processes on different ports that
both hit the IPC server simultaneously
- Add .vscode/launch.json + tasks.json for F5 debugging
---------
Co-authored-by: Barbaros Gören <tayyipgoren@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The click-outside handler matched only z-[90]/z-[100] classes, missing the
ImageAnnotator portal at z-[200]. Replace fragile z-index class matching with
a data-popover-layer attribute on all overlay portals.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add What's New v0.11.0 first-open dialog
One-time announcement dialog highlighting auto-save drafts, short link
sharing with E2E encryption, and Obsidian vault browser. Supersedes the
PlanDiffMarketing dialog in the chain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore PlanDiffMarketing dialog in the chain
PlanDiffMarketing was incorrectly removed. It now stays in the sequence:
PermissionMode → UIFeatures → PlanDiffMarketing → WhatsNew v0.11.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore showPlanDiffMarketing to useEffect dependency arrays
The fix commit restored it to guard conditions but missed the dep
arrays, creating a stale closure bug for keyboard shortcuts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(pi-extension): blocklist-only bash safety + interactive plan file path
Switch from allowlist+blocklist to blocklist-only for bash command gating
during planning. Commands are now allowed unless they match a destructive
pattern, fixing false positives that blocked web fetching (curl, wget).
Preserve tools from other extensions during planning instead of clobbering
with a hardcoded tool list. Capture active tools on enter, restore on exit.
Add interactive plan file path selection: /plannotator accepts optional path
arg or shows input dialog. New /plannotator-set-file command for mid-session
path changes. Enables monorepo workflows with per-feature plan files.
Closes#221
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(marketing): add Pi to landing page badge and video links
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(pi-extension): remove bash regex gating, use prompt-based guidance
Delete DESTRUCTIVE_PATTERNS and isDestructiveCommand() from utils.ts.
Remove bash interception from tool_call handler. Bash is now unrestricted
during planning — the system prompt guides the agent not to run destructive
commands. Write/edit file restrictions to the plan file remain enforced.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: replace comment textarea with CommentPopover (#219)
Replace the cramped toolbar textarea and awkward global comment inline
form with a new CommentPopover component that provides a proper writing
surface for long comments.
- New CommentPopover with popover mode (w-96) and expand-to-dialog mode
- Simplified AnnotationToolbar to menu-only (removed input step)
- Global comment button now opens CommentPopover instead of inline form
- Cmd/Ctrl+Enter to submit, Enter for newline (GitHub/Linear pattern)
- Popover tracks anchor element on scroll to stay pinned to content
- Image attachments supported in both modes
Closes#219
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: dismiss CommentPopover on new selection and memoize close handler
- Clear commentPopover state in highlighter CREATE handler when a new
selection is made (prevents popover+toolbar coexisting)
- Wrap handleCommentClose in useCallback to prevent the click-outside
effect from re-registering on every Viewer render
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: auto-save annotation drafts to survive server crashes
Adds server-side draft persistence so annotations are not lost when the
server process dies. Drafts are saved to ~/.plannotator/drafts/ keyed by
a SHA-256 content hash, debounced at 500ms. On reload, a dialog prompts
the user to restore or dismiss. Drafts are auto-deleted on approve/deny/feedback.
Closes#212
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove duplicate imports and update CLAUDE.md for draft feature
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wrap handleRestoreDraft in useCallback and update shared-handlers comment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract shared route handlers and modularize plan server
Pull duplicated /api/image, /api/upload, /api/agents handlers and
handleServerReady into shared-handlers.ts. Move /api/doc and Obsidian
reference endpoints into reference-handlers.ts. Eliminates ~120 lines
of verbatim duplication across index.ts, review.ts, and annotate.ts.
Closes#210
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: deduplicate OpencodeClient type and fix stale module comment
Export OpencodeClient interface from shared-handlers.ts and import it
in index.ts and review.ts instead of redefining inline. Also corrects
the module doc comment about /api/agents usage scope.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Obsidian vault browser sidebar tab (#207)
Add a third sidebar tab that lets users browse their Obsidian vault's
file tree, click any markdown file to open and annotate it as part of
plan feedback. Includes wikilink support for navigating between vault
files.
- New /api/reference/obsidian/files and /api/reference/obsidian/doc endpoints
- useVaultBrowser hook and VaultBrowser sidebar component
- Wikilink [[filename]] and [[filename|display]] detection in Viewer
- "Vault Browser" toggle in Settings > Obsidian
- Bare filename search for wikilink resolution within vault
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update docs and clear vault state on disable
- Add new reference endpoints to CLAUDE.md API table
- Update stale doc comments referencing "two tabs" in sidebar files
- Clear vaultPath and activeFile when vault browser is disabled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: simplify vault browser prop passing and derived state
- Move VaultNode to packages/ui/types.ts (shared type location)
- Pass useVaultBrowser return as single object prop through SidebarContainer
- Replace showVaultTab/vaultPath useState with useMemo derivations
- Extract buildVaultDocUrl helper to deduplicate URL template
- Wrap onVaultFetchTree in useCallback
- Fix toggle switch size to match existing toggles (h-6 w-11)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: open v0.10.0 pre-release fix branch
Tracking two bugs found during pre-release testing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: show error dialog on shared URL failure and fix import annotation merge
Bug 1: When a shared URL fails to load (truncated hash, expired paste,
missing encryption key), the portal now shows a warning dialog explaining
the failure and an amber "Demo" badge so users know they're seeing sample
content — not their data.
Bug 2: Importing a teammate's share URL no longer replaces existing
annotations. Uses React functional state updaters to avoid stale closures
and passes the full merged annotation list to pendingSharedAnnotations so
DOM highlights are re-applied for all annotations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The hook server runs on random ports, so the paste service must accept
requests from any localhost origin, not just :3001.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add customizable filename format for Obsidian export (#167)
Add a filename format setting to the Obsidian integration that supports
template variables ({title}, {YYYY}, {MM}, {DD}, {Mon}, {D}, {HH}, {h},
{hh}, {mm}, {ss}, {ampm}). Default format preserves existing behavior.
Settings UI shows a live preview of the resulting filename.
https://claude.ai/code/session_01QycvXHHvAy38N36PVP3jFF
* fix: guard against double .md extension in custom filename format
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Unit tests for encrypt/decrypt round-trip (key correctness, IV
uniqueness, tamper detection) and live integration tests that verify
the full compress → encrypt → POST → GET → decrypt → decompress
pipeline against the deployed Cloudflare Worker.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: Deploy paste service with end-to-end encryption (zero-knowledge)
Add AES-256-GCM client-side encryption to the short URL sharing flow.
Plans are encrypted in the browser before upload — the paste service
stores only ciphertext it cannot read. The decryption key lives only
in the URL fragment (#key=...) and never leaves the browser.
- New packages/shared/crypto.ts with encrypt/decrypt via Web Crypto API
- Encrypt before POST in createShortShareUrl, decrypt on load
- Parse #key= fragment in useSharing.ts for both direct and import URLs
- Deploy Cloudflare Worker with KV namespaces to workers.dev
- Add deploy-paste CI/CD job to deploy.yml
- Fix Cache-Control from public to private, no-store
- Update ExportModal strings with encryption messaging
- Update README, docs, and blog with zero-knowledge encryption details
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Address code review: fix short URL fallthrough and regex anchor
- Return false early when paste fetch fails on /p/<id> path instead of
falling through to parseShareHash() (which can never succeed since the
hash contains #key=, not plan data)
- Add end-anchor to importFromShareUrl regex to prevent false matches on
URLs with extra path segments (regression from PR #188)
- Remove demo plan NOTE from README
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
CJK users press Enter to confirm IME character selection, which
was triggering form submission instead. Add !e.nativeEvent.isComposing
guard to all Enter-to-submit handlers in the plan editor UI.
Fixes#191
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* 🐛 fix: force standard a/b diff prefix to handle mnemonic prefix config
When diff.mnemonicPrefix is enabled in git config, `git diff` uses
context-dependent prefixes (c/ for commit, w/ for worktree, i/ for
index) instead of the standard a/b. Both the internal parseDiffToFiles
and @pierre/diffs library expect a/b prefixes, causing silent parse
failures that result in an empty file list.
Add --src-prefix=a/ --dst-prefix=b/ to all git diff invocations to
ensure consistent output regardless of user git configuration.
* ✨ feat: propagate git diff errors to review UI
Previously, git diff errors were silently caught and returned as empty
patches. The UI showed "No changes" with no indication of failure.
- Add error field to DiffResult and propagate through ReviewServerOptions
- Include error in /api/diff and /api/diff/switch responses
- Show distinct error state in review UI (red icon + error message)
- Clear/set error state on diff type switch
* Fix mnemonic prefix handling in pi-extension runGitDiff
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>
* 🐛 Clear diff view when switching from Versions to Contents tab
sidebar.activeTab and isPlanDiffActive were independent states with
no synchronization. Switching to TOC tab left the diff viewer active
in the main area. Add useEffect to reset isPlanDiffActive when tab
changes to "toc".
* 🐛 Dismiss diff view on Escape key and add dev mock API for version history
- Add keydown listener to clear isPlanDiffActive on Escape
- Add Vite dev plugin (dev-mock-api.ts) serving mock /api/plan endpoints
with 3 plan versions so the Versions tab works during local development
- Guard setMarkdown against undefined plan from API response
Add a 10-minute timeout on waitForDecision() with proper clearTimeout cleanup, and use writeRemoteShareLink for remote URL notification instead of os.hostname()/localhost.
As per https://specifications.freedesktop.org/basedir/latest/#variables
> User-specific executable files may be stored in $HOME/.local/bin.
There is no env var for users to set to change this behaviour, and
$XDG_DATA_HOME isn't a reasonable place to put them.
* Add short URL sharing via paste service to fix URL truncation on Slack/messaging apps
Share URLs for large plans can be 10-40KB+ because the entire plan + annotations
are compressed into the URL hash. Services like Slack, WhatsApp, and Twitter
truncate these URLs, making shared plans unviewable (related: #142).
This adds an optional paste-service backend that stores compressed payloads and
returns short ~60-char URLs (e.g. share.plannotator.ai/p/aBcDeFgH).
Changes:
- Add Cloudflare Worker paste service (apps/paste-worker/) with KV storage
and 90-day TTL for stored plans
- Add createShortShareUrl() and loadFromPasteId() to sharing utils
- Update useSharing hook to auto-generate short URLs with 1s debounce
- Update ExportModal to show short URL as primary copy target with
full hash URL as backup
- Portal automatically supports /p/<id> paths via useSharing hook
- Fully backward compatible: hash-based URLs continue to work unchanged
- Graceful degradation: falls back to hash URLs if paste service is unavailable
* Fix critical bugs found during code review of short URL feature
- Worker: return only { id } so client constructs URL with its own
shareBaseUrl (fixes self-hosted deployments)
- importFromShareUrl: handle /p/<id> short URLs in addition to hash
URLs (fixes teammate import via short links)
- Anchor /p/<id> regex with ^ to prevent false matches on nested paths
- Pass shareBaseUrl to loadFromPasteId (was always defaulting)
- replaceState preserves base path instead of hardcoding /
- Clear stale shortShareUrl immediately on debounce to prevent showing
outdated link during the 1s delay
- Add shareBaseUrl to dependency arrays for loadFromHash and
importFromShareUrl callbacks
- Remove unused url field fallback from paste API response parsing
* Fix pasteApiUrl/shareBaseUrl confusion and add remote session share URLs
Bug fix (addresses @backnotprop's code review):
- useSharing hook now accepts separate `pasteApiUrl` parameter instead of
incorrectly passing `shareBaseUrl` to `loadFromPasteId`. These are
different domains (share portal vs paste backend). When omitted, the
default `https://paste.plannotator.ai` is used correctly.
- Fixed in all three call sites: loadFromHash, importFromShareUrl, and
generateShortUrl (via createShortShareUrl options).
Remote session share URLs (implements #192):
- When running on a remote instance (SSH, devcontainer), Plannotator now
generates a share.plannotator.ai URL and prints it to stderr so the
user can open the plan review in their local browser.
- Works for all three modes: plan review, code review, and annotate.
- Uses the same deflate-raw + base64url encoding as the client.
- Fails silently if URL generation fails — port forwarding still works.
- New server utility: packages/server/share-url.ts
* Address code review findings: fix spread limit, CORS, deps array
- share-url.ts: Replace btoa(String.fromCharCode(...compressed)) with
a loop to avoid RangeError on plans >65K compressed bytes
- share-url.ts: Add cross-reference comment noting the server-side
SharePayload is an intentional subset of the canonical UI type
- paste-worker: Only return CORS headers for allowed origins; disallowed
origins get no CORS headers instead of misleading partial headers
- useSharing.ts: Remove unused shareBaseUrl from loadFromHash dependency
array (loadFromHash only uses pasteApiUrl, not shareBaseUrl)
* Restructure paste service with pluggable storage, fix consent flow and security issues
Replaces monolithic paste-worker with multi-target paste-service architecture:
- Core logic (handler, storage interface, CORS) separated from deployment targets
- Filesystem store (self-hosted), KV store (Cloudflare), S3 stub (future)
- Bun binary target + Cloudflare Worker target
- Fix auto-upload removed: short URLs only created on explicit user click
- Clear stale short URL state when content changes
- Fix require('fs') in ESM module, add path-traversal guard in FsPasteStore
- Return 400 (not 500) for malformed JSON in both targets
- Fix compress() spread limit for large plans
- Updated docs: self-hosting guide, sharing guide, env vars, API endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Simplify paste service: extract shared router, remove TOCTOU patterns
- Extract duplicated HTTP routing from bun.ts and cloudflare.ts into
handleRequest() in core/handler.ts — targets are now thin wrappers
- Move ID_PATTERN regex to core (was duplicated in both targets)
- Remove existsSync guard before mkdirSync({recursive:true}) in fs.ts
- Remove file.exists() check before file.json() in fs.ts get() — the
try/catch already handles missing files
- Extract DEFAULT_SHARE_BASE constant in sharing.ts (was hardcoded twice)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Deduplicate compression, image parsing, and remote share URL logic
- Create packages/shared/ with compress/decompress (single source of truth)
— both @plannotator/server and @plannotator/ui import from here
- Export parseShareableImages from sharing.ts, remove duplicate
parseGlobalAttachments from useSharing.ts
- Extract writeRemoteShareLink() helper in share-url.ts, replace 3
near-identical blocks in apps/hook/server/index.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix modulo bias in paste ID generation and remove nonexistent doc artifacts
- Use rejection sampling in generateId() to eliminate modulo bias
(bytes >= 248 discarded, uniform distribution over 62 chars)
- Remove references to install-paste.sh and Docker image that don't
exist yet; point self-hosting docs to GitHub Releases binaries
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>
* feat: open linked .md files in read-only tabs with TOC navigation
When a plan references local .md files via markdown links, they now open
in a new browser tab using the full Plannotator viewer in read-only mode.
Changes:
- server: add /api/doc?path= endpoint with 4-strategy path resolution
(absolute, relative to project root, bare filename search). Returns 400
with match list on ambiguous filenames, 404 when not found, 403 on
path traversal. Only resolves files within process.cwd().
- Viewer: detect local .md links in InlineMarkdown and route them to
/?doc=<path>&readonly=true with a new-tab icon. Add isReadOnly prop
that guards the web-highlighter init (prevents phantom highlights) and
suppresses annotation toolbars.
- App: parse ?doc= URL param on load and fetch from /api/doc instead of
/api/plan. Set isReadOnly mode which shows a read-only banner, hides
approve/deny/feedback buttons, ModeSwitcher, Settings, AnnotationPanel,
and annotation panel toggle. Shows an error view (not a silent fallback)
when the document cannot be found or is ambiguous. Opens the sidebar to
the TOC tab automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: upgrade linked docs to same-view annotatable navigation with aggregated feedback
Replace the new-tab read-only approach with same-view navigation: clicking a
.md link swaps the plan content in-place, preserving all annotations via a
docCache. Users can annotate linked docs and all feedback (plan + linked docs)
is aggregated into the deny/approve payload sent to Claude.
Key changes:
- New useLinkedDoc hook for state swapping, caching, and highlight restoration
- React key on Viewer forces clean unmount/remount (fixes web-highlighter DOM crash)
- exportLinkedDocAnnotations() aggregates linked doc feedback with filepath context
- UI: 2px primary border, "Linked File" badge, "Copy file" button, TOC filepath
indicator with back-to-plan button
- Fix Tailwind v4 cascade: move * { border-color } to @layer base so utility
classes like border-primary actually override it
- Compact TOC spacing (padding, gaps, line-height) without affecting Version Browser
- Exclude node_modules/.git from /api/doc bare filename glob
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: harden /api/doc endpoint and align linked doc annotation output
Restrict /api/doc to .md/.mdx files only, fix heading hierarchy and
sorting in exportLinkedDocAnnotations, and update CLAUDE.md docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: complete linked doc annotations in getDocAnnotations and fix useMemo deps
- getDocAnnotations() now includes the active linked doc's live
annotations alongside the cache, removing the implicit requirement
to call back() before reading
- Remove unstable linkedDocHook object from annotationsOutput useMemo
dependency array (was defeating memoization every render)
- Guard Cmd+Enter shortcut against linkedDocHook.isActive to prevent
accidental approve/deny while viewing a linked doc
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: include linked doc annotations in approve feedback gate
The handleApprove gate only checked plan-level annotations, silently
dropping linked-doc-only feedback on "approve with notes" (OpenCode).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add getDocAnnotations to annotationsOutput useMemo deps
Ensures memo recomputes when navigating to/from linked docs, preventing
stale annotation output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
* Add feature plan for VS Code diff viewer button
Scoping document for a new PlanDiffViewer button that opens
the plan diff in VS Code's native diff viewer via `code --diff`.
https://claude.ai/code/session_01FYcJkGsWfuiGy53x3pwCSW
* feat: add "Open in VS Code" button to plan diff viewer
Add a button in the PlanDiffViewer toolbar that opens the current
plan diff in VS Code's native side-by-side diff viewer. The server
writes both versions to temp files and spawns `code --diff`.
- New POST /api/plan/vscode-diff endpoint in packages/server/index.ts
- VS Code button with loading/error states in PlanDiffViewer.tsx
- Wire currentPlan/basePlan/baseVersion props from App.tsx
https://claude.ai/code/session_01FYcJkGsWfuiGy53x3pwCSW
* chore: update bun.lock after install
https://claude.ai/code/session_01FYcJkGsWfuiGy53x3pwCSW
* refactor: server reads plan versions directly instead of receiving from client
The server already has the current plan in its closure and can read
any version from disk via getPlanVersion(). No need to send plan
text from the UI — just send baseVersion.
https://claude.ai/code/session_01FYcJkGsWfuiGy53x3pwCSW
* chore: remove plan.md scoping document from repo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract editor diff logic into shared packages/server/editor.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: use official VS Code SVG icon in plan diff viewer button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use history file paths directly for VS Code diff instead of writing temp files
Both plan versions already exist on disk in ~/.plannotator/history/.
Eliminates redundant file writes, fixes UPLOAD_DIR semantic misuse,
and corrects HTTP status codes for editor errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: rename editor.ts to ide.ts to avoid confusion with plan editor UI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add ide.ts and vscode-diff endpoint to CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Adds an npm-publish job to the release workflow that publishes
@plannotator/opencode and @plannotator/pi-extension to npm with
OIDC trusted publishing and provenance attestation.
- Add pull_request and workflow_dispatch triggers (with dry-run input)
- Derive DRY_RUN env var; publish only on tag pushes or explicit opt-in
- Pin all action refs to SHA for supply-chain security
- Gate the release job on tag refs to avoid spurious GH releases
- Add id-token: write permission at workflow and job level for OIDC