Commit Graph

1098 Commits

Author SHA1 Message Date
Michael Ramos 6b84ce4107 feat(review): custom diff font family and size overrides (#441)
* feat(review): custom font family and size overrides for code review diffs

Add Code Font and Code Font Size settings to the review Display tab,
letting users override the theme's monospace font in the diff viewer.

Supports 9 fonts (Fira Code, Hack, IBM Plex Mono, Inconsolata,
JetBrains Mono, Red Hat Mono, Roboto Mono, Source Code Pro, Atkinson
Hyperlegible Mono) loaded on demand from Google Fonts / jsDelivr CDN.
Font size uses a continuous slider (8–24px) with a Reset button.

Settings persist via cookies and sync to ~/.plannotator/config.json
under diffOptions.fontFamily / diffOptions.fontSize, matching the
existing config pattern.

For provenance purposes, this commit was AI assisted.

* fix(review): line-height scaling, inline annotation font coverage, and comment card cleanup

- Add line-height: 1.5 alongside font-size in Pierre shadow DOM so large sizes don't clip
- Include .review-comment-body selectors in font override CSS so inline annotation code spans get the custom font
- Simplify comment card: remove drop shadow, left border accent, translateX hover; use tight border with subtle hover

For provenance purposes, this commit was AI assisted.
2026-03-30 13:45:37 -07:00
Michael Ramos b0980ef928 fix(opencode): remove verbose logs that flood the TUI (#440)
Remove writeRemoteShareLink stderr output from the OpenCode plugin —
the base64 share URL was flooding the TUI on remote sessions. Also
strip leftover console.log debug statements from the PR viewed files
feature in the review server.

Closes #435

For provenance purposes, this commit was AI assisted.
2026-03-30 10:51:27 -07:00
Michael Ramos a633e70597 chore: bump version to 0.16.1 v0.16.1 2026-03-30 09:13:52 -07:00
foxytanuki 16756e082b fix(server): keep external annotation SSE streams alive (#439) 2026-03-30 08:49:34 -07:00
Michael Ramos a88da925de chore: bump version to 0.16.0 v0.16.0 2026-03-29 22:13:09 -07:00
Michael Ramos e0dd5783b2 fix(ui): type cleanse — fix stale types, missing initializer fields, React 19 compat
- CommentPopover: add width to position state type (was returned but not declared)
- Settings: add missing ObsidianSettings fields (filenameSeparator, autoSave, vaultBrowserEnabled)
- Settings: add missing planWidth to UIPreferences initializer
- Viewer: use React.ElementType for dynamic heading tag
- useLinkedDoc: use SidebarTab instead of string for sidebar.open
- useActiveSection: accept RefObject<HTMLElement | null> (React 19)
- useInputMethodSwitch: pass explicit undefined to useRef
- package.json: add exports for config, ImageAnnotator, sidebar/*, plan-diff/*
- types.d.ts: add __APP_VERSION__ global declaration

For provenance purposes, this commit was AI assisted.
2026-03-29 21:43:00 -07:00
Aviad Shiber 14f8bdba62 feat: bot callback URL params with componentized toolbar buttons (#416)
Adds ?cb=<encoded_url>&ct=<token> URL params for bot-initiated plan review callbacks.
When a bot posts a Plannotator share link with these params, Approve/Feedback buttons
appear and POST the user's decision (with annotated plan URL) back to the bot.

Changes:
- callback.ts: parse callback config from URL search/hash params, execute POST
- ToolbarButtons.tsx: extract shared FeedbackButton/ApproveButton components (6 instances → 2 components)
- sharing.ts: strip ?cb=&ct= from hash before decompressing share payload
- App.tsx: callback buttons gated on isSharedSession + shareUrl readiness
- 19 tests covering config parsing, URL validation, presigned URL preservation, and POST execution

Co-authored-by: aviadshiber <aviadshiber@users.noreply.github.com>

For provenance purposes, this commit was AI assisted.
2026-03-29 21:08:41 -07:00
Michael Ramos df76f9ea63 copy: change "any agent" to "your agents" in hero tagline
For provenance purposes, this commit was AI assisted.
2026-03-29 20:59:52 -07:00
Michael Ramos 1f7ff288b5 feat(marketing): add GitHub Copilot to harness selector on landing page
For provenance purposes, this commit was AI assisted.
2026-03-29 20:54:32 -07:00
Michael Ramos 75a6d8f2a5 chore: replace CLAUDE.md with symlink to AGENTS.md
Single source of truth — edit AGENTS.md, both Claude Code and other
agents see the same instructions.

For provenance purposes, this commit was AI assisted.
2026-03-29 20:39:51 -07:00
Michael Ramos 7c82a3e01b chore: untrack .claude/skills/release — source of truth is .agents/skills/
Symlinked from ~/.claude/skills/ to the repo's .agents/skills/ directory
so changes are reflected instantly without copying.

For provenance purposes, this commit was AI assisted.
2026-03-29 20:35:42 -07:00
Michael Ramos 9937bf8b86 fix(skill): trim compound skill description under 250-char limit (#430)
Use `disable-model-invocation: true` frontmatter instead of contradictory
prose that both listed trigger phrases and said "do not trigger automatically".

Closes #412

For provenance purposes, this commit was AI assisted.
2026-03-29 20:30:54 -07:00
Michael Ramos 4627f75426 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
Kiwi 9b69d17580 test: cover PR/MR platform helpers (#426) 2026-03-29 15:41:02 -07:00
Michael Ramos 2ae4f2a292 fix(parser): indented fences, trailing text, table detection, and escaped pipes (#429)
Three fixes to parseMarkdownToBlocks and one to table cell rendering:

1. Indented closing fences — allow leading whitespace so `  ``` ` inside
   list items closes the code block instead of swallowing to EOF.
2. Trailing text after closing fence — drop end-of-line anchor so
   ` ``` some text` still closes the block.
3. False table detection — require lines start with `|` instead of
   matching any line with 2+ pipe characters.
4. Escaped pipes in table cells — split on unescaped `|` only, so
   `\|` renders as a literal pipe instead of creating extra columns.

Closes #427

For provenance purposes, this commit was AI assisted.
2026-03-29 13:25:17 -07:00
Michael Ramos 9b016da4ae feat(review): diff display options with ConfigStore integration (#428)
* feat(config): add DiffOptions to PlannotatorConfig with deep-merge support

Extends the server-side config system to support diff display options
namespaced under `diffOptions` in ~/.plannotator/config.json. The
saveConfig deep-merge ensures partial writes (e.g. changing only
overflow) don't clobber sibling keys.

For provenance purposes, this commit was AI assisted.

* feat(server): accept diffOptions in POST /api/config handlers

All six server handlers (plan, review, annotate x Bun + Pi) now
whitelist diffOptions alongside displayName, forwarding it to
saveConfig for deep-merged disk persistence.

For provenance purposes, this commit was AI assisted.

* feat(config): register diff display settings with deep-merge write batching

Adds 6 diff option settings to the ConfigStore registry (diffStyle,
diffOverflow, diffIndicators, diffLineDiffType, diffShowLineNumbers,
diffShowBackground), all synced to disk under the diffOptions namespace.

Fixes write batching for nested server keys — replaces Object.assign
with deepMerge so rapid changes to multiple diff options within the
300ms debounce window don't clobber each other. Includes legacy cookie
migration for the old review-diff-style key.

For provenance purposes, this commit was AI assisted.

* feat(review): wire diff display options through to @pierre/diffs

Migrates diffStyle from raw cookie storage to ConfigStore. Reads all 6
diff options via useConfigValue and forwards them to FileDiff: overflow,
diffIndicators, lineDiffType, disableLineNumbers, disableBackground.

Adds a Scroll/Wrap toolbar toggle alongside the existing Split/Unified
toggle. Disables the custom resizable split dragger in wrap mode since
Pierre manages its own grid layout. Fixes the unsafeCSS grid override
to only target scroll mode, preventing per-character wrapping in split
view.

For provenance purposes, this commit was AI assisted.

* feat(review): add Display tab to review-mode Settings dialog

Adds a Display tab when Settings is in review mode, exposing all 6 diff
options: Diff Style, Line Overflow, Change Indicators, Inline Diff
Granularity, Show Line Numbers, and Show Diff Background. Changes take
effect immediately and persist via ConfigStore (cookie + disk).

Extracts reusable SegmentedControl and ToggleSwitch helpers for the
review display controls.

For provenance purposes, this commit was AI assisted.

* chore: update bun.lock

For provenance purposes, this commit was AI assisted.

* fix(review): reapply search highlights after display option changes

Add new diff display props to the search highlight and scroll-to-match
effect dependency arrays so highlights are restored after Pierre re-renders.

For provenance purposes, this commit was AI assisted.
2026-03-29 13:20:22 -07:00
Stacey Haffner a0a6edd323 feat: print support with export menu integration and keyboard shortcut (#420)
* feat: add print stylesheet for white paper output

- Create packages/ui/styles/print.css with @media print rules
- White background and black text for paper output
- Monochrome code blocks for readability on white paper
- Hide UI chrome (annotations, toolbars, sidebars) during print
- Proper typography for headers, code, tables, lists, blockquotes
- Page break rules to avoid orphaned content
- Support for diagrams (Mermaid, Graphviz) in print
- Import print.css in packages/ui/theme.css

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add Print button to toolbar

- Add Print button next to Export button in plan editor toolbar
- Triggers window.print() for native browser print dialog
- Shows printer icon + 'Print' label (icon-only on mobile)
- Tooltip: 'Print plan (Ctrl+P)'
- Uses muted button styling consistent with other toolbar buttons

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: rewrite print.css selectors to match real DOM structure

Code review found the original selectors used class names that don't
exist in the actual component tree. Fixed:

- Target header.sticky / header.h-12 (not generic 'header')
- Target aside elements directly (SidebarContainer + AnnotationPanel)
- Target .annotation-toolbar (portalled floating toolbar)
- Target .fixed overlays (modals, export dialog)
- Target article element (Viewer renders <article>, not <main>)
- Target .bg-grid for background pattern removal
- Flatten .h-screen height for print flow
- Simplified hljs monochrome rule to [class*='hljs']

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: print stylesheet — hide all toolbar UI, fix code block colors

Issues fixed from user testing:
- Header toolbar (Export/Print/Settings) now hidden via 'header' selector
- AnnotationToolstrip (Select/Markup) hidden via '.flex-wrap'
- Action buttons (Images/Comment/Copy) hidden via '.float-right'
- Code block copy buttons hidden via '.group > button.absolute'
- Fenced code blocks: override github-dark.css hljs theme to monochrome
  with light background (#f5f5f5) and dark text (#1a1a1a)
- Inline code: solid light gray background with visible border
- All text forced to solid black (override theme muted/foreground vars)
- hljs span elements explicitly overridden for print

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: aggressive code block print overrides for dark theme

- Override CSS custom properties (--muted, --foreground, etc.) in print
- Use both background AND background-color on pre/code elements
- Target pre[class], code[class], code.hljs with higher specificity
- Explicitly list every hljs- class for monochrome override
- Ensures github-dark.css hljs theme is fully overridden in print

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use JS beforeprint/afterprint class for code block print styling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: add visibilitychange fallback for Firefox print cleanup

Firefox may not fire afterprint when print preview is closed without
printing, leaving the .plannotator-print class stuck on <html>. This
adds a visibilitychange listener that removes the class when the user
returns to the page, plus cleanup on unmount.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: extract usePrintMode hook, replace brittle CSS selectors with data attributes

- Extract print event listeners from App.tsx into packages/ui/hooks/usePrintMode.ts
- Add data-print-region attributes to layout elements (root, content, document, article)
- Add data-print-hide attribute to Viewer action buttons
- Replace .h-screen, .flex-1.flex.overflow-hidden, .bg-card, .bg-grid, .float-right,
  .flex-wrap selectors with stable data-attribute and semantic element selectors
- Follows existing hook patterns (useIsMobile, useDismissOnOutsideAndEscape, etc.)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: hide annotation toolstrip and repo badges in print output

Added data-print-hide to the AnnotationToolstrip wrapper and the
repo/branch badges div inside the article.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: collapse top whitespace gap in print output

Zero out article padding, add margin/padding reset to data-print-hide
elements, and collapse first h1 top margin for a tight print layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: move print button into export menu and add Ctrl/Cmd+P shortcut

* chore: remove accidentally introduced annotate command files

Remove apps/hook/commands/annotate.md and apps/opencode-plugin/commands/annotate.md
that were unintentionally added in the print-styling PR.

For provenance purposes, this commit was AI assisted.

---------

Co-authored-by: Yecats <Yecats@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-03-29 11:04:32 -07:00
Stacey Haffner 08fcaaf086 feat: Interactive checkboxes with annotation tracking (#423)
* feat: interactive checkboxes — click to toggle with annotation

Checkboxes in rendered markdown task lists are now clickable.
Toggling is visual-only (does not modify source markdown) and
automatically creates a comment annotation noting the change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: uncheck checkbox removes override and deletes annotation (undo)

When toggling back to the original state, the checkbox override is
deleted and the associated annotation is removed — acting as an undo.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: address all code review issues for checkbox feature

1. Fix annotation prefix collision: match on blockId field instead of
   string prefix to avoid block-1 matching block-10, block-11 etc.
2. Fix rapid toggle orphans: delete ALL existing checkbox annotations
   for the block before creating a new one, preventing duplicates.
3. Fix stale overrides: useEffect cleans up checkboxOverrides entries
   when their block IDs no longer exist in the current blocks array.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use actionable annotation text for checkbox toggles

Annotations now read 'Mark as completed: {item}' or 'Mark as not
completed: {item}' so the AI knows to action on them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: include section heading in checkbox annotation for disambiguation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: revert checkbox override when annotation is deleted from panel

When a user deletes a checkbox annotation via the annotation panel,
the checkboxOverrides entry is now cleared so the checkbox reverts
to its original visual state. Previously only toggling the checkbox
itself would revert the override.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: extract removeAnnotation to avoid redundant override cleanup in checkbox toggle

* refactor: extract useCheckboxOverrides hook from App.tsx

---------

Co-authored-by: Yecats <Yecats@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-29 09:43:03 -07:00
Stacey Haffner 2d90d381c9 fix: Detect calling agent via env vars and centralize agent config (#418)
* fix: detect calling agent via env vars for correct badge display

Add prioritized agent detection chain: CODEX_THREAD_ID (codex) >
COPILOT_CLI (copilot-cli) > default (claude-code). All subcommands
use the shared detectedOrigin constant. Display name updated from
'Copilot CLI' to 'GitHub Copilot'. TypeScript origin union type
updated to include 'copilot-cli'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: centralize agent config into @plannotator/shared/agents

- Create packages/shared/agents.ts as single source of truth for Origin type,
  agent display names (AGENT_CONFIG), getAgentName(), and getAgentBadge()
- Replace all inline origin type unions across 8 files with Origin import
- Type detectedOrigin and ServerOptions.origin as Origin for compile-time safety
- Add @plannotator/shared dependency to @plannotator/editor package.json
- Adding a new agent is now a one-file change to AGENT_CONFIG

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore gitContext in startReviewServer call

Accidentally removed alongside the origin refactor. Without it, local
code reviews lose branch detection, diff-type switching, and CWD resolution.

For provenance purposes, this commit was AI assisted.

---------

Co-authored-by: Yecats <Yecats@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-03-28 16:37:56 -07:00
Michael Ramos 8280cc0aca perf(opencode): lazy-load HTML to fix plugin startup time (#411)
* perf(opencode): lazy-load HTML to cut plugin startup from ~160ms to ~35ms

The two SPA HTML files (~20 MB combined) were inlined as string literals
via Bun's `with { type: "text" }` imports, forcing Bun to parse a 21 MB
bundle at module load time. Replace with lazy readFileSync getters and
background preload during plugin init, reducing the bundle to 0.81 MB.

Closes #410

For provenance purposes, this commit was AI assisted.

* test: add OpenCode plugin startup benchmark script

Measures real-world startup time across three scenarios:
no plugin, published npm, and local optimized. Uses
`opencode run` for non-interactive measurement and parses
log timing.

For provenance purposes, this commit was AI assisted.

* fix(bench): resolve project dir to repo root and auto-build before scenario 3

PROJECT_DIR pointed to tests/ instead of the repo root, so the local
plugin path was invalid and scenario 3 silently measured a no-plugin run.
Also auto-run build:opencode when dist/index.js is missing.

For provenance purposes, this commit was AI assisted.
2026-03-27 20:09:09 -07:00
Michael Ramos 111934f658 chore: update release skill with Copilot/OpenCode version files and Pi parity gate
For provenance purposes, this commit was AI assisted.
2026-03-27 13:56:37 -07:00
Stacey Haffner 15af1c40eb Add GitHub Copilot CLI integration (#409)
* feat: add GitHub Copilot CLI integration

Add full Copilot CLI support via a new plugin (apps/copilot/) with:
- preToolUse hook intercepting exit_plan_mode for plan review
- copilot-plan subcommand with correct permissionDecision output format
- copilot-last subcommand parsing events.jsonl for last message annotation
- Copilot CLI session parser (copilot-session.ts)
- Plugin manifest, hooks.json, and slash commands
- UI origin branding (blue badge for Copilot CLI)
- Marketplace entry for plugin discovery
- README install section

Key design decisions:
- Intercepts exit_plan_mode (not create) to avoid double-review
- Uses sessionId (with fallback) to locate plan.md from session state
- Follows Codex integration pattern: same CLI binary, new plugin dir

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: handle actual Copilot CLI preToolUse input format (toolCalls array)

The Copilot CLI hook input uses { toolCalls: [{ name, args }] } format
instead of the documented { toolName, toolArgs } format. The filter was
checking event.toolName (always undefined), causing exit_plan_mode to
pass through unintercepted. Now extracts tool name from either format.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Revert "fix: handle actual Copilot CLI preToolUse input format (toolCalls array)"

This reverts commit b214bc02c58fc5d8f305b128457099afb2ea6ecc.

* fix: use absolute path in hooks.json for reliable binary discovery

The hook subprocess inherits the PATH from when Copilot CLI was started,
which may not include the plannotator binary directory. Using the full
absolute path via env vars ensures the hook always finds the binary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Finishing the feature

* fix: remove Copilot CLI plugin from Claude Code marketplace.json

.claude-plugin/marketplace.json is for Claude Code's plugin marketplace.
The Copilot CLI plugin has its own install path and doesn't belong here.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: align Copilot CLI plugin with official spec

- Add .github/plugin/marketplace.json for Copilot CLI plugin discovery
- Change allowed-tools from Bash() to shell() syntax in commands
- Respect COPILOT_HOME env var in session path discovery
- Update plugin.json version to 0.14.5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Yecats <Yecats@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: stahaff <222254525+stahaff@users.noreply.github.com>
2026-03-27 10:20:49 -07:00
Michael Ramos 9746344779 chore: bump version to 0.15.5
For provenance purposes, this commit was AI assisted.
v0.15.5
2026-03-26 22:34:24 -07:00
Michael Ramos bcddced148 perf(review): debounce search highlights and O(1) active match swap (#407)
* perf(review): debounce search highlights and O(1) active match swap

Split the single search highlight effect into two paths:

1. Full rebuild (debounced 100ms) — fires on query/matches/file/style
   changes, collapsing rapid keystrokes into one DOM update instead of
   one per character typed.

2. Active swap (instant) — fires when stepping between matches
   (Enter/Shift+Enter), changing two elements' background color via
   querySelector instead of tearing down and rebuilding every <mark>
   in the shadow DOM.

Also removes augmentedDiff from the effect deps (was triggering
unnecessary rebuilds on context expansion) and exports
clearSearchHighlights for the immediate-clear path on Escape.

Closes #405

For provenance purposes, this commit was AI assisted.

* perf(review): debounce search query and pre-index searchable lines

The search input was laggy on large PRs because findReviewSearchMatches
re-parsed every file's patch and scanned all lines synchronously on
every keystroke, blocking React from re-rendering the input value.

Two complementary fixes:

1. Pre-index: Build SearchableLine[] once when files change (useMemo),
   with pre-lowercased normalizedText, so matching skips repeated patch
   parsing and per-line toLowerCase() calls.

2. Debounced query: searchQuery updates instantly (input responsive),
   debouncedSearchQuery trails by 200ms and drives all expensive work.
   During the pending window, sidebar shows "Searching…", diff clears
   stale highlights, match count hides, and stepping is disabled.

Also: refactors findReviewSearchMatches to delegate to the new
findMatchesInIndex so there is a single matching implementation,
removes the now-redundant 100ms DOM debounce in DiffViewer (query
debounce handles it upstream), trims query in highlightQuery to
prevent whitespace-only edits from breaking snippet highlighting,
and restores augmentedDiff in the highlight effect deps.

For provenance purposes, this commit was AI assisted.
2026-03-26 22:24:50 -07:00
Yoni Hornstein 232cae265e feat(review): add expand/collapse all buttons to file tree sidebar (#403)
Add two icon buttons in the file tree header that allow expanding or
collapsing all directory groups at once.
2026-03-26 13:08:31 -07:00
Alexander Tarnavsky Eitan adca453380 Fix project slug derivation to match Claude Code's actual algorithm (#401)
Claude Code replaces all non-alphanumeric characters (except `-`) with `-`
when deriving the project slug from the working directory path. The previous
implementation only replaced `/`, causing `annotate-last` to fail for paths
containing dots (e.g. `.worktrees`) or underscores (e.g. `feat_branch`).

The computed slug wouldn't match the actual session log directory, resulting
in "No rendered assistant message found" errors.

Co-authored-by: alex <alexander.eitan@contiamo.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-03-26 13:03:35 -07:00
Michael Ramos 401793e35f feat: custom display name + config file foundation (#399)
Adds user-editable display names and persistent config via ~/.plannotator/config.json.

- ConfigStore singleton with precedence: server config file > cookie > default
- Editable identity input in Settings with "Use git name" and regenerate buttons
- POST /api/config endpoint for write-back across all 6 servers
- getServerConfig() reads config fresh per request (no stale cache)
- Eager constructor hydration so the store is safe to read before init()
- vendor.sh as single source of truth for Pi extension vendoring
- Vendor parity test to prevent missing generated modules

Closes #396
2026-03-26 11:54:22 -07:00
Michael Ramos e53e76cc3e docs: document diff type switcher in code review docs (#398)
The review UI supports switching between uncommitted, staged, unstaged,
last commit, and branch diffs, but the docs only mentioned uncommitted
changes. This led users to think a branch diff view wasn't supported.

Closes #397

For provenance purposes, this commit was AI assisted.
2026-03-25 20:48:30 -07:00
Rock Neurotiko a8260eb389 Sync github Viewed files (#393) 2026-03-25 13:33:33 -07:00
Alexandre Silvestri 81f29dfe6e unix update command for wsl users on UpdateBanner (#395) 2026-03-25 11:06:29 -07:00
Michael Ramos 090b8d9fc2 feat(pi): ship compound planning skill in published package
Adds skills/ to the pi manifest and files array so Pi auto-discovers
the compound-planning SKILL.md. Build script copies from apps/skills/.

For provenance purposes, this commit was AI assisted.
2026-03-25 09:49:51 -07:00
Michael Ramos 7ccace60e7 chore: bump pi-extension to 0.15.3
Hotfix for missing tool-scope.ts in published package (#391, #392).

For provenance purposes, this commit was AI assisted.
2026-03-25 09:37:22 -07:00
Jason O'Donnell 3c6c5b06f3 Fix plan tool scoping import (#392) 2026-03-25 07:57:53 -07:00
Michael Ramos d49fb49281 chore: bump version to 0.15.2
For provenance purposes, this commit was AI assisted.
v0.15.2
2026-03-25 00:34:25 -07:00
Michael Ramos 1175ef65c1 fix(pi): bundle AI backbone into generated/ for published package
@plannotator/ai is a private workspace package unavailable on npm.
Pi's dynamic import silently failed, so AI features never loaded for
published package users. Now copies all AI files into generated/ai/
at build time, same pattern as the shared utils.

For provenance purposes, this commit was AI assisted.
2026-03-25 00:22:33 -07:00
Michael Ramos f2a05ece4f feat: install skills via git sparse-checkout in platform install scripts (#390)
Adds a skills installation phase to all three install scripts (sh, ps1, cmd).
Uses git sparse-checkout to fetch only apps/skills/ from the repo at the
release tag, then copies into ~/.claude/skills/ and ~/.config/agents/skills/.
Gracefully skips if git is unavailable or the clone fails.

For provenance purposes, this commit was AI assisted.
2026-03-25 00:17:19 -07:00
Michael Ramos 919f582e0c feat: support folder annotation via plannotator annotate <folder/> (#389)
* feat: add hasMarkdownFiles() and folderPath support for annotate servers

Add hasMarkdownFiles() to resolve-file.ts — a shared validation function
that checks if a directory contains at least one markdown file, skipping
IGNORED_DIRS. Lives alongside the existing walkMarkdownFiles pattern.

Both annotate servers (Bun + Pi) accept an optional folderPath that
overrides projectRoot in the /api/plan response, and a new
"annotate-folder" mode value.

For provenance purposes, this commit was AI assisted.

* feat: support folder annotation via plannotator annotate <folder/>

Wire folder detection through both CLI entry points (Hook + Pi) and
the plan editor UI. When a directory is passed instead of a file:

- CLI validates markdown files exist via hasMarkdownFiles()
- Server starts with empty markdown and mode "annotate-folder"
- UI clears demo content, auto-opens sidebar to Files tab
- Empty state prompts user to select a file from the tree
- Linked doc system handles per-file annotation caching
- Feedback exports with per-file headers via existing infrastructure

Single-file annotation is unchanged.

For provenance purposes, this commit was AI assisted.

* fix: address review findings for folder annotation

- Include .mdx files in file browser globs (reference-handlers.ts)
- Fix imageBaseDir in folder mode — use folder path directly instead of
  stripping last segment, which produced the parent directory
- Guard statSync in Pi extension with try/catch to match hook server pattern

For provenance purposes, this commit was AI assisted.

* fix: align folder validation exclusions with file browser

hasMarkdownFiles() now accepts an excludedDirs parameter. Both CLI
entry points pass FILE_BROWSER_EXCLUDED so the preflight check
matches the file browser — no more empty sidebar after validation.

For provenance purposes, this commit was AI assisted.
2026-03-24 23:37:19 -07:00
Michael Ramos 5e36960698 feat: add /plannotator-archive slash command (#388)
* feat: add /plannotator-archive slash command for Claude Code and OpenCode

The archive browser was only accessible via CLI (plannotator archive) and
Pi (/plannotator-archive). This adds slash command parity so users can
browse saved plan decisions from within Claude Code and OpenCode sessions.

Relates to #362

For provenance purposes, this commit was AI assisted.

* fix: re-fetch archive plans with custom path in standalone mode

The server pre-loads plans from ~/.plannotator/plans/ (default) because
no runtime passes customPlanPath. For users with a custom save directory,
the plan list was wrong and clicking any plan 404'd. Calling fetchPlans()
after init() re-fetches with the user's cookie-based custom path setting.

For provenance purposes, this commit was AI assisted.
2026-03-24 15:00:30 -07:00
Dillon Mulroy 5773f55952 Fix Pi plan tool scoping (#387) 2026-03-24 12:27:21 -07:00
Michael Ramos afc476c310 chore: bump version to 0.15.0
For provenance purposes, this commit was AI assisted.
v0.15.0
2026-03-24 01:25:10 -07:00
Michael Ramos 3ac3d5bc17 fix(ui): sidebar resize handle touch area + linked doc border styling
ResizeHandle: the v0.14.5 archive browser PR simplified the touch area
to treat both sides identically, making the left sidebar handle
ungrabable. Now uses asymmetric positioning per side — extends slightly
toward the panel, more toward the content, without covering scrollbars.

Viewer: linked doc border reduced from 2px full-opacity primary to 1px
at 40% opacity for a subtler visual distinction.

For provenance purposes, this commit was AI assisted.
2026-03-24 01:22:25 -07:00
Michael Ramos f96758da0a feat(pi): complete Pi server rewrite — modular architecture, full Bun parity, shared code extraction (#382)
* feat(pi): add missing endpoints to plan, review, and annotate servers

Phase 1-3 of Pi endpoint parity:

Plan server: image, upload, draft, editor-annotations, agents, favicon,
linked documents, Obsidian vaults/files/doc, file browser, VS Code diff

Annotate server: image, upload, draft, favicon, linked documents, file browser

Review server: extract shared handlers, add favicon

Shared utilities extracted from review server inline code into reusable
functions (handleImageRequest, handleUploadRequest, handleDraftRequest,
handleFavicon). Reference handlers (doc, Obsidian, file browser)
implemented using Node.js fs APIs replacing Bun.Glob/Bun.file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(pi): add PR review endpoints and Node.js PR runtime adapter

Phase 4 of Pi endpoint parity:

- Node.js PRRuntime using child_process.spawn (matches Bun adapter pattern)
- GET /api/pr-context — fetch PR summary, comments, checks
- POST /api/pr-action — submit review to GitHub/GitLab
- PR mode guards on /api/diff/switch and /api/git-add
- /api/diff response includes prMetadata and platformUser in PR mode
- /api/file-content fetches from platform API in PR mode
- Build script copies pr-provider, pr-github, pr-gitlab from shared

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(pi): wire AI backbone with Node.js Pi SDK provider

Phase 5 of Pi endpoint parity:

- Create packages/ai/providers/pi-sdk-node.ts — PiProcessNode class
  using child_process.spawn instead of Bun.spawn, same RPC protocol
- Register 4 AI providers in Pi review server (claude-agent-sdk,
  codex-sdk, pi-sdk-node, opencode-sdk) with graceful degradation
- Route /api/ai/* endpoints through createAIEndpoints handlers
- Pipe Web Response → node:http response with ReadableStream support
  for SSE streaming
- Dispose AI sessions and registry on server stop

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(pi): address parity audit findings across all three servers

Plan server:
- /api/plan: add repoInfo and projectRoot to response
- /api/approve: pass agentSwitch and permissionMode in decision
- Update decision promise type to include agentSwitch, permissionMode

Review server:
- /api/diff/switch: pass gitContext.cwd to runGitDiff
- /api/file-content: pass gitContext.cwd to getFileContentsForDiffCore
- /api/git-add: add fallback to gitContext.cwd when worktree parse fails

Annotate server:
- /api/plan: add repoInfo and projectRoot to response
- /api/feedback: capture annotations array (was silently dropped)
- Update decision promise type to include annotations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(pi): complete parity — integrations, planSave, save-notes

Ports all remaining missing functionality:

- Node.js versions of saveToObsidian, saveToBear, saveToOctarine
  (Bun.write → writeFileSync, Bun.$ → spawn)
- Node.js detectProjectNameSync (Bun.$ → execSync)
- extractTags, generateFrontmatter, generateFilename, extractTitle
- POST /api/save-notes — decoupled note saving
- POST /api/approve — full implementation: note integrations,
  planSave snapshots, saveAnnotations, saveFinalSnapshot
- POST /api/deny — planSave snapshots on denial
- Import saveAnnotations, saveFinalSnapshot from storage.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(pi): wire domain module imports and fix type errors

- Add all missing imports from ./server/* domain modules to server.ts
- Export interfaces from integrations.ts (ObsidianConfig, BearConfig, etc.)
- Move toWebRequest to helpers.ts, remove duplicate from handlers.ts
- Add git() helper to project.ts (was in server.ts, needed by getRepoInfo)
- Fix os default import → named imports in handlers.ts and network.ts
- Fix readdirSync Dirent type in reference.ts
- Fix Headers.entries() → forEach for Node compat in AI endpoint piping
- Fix ReadableStream type cast in AI SSE streaming
- Fix matchAll iterator compat in integrations.ts (use while + exec)
- Cast pi-sdk provider config to any (PiSDKConfig not in base union)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(pi): move generated shared files to generated/ directory

Moves all build-time copied shared files (feedback-templates, review-core,
storage, draft, project, pr-provider, pr-github, pr-gitlab) from the
pi-extension root into generated/ subdirectory.

Updates build script to output there. Updates all imports in server.ts,
index.ts, and server/ domain modules to use ./generated/ paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(pi): replace hand-maintained utils.ts with generated checklist

utils.ts was a manual copy of parseChecklist, extractDoneSteps, and
markCompletedSteps from packages/shared/checklist.ts. Add checklist
to the build-time copy list and import from generated/checklist.js.
Delete the redundant utils.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(pi): gitignore generated/ and built HTML files

These are build artifacts created by `bun run build:pi`. Untrack them
and add .gitignore to prevent re-adding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(pi): split server.ts into domain-organized modules

- server.ts is now a barrel re-exporting from server/ modules
- server/serverPlan.ts — plan review server
- server/serverReview.ts — code review server
- server/serverAnnotate.ts — annotate server
- server/helpers.ts — add requestUrl() to eliminate non-null assertions
- server/project.ts — linter fix (sanitizeTag import path)
- packages/ai/package.json — add pi-sdk-node export entry
- index.ts — fix waitForDone non-null assertion with guard check,
  update imports for generated/checklist.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(pi): parity audit fixes + shared code extraction

Systematic side-by-side audit of Pi vs Bun servers (A1-A22, B1-B2 complete).

Fixes found during audit:
- PlanServerResult.waitForDecision missing savedPath/agentSwitch/permissionMode
- Missing permissionMode option and /api/plan response field
- editorAnnotations created unnecessarily in archive mode
- repoInfo called per-request instead of cached at init
- Approve handler missing effectivePermissionMode fallback
- Deny handler missing savedPath in decision resolution
- Archive /api/plan response had extra pasteApiUrl
- Missing GET method guards on archive/plans, archive/plan, doc, obsidian/files, obsidian/doc, reference/files
- Review server had stray pasteApiUrl option/response field
- AI getCwd missing worktree support

Shared code extraction:
- packages/shared/favicon.ts — single source for favicon SVG
- packages/shared/integrations-common.ts — note app pure functions
- packages/shared/reference-common.ts — file tree building
- packages/shared/repo.ts — git remote parsing
- Updated all consumers to import from shared sources

For provenance purposes, this commit was AI assisted.

* fix: parity audit B3-C10 — review + annotate server fixes

Review server (B3-B17):
- diff/switch missing try/catch error handling
- git-add parseBody outside try/catch
- feedback missing try/catch error handling
- Unknown /api/ai/* paths now return 404 (both Bun and Pi)

Annotate server (C1-C10):
- Bun annotate server missing pasteApiUrl (short URL sharing broken)
- Added pasteApiUrl to Bun options, response, and both hook callers
- Pi repoInfo called per-request instead of cached at init
- Pi feedback missing try/catch error handling
- Missing GET method guards on doc and reference/files

For provenance purposes, this commit was AI assisted.

* fix: parity audit D3-D5 — draft error handling, editor annotations, resolve-file extraction

D3: Pi draft save handler missing error handling — added .catch() with 500 + console.error
D4: Pi editor annotation POST missing try/catch — added with "Invalid JSON" 400
D5: Extracted resolveMarkdownFile to packages/shared/resolve-file.ts
  - Replaced Bun.Glob with runtime-agnostic walkMarkdownFiles (readdirSync)
  - Made function sync (no longer async)
  - Pi handleDocRequest now uses shared resolveMarkdownFile instead of inline resolution
  - Gains Windows path normalization, isWithinProjectRoot security check
  - Deleted packages/server/resolve-file.ts re-export, consumers import from shared
  - Cleaned up stale await calls in hook entry, reference handler, and tests
  - All 19 resolve-file tests pass

For provenance purposes, this commit was AI assisted.

* fix: parity audit D6-D10 — integrations, PR naming, shared modules

D6: Fixed broken detectProjectNameSync — was using require() for
    non-existent exports. Now uses basename + sanitizeTag directly.
D7: Renamed checkAuth → checkPRAuth, getUser → getPRUser across
    Bun server, hook, and OpenCode plugin to match Pi naming.
    Also fixed stale resolve-file import in OpenCode plugin.
D8-D10: Verified clean — ide, project detection, network.

For provenance purposes, this commit was AI assisted.

* update openpackage.yml

* fix: bump Pi git-add test timeout to 15s for parallel suite stability

For provenance purposes, this commit was AI assisted.

* test: add route parity test — Bun ↔ Pi server route drift detection

For provenance purposes, this commit was AI assisted.

* fix(ci): update Pi generate step to use generated/ directory with full file list

The Pi extension was refactored to use generated/ subdirectory but the CI
generate step still used the old flat layout with a subset of files.

For provenance purposes, this commit was AI assisted.

* fix(ci): update release workflow Pi generate step to match new layout

Same stale generate step as test.yml — old flat layout, missing files.

For provenance purposes, this commit was AI assisted.

* fix(pi): update files array for modular server layout

The files array still referenced the old flat layout (server.ts monolith,
root-level generated files, deleted utils.ts). npm publish would have
produced a broken package missing server/ and generated/ directories.

For provenance purposes, this commit was AI assisted.

* feat: add TypeScript type-checking to CI pipeline

- Fix broken barrel export: buildFileTree/VaultNode re-exported from
  @plannotator/shared instead of reference-handlers (P1 bug)
- Fix server.port type narrowing in all 3 servers
- Fix AI provider type errors (claude-agent-sdk, codex-sdk, opencode-sdk, pi-sdk)
- Extract mapPiEvent to pi-events.ts to break Bun→Node type chain
- Add tsconfig.json to packages/shared, packages/ai, packages/server, apps/pi-extension
- Add `typecheck` script to root package.json
- Add type-check step to test.yml and release.yml CI workflows

For provenance purposes, this commit was AI assisted.

* fix(ci): use bun-types instead of @types/node for typecheck

CI environment has bun-types (includes Node types) but not
@types/node as a standalone package.

For provenance purposes, this commit was AI assisted.

* fix(ci): add @types/node for Node-runtime type checks

Pi extension and packages/shared run on Node, not Bun — they should
type-check against @types/node, not bun-types. Added @types/node as
a dev dependency so CI resolves it.

For provenance purposes, this commit was AI assisted.

* fix: cast Uint8Array.buffer to ArrayBuffer for TS 5.9 compat

crypto.subtle.importKey expects BufferSource, but TS 5.9 is stricter
about Uint8Array.buffer being ArrayBufferLike (includes SharedArrayBuffer)
vs ArrayBuffer. Explicit cast resolves the overload mismatch.

Astro pulls in TS 5.9 transitively, so CI resolves a different
TypeScript version than local dev. This fix works on both 5.8 and 5.9.

For provenance purposes, this commit was AI assisted.

* fix(ci): add bun-types as explicit devDependency

CI's bun install doesn't hoist bun-types to root node_modules when
it's only a transitive dep of @types/bun. Adding it as a direct
devDependency guarantees tsc can resolve it.

For provenance purposes, this commit was AI assisted.

* fix(ci): remove Pi extension from typecheck

Pi extension depends on @mariozechner/pi-* peer dependencies that
aren't installed in CI. Type-checking it requires Pi's runtime
environment. The three packages we check (shared, ai, server) are
sufficient to catch barrel export bugs and type errors. Pi extension
coverage comes from route parity tests and bun test.

For provenance purposes, this commit was AI assisted.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 00:44:54 -07:00
Michael Ramos 54a00e8552 feat: resizable split pane for code review diff viewer (#383)
Add a draggable divider between left/right panes in split diff view,
allowing users to resize each side. Uses CSS custom properties that
inherit into Pierre's shadow DOM for zero-rerender drag performance.
Ratio persists across sessions via cookie storage.

Also adds pierre-guard skill to protect the @pierre/diffs integration
surface (shadow DOM selectors, CSS variables, grid layout assumptions).

For provenance purposes, this commit was AI assisted.
2026-03-23 23:50:07 -07:00
Michael Ramos d4ff4a8df7 feat(ai): OpenCode provider via HTTP + SSE (#379)
* feat(ai): add OpenCode provider via HTTP + SSE

Bundle @opencode-ai/sdk to spawn `opencode serve` and communicate via
HTTP REST + SSE. Richest provider: fork, resume, streaming, tools, and
runtime permission approvals all work natively.

- OpenCodeProvider: lazy server spawn, shared HTTP client, dynamic model
  discovery via GET /provider
- OpenCodeSession: SSE event drain loop, system prompt via API field,
  permission reply (once/reject), abort via HTTP
- mapOpenCodeEvent: SSE → AIMessage mapping (text deltas, tool lifecycle,
  permissions, session status, errors)
- 11 new event mapping tests (78 total, 157 expect calls)

For provenance purposes, this commit was AI assisted.

* docs(ai): add OpenCode to AI features guide and code review docs

For provenance purposes, this commit was AI assisted.

* fix(ai): prevent double server spawn in OpenCode ensureServer()

Use a promise-mutex pattern so concurrent callers (e.g. fetchModels +
createSession) share a single server spawn instead of racing to create two.
Also remove empty finally block from SSE drain loop.

For provenance purposes, this commit was AI assisted.

* fix(ai): await SSE subscribe in OpenCode provider

The SDK's event.subscribe() is async — must be awaited before
destructuring the stream.

For provenance purposes, this commit was AI assisted.

* fix(ui): scrollable model dropdown with search filter

Cap dropdown height at min(320px, 50vh) so it doesn't overflow the
viewport when providers expose many models (e.g. OpenCode with 20+).
Add a search/filter input when there are more than 8 models.

For provenance purposes, this commit was AI assisted.

* fix(ai): close SSE stream, rename to opencode-sdk, move dep to root

- Close SSE stream in finally block to prevent connection leak on
  abort/early return
- Rename provider from opencode to opencode-sdk for consistency with
  claude-agent-sdk, codex-sdk, pi-sdk
- Move @opencode-ai/sdk dependency to root package.json alongside the
  other SDK dependencies

For provenance purposes, this commit was AI assisted.

* feat(ui): add first-run AI setup dialog for provider selection

Show a one-time modal on first review when AI providers are available.
Users pick a default provider and model from a 2-column grid, with a
brief feature intro and auth disclaimer. Persists via cookie, can be
changed later in Settings.

For provenance purposes, this commit was AI assisted.

* fix(ai): clear startPromise on failure so OpenCode provider can retry

If doStart() fails (port conflict, timeout, binary crash), the cached
rejected promise prevented all future ensureServer() calls from
retrying. Now cleared on failure and on dispose().

Also updates stale "future" comment in types.ts.

For provenance purposes, this commit was AI assisted.
2026-03-23 17:37:56 -07:00
Michael Ramos 877b0c78ac feat(ai): Pi provider via RPC subprocess (#377)
* refactor(ai): extract BaseSession and buildEffectivePrompt

Pull shared session lifecycle (~80 lines duplicated across Claude and
Codex providers) into BaseSession: query guard, abort, ID resolution,
generation counter. Extract first-query prompt prepending into
buildEffectivePrompt() in context.ts.

Both providers now extend BaseSession. Behavior-preserving — all 54
existing tests pass unchanged.

For provenance purposes, this commit was AI assisted.

* feat(ai): add Pi provider via RPC subprocess

Spawns `pi --mode rpc` and communicates via JSONL over stdio. No Pi SDK
bundled — user must have the pi CLI installed. Provider auto-discovers
available models at startup via get_available_models.

Includes: PiProcess (JSONL client), PiSDKProvider, PiSDKSession extending
BaseSession, mapPiEvent for streaming, Pi icon in provider UI, and 12
new tests (9 event mapping + 3 buildEffectivePrompt).

For provenance purposes, this commit was AI assisted.

* fix(ai): harden Pi provider error paths

- Surface startup errors when Pi process dies (bad config, missing API keys)
  instead of hanging the chat indefinitely
- Map tool-call errors as non-terminal tool_result instead of AIErrorMessage,
  so Pi can continue reasoning after a failed tool call
- Fix subprocess leak in fetchModels() by moving proc.kill() to finally block

For provenance purposes, this commit was AI assisted.

* fix(ai): detect Pi prompt rejections and process crashes

- Use sendAndWait for prompt command so RPC-level rejections (expired
  credentials, invalid session) surface as errors instead of hanging
- Emit process_exited instead of synthetic agent_end when Pi subprocess
  dies, so crashes show as errors instead of silent empty successes

For provenance purposes, this commit was AI assisted.

* docs(ai): add Pi to AI features guide, code review docs, and installation

For provenance purposes, this commit was AI assisted.

* style(ai): apply biome formatting to new files

For provenance purposes, this commit was AI assisted.
2026-03-23 15:50:30 -07:00
Michael Ramos b675e8677c feat: markdown file browser sidebar tab (#378)
* feat: markdown file browser sidebar tab (#367)

Add a Files sidebar tab that lets users browse and annotate markdown
files from their project. The project root is shown automatically —
no configuration needed. Users can add extra directories in Settings.

- Server: new /api/reference/files endpoint scans directories for .md
  files with broad exclusion list (node_modules, .git, dist, etc.)
- Server: projectRoot sent in /api/plan response (plan + annotate)
- UI: FileBrowser component with per-directory collapsible sections
- UI: useFileBrowser hook manages multi-directory tree state
- UI: Files tab in sidebar (collapsed + expanded), between Versions
  and Vault
- UI: Settings integration tab for additional directories
- Wired into linked doc system for annotation support

Closes #367

For provenance purposes, this commit was AI assisted.

* fix: simplify file browser - compute activeDirPath, fix re-fetch bug, dedupe settings logic

- Compute activeDirPath from activeFile + dirs instead of storing as
  separate state (removes state setter hack that abused setDirs as a
  read accessor)
- Fix auto-fetch not detecting directory changes: compare loaded paths
  against configured dirs instead of checking dirs.length === 0
- Extract addDirectory() helper in Settings to eliminate copy-pasted
  trim-validate-add-clear logic
- Remove "what" comments that described obvious code

For provenance purposes, this commit was AI assisted.

* fix: file browser prefix match, settings reactivity, and vault→files routing

- Add trailing slash to activeDirPath startsWith check to prevent prefix collisions
- Spread uiPrefs in handleFileBrowserChange so React sees a new reference on each settings edit
- Clear vaultBrowser.activeFile when opening a file browser doc to prevent misrouted links

For provenance purposes, this commit was AI assisted.
2026-03-23 15:47:58 -07:00
Michael Ramos 787923182b fix: allow empty submit and count linked doc annotations in annotate mode (#376)
* fix: allow empty submit and count linked doc annotations in annotate mode (#371, #373)

Annotate mode previously blocked submission when the main document had no
annotations, even if linked documents did. This removes the validation gate
in annotate mode entirely — the button always submits. The label dynamically
shows "Done" when empty or "Send Annotations" when any annotations exist
(including linked docs via a new reactive docAnnotationCount from useLinkedDoc).

Also fixes plan mode's Send Feedback button to include linked doc annotations
in its validation check, and updates the empty feedback message to
"User reviewed the document and has no feedback."

Closes #371, closes #373

For provenance purposes, this commit was AI assisted.

* fix: check linked doc annotations in Cmd+Enter keyboard shortcut path

The button click handler was updated to check getDocAnnotations() but
the keyboard shortcut path was missed, causing Cmd+Enter to approve
instead of sending feedback when only linked docs had annotations.

For provenance purposes, this commit was AI assisted.
2026-03-23 13:43:50 -07:00
Michael Ramos bf08dabc61 fix: remove duplicate 'Code Review' header in opencode plugin review feedback (#375)
Same issue as #370 — exportReviewFeedback() already includes a
'# Code Review Feedback' header. The opencode plugin was wrapping
that output with another header, causing the heading to appear twice.

Closes #374

For provenance purposes, this commit was AI assisted.
2026-03-23 12:21:04 -07:00
Michael Ramos 4b097197a0 feat(ai): AI backbone + inline chat for code review (#363)
* feat(ai): add provider-agnostic AI backbone for inline chat

Introduces @plannotator/ai — a pluggable provider layer that powers
AI features (inline chat, plan Q&A, code review assistance) across
all Plannotator surfaces.

- ProviderRegistry: class-based, multi-instance provider management
- SessionManager: in-memory session tracking with eviction, ID aliasing
- Context builders: system prompts and fork preambles for all three modes
- SSE streaming endpoints: session create/fork, query, abort, list
- Claude Agent SDK provider: fork from parent sessions, read-only tools,
  per-query abort, cached SDK import, concurrent query guard
- 34 passing tests covering all layers

For provenance purposes, this commit was AI assisted.

* fix(ai): address code review findings from PR #363

- Concurrent query race: reject with session_busy error instead of
  racing; add query generation counter so stale finally blocks don't
  clobber newer query state
- Text dropped before tool_use: mapSDKMessage now returns AIMessage[]
  and flushes accumulated text before each tool_use block
- defaultProvider returns instance ID: ProviderRegistry.getDefault()
  returns { id, provider } so capabilities endpoint sends the correct
  key for multi-instance setups
- Fork preamble re-sent on error: use _firstQuerySent boolean instead
  of _resolvedId to guard preamble injection
- Dead abortController params: remove unused abortController from
  CreateSessionOptions and AIProvider.resumeSession
- Unused context in memory: remove context from SessionConfig and
  baseConfig — already consumed to build systemPrompt/forkPreamble
- Fix tool_result mapping: SDK wraps tool results in SDKUserMessage
  (type "user"), not a "tool_result" type

For provenance purposes, this commit was AI assisted.

* feat(ai): inline AI chat on code review diffs

Add "Ask AI" to the code review toolbar — select lines, ask questions
about agent-written code, get streaming responses. Conversation persists
across file switches with inline markers on the diff showing where
questions were asked.

- useAIChat hook: SSE streaming, lazy session creation, multi-turn
- AskAIInput: question input with per-selection AI history
- InlineAIMarker: compact sparkle markers on diff lines via @pierre
- AITab: sidebar conversation grouped by file with collapsible sections
- SparklesIcon: shared 3-star sparkle from automations branch
- ReviewPanel: AI tab with message count badge, file group flash highlight
- Server: mount AI endpoints in review server with graceful fallback
- Support three question scopes: line, file, and general
- Toolbar ergonomics: typed text submits as AI question, no double popover

For provenance purposes, this commit was AI assisted.

* fix(ai): toolbar popover ergonomics and visual consistency

- X button closes entire toolbar in both modes (no ghost popover)
- Comment/Ask AI toggle is inline in the action row, same position
  in both modes (left side, mirrored layout)
- AI history appears below the input, not above
- File group flash highlight uses --muted to match file tree hover

For provenance purposes, this commit was AI assisted.

* fix(ai): visual consistency, shared primitives, markdown rendering

Visual consistency:
- AI tab file group headers match FileTree pattern (sizing, hover, badges)
- Q&A bubbles match annotation card padding and border-radius
- Empty state uses circle-bg icon (same as annotations)
- General input matches search input styling
- Removed redundant CSS classes, moved to Tailwind

Shared primitives:
- Extract formatTimestamp to shared utility (was duplicated)
- CountBadge component for consistent badge styling
- CopyButton component with hover-reveal + "Copied" flash

Affordances:
- Copy button on AI responses (hover-reveal)
- Send button on general input with keyboard shortcut hint
- Timestamps on Q&A pairs
- Keyboard shortcut title on Ask button

Markdown rendering:
- Add marked dependency for full block-level markdown in AI responses
- renderMarkdown utility using marked + DOMPurify
- Scoped .ai-markdown CSS for headings, lists, code blocks, blockquotes

For provenance purposes, this commit was AI assisted.

* feat(ai): permission inheritance + inline approval UI

The inline AI agent now inherits the user's existing Claude Code
permission rules via settingSources: ['user', 'project']. Tools
already approved in ~/.claude/settings.json auto-approve silently.

For tools not covered by existing rules, the SDK emits control_request
messages. These are mapped to a new AIPermissionRequestMessage type and
surfaced as inline approval cards in the AI tab — showing the tool name,
command/input, and Allow/Deny buttons.

Backend:
- Map control_request to AIPermissionRequestMessage in provider
- Store Query object on session for sending control_response back
- respondToPermission() method on AISession sends allow/deny
- POST /api/ai/permission endpoint for frontend → backend decisions
- Add settingSources to ClaudeSDKQueryOptions

Frontend:
- PermissionCard component with Allow/Deny buttons + resolved state
- useAIChat tracks permissionRequests array
- respondToPermission() POSTs decision to server
- AITab renders pending permission cards inline in conversation

For provenance purposes, this commit was AI assisted.

* fix(ai): streaming reactivity, maxTurns, popover→sidebar linking

Streaming fixes:
- State updates now create new object references via prev.map()
  instead of mutating objects inside the array — fixes React not
  re-rendering the sidebar during streaming
- Complete assistant text messages only used as fallback when no
  streaming deltas received (prevents duplicate text)
- Result text used as fallback when response is empty (agent spent
  all turns on tools)

Config:
- maxTurns raised to 99 (was 3 — agent ran out of turns reading
  files and never got to write its answer)

Popover → sidebar linking:
- Clicking AI history items in comment popover now passes questionId
  through to handleViewAIResponse, triggering the file group flash
  highlight in the sidebar (same behavior as clicking inline markers)

Debug logging (temporary):
- Server-side: [AI STREAM] logs every message the provider yields
- Client-side: [AI SSE] logs every message received in the browser

For provenance purposes, this commit was AI assisted.

* refactor(ai): simplification pass — types, perf, cleanup

From the three-agent code review analysis:

Quality:
- Rename AIMessage → AIChatEntry in review-editor to avoid collision
  with the wire-protocol AIMessage from packages/ai
- Type permissionMode as union (not string) in SessionConfig + QueryOptions
- Type _activeQuery with narrow interface instead of any
- Make toolUseId optional on AIToolResultMessage (SDK doesn't populate it)
- Remove redundant sessionIdRef double-write in useAIChat
- Fix handleAskGeneral useCallback dep: [aiChat] → [aiChat.ask]

Performance:
- Wrap QAPair in React.memo to prevent re-renders on sibling updates
- Memoize renderMarkdown output via useMemo (avoid re-parsing on every token)
- Fix auto-scroll useEffect: depend on messages.length not messages ref
  (prevents querySelectorAll on every streaming token)

Cleanup:
- Remove debug console.log from SSE endpoint and browser parser
- Extract isMac/submitHint to shared utils/platform.ts
- Extract generateId to shared utils/generateId.ts

Net: -2 lines. No behavior changes.

For provenance purposes, this commit was AI assisted.

* chore: biome lint fixes — import types, button a11y, unused import

- Use `import type` where only types are consumed (endpoints, CountBadge,
  CopyButton, renderMarkdown)
- Remove unused AIContext import from session-manager
- Template literal instead of string concat in context.ts truncate()
- Add type="button" + aria-hidden on CopyButton SVGs

For provenance purposes, this commit was AI assisted.

* feat(ai): Codex SDK provider + multi-provider backend

- Add CodexSDKProvider implementing AIProvider interface
  - Maps Codex ThreadEvents to AIMessage types
  - Delta tracking for streaming text from cumulative item.updated events
  - Supports resume, abort, sandbox modes
  - Factory self-registration pattern matching Claude provider
- Enhance capabilities endpoint with provider models + metadata
- Register both providers in review server (graceful degradation)
- Add CodexSDKConfig type, models list (GPT-5.4 family)
- Add reasoningEffort to CreateSessionOptions (Codex only)
- Extract DiffFile interface to shared types.ts
- Update CLAUDE.md with build order documentation
- 54 tests passing (20+ new for Codex event mapping)

For provenance purposes, this commit was AI assisted.

* feat(ai): provider settings UI + inline config bar

- Settings → AI tab: provider cards with per-provider model dropdowns
- Inline config bar in AI chat sidebar (provider, model, reasoning effort)
- Provider icons: Claude SVG + Codex PNG from marketing assets
- Cookie-based persistence for provider + per-provider model preferences
- useAIChat accepts providerId, model, reasoningEffort from props
- Session reset on provider/model switch (next question starts fresh)
- Reasoning effort selector (Codex only: Low/Medium/High/Max)
- Update code review docs with AI provider documentation

For provenance purposes, this commit was AI assisted.

* fix(ai): review fixes — reasoning props, input blocking, cwd resolution, codex-sdk dep

- Add missing selectedReasoningEffort + onReasoningEffortChange props to
  populated-state AIConfigBar (was only on empty-state, causing crash)
- Disable GeneralInput while streaming to prevent concurrent queries
- Thread cwd through CreateSessionOptions so AI sessions use the correct
  working directory (worktree-aware via getCwd resolver)
- Add @openai/codex-sdk as a real dependency (was only an optional peer)
- Eagerly verify codex-sdk importability at startup to avoid advertising
  a broken provider

For provenance purposes, this commit was AI assisted.

* fix(ai): polish config bar, input, and sidebar header

- Replace native <select> dropdowns with custom popover menus that show
  provider icons, model names, and checkmarks on active selection
- Remove disabled gear placeholder button
- Auto-grow textarea upward on multi-line input (capped at 6 lines)
- Push tab icons to far right in sidebar header (w-full on flex container)
- Add centered divider between file-scoped and general questions

For provenance purposes, this commit was AI assisted.

* fix(ai): honest fork capability + permissionMode doc accuracy

- Endpoint now checks capabilities.fork before calling forkSession(),
  falling back to createSession() for providers that can't truly fork
- Codex forkSession() now throws as the interface contract requires
  (was silently faking a fork with no history — misleading UX)
- Fixed permissionMode JSDoc: said "plan" but code defaults to "default"

For provenance purposes, this commit was AI assisted.

* fix(review): move search result count inside input field

Count now appears inline to the left of the X button instead of
squeezing the input from outside. Dynamic right padding accommodates
the count + clear button when a query is active.

For provenance purposes, this commit was AI assisted.

* fix(ai): use claude_code preset, pass selected code to queries

- Claude provider now uses { preset: "claude_code", append } instead of
  a raw system prompt, so inline chat inherits Claude Code's full
  built-in capabilities
- Stripped custom role/guidelines preamble from context builders — just
  emit the review context (diff, plan, document)
- handleAskAI now extracts selected code via extractLinesFromPatch and
  sends it with the query, fixing hallucinations on large PRs where the
  file's diff was truncated out of the system prompt

For provenance purposes, this commit was AI assisted.

* fix(ai): bundle both SDKs, remove peer dependencies

Both @anthropic-ai/claude-agent-sdk and @openai/codex-sdk are now
direct imports (no string indirection) so bun build --compile bundles
them into the binary. Users don't need to install SDKs separately.

Removed peer dependency declarations from packages/ai/package.json —
meaningless for a compiled binary.

For provenance purposes, this commit was AI assisted.

* refactor: canonical platform.ts, file renames, file-level comments

- Created packages/ui/utils/platform.ts as the single source for
  isMac, modKey, altKey, submitHint, isWindows
- Consolidated inline navigator.platform checks from 6 files
- Renamed renderMarkdown → renderChatMarkdown (distinct from plan editor)
- Renamed formatTimestamp → formatRelativeTime (clearer purpose)
- Added file-level comments to InlineAIMarker, PermissionCard, generateId

For provenance purposes, this commit was AI assisted.

* docs(ai): add AI features guide + link from settings

New docs page at guides/ai-features covering providers, models,
configuration, session behavior, permissions, and available settings.

AISettingsTab now links to the docs in both empty and populated states.

For provenance purposes, this commit was AI assisted.
2026-03-23 12:13:03 -07:00
Tiago Jacinto 5c8239f1d8 Add OpenPackage manifest (#368) 2026-03-23 11:37:20 -07:00