* chore: pin GitHub Actions to SHAs and add Renovate config
Pin all actions in deploy.yml to full commit SHAs to prevent tag
force-push supply chain attacks. Upgrade actions/checkout from v4
to v6.0.2 to match release.yml.
Add renovate.json scoped to GitHub Actions only — weekly grouped
PRs with automerge, zero noise from npm dependencies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: move renovate.json to .github/renovate.json
Reduces root directory clutter. Renovate supports this path natively.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
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>
* 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>
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
## 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
The hook's server/index.ts imports from @plannotator/server, but the
package wasn't declared in dependencies. Bun only creates workspace
symlinks for declared dependencies, causing the build to fail in CI.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Bun supports cross-compilation, so we build all 4 targets
(darwin-arm64, darwin-x64, linux-x64, win32-x64) from a single
Ubuntu runner. This avoids the retired macos-13 runner issue.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
npm distribution:
- Add npm/ directory with main package and platform-specific packages
- Add JS wrapper for cross-platform binary resolution
- Add GitHub Actions workflow for OIDC-based npm publishing
- Configure for darwin-arm64, darwin-x64, linux-x64, win32-x64
Plugin changes:
- Simplify apps/hook to config-only (calls `plannotator` from PATH)
- Add README with installation instructions
UI updates:
- Add theme toggle to Landing nav
- Update footer with author links and copyright
- Update copy: "How it works" step 3, Solution section
- Flip hero image horizontally
- Fix tater sprite z-index (behind dialogs)
- Change tater breakpoint from lg to md (show on tablets)
- Remove icon from editor header
Package metadata:
- Update root and npm package.json with full metadata
- Set version to 0.1.0
- Set license to BSL-1.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>