* 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>
* 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>
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>
* feat: expandable diff context in code review (closes#243)
Switch from PatchDiff to FileDiff component from @pierre/diffs to enable
GitHub-style "show more lines" buttons between hunks. The library handles
all expansion UI when provided full file contents via oldLines/newLines.
- Add /api/file-content endpoint to serve old/new file content per diff type
- Add getFileContentsForDiff() helper with ref mapping for all diff types
- Parse patch client-side via getSingularPatch(), augment with file contents
- Update @pierre/diffs from 1.0.4 to 1.0.11 (scroll sync fix, no breaking changes)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: upgrade @pierre/diffs to beta, re-parse patch with full file contents
The previous approach spread file content arrays onto a partial-mode
FileDiffMetadata, causing hunk index mismatches and Shiki decoration
errors. Now uses processFile() to re-parse the patch with oldFile/newFile
so hunk indices are computed against the full file (isPartial: false),
which is required for expansion to work correctly.
Also fixes a flash error on file switch by tagging fileContents with the
filePath they were fetched for, preventing stale contents from being
paired with the wrong patch during the render before useEffect fires.
- Upgrade @pierre/diffs from ^1.0.x to ^1.1.0-beta.19 in all workspaces
- Add test fixtures for disjoint hunks, deleted/renamed/new files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: abort stale file-content fetches, validate path params
- Add AbortController to file-content fetch effect to cancel in-flight
requests when switching files
- Reject path traversal (.. or absolute paths) on /api/file-content
- Document /api/file-content endpoint in CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* 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: 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>
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>
* 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>
* 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>
* 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>
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: 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>
* 🐛 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.
* 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: add Plan Diff marketing dialog, Pi origin support, and docs
- Add PlanDiffMarketing first-run dialog announcing the Plan Diff feature
with per-origin video demo URLs (Claude Code, OpenCode, Pi)
- Add 'pi' as a first-class origin with display name and violet badge
- Add Plan Diff blog post (plan-diff-see-what-changed.md)
- Add brief Plan Diff mentions across READMEs and marketing docs
* polish: tighten blog post copy and reduce em dash usage
* fix: move PlanDiffMarketing to plan-diff/ and fix stale useEffect deps
* add plan diff preview screenshot for marketing dialog
* feat: add plan diff UI with sidebar, badge, and dual view modes
Shows what changed between plan iterations when Claude revises after
feedback. Adds a +N/-M badge below repo info that toggles the diff view,
a shared left sidebar with TOC and Version Browser tabs, and two diff
modes: rendered (color-coded borders) and raw markdown (+/- lines).
Closes#138, closes#111
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md project structure and align first-run dialog labels
- Add plan-diff/ and sidebar/ component subdirectories to CLAUDE.md
- Add new hooks and utils to CLAUDE.md project structure
- Rename "Table of Contents" to "Auto-open Sidebar" in UIFeaturesSetup
to match Settings.tsx label
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address code review findings for plan diff UX
- Fix badge stats mixing block counts with line counts (modifications
now fold into additions/deletions)
- Gate hasPreviousVersion on diffBasePlan being loaded to prevent
"Show Changes" no-op and ModeSwitcher disappearing
- Make sidebar reactive to Settings toggle (useEffect on tocEnabled)
- Match PlanDiffViewer badge layout to Viewer (flex-col) so badge
doesn't jump position on toggle
- Add "Exit Diff" label to the close button in diff view
- Remove dead CSS (plan-diff-removed-marker, plan-diff-modified)
- Clean up stale header comment and unused lines prop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: second-round review cleanup for plan diff UX
- Fix stale "amber border" JSDoc in PlanCleanDiffView (actually green)
- Rename sidebar tab from "diff" to "versions" for clarity
- Gate VersionBrowser fetch on versionInfo being available
- Move .sidebar-tab-flag CSS into its own Sidebar section
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add loading state for version selection in sidebar
Add isSelectingVersion to selectBaseVersion, mirroring the existing
isLoadingVersions pattern. Shows "Loading..." on the selected version
button while the fetch is in progress.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address third-round code review findings
- Fix duplicate border/backdrop on TOC inside sidebar (className override)
- Fix loading indicator targeting wrong version button (fetchingVersion state)
- Fix "Show Changes" button silent no-op (gate on hasPreviousVersion)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move date to slug suffix, improve Other Plans UX
- Slug format changed from YYYY-MM-DD-{heading} to {heading}-YYYY-MM-DD
- Other Plans: single "coming soon" banner instead of per-item labels
- Strip date suffix from plan names in sidebar for readability
- Remove cursor-not-allowed from Other Plans items
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add Plan Diff section to CLAUDE.md, alert on version fetch failure
- Document plan diff feature: engine, view modes, state management, sidebar
- Update slug format documentation to {heading}-YYYY-MM-DD
- Show native alert when version fetch fails instead of silent swallow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add table rendering to clean diff view
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add plan version history (backend + API)
Save plan versions to ~/.plannotator/history/{project}/{slug}/ on every
plan arrival. Serves previousPlan and versionInfo via /api/plan, and adds
GET /api/plan/version?v=N for fetching any historical version. Enables
future diff UI. Deduplicates identical resubmissions.
Closes#138, closes#111
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add version listing and project history APIs
Add GET /api/plan/versions to list all versions of the current plan with
timestamps, and GET /api/plan/history to list all plans in the current
project. Both endpoints available in shared server and pi extension.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add version history section and API endpoints to CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: rename misleading "diff" terminology to "annotations" in plan review context
The plan review flow used "diff" naming (exportDiff, diffOutput, .diff.md,
"Raw Diff", "Download .diff") for what is actually user annotations/feedback
on a plan. This was confusing since the code review flow legitimately uses
"diff" for actual git diffs. Renames all plan-review-context "diff" references
to "annotations" across code, UI labels, file extensions, and docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update stale exportDiff references in ANNOTATE.md
Missed during the diff→annotations rename. Updates two references
to exportDiff() → exportAnnotations() in the annotate flow docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: rename remaining "Download Diff" label and tab type in App.tsx
The quick-save dropdown button still showed "Download Diff" and the
initialExportTab state type still used 'diff' instead of 'annotations'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add blog post about local diff review for coding agents
Covers the /plannotator-review command flow: slash command triggers
git diff capture, browser-based diff viewer with file tree and
line-level annotations, structured feedback sent back to the agent.
Technical details verified against server/index.ts, git.ts,
review.ts, App.tsx, DiffViewer.tsx, and FileTree.tsx.
https://claude.ai/code/session_01DxFhdSxDAcAQy6MYL49K4H
* Add blog post introducing Plannotator's Pi coding agent integration
Covers the full extension architecture: three-phase state machine
(idle/planning/executing), bash safety gating, file-based plan mode
with PLAN.md, browser-based review UI, progress tracking via [DONE:n]
markers, Node.js server constraint, and comparison table across all
three supported agents (Claude Code, OpenCode, Pi).
The workspace:* protocol is unresolvable outside the monorepo, causing
BunInstallFailedError for npm consumers. Since the build bundles
@plannotator/server into dist/index.js rather than externalizing it,
the dependency is build-time only and belongs in devDependencies.
* 🐛 fix(opencode): add missing @plannotator/server dependency
The opencode build failed because @plannotator/server was not declared
in apps/opencode-plugin/package.json, causing bun bundler to fail
resolving workspace packages.
* ✨ feat(ui): add auto-close delay with countdown on completion overlay
- Extend auto-close setting from boolean to delay options (off/immediate/3s/5s)
- Extract shared CompletionOverlay component from both editor and review apps
- Show countdown timer on completion screen when delay is set
- Add checkbox on completion overlay to enable auto-close (defaults to 3s)
- Replace toggle with select dropdown in Settings
* ♻️ refactor(ui): extract useAutoClose hook and clean up completion overlay
- Extract countdown logic into useAutoClose hook with phase state machine
- Extract SVG icons into named components (CheckIcon, ChatBubbleIcon)
- Add AUTO_CLOSE_OPTIONS constant for dropdown single source of truth
- Remove dead getAutoClose/setAutoClose boolean wrappers
* 🐛 fix(ui): handle window.close() failure with fallback message
Browsers block window.close() on tabs not opened by script. Add tryClose()
helper that detects failure via window.closed check, and show a fallback
message guiding the user to close manually.
* feat: use BROWSER env var as fallback for opening UI
Closes#119
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add missing reference docs (were gitignored by reference/ pattern)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>