2026-03-12 01:29:32 -07:00
# Plannotator
A plan review UI for Claude Code that intercepts `ExitPlanMode` via hooks, letting users approve or request changes with annotated feedback. Also provides code review for git diffs and annotation of arbitrary markdown files.
## Project Structure
```
plannotator/
├── apps/
│ ├── hook/ # Claude Code plugin
│ │ ├── .claude-plugin/plugin.json
│ │ ├── commands/ # Slash commands (plannotator-review.md, plannotator-annotate.md)
│ │ ├── hooks/hooks.json # PermissionRequest hook config
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
│ │ ├── server/index.ts # Entry point (plan + review + annotate + archive subcommands)
2026-03-12 01:29:32 -07:00
│ │ └── dist/ # Built single-file apps (index.html, review.html)
│ ├── opencode-plugin/ # OpenCode plugin
│ │ ├── commands/ # Slash commands (plannotator-review.md, plannotator-annotate.md)
│ │ ├── index.ts # Plugin entry with submit_plan tool + review/annotate event handlers
│ │ ├── plannotator.html # Built plan review app
│ │ └── review-editor.html # Built code review app
│ ├── marketing/ # Marketing site, docs, and blog (plannotator.ai)
│ │ └── astro.config.mjs # Astro 5 static site with content collections
│ ├── paste-service/ # Paste service for short URL sharing
│ │ ├── core/ # Platform-agnostic logic (handler, storage interface, cors)
│ │ ├── stores/ # Storage backends (fs, kv, s3)
│ │ └── targets/ # Deployment entries (bun.ts, cloudflare.ts)
│ ├── review/ # Standalone review server (for development)
│ │ ├── index.html
│ │ ├── index.tsx
│ │ └── vite.config.ts
│ └── vscode-extension/ # VS Code extension — opens plans in editor tabs
│ ├── bin/ # Router scripts (open-in-vscode, xdg-open)
│ ├── src/ # extension.ts, cookie-proxy.ts, ipc-server.ts, panel-manager.ts, editor-annotations.ts, vscode-theme.ts
│ └── package.json # Extension manifest (publisher: backnotprop)
├── packages/
│ ├── server/ # Shared server implementation
│ │ ├── index.ts # startPlannotatorServer(), handleServerReady()
│ │ ├── review.ts # startReviewServer(), handleReviewServerReady()
│ │ ├── annotate.ts # startAnnotateServer(), handleAnnotateServerReady()
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
│ │ ├── storage.ts # Re-exports from @plannotator/shared/storage
2026-03-12 01:29:32 -07:00
│ │ ├── share-url.ts # Server-side share URL generation for remote sessions
│ │ ├── remote.ts # isRemoteSession(), getServerPort()
│ │ ├── browser.ts # openBrowser()
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
│ │ ├── draft.ts # Re-exports from @plannotator/shared/draft
2026-03-12 01:29:32 -07:00
│ │ ├── integrations.ts # Obsidian, Bear integrations
│ │ ├── ide.ts # VS Code diff integration (openEditorDiff)
│ │ ├── editor-annotations.ts # VS Code editor annotation endpoints
│ │ └── project.ts # Project name detection for tags
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
│ ├── ui/ # Shared React components + theme
│ │ ├── theme.css # Single source of truth for color tokens + Tailwind bridge
2026-03-12 01:29:32 -07:00
│ │ ├── components/ # Viewer, Toolbar, Settings, etc.
│ │ │ ├── plan-diff/ # PlanDiffBadge, PlanDiffViewer, clean/raw diff views
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
│ │ │ └── sidebar/ # SidebarContainer, SidebarTabs, VersionBrowser, ArchiveBrowser
2026-03-12 01:29:32 -07:00
│ │ ├── utils/ # parser.ts, sharing.ts, storage.ts, planSave.ts, agentSwitch.ts, planDiffEngine.ts
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
│ │ ├── hooks/ # useAnnotationHighlighter.ts, useSharing.ts, usePlanDiff.ts, useSidebar.ts, useLinkedDoc.ts, useAnnotationDraft.ts, useCodeAnnotationDraft.ts, useArchive.ts
2026-03-12 01:29:32 -07:00
│ │ └── types.ts
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
│ ├── ai/ # Provider-agnostic AI backbone (providers, sessions, endpoints)
│ ├── shared/ # Shared types, utilities, and cross-runtime logic
│ │ ├── storage.ts # Plan saving, version history, archive listing (node:fs only)
│ │ ├── draft.ts # Annotation draft persistence (node:fs only)
│ │ └── project.ts # Pure string helpers (sanitizeTag, extractRepoName, extractDirName)
2026-03-12 01:29:32 -07:00
│ ├── editor/ # Plan review App.tsx
│ └── review-editor/ # Code review UI
│ ├── App.tsx # Main review app
2026-04-01 14:58:45 -07:00
│ ├── components/ # DiffViewer, FileTree, ReviewSidebar
│ ├── dock/ # Dockview center panel infrastructure
2026-03-12 01:29:32 -07:00
│ ├── demoData.ts # Demo diff for standalone mode
│ └── index.css # Review-specific styles
├── .claude-plugin/marketplace.json # For marketplace install
└── legacy/ # Old pre-monorepo code (reference only)
```
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
## Server Runtimes
There are two separate server implementations with the same API surface:
- **Bun server** (`packages/server/` ) — used by both Claude Code (`apps/hook/` ) and OpenCode (`apps/opencode-plugin/` ). These plugins import directly from `@plannotator/server` .
- **Pi server** (`apps/pi-extension/server/` ) — a standalone Node.js server for the Pi extension. It mirrors the Bun server's API but uses `node:http` primitives instead of Bun's `Request` /`Response` APIs.
When adding or modifying server endpoints, both implementations must be updated. Runtime-agnostic logic (store, validation, types) lives in `packages/shared/` and is imported by both.
2026-03-12 01:29:32 -07:00
## Installation
**Via plugin marketplace ** (when repo is public):
```
/plugin marketplace add backnotprop/plannotator
```
**Local testing: **
``` bash
claude --plugin-dir ./apps/hook
```
## Environment Variables
| Variable | Description |
|----------|-------------|
2026-04-05 05:17:16 +09:00
| `PLANNOTATOR_REMOTE` | Set to `1` / `true` for remote mode, `0` / `false` for local mode, or leave unset for SSH auto-detection. Uses a fixed port in remote mode; browser-opening behavior depends on the environment. |
2026-03-12 01:29:32 -07:00
| `PLANNOTATOR_PORT` | Fixed port to use. Default: random locally, `19432` for remote sessions. |
| `PLANNOTATOR_BROWSER` | Custom browser to open plans in. macOS: app name or path. Linux/Windows: executable path. |
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
| `PLANNOTATOR_SHARE` | Set to `disabled` to turn off URL sharing entirely. Default: enabled. |
2026-03-12 01:29:32 -07:00
| `PLANNOTATOR_SHARE_URL` | Custom base URL for share links (self-hosted portal). Default: `https://share.plannotator.ai` . |
| `PLANNOTATOR_PASTE_URL` | Base URL of the paste service API for short URL sharing. Default: `https://plannotator-paste.plannotator.workers.dev` . |
2026-04-05 05:17:16 +09:00
**Legacy: ** `SSH_TTY` and `SSH_CONNECTION` are still detected when `PLANNOTATOR_REMOTE` is unset. Set `PLANNOTATOR_REMOTE=1` / `true` to force remote mode or `0` / `false` to force local mode.
2026-03-12 01:29:32 -07:00
**Devcontainer/SSH usage: **
``` bash
export PLANNOTATOR_REMOTE = 1
export PLANNOTATOR_PORT = 9999
```
## Plan Review Flow
```
Claude calls ExitPlanMode
↓
PermissionRequest hook fires
↓
Bun server reads plan from stdin JSON (tool_input.plan)
↓
Server starts on random port, opens browser
↓
User reviews plan, optionally adds annotations
↓
Approve → stdout: {"hookSpecificOutput":{"decision":{"behavior":"allow"}}}
Deny → stdout: {"hookSpecificOutput":{"decision":{"behavior":"deny","message":"..."}}}
```
## Code Review Flow
```
User runs /plannotator-review command
↓
Claude Code: plannotator review subcommand runs
OpenCode: event handler intercepts command
↓
git diff captures unstaged changes
↓
Review server starts, opens browser with diff viewer
↓
User annotates code, provides feedback
↓
Send Feedback → feedback sent to agent session
Approve → "LGTM" sent to agent session
```
## Annotate Flow
```
User runs /plannotator-annotate <file.md> command
↓
Claude Code: plannotator annotate subcommand runs
OpenCode: event handler intercepts command
↓
Markdown file read from disk
↓
Annotate server starts (reuses plan editor HTML with mode:"annotate")
↓
User annotates markdown, provides feedback
↓
Send Annotations → feedback sent to agent session
```
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
## Archive Flow
```
User runs plannotator archive (CLI) or /plannotator-archive (Pi)
↓
Server starts in mode:"archive", reads ~/.plannotator/plans/
↓
Browser opens read-only archive viewer (sharing disabled)
↓
User browses saved plan decisions with approved/denied badges
↓
Done → POST /api/done closes the browser
```
During normal plan review, an Archive sidebar tab provides the same browsing via linked doc overlay without leaving the current session.
2026-03-12 01:29:32 -07:00
## Server API
### Plan Server (`packages/server/index.ts`)
| Endpoint | Method | Purpose |
| --------------------- | ------ | ------------------------------------------ |
feat: plan archive browser (#369)
* feat: plan archive browser with in-session sidebar tab (#362)
Add `plannotator archive` subcommand and archive sidebar tab for browsing
saved plan decisions from ~/.plannotator/plans/. Plans show approved/denied
badges and timestamps. In-session mode uses the linked doc overlay pattern
so users can reference old plans while reviewing a current one.
- New archive server (packages/server/archive.ts) following annotate pattern
- New ArchiveBrowser sidebar component, reusable in both contexts
- Archive listing/parsing functions in storage.ts (reads decision snapshots)
- Archive endpoints on plan server for in-session use (/api/archive/plans)
- Remove dead "Other Plans" UI, projectPlans state, /api/plan/history endpoint
- Fix resize handle touch area covering scrollbars in sidebar/main content
- Fix sidebar tab bar overflow when narrow
For provenance purposes, this commit was AI assisted.
* fix: code quality sweep for plan archive
- Remove `as any` cast: add "archive" to SessionInfo.mode union
- Replace inline import() type with proper import for ArchivedPlan
- Replace any[] with ArchivedPlan[] in fetch response types
- Fix infinite re-fetch when archive is empty (use hasFetched ref)
- Cache archive plan list in plan server (avoid re-scanning filesystem)
- Document ResizeHandle side prop behavior
- Remove redundant comment on Viewer archiveInfo prop
For provenance purposes, this commit was AI assisted.
* chore: remove dead marketing components
Step.astro and Landing.astro are unused — landing page inlines
step markup and pages use Base.astro directly.
For provenance purposes, this commit was AI assisted.
* fix: address code review findings for plan archive
- Path traversal: use resolve() + trailing separator guard (matches reference-handlers.ts)
- Thread customPath into in-session archive endpoints via query param
- Sort same-day archive entries by mtime instead of title
- Clear selectedArchiveFile on linked doc back to prevent badge leak
- Hide archive tab in annotate mode (server doesn't serve those endpoints)
- Add targetTab param to useLinkedDoc.open() to preserve calling sidebar tab
- Replace mutable render variable with index-based date grouping
For provenance purposes, this commit was AI assisted.
* refactor: collapse standalone archive server into plan server
Delete packages/server/archive.ts (187 lines) — nearly all duplicated
from the plan server. Add mode:"archive" option to startPlannotatorServer
instead. Fixes two bugs from code review:
- handleArchiveCopy now splits on "# Plan Feedback" marker instead of
bare "---", preventing truncation at horizontal rules in plan content
- customPath support works in standalone archive mode (was only working
in-session because the standalone server never received it)
For provenance purposes, this commit was AI assisted.
* refactor: extract useArchive hook from App.tsx
Move archive state (archiveMode, plans, selectedFile, isLoading) and
handlers (select, fetchPlans, done, copy) into a dedicated useArchive
hook. Reduces App.tsx by ~75 lines and makes the archive feature
self-contained.
For provenance purposes, this commit was AI assisted.
* feat: Pi archive parity + eliminate server duplication
Move runtime-agnostic storage, draft, and project functions from
packages/server/ to packages/shared/ — eliminating ~250 lines of
duplicated code in Pi's server.ts. Server package becomes thin
re-exports, preserving all existing import paths.
Add archive mode to Pi's plan review server (mode, routes, waitForDone)
and register /plannotator-archive command in the Pi extension. Consolidate
ArchivedPlan type to single definition in shared/storage.ts.
Simplify archive copy to include full content with feedback.
For provenance purposes, this commit was AI assisted.
* fix: drop -core suffix from Pi shared copies
The -core suffix broke cross-file imports — storage.ts imports from
./project which didn't resolve to project-core.ts. Using the original
filenames (no collision) lets relative imports work naturally.
For provenance purposes, this commit was AI assisted.
* fix: archive custom path bugs, disable sharing, update docs
- Normalize planDir via resolve() in getPlanDir() to handle relative
paths and trailing slashes in the path traversal guard
- Re-fetch archive plans client-side with cookie-backed customPath
so standalone archive respects the user's configured save location
- Disable sharing in archive mode (read-only viewer, no need)
- Remove dead /api/plan/history endpoint and listProjectPlans import
from Pi extension
- Remove dead /api/plan/history mock from dev-mock-api
- Update CLAUDE.md and AGENTS.md: add archive flow, archive API
endpoints, shared package structure, correct storage location,
sidebar tab count, remove stale /api/plan/history references
- Update hook server docstring from four to five modes
For provenance purposes, this commit was AI assisted.
* fix: empty archive shows demo content, stale viewer after customPath fetch
- Clear demo markdown when archive opens with no plans (plan: "" was falsy,
so setMarkdown was never called)
- Remove redundant fetchPlans() from archive init — server already sends
archivePlans in initial response
- After fetchPlans() resolves with customPath results, auto-select and load
the first plan into the viewer
- Remove dead listProjectPlans re-export from server barrel
For provenance purposes, this commit was AI assisted.
* refactor: gitignore Pi shared copies, add @generated headers
Pi's copied .ts files (storage, draft, project, feedback-templates,
review-core) are build artifacts generated from packages/shared/. They
looked like editable source files, leading to confusion about which file
to edit. Now gitignored like the HTML copies, with @generated headers
prepended by the build script.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in CI before tests
The Pi .ts copies are now gitignored build artifacts. CI needs to
generate them before running tests since server.test.ts transitively
imports them via server.ts.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in release pipeline test job
Same fix as test.yml — the Pi .ts copies are gitignored, so the test
job in the release pipeline also needs to generate them before bun test.
For provenance purposes, this commit was AI assisted.
* fix: use block scalar in CI workflow to avoid YAML parse error
The inline `run:` had a colon in the printf string that YAML
interpreted as a mapping key. Switch to `run: |` block scalar.
For provenance purposes, this commit was AI assisted.
2026-03-23 11:31:38 -07:00
| `/api/plan` | GET | Returns `{ plan, origin, previousPlan, versionInfo }` (plan mode) or `{ plan, origin, mode: "archive", archivePlans }` (archive mode) |
2026-03-12 01:29:32 -07:00
| `/api/plan/version` | GET | Fetch specific version (`?v=N` ) |
| `/api/plan/versions` | GET | List all versions of current plan |
feat: plan archive browser (#369)
* feat: plan archive browser with in-session sidebar tab (#362)
Add `plannotator archive` subcommand and archive sidebar tab for browsing
saved plan decisions from ~/.plannotator/plans/. Plans show approved/denied
badges and timestamps. In-session mode uses the linked doc overlay pattern
so users can reference old plans while reviewing a current one.
- New archive server (packages/server/archive.ts) following annotate pattern
- New ArchiveBrowser sidebar component, reusable in both contexts
- Archive listing/parsing functions in storage.ts (reads decision snapshots)
- Archive endpoints on plan server for in-session use (/api/archive/plans)
- Remove dead "Other Plans" UI, projectPlans state, /api/plan/history endpoint
- Fix resize handle touch area covering scrollbars in sidebar/main content
- Fix sidebar tab bar overflow when narrow
For provenance purposes, this commit was AI assisted.
* fix: code quality sweep for plan archive
- Remove `as any` cast: add "archive" to SessionInfo.mode union
- Replace inline import() type with proper import for ArchivedPlan
- Replace any[] with ArchivedPlan[] in fetch response types
- Fix infinite re-fetch when archive is empty (use hasFetched ref)
- Cache archive plan list in plan server (avoid re-scanning filesystem)
- Document ResizeHandle side prop behavior
- Remove redundant comment on Viewer archiveInfo prop
For provenance purposes, this commit was AI assisted.
* chore: remove dead marketing components
Step.astro and Landing.astro are unused — landing page inlines
step markup and pages use Base.astro directly.
For provenance purposes, this commit was AI assisted.
* fix: address code review findings for plan archive
- Path traversal: use resolve() + trailing separator guard (matches reference-handlers.ts)
- Thread customPath into in-session archive endpoints via query param
- Sort same-day archive entries by mtime instead of title
- Clear selectedArchiveFile on linked doc back to prevent badge leak
- Hide archive tab in annotate mode (server doesn't serve those endpoints)
- Add targetTab param to useLinkedDoc.open() to preserve calling sidebar tab
- Replace mutable render variable with index-based date grouping
For provenance purposes, this commit was AI assisted.
* refactor: collapse standalone archive server into plan server
Delete packages/server/archive.ts (187 lines) — nearly all duplicated
from the plan server. Add mode:"archive" option to startPlannotatorServer
instead. Fixes two bugs from code review:
- handleArchiveCopy now splits on "# Plan Feedback" marker instead of
bare "---", preventing truncation at horizontal rules in plan content
- customPath support works in standalone archive mode (was only working
in-session because the standalone server never received it)
For provenance purposes, this commit was AI assisted.
* refactor: extract useArchive hook from App.tsx
Move archive state (archiveMode, plans, selectedFile, isLoading) and
handlers (select, fetchPlans, done, copy) into a dedicated useArchive
hook. Reduces App.tsx by ~75 lines and makes the archive feature
self-contained.
For provenance purposes, this commit was AI assisted.
* feat: Pi archive parity + eliminate server duplication
Move runtime-agnostic storage, draft, and project functions from
packages/server/ to packages/shared/ — eliminating ~250 lines of
duplicated code in Pi's server.ts. Server package becomes thin
re-exports, preserving all existing import paths.
Add archive mode to Pi's plan review server (mode, routes, waitForDone)
and register /plannotator-archive command in the Pi extension. Consolidate
ArchivedPlan type to single definition in shared/storage.ts.
Simplify archive copy to include full content with feedback.
For provenance purposes, this commit was AI assisted.
* fix: drop -core suffix from Pi shared copies
The -core suffix broke cross-file imports — storage.ts imports from
./project which didn't resolve to project-core.ts. Using the original
filenames (no collision) lets relative imports work naturally.
For provenance purposes, this commit was AI assisted.
* fix: archive custom path bugs, disable sharing, update docs
- Normalize planDir via resolve() in getPlanDir() to handle relative
paths and trailing slashes in the path traversal guard
- Re-fetch archive plans client-side with cookie-backed customPath
so standalone archive respects the user's configured save location
- Disable sharing in archive mode (read-only viewer, no need)
- Remove dead /api/plan/history endpoint and listProjectPlans import
from Pi extension
- Remove dead /api/plan/history mock from dev-mock-api
- Update CLAUDE.md and AGENTS.md: add archive flow, archive API
endpoints, shared package structure, correct storage location,
sidebar tab count, remove stale /api/plan/history references
- Update hook server docstring from four to five modes
For provenance purposes, this commit was AI assisted.
* fix: empty archive shows demo content, stale viewer after customPath fetch
- Clear demo markdown when archive opens with no plans (plan: "" was falsy,
so setMarkdown was never called)
- Remove redundant fetchPlans() from archive init — server already sends
archivePlans in initial response
- After fetchPlans() resolves with customPath results, auto-select and load
the first plan into the viewer
- Remove dead listProjectPlans re-export from server barrel
For provenance purposes, this commit was AI assisted.
* refactor: gitignore Pi shared copies, add @generated headers
Pi's copied .ts files (storage, draft, project, feedback-templates,
review-core) are build artifacts generated from packages/shared/. They
looked like editable source files, leading to confusion about which file
to edit. Now gitignored like the HTML copies, with @generated headers
prepended by the build script.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in CI before tests
The Pi .ts copies are now gitignored build artifacts. CI needs to
generate them before running tests since server.test.ts transitively
imports them via server.ts.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in release pipeline test job
Same fix as test.yml — the Pi .ts copies are gitignored, so the test
job in the release pipeline also needs to generate them before bun test.
For provenance purposes, this commit was AI assisted.
* fix: use block scalar in CI workflow to avoid YAML parse error
The inline `run:` had a colon in the printf string that YAML
interpreted as a mapping key. Switch to `run: |` block scalar.
For provenance purposes, this commit was AI assisted.
2026-03-23 11:31:38 -07:00
| `/api/archive/plans` | GET | List archived plan decisions (`?customPath=` ) |
| `/api/archive/plan` | GET | Fetch archived plan content (`?filename=&customPath=` ) |
| `/api/done` | POST | Close archive browser (archive mode only) |
2026-03-12 01:29:32 -07:00
| `/api/approve` | POST | Approve plan (body: planSave, agentSwitch, obsidian, bear, feedback) |
| `/api/deny` | POST | Deny plan (body: feedback, planSave) |
| `/api/image` | GET | Serve image by path query param |
| `/api/upload` | POST | Upload image, returns `{ path, originalName }` |
| `/api/obsidian/vaults` | GET | Detect available Obsidian vaults |
| `/api/reference/obsidian/files` | GET | List vault markdown files as nested tree (`?vaultPath=<path>` ) |
| `/api/reference/obsidian/doc` | GET | Read a vault markdown file (`?vaultPath=<path>&path=<file>` ) |
| `/api/plan/vscode-diff` | POST | Open diff in VS Code (body: baseVersion) |
| `/api/doc` | GET | Serve linked .md/.mdx file (`?path=<path>` ) |
| `/api/draft` | GET/POST/DELETE | Auto-save annotation drafts to survive server crashes |
| `/api/editor-annotations` | GET | List editor annotations (VS Code only) |
| `/api/editor-annotation` | POST/DELETE | Add or remove an editor annotation (VS Code only) |
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
| `/api/external-annotations/stream` | GET | SSE stream for real-time external annotations |
| `/api/external-annotations` | GET | Snapshot of external annotations (polling fallback, `?since=N` for version gating) |
| `/api/external-annotations` | POST | Add external annotations (single or batch `{ annotations: [...] }` ) |
| `/api/external-annotations` | PATCH | Update fields on a single annotation (`?id=` ) |
| `/api/external-annotations` | DELETE | Remove by `?id=` , `?source=` , or clear all |
2026-03-12 01:29:32 -07:00
### Review Server (`packages/server/review.ts`)
| Endpoint | Method | Purpose |
| --------------------- | ------ | ------------------------------------------ |
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
| `/api/diff` | GET | Returns `{ rawPatch, gitRef, origin, diffType, gitContext }` |
2026-03-12 01:29:32 -07:00
| `/api/file-content` | GET | Returns `{ oldContent, newContent }` for expandable diff context |
| `/api/git-add` | POST | Stage/unstage a file (body: `{ filePath, undo? }` ) |
| `/api/feedback` | POST | Submit review (body: feedback, annotations, agentSwitch) |
| `/api/image` | GET | Serve image by path query param |
| `/api/upload` | POST | Upload image, returns `{ path, originalName }` |
| `/api/draft` | GET/POST/DELETE | Auto-save annotation drafts to survive server crashes |
| `/api/editor-annotations` | GET | List editor annotations (VS Code only) |
| `/api/editor-annotation` | POST/DELETE | Add or remove an editor annotation (VS Code only) |
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
| `/api/ai/capabilities` | GET | Check if AI features are available |
| `/api/ai/session` | POST | Create or fork an AI session |
| `/api/ai/query` | POST | Send a message and stream the response (SSE) |
| `/api/ai/abort` | POST | Abort the current query |
| `/api/ai/permission` | POST | Respond to a permission request |
| `/api/ai/sessions` | GET | List active sessions |
| `/api/external-annotations/stream` | GET | SSE stream for real-time external annotations |
| `/api/external-annotations` | GET | Snapshot of external annotations (polling fallback, `?since=N` for version gating) |
| `/api/external-annotations` | POST | Add external annotations (single or batch `{ annotations: [...] }` ) |
| `/api/external-annotations` | PATCH | Update fields on a single annotation (`?id=` ) |
| `/api/external-annotations` | DELETE | Remove by `?id=` , `?source=` , or clear all |
feat: agentic review — background job runner with SSE streaming (#443)
* feat(server): add agent job manager with process lifecycle and SSE streaming
Introduces the backend for agentic review — a server-agnostic job runner
that spawns agent processes, monitors their lifecycle via proc.exited, and
broadcasts status updates over a dedicated SSE stream.
New files:
- packages/shared/agent-jobs.ts: runtime-agnostic types, SSE helpers, state machine
- packages/server/agent-jobs.ts: Bun HTTP adapter with spawn/kill/SSE/REST routes
Wired into the review server with late-bound serverUrl, killAll on stop
and process exit for defense-in-depth cleanup. Capability detection via
Bun.which() for Claude Code, Codex CLI, and shell.
For provenance purposes, this commit was AI assisted.
* feat(ui): add Review Agents tab with job management and live status
Adds the client-side UI for agentic review:
- useAgentJobs hook: SSE primary with polling fallback, capabilities fetch
- AgentsTab: shared component with provider dropdown, job cards, kill controls
- ReviewAgentsIcon: magnifying glass with cog icon
- ReviewPanel: new "Review Agents" tab conditional on detected capabilities
- App.tsx: initializes hook and passes props to ReviewPanel
Jobs show live elapsed time, status badges, annotation counts per source,
and expandable error details. Stub command (sleep 10-30s) for initial testing.
For provenance purposes, this commit was AI assisted.
* fix(server): narrow proc.stderr type to satisfy TS
Bun types proc.stderr as ReadableStream | number. Add typeof guard
to narrow before passing to Response constructor.
For provenance purposes, this commit was AI assisted.
* fix(server): prevent stdout deadlock, stderr pipe stall, and worktree cwd mismatch
- Change stdout from "pipe" to "ignore" — agents post findings via the
annotations API, not stdout. Unconsumed pipe buffer caused deadlock.
- Drain stderr continuously instead of reading after exit — prevents
pipe-full stall on chatty agents.
- Make agent getCwd worktree-aware using the same currentDiffType check
the AI endpoints use, so agents spawn in the correct checkout.
- Exclude 'agents' tab from PR context fetch guard in ReviewPanel.
For provenance purposes, this commit was AI assisted.
* feat(pi): mirror agent-job endpoints in Pi review server
Add apps/pi-extension/server/agent-jobs.ts — a Node.js port of the Bun
agent-jobs handler using child_process.spawn, res.write() SSE, and
execFileSync for capability detection.
Wire into serverReview.ts: route delegation, killAll in stop(),
process.on("exit") guard, worktree-aware getCwd, and serverUrl
late-binding. Add agent-jobs to vendor.sh for shared type vendoring.
For provenance purposes, this commit was AI assisted.
* fix(agents): harden agent jobs — listener leak, provider allowlist, Windows support, docs
- Use process.once instead of process.on to prevent exit listener accumulation
- Validate provider against known capabilities before spawning (rejects unknown providers)
- Use platform-aware which/where for CLI detection on Windows
- Broadcast jobs:cleared event from kill-all endpoint (was dead client code)
- Document all /api/agents/* endpoints in CLAUDE.md Review Server table
For provenance purposes, this commit was AI assisted.
* fix(agents): SSE ref reset, jobs:cleared divergence, exit listener cleanup
- Reset receivedSnapshotRef/fallbackRef on re-mount so polling fallback
activates correctly on subsequent tab switches
- Remove jobs:cleared broadcast from kill-all — killAll() already fires
individual job:completed events, so the UI shows status transitions
instead of going blank
- Store exit handler ref so stop() can removeListener, preventing
accumulation in long-lived hosts (OpenCode, Pi)
For provenance purposes, this commit was AI assisted.
2026-03-31 10:06:18 -07:00
| `/api/agents/capabilities` | GET | Check available agent providers (claude, codex, shell) |
| `/api/agents/jobs/stream` | GET | SSE stream for real-time agent job status updates |
| `/api/agents/jobs` | GET | Snapshot of agent jobs (polling fallback, `?since=N` for version gating) |
| `/api/agents/jobs` | POST | Launch an agent job (body: `{ provider, command, label }` ) |
| `/api/agents/jobs` | DELETE | Kill all running agent jobs |
| `/api/agents/jobs/:id` | DELETE | Kill a specific agent job |
2026-03-12 01:29:32 -07:00
### Annotate Server (`packages/server/annotate.ts`)
| Endpoint | Method | Purpose |
| --------------------- | ------ | ------------------------------------------ |
| `/api/plan` | GET | Returns `{ plan, origin, mode: "annotate", filePath }` |
| `/api/feedback` | POST | Submit annotations (body: feedback, annotations) |
| `/api/image` | GET | Serve image by path query param |
| `/api/upload` | POST | Upload image, returns `{ path, originalName }` |
| `/api/draft` | GET/POST/DELETE | Auto-save annotation drafts to survive server crashes |
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
| `/api/external-annotations/stream` | GET | SSE stream for real-time external annotations |
| `/api/external-annotations` | GET | Snapshot of external annotations (polling fallback, `?since=N` for version gating) |
| `/api/external-annotations` | POST | Add external annotations (single or batch `{ annotations: [...] }` ) |
| `/api/external-annotations` | PATCH | Update fields on a single annotation (`?id=` ) |
| `/api/external-annotations` | DELETE | Remove by `?id=` , `?source=` , or clear all |
2026-03-12 01:29:32 -07:00
All servers use random ports locally or fixed port (`19432` ) in remote mode.
### Paste Service (`apps/paste-service/`)
| Endpoint | Method | Purpose |
| --------------------- | ------ | ------------------------------------------ |
| `/api/paste` | POST | Store compressed plan data, returns `{ id }` |
| `/api/paste/:id` | GET | Retrieve stored compressed data |
Runs as a separate service on port `19433` (self-hosted) or as a Cloudflare Worker (hosted).
## Plan Version History
Every plan is automatically saved to `~/.plannotator/history/{project}/{slug}/` on arrival, before the user sees the UI. Versions are numbered sequentially (`001.md` , `002.md` , etc.). The slug is derived from the plan's first `# Heading` + today's date via `generateSlug()` , scoped by project name (git repo or cwd). Same heading on the same day = same slug = same plan being iterated on. Identical resubmissions are deduplicated (no new file if content matches the latest version).
feat: plan archive browser (#369)
* feat: plan archive browser with in-session sidebar tab (#362)
Add `plannotator archive` subcommand and archive sidebar tab for browsing
saved plan decisions from ~/.plannotator/plans/. Plans show approved/denied
badges and timestamps. In-session mode uses the linked doc overlay pattern
so users can reference old plans while reviewing a current one.
- New archive server (packages/server/archive.ts) following annotate pattern
- New ArchiveBrowser sidebar component, reusable in both contexts
- Archive listing/parsing functions in storage.ts (reads decision snapshots)
- Archive endpoints on plan server for in-session use (/api/archive/plans)
- Remove dead "Other Plans" UI, projectPlans state, /api/plan/history endpoint
- Fix resize handle touch area covering scrollbars in sidebar/main content
- Fix sidebar tab bar overflow when narrow
For provenance purposes, this commit was AI assisted.
* fix: code quality sweep for plan archive
- Remove `as any` cast: add "archive" to SessionInfo.mode union
- Replace inline import() type with proper import for ArchivedPlan
- Replace any[] with ArchivedPlan[] in fetch response types
- Fix infinite re-fetch when archive is empty (use hasFetched ref)
- Cache archive plan list in plan server (avoid re-scanning filesystem)
- Document ResizeHandle side prop behavior
- Remove redundant comment on Viewer archiveInfo prop
For provenance purposes, this commit was AI assisted.
* chore: remove dead marketing components
Step.astro and Landing.astro are unused — landing page inlines
step markup and pages use Base.astro directly.
For provenance purposes, this commit was AI assisted.
* fix: address code review findings for plan archive
- Path traversal: use resolve() + trailing separator guard (matches reference-handlers.ts)
- Thread customPath into in-session archive endpoints via query param
- Sort same-day archive entries by mtime instead of title
- Clear selectedArchiveFile on linked doc back to prevent badge leak
- Hide archive tab in annotate mode (server doesn't serve those endpoints)
- Add targetTab param to useLinkedDoc.open() to preserve calling sidebar tab
- Replace mutable render variable with index-based date grouping
For provenance purposes, this commit was AI assisted.
* refactor: collapse standalone archive server into plan server
Delete packages/server/archive.ts (187 lines) — nearly all duplicated
from the plan server. Add mode:"archive" option to startPlannotatorServer
instead. Fixes two bugs from code review:
- handleArchiveCopy now splits on "# Plan Feedback" marker instead of
bare "---", preventing truncation at horizontal rules in plan content
- customPath support works in standalone archive mode (was only working
in-session because the standalone server never received it)
For provenance purposes, this commit was AI assisted.
* refactor: extract useArchive hook from App.tsx
Move archive state (archiveMode, plans, selectedFile, isLoading) and
handlers (select, fetchPlans, done, copy) into a dedicated useArchive
hook. Reduces App.tsx by ~75 lines and makes the archive feature
self-contained.
For provenance purposes, this commit was AI assisted.
* feat: Pi archive parity + eliminate server duplication
Move runtime-agnostic storage, draft, and project functions from
packages/server/ to packages/shared/ — eliminating ~250 lines of
duplicated code in Pi's server.ts. Server package becomes thin
re-exports, preserving all existing import paths.
Add archive mode to Pi's plan review server (mode, routes, waitForDone)
and register /plannotator-archive command in the Pi extension. Consolidate
ArchivedPlan type to single definition in shared/storage.ts.
Simplify archive copy to include full content with feedback.
For provenance purposes, this commit was AI assisted.
* fix: drop -core suffix from Pi shared copies
The -core suffix broke cross-file imports — storage.ts imports from
./project which didn't resolve to project-core.ts. Using the original
filenames (no collision) lets relative imports work naturally.
For provenance purposes, this commit was AI assisted.
* fix: archive custom path bugs, disable sharing, update docs
- Normalize planDir via resolve() in getPlanDir() to handle relative
paths and trailing slashes in the path traversal guard
- Re-fetch archive plans client-side with cookie-backed customPath
so standalone archive respects the user's configured save location
- Disable sharing in archive mode (read-only viewer, no need)
- Remove dead /api/plan/history endpoint and listProjectPlans import
from Pi extension
- Remove dead /api/plan/history mock from dev-mock-api
- Update CLAUDE.md and AGENTS.md: add archive flow, archive API
endpoints, shared package structure, correct storage location,
sidebar tab count, remove stale /api/plan/history references
- Update hook server docstring from four to five modes
For provenance purposes, this commit was AI assisted.
* fix: empty archive shows demo content, stale viewer after customPath fetch
- Clear demo markdown when archive opens with no plans (plan: "" was falsy,
so setMarkdown was never called)
- Remove redundant fetchPlans() from archive init — server already sends
archivePlans in initial response
- After fetchPlans() resolves with customPath results, auto-select and load
the first plan into the viewer
- Remove dead listProjectPlans re-export from server barrel
For provenance purposes, this commit was AI assisted.
* refactor: gitignore Pi shared copies, add @generated headers
Pi's copied .ts files (storage, draft, project, feedback-templates,
review-core) are build artifacts generated from packages/shared/. They
looked like editable source files, leading to confusion about which file
to edit. Now gitignored like the HTML copies, with @generated headers
prepended by the build script.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in CI before tests
The Pi .ts copies are now gitignored build artifacts. CI needs to
generate them before running tests since server.test.ts transitively
imports them via server.ts.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in release pipeline test job
Same fix as test.yml — the Pi .ts copies are gitignored, so the test
job in the release pipeline also needs to generate them before bun test.
For provenance purposes, this commit was AI assisted.
* fix: use block scalar in CI workflow to avoid YAML parse error
The inline `run:` had a colon in the printf string that YAML
interpreted as a mapping key. Switch to `run: |` block scalar.
For provenance purposes, this commit was AI assisted.
2026-03-23 11:31:38 -07:00
This powers the version history API (`/api/plan/version` , `/api/plan/versions` ) and the plan diff system.
2026-03-12 01:29:32 -07:00
feat: plan archive browser (#369)
* feat: plan archive browser with in-session sidebar tab (#362)
Add `plannotator archive` subcommand and archive sidebar tab for browsing
saved plan decisions from ~/.plannotator/plans/. Plans show approved/denied
badges and timestamps. In-session mode uses the linked doc overlay pattern
so users can reference old plans while reviewing a current one.
- New archive server (packages/server/archive.ts) following annotate pattern
- New ArchiveBrowser sidebar component, reusable in both contexts
- Archive listing/parsing functions in storage.ts (reads decision snapshots)
- Archive endpoints on plan server for in-session use (/api/archive/plans)
- Remove dead "Other Plans" UI, projectPlans state, /api/plan/history endpoint
- Fix resize handle touch area covering scrollbars in sidebar/main content
- Fix sidebar tab bar overflow when narrow
For provenance purposes, this commit was AI assisted.
* fix: code quality sweep for plan archive
- Remove `as any` cast: add "archive" to SessionInfo.mode union
- Replace inline import() type with proper import for ArchivedPlan
- Replace any[] with ArchivedPlan[] in fetch response types
- Fix infinite re-fetch when archive is empty (use hasFetched ref)
- Cache archive plan list in plan server (avoid re-scanning filesystem)
- Document ResizeHandle side prop behavior
- Remove redundant comment on Viewer archiveInfo prop
For provenance purposes, this commit was AI assisted.
* chore: remove dead marketing components
Step.astro and Landing.astro are unused — landing page inlines
step markup and pages use Base.astro directly.
For provenance purposes, this commit was AI assisted.
* fix: address code review findings for plan archive
- Path traversal: use resolve() + trailing separator guard (matches reference-handlers.ts)
- Thread customPath into in-session archive endpoints via query param
- Sort same-day archive entries by mtime instead of title
- Clear selectedArchiveFile on linked doc back to prevent badge leak
- Hide archive tab in annotate mode (server doesn't serve those endpoints)
- Add targetTab param to useLinkedDoc.open() to preserve calling sidebar tab
- Replace mutable render variable with index-based date grouping
For provenance purposes, this commit was AI assisted.
* refactor: collapse standalone archive server into plan server
Delete packages/server/archive.ts (187 lines) — nearly all duplicated
from the plan server. Add mode:"archive" option to startPlannotatorServer
instead. Fixes two bugs from code review:
- handleArchiveCopy now splits on "# Plan Feedback" marker instead of
bare "---", preventing truncation at horizontal rules in plan content
- customPath support works in standalone archive mode (was only working
in-session because the standalone server never received it)
For provenance purposes, this commit was AI assisted.
* refactor: extract useArchive hook from App.tsx
Move archive state (archiveMode, plans, selectedFile, isLoading) and
handlers (select, fetchPlans, done, copy) into a dedicated useArchive
hook. Reduces App.tsx by ~75 lines and makes the archive feature
self-contained.
For provenance purposes, this commit was AI assisted.
* feat: Pi archive parity + eliminate server duplication
Move runtime-agnostic storage, draft, and project functions from
packages/server/ to packages/shared/ — eliminating ~250 lines of
duplicated code in Pi's server.ts. Server package becomes thin
re-exports, preserving all existing import paths.
Add archive mode to Pi's plan review server (mode, routes, waitForDone)
and register /plannotator-archive command in the Pi extension. Consolidate
ArchivedPlan type to single definition in shared/storage.ts.
Simplify archive copy to include full content with feedback.
For provenance purposes, this commit was AI assisted.
* fix: drop -core suffix from Pi shared copies
The -core suffix broke cross-file imports — storage.ts imports from
./project which didn't resolve to project-core.ts. Using the original
filenames (no collision) lets relative imports work naturally.
For provenance purposes, this commit was AI assisted.
* fix: archive custom path bugs, disable sharing, update docs
- Normalize planDir via resolve() in getPlanDir() to handle relative
paths and trailing slashes in the path traversal guard
- Re-fetch archive plans client-side with cookie-backed customPath
so standalone archive respects the user's configured save location
- Disable sharing in archive mode (read-only viewer, no need)
- Remove dead /api/plan/history endpoint and listProjectPlans import
from Pi extension
- Remove dead /api/plan/history mock from dev-mock-api
- Update CLAUDE.md and AGENTS.md: add archive flow, archive API
endpoints, shared package structure, correct storage location,
sidebar tab count, remove stale /api/plan/history references
- Update hook server docstring from four to five modes
For provenance purposes, this commit was AI assisted.
* fix: empty archive shows demo content, stale viewer after customPath fetch
- Clear demo markdown when archive opens with no plans (plan: "" was falsy,
so setMarkdown was never called)
- Remove redundant fetchPlans() from archive init — server already sends
archivePlans in initial response
- After fetchPlans() resolves with customPath results, auto-select and load
the first plan into the viewer
- Remove dead listProjectPlans re-export from server barrel
For provenance purposes, this commit was AI assisted.
* refactor: gitignore Pi shared copies, add @generated headers
Pi's copied .ts files (storage, draft, project, feedback-templates,
review-core) are build artifacts generated from packages/shared/. They
looked like editable source files, leading to confusion about which file
to edit. Now gitignored like the HTML copies, with @generated headers
prepended by the build script.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in CI before tests
The Pi .ts copies are now gitignored build artifacts. CI needs to
generate them before running tests since server.test.ts transitively
imports them via server.ts.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in release pipeline test job
Same fix as test.yml — the Pi .ts copies are gitignored, so the test
job in the release pipeline also needs to generate them before bun test.
For provenance purposes, this commit was AI assisted.
* fix: use block scalar in CI workflow to avoid YAML parse error
The inline `run:` had a colon in the printf string that YAML
interpreted as a mapping key. Switch to `run: |` block scalar.
For provenance purposes, this commit was AI assisted.
2026-03-23 11:31:38 -07:00
History saves independently of the `planSave` user setting (which controls decision snapshots in `~/.plannotator/plans/` ). Storage functions live in `packages/shared/storage.ts` (runtime-agnostic, re-exported by `packages/server/storage.ts` ). Pi copies the shared files at build time. Slug format: `{sanitized-heading}-YYYY-MM-DD` (heading first for readability).
2026-03-12 01:29:32 -07:00
## Plan Diff
When a user denies a plan and Claude resubmits, the UI shows what changed between versions. A `+N/-M` badge appears below the document card; clicking it toggles between normal view and diff view.
**Diff engine ** (`packages/ui/utils/planDiffEngine.ts` ): Uses the `diff` npm package (`diffLines()` ) to compute line-level diffs. Groups consecutive remove+add into "modified" blocks. Returns `PlanDiffBlock[]` and `PlanDiffStats` .
**Two view modes ** (toggle via `PlanDiffModeSwitcher` ):
- **Rendered** (`PlanCleanDiffView` ): Color-coded left borders — green (added), red (removed/strikethrough), yellow (modified)
- **Raw** (`PlanRawDiffView` ): Monospace `+/-` lines, git-style
**State ** (`packages/ui/hooks/usePlanDiff.ts` ): Manages base version selection, diff computation, and version fetching. The server sends `previousPlan` with the initial `/api/plan` response; the hook auto-diffs against it. Users can select any prior version from the sidebar Version Browser.
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
**Diff annotations: ** The clean diff view supports block-level annotation — hover over added/removed/modified sections to annotate entire blocks. Annotations carry a `diffContext` field (`added` /`removed` /`modified` ). Exported feedback includes `[In diff content]` labels.
**Annotation hook ** (`packages/ui/hooks/useAnnotationHighlighter.ts` ): Annotation infrastructure used by `Viewer.tsx` . Manages web-highlighter lifecycle, toolbar/popover state, annotation creation, text-based restoration, and scroll-to-selected. The diff view uses its own block-level hover system instead.
**Sidebar ** (`packages/ui/hooks/useSidebar.ts` ): Shared left sidebar with three tabs — Table of Contents, Version Browser, and Archive. The "Auto-open Sidebar" setting controls whether it opens on load (TOC tab only). In archive mode, the sidebar opens to the Archive tab automatically.
2026-03-12 01:29:32 -07:00
## Data Types
**Location: ** `packages/ui/types.ts`
``` typescript
enum AnnotationType {
DELETION = "DELETION" ,
COMMENT = "COMMENT" ,
GLOBAL_COMMENT = "GLOBAL_COMMENT" ,
}
interface ImageAttachment {
path : string ; // temp file path
name : string ; // human-readable label (e.g., "login-mockup")
}
interface Annotation {
id : string ;
blockId : string ;
startOffset : number ;
endOffset : number ;
type : AnnotationType ;
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
text? : string ; // For comment
2026-03-12 01:29:32 -07:00
originalText : string ; // The selected text
createdA : number ; // Timestamp
author? : string ; // Tater identity
images? : ImageAttachment [ ] ; // Attached images with names
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
source? : string ; // External tool identifier (e.g., "eslint") — set when annotation comes from external API
diffContext ? : 'added' | 'removed' | 'modified' ; // Set when annotation created in plan diff view
2026-03-12 01:29:32 -07:00
startMeta ? : { parentTagName ; parentIndex ; textOffset } ;
endMeta ? : { parentTagName ; parentIndex ; textOffset } ;
}
interface Block {
id : string ;
type : "paragraph" | "heading" | "blockquote" | "list-item" | "code" | "hr" ;
content : string ;
level? : number ; // For headings (1-6)
language? : string ; // For code blocks
order : number ;
startLine : number ;
}
```
## Markdown Parser
**Location: ** `packages/ui/utils/parser.ts`
`parseMarkdownToBlocks(markdown)` splits markdown into Block objects. Handles:
- Headings (`#` , `##` , etc.)
- Code blocks (``` with language extraction)
- List items (`-` , `*` , `1.` )
- Blockquotes (`>` )
- Horizontal rules (`---` )
- Paragraphs (default)
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
`exportAnnotations(blocks, annotations, globalAttachments)` generates human-readable feedback for Claude. Images are referenced by name: `[image-name] /tmp/path...` . Annotations with `diffContext` include `[In diff content]` labels.
2026-03-12 01:29:32 -07:00
## Annotation System
**Selection mode: ** User selects text → toolbar appears → choose annotation type
**Redline mode: ** User selects text → auto-creates DELETION annotation
Text highlighting uses `web-highlighter` library. Code blocks use manual `<mark>` wrapping (web-highlighter can't select inside `<pre>` ).
## URL Sharing
**Location: ** `packages/ui/utils/sharing.ts` , `packages/ui/hooks/useSharing.ts`
Shares full plan + annotations via URL hash using deflate compression. For large plans, short URLs are created via the paste service (user must explicitly confirm).
**Payload format: **
``` typescript
// Image in shareable format: plain string (old) or [path, name] tuple (new)
type ShareableImage = string | [ string , string ] ;
interface SharePayload {
p : string ; // Plan markdown
a : ShareableAnnotation [ ] ; // Compact annotations
g? : ShareableImage [ ] ; // Global attachments
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
d ? : ( string | null ) [ ] ; // diffContext per annotation, parallel to `a`
2026-03-12 01:29:32 -07:00
}
type ShareableAnnotation =
| [ "D" , string , string | null , ShareableImage [ ] ? ] // [type, original, author, images?]
| [ "C" , string , string , string | null , ShareableImage [ ] ? ] // [type, original, comment, author, images?]
| [ "G" , string , string | null , ShareableImage [ ] ? ] ; // [type, comment, author, images?]
```
**Compression pipeline: **
1. `JSON.stringify(payload)`
2. `CompressionStream('deflate-raw')`
3. Base64 encode
4. URL-safe: replace `+/=` with `-_`
**On load from shared URL: **
1. Parse hash, decompress, restore annotations
2. Find text positions in rendered DOM via text search
3. Apply `<mark>` highlights
4. Clear hash from URL (prevents re-parse on refresh)
## Settings Persistence
**Location: ** `packages/ui/utils/storage.ts` , `planSave.ts` , `agentSwitch.ts`
Uses cookies (not localStorage) because each hook invocation runs on a random port. Settings include identity, plan saving (enabled/custom path), and agent switching (OpenCode only).
## Syntax Highlighting
Code blocks use bundled `highlight.js` . Language is extracted from fence (```rust) and applied as ` language-{lang}`class. Each block highlighted individually via` hljs.highlightElement()`.
## Requirements
- Bun runtime
- Claude Code with plugin/hooks support, or OpenCode
- Cross-platform: macOS (` open`), Linux (` xdg-open`), Windows (` start`)
## Development
` ``bash
bun install
# Run any app
bun run dev:hook # Hook server (plan review)
bun run dev:review # Review editor (code review)
bun run dev:portal # Portal editor
bun run dev:marketing # Marketing site
bun run dev:vscode # VS Code extension (watch mode)
` ``
## Build
` ``bash
bun run build:hook # Single-file HTML for hook server
bun run build:review # Code review editor
bun run build:opencode # OpenCode plugin (copies HTML from hook + review)
bun run build:portal # Static build for share.plannotator.ai
bun run build:marketing # Static build for plannotator.ai
bun run build:vscode # VS Code extension bundle
bun run package:vscode # Package .vsix for marketplace
bun run build # Build hook + opencode (main targets)
` ``
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
**Important: Build order matters.** The hook build (` build:hook`) copies pre-built HTML from ` apps/review/dist/`. If you change UI code in ` packages/ui/`, ` packages/editor/`, or ` packages/review-editor/`, you **must** rebuild the review app first, then the hook:
` ``bash
bun run --cwd apps/review build && bun run build:hook # For review UI changes
bun run build:hook # For plan UI changes only
bun run build:hook && bun run build:opencode # For OpenCode plugin
` ``
Running only ` build:hook` after review-editor changes will copy stale HTML files. When testing locally with a compiled binary, the full sequence is:
2026-03-12 01:29:32 -07:00
` ``bash
feat: external annotations API with real-time SSE (#400)
Adds a general-purpose External Annotations API that allows external programs (linters, AI tools, security scanners) to push annotations into a live Plannotator session via HTTP, with real-time delivery over SSE.
## What's included
- **Shared core** (`packages/shared/external-annotation.ts`): types, in-memory store, input validation, SSE serialization
- **Server handlers**: Bun + Pi implementations with full CRUD (GET/POST/PATCH/DELETE) + SSE streaming
- **Client hook** (`useExternalAnnotations`): EventSource with polling fallback, optimistic updates
- **Editor integration**: two-array state model (local + external), content-aware dedup, ID-based routing
- **Persistence**: source field preserved through share URLs and crash-recovery drafts
- **Docs**: new Integrations category with API overview page, updated API reference
## API surface
All three servers (plan, review, annotate) expose:
- `GET /api/external-annotations/stream` - SSE stream
- `GET /api/external-annotations` - JSON snapshot (polling fallback)
- `POST /api/external-annotations` - Add annotations (single or batch)
- `PATCH /api/external-annotations?id=` - Update fields
- `DELETE /api/external-annotations` - Remove by id, source, or clear all
For provenance purposes, this commit was AI assisted.
2026-03-29 17:18:31 -07:00
bun run --cwd apps/review build && bun run build:hook && \
bun build apps/hook/server/index.ts --compile --outfile ~/.local/bin/plannotator
2026-03-12 01:29:32 -07:00
` ``
Running only ` build:opencode` will copy stale HTML files.
## Marketing Site
` apps/marketing/` is the plannotator.ai website — landing page, documentation, and blog. Built with Astro 5 (static output, zero client JS except a theme toggle island). Docs are markdown files in ` src/content/docs/`, blog posts in ` src/content/blog/`, both using Astro content collections. Tailwind CSS v4 via ` @tailwindcss/vite `. Deploys to S3/CloudFront via GitHub Actions on push to main.
## Test plugin locally
` ``
claude --plugin-dir ./apps/hook
` ``