* 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>
* 🐛 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>
* 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: named image references and annotate command (#67, #109)
Add human-readable names to image attachments throughout the annotation
pipeline, and add a new `plannotator annotate <file.md>` command for
annotating arbitrary markdown files.
Image names: ImageAttachment type replaces plain string paths, upload
endpoints return originalName, editable name inputs under thumbnails,
[name] path format in exported feedback, backward-compatible sharing.
Annotate command: new server module reusing plan editor HTML with
mode:"annotate", CLI subcommand, slash commands for Claude Code and
OpenCode, annotate mode UI (hides Approve, shows Send Annotations).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move image name input to ImageAnnotator screen
The name input now appears on the full-screen annotator modal that opens
immediately when uploading/pasting an image, pre-populated from the
filename. Removes the disruptive inline name editing from thumbnails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: stale closure in paste handler, update CLAUDE.md for new features
Fix race condition where globalAttachments was captured as empty array
in the paste event listener (missing dependency). Also update CLAUDE.md
to document ImageAttachment type, annotate server/flow, updated sharing
format with image support, and new slash commands.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add PLANNOTATOR_SHARE_URL env var so users can point share links at
their own self-hosted portal instance instead of share.plannotator.ai.
Threads the base URL through the same path as sharingEnabled: env var →
server options → API response → editor state → useSharing hook →
generateShareUrl(). Includes self-hosting guide and documentation.
Closes#12
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add deployment-level configuration to disable URL sharing for
security/privacy concerns.
Configuration methods:
- Claude Code: PLANNOTATOR_SHARE=disabled env var
- OpenCode: "share": "disabled" in opencode.json (primary), env var fallback
When disabled:
- Quick Share button hidden in annotation panel
- Share tab hidden in export modal (shows only Raw Diff)
Closes#69
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add permission mode utility and setup dialog
Add cookie-based storage for permission mode preference with first-time
setup dialog for Claude Code users. Supports three modes:
- Auto-accept Edits (default)
- Bypass Permissions (equivalent to --dangerously-skip-permissions)
- Manual Approval
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: integrate permission mode in editor and settings
- Show first-time setup dialog for Claude Code users
- Add permission mode dropdown to Settings (Claude Code only)
- Pass selected mode in approve request
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: output updatedPermissions in hook response
Use Claude Code 2.1.7+ hook protocol to preserve permission mode after
plan approval. Extracts permission_mode from hook input, allows client
to override via request, and outputs setMode in updatedPermissions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Remove early exit when no uncommitted changes - user can switch to
"Last commit" or other diff types via dropdown.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
Complete code review system for reviewing git diffs with annotations.
### Features
- Interactive diff viewer with split/unified views
- Line-level annotation system
- Diff type selector (uncommitted, last commit, vs main branch)
- Dynamic default branch detection
- Empty state handling
- Simplified UX with streamlined feedback flow
Closes#51Closes#56
* Refactor: shared server package with PLANNOTATOR_REMOTE env var
- Create packages/server/ with shared server implementation
- Add PLANNOTATOR_REMOTE=1 env var for devcontainer/SSH mode
- Deprecate SSH_CONNECTION detection (still works with warning)
- Both Claude Code and OpenCode now use identical server logic
- OpenCode gains Obsidian/Bear integrations and remote detection
- Update documentation with environment variables section
Fixes#27🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Remove PLANNOTATOR_ORIGIN env var, fix onReady callback timing
- Remove PLANNOTATOR_ORIGIN hack from Claude Code hook (hardcode "claude-code")
- Fix onReady callback to pass port directly (was referencing undefined server)
- Create tests/manual/test-server.ts for testing either origin
- Update test-hook-2.sh to use new test server with opencode origin
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add devcontainer test setup, reduce logging verbosity
- Add tests/devcontainer/ with devcontainer.json for testing remote mode
- Add tests/opencode-local/ for local OpenCode testing
- Update package.json build script for proper bundling
- Remove verbose multi-line error messages, keep useful errors
- Clean up unused deprecationWarned variable
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add devcontainer support and documentation
- Add PLANNOTATOR_REMOTE env var for container/remote detection
- Remove console logging (silent operation)
- Add devcontainer.md with full setup instructions
- Add devcontainer section to OpenCode plugin README
- Add port-only test setup to reproduce common misconfiguration
- Update test devcontainers to forward port 4096 for opencode web
- Bump version to 0.4.1
Fixes#27🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update CLAUDE.md: legacy SSH detection, not deprecated
Removed incorrect mention of deprecation warning - SSH_TTY/SSH_CONNECTION
detection is silent by design.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Fix OpenCode agent switching after plan approval
After approving a plan, the conversation context stayed in "plan" mode
even though the TUI showed "build". This caused edits to fail because
the plan agent has edit permissions denied.
The fix uses session.prompt() to inject a message with agent: "build",
which triggers a new agentic loop with proper build agent permissions.
Fixes#29🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add global (plan-level) and per-annotation image attachments
- Image upload via drag-drop, file picker, or clipboard paste (Cmd+V)
- ImageAnnotator component for drawing on images before saving
- Freehand pen, arrow, and circle tools
- Adjustable stroke size and color presets
- Edge-to-edge circle drawing
- Server endpoints for image upload (/api/upload) and serving (/api/image)
- Images stored as paths in /tmp/plannotator/, included in export for Claude
- Preserve image paths in URL sharing for round-trip scenarios
- Uses perfect-freehand (~2KB) for smooth pen strokes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Server returns origin field in /api/plan response
- UI displays badge next to version in header
- Claude Code: orange badge
- OpenCode: neutral zinc/gray badge
- Supports PLANNOTATOR_ORIGIN env var for testing
Closes#22🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add project.ts utility for detecting git repo name
- Fallback to directory name if not in a git repo
- Include project name as second tag after "plannotator"
- Add 21 unit tests for project detection
- Increase tag limit from 6 to 7
Closes#24🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add bear.ts utility for settings storage
- Add Bear toggle to Settings UI (simple on/off)
- Add saveToBear() using x-callback-url protocol
- Reuse extractTags() for inline #hashtags
- Change base tag from "plan" to "plannotator"
- Add test-hook-2.sh with alternate plan for testing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add Obsidian integration for auto-saving approved plans
- Auto-detect Obsidian vaults from system config
- Settings UI with vault dropdown and folder config
- Save plans with YAML frontmatter (created, source, tags)
- Extract tags heuristically from H1 title and code fence languages
- Human-readable filenames: "Title - Jan 2, 2026 2-30pm.md"
- Cross-platform support (macOS, Windows, Linux)
- Add local test script for hook simulation
* Add backlinks and improve filename generation
- Add [[Plannotator Plans]] backlink to saved notes for graph connectivity
- Extract title from H1 for human-readable filenames
- Format: "Title - Jan 2, 2026 2-30pm.md"
- Add bulk test script for ~/.claude/plans
- Add fix script to add backlinks to existing vault files
* Add Obsidian integration docs to README, landing page, and plugins
- Root README: Quick note about Obsidian support after video table
- apps/hook README: Detailed setup and example file format
- apps/opencode-plugin README: Feature list and setup instructions
- Landing page: New "Save to Obsidian" feature card
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Add Obsidian Integration section to README
* Screenshot Obsidian settings
Added an implementation plan section for user authentication.
* Update README with Obsidian auto-save feature
Added information about auto-saving approved plans to Obsidian.
* Update README with Obsidian integration link
Added link to details about Obsidian integration.
* Update README to include Obsidian auto-saving feature
Clarified the new feature for saving to Obsidian with a link.
---------
* Add SSH remote session support with fixed port
- Auto-detect SSH sessions via SSH_TTY/SSH_CONNECTION env vars
- Use fixed port 19432 over SSH for predictable port forwarding
- Add PLANNOTATOR_PORT env var for custom port configuration
- Add retry loop (5 attempts) for port conflicts
- Print SSH config hint when running remotely
- Skip browser auto-open in SSH sessions
* integration test for ssh
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
The server was reading dist/index.html from filesystem at runtime,
which fails in compiled standalone binaries. Changed to use Bun's
import attributes to embed the HTML at compile time.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>