mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
feat/opencode2-native-commands
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
be2d06a7c2 |
Make HTML annotations render HTML by default
* feat(annotate): render html files by default * fix(annotate): support raw html assets and sharing * fix(annotate): address html first review followups * fix(editor): avoid raw html sidebar init crash * fix(annotate): support portable html shares * fix(annotate): harden html share support * fix(share): clear attachments when loading shared payloads * fix(share): warn on remote share link failures * perf(annotate): lazy-build html share payloads * test(annotate): guard lazy html share generation * test(annotate): drop flaky html share server test |
||
|
|
ea758f9978 |
Add configurable paste service URL for self-hosting (#582)
* Wire PLANNOTATOR_PASTE_URL through opencode/pi servers and Landing demo link OpenCode plugin only read PLANNOTATOR_SHARE_URL; add a getPasteApiUrl helper and thread it into plan/annotate/archive server starts. Pi extension's serverReview gains the same shareBaseUrl/pasteApiUrl env-var pair already used by serverPlan/serverAnnotate. Landing.tsx now accepts a shareBaseUrl prop for self-hosters' demo link. Paste-service CORS defaults grow a comment clarifying that self-hosters must override ALLOWED_ORIGINS. * Embed custom paste origin in short URL fragment When PLANNOTATOR_PASTE_URL is set to a non-default paste service, the generated short link now includes a base64url-encoded paste param in the fragment (#key=...&paste=...). The share portal and importFromShareUrl extract it on load so they can fetch from the right paste backend without needing a server — fixing broken short links for self-hosters who use a custom paste service but keep the hosted share portal. Backward compatible: links without a paste param continue to use the default or server-provided paste API URL as before. For provenance purposes, this commit was AI assisted. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2739660fa6 |
feat: pinpoint mode for point-and-click annotations (#232)
* 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> |
||
|
|
ce8946b065 |
fix(paste-service): allow CORS from any localhost port
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> |
||
|
|
b1f91936d6 |
Deploy paste service with E2E encryption (#203)
* 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> |
||
|
|
c470dcebf1 |
Add short URL sharing via paste service to fix URL truncation (#188)
* 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> |