400 Commits

Author SHA1 Message Date
Michael Ramos fa4482d7d5 chore: bump version to 0.19.20
For provenance purposes, this commit was AI assisted.
2026-05-19 08:30:48 -07:00
Yoni Hornstein 42c85f0add fix(browser): treat no-op BROWSER values as unset so headless sessions can fall back (#756)
Claude Code's agent view (and similar background/headless environments) sets
$BROWSER=true to mean "do not launch a browser." The previous logic took that
literally and ran `true http://localhost:NNNN` via `Bun.$`, which exits 0 without
opening anything. The server then sits forever in waitForDecision() with no
visible UI and no way for the user to recover the URL.

Detect the documented no-op sentinels (`true`, `false`, `none`, `:`, `0`, `1`)
on both PLANNOTATOR_BROWSER and BROWSER and treat them as if the variable were
unset. This lets shouldTryRemoteBrowserFallback() reach the VS Code IPC
fallback in remote sessions and lets openBrowser() fall through to the
platform default when nothing else is configured.

Refs #154.
2026-05-19 08:03:49 -07:00
Michael Ramos 82636e1286 Add interactive goal setup UI (#731)
* Add interactive goal setup UI

* Refine goal interview skip and question flow

* Fix review findings: recommendation combo, option-only recs, single deselect

* Persist goal setup working JSON files

* Refine goal setup copy and facts controls

* Remove generated goal package from PR

* Address goal setup review issues

* Remove goal setup slash command adapters

* Disable fact comment attachments

* Fix goal setup fact comment state

* Address goal setup review cleanup

* Fix goal setup fact submission edge cases
2026-05-18 08:39:29 -07:00
Michael Ramos f471ebf99e chore: bump version to 0.19.18
For provenance purposes, this commit was AI assisted.
2026-05-17 23:53:11 -07:00
Andrea Arturo Venti Fuentes 1ec5243448 chore(deps): drop stub @types/dompurify
dompurify ships its own types since v3.x — the @types package is an
empty stub. Removes it from both root and packages/ui devDependencies.

Credit to @av1155 who identified this in #726.

For provenance purposes, this commit was AI assisted.
2026-05-17 23:24:41 -07:00
Michael Ramos 9fed785141 chore: bump version to 0.19.17
For provenance purposes, this commit was AI assisted.
2026-05-14 17:36:52 -07:00
Michael Ramos 12e3e2d35b chore: bump version to 0.19.16
For provenance purposes, this commit was AI assisted.
2026-05-13 12:42:12 -07:00
Michael Ramos 95750ab657 feat: search-based code navigation with peek view (#711)
* feat: search-based code navigation with peek view (#694)

Add IDE-like code navigation to the review UI. Cmd/Ctrl+click a token
in a diff to find its definitions and references across the repo via
ripgrep, displayed in a VS Code-style peek panel below the diff.

Backend: bounded rg search with language-aware definition patterns
(TS/JS, Python, Go, Rust), ranked results (same file > changed files >
same directory), confidence labels, and graceful degradation when rg
is not installed. Both Bun and Pi servers implement the endpoints.

Frontend: Dockview peek panel with syntax-highlighted full-file preview
on the left and grouped reference list on the right. Clicking a
reference scrolls the preview; double-clicking an in-diff result
navigates to the file with a gold line flash.

Closes #694

* fix: resolve Pi server TypeScript errors for code-nav

Add missing spawn import, type the close callback parameter,
and use double-cast for parseBody → CodeNavRequest.

* fix: clear loading state on cached preview hits

Without this, clicking a cached file while a fetch is in-flight
leaves isLoading stuck true — the spinner hides the preview.

* feat: show pointer cursor on Cmd/Ctrl+hover for navigable tokens

Adds pn-token-nav class with thicker underline and pointer cursor
when hovering a token while holding the modifier key, signaling
the token is Cmd+clickable for code navigation.

* refactor: remove go-to-diff navigation from peek panel

Strip the in-diff badge, double-click-to-jump, highlightDiffLine
wiring, and onCodeNavGoToDiff from the peek panel. The peek view
is the primary interaction — jump-to-diff adds complexity without
clear value at this stage.

* chore: remove dead code from code-nav cleanup

Delete unused highlightDiffLine.ts, remove codeNavChangedFiles
and codeNavActiveSide from context and App.tsx, drop stale
extractChangedFiles import.

* chore: add code-nav endpoints to AGENTS.md, remove dead activeSide state

* fix: don't classify bare indented calls as definitions

Change the TS/JS method pattern from zero-or-more (*) to
one-or-more (+) declaration keywords, so plain calls like
startServer(config) are no longer misclassified as definitions.

* feat: show toast when code-nav is unavailable in platform-only PR mode

Instead of opening the peek panel and showing misleading "No results",
Cmd+click in non-local PR mode shows a brief toast explaining that
code navigation requires a local checkout.

* fix: strip hljs hardcoded background from code-nav preview

The highlight.js github-dark theme sets a fixed dark background
on all .hljs elements. Apply transparent override for the entire
peek preview so code inherits the active theme's background.
2026-05-13 12:24:37 -07:00
Michael Ramos 32e871415f chore: bump version to 0.19.15
For provenance purposes, this commit was AI assisted.
2026-05-13 08:18:34 -07:00
Michael Ramos 6db7ca9184 feat(review): pick a commit as the diff base (#709) (#723)
* feat(review): pick a commit as the diff base (#709)

Widens the base picker for `branch` and `merge-base` diff modes from
"branch only" to any commit-ish. `getGitContext` now returns the last
20 commits on HEAD; the picker shows them in a new "Recent commits"
group, and SHA-like or HEAD~N queries get a "Use … as base" affordance
for manual entry. Backend changes are minimal because git diff already
accepts any commit-ish on the left side of the range.

* feat(review): tabbed Branches/Commits in base picker (#709)

Replaces the three-section list (Local / Remote / Recent commits) with
a Branches | Commits tab strip — commits were below a scroll wall in
repos with many branches. SHA-like and HEAD~N queries auto-focus the
Commits tab so manual entry stays one keystroke away.
2026-05-13 07:09:17 -07:00
Cody Partington b04c9e1f30 fix(review): persist file comment drafts across close/reopen (#721)
* fix(review): persist file comment drafts across close/reopen

File comment popover lost in-progress text when dismissed via click-outside / Escape / X — local state died with the unmount. Line-comment toolbar already does this via a module-level draft map; mirror the pattern in `CommentPopover` behind an opt-in `draftKey` prop and wire both file-comment call sites (`AllFilesDiffView`, `DiffViewer`).

* fix(review): scope file-comment draft key by PR identity

File-path-only key leaked drafts across in-place PR switches.

* fix(review): remount all-files comment popover on context change

The all-files panel shares one CommentPopover instance across every
file. Switching files (or PR/scope) batches close+open into a single
state transition, so React reuses the instance with stale text in
state and the draft-sync effect writes it under the new draftKey.
Key the popover by the same expression as draftKey so any change in
file, PR, or scope forces a fresh mount that reads its own draft.
Also clear the anchor when the file list reloads so a popover
anchored to a removed DOM node doesn't survive a diff swap.

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-05-13 06:54:33 -07:00
Michael Ramos 927ca6d27e fix(gitlab): handle concatenated JSON pages from glab --paginate (#717)
glab api --paginate joins page responses as adjacent JSON arrays
(`[...][...]`), which JSON.parse rejects. This caused MR reviews to fail
with "JSON Parse error" for any MR with more than 100 changed files.

Replace the bare JSON.parse with a walker that splits the output into
top-level arrays (string- and depth-aware so `][` inside diff content
is not mistaken for a page boundary) and merges them.

Fixes #714
2026-05-13 05:33:07 -07:00
Michael Ramos 3fb0b9cf03 fix(gitlab): persist unposted inline comments + split pr-provider from browser-safe pr-types (#719)
Closes #680. Two changes that landed together because the persistence fix
exposed a hidden architectural constraint.

1. GitLab inline comments: when one or more discussion POSTs failed
   (e.g. transient `i/o timeout`), the failed comment bodies were lost.
   Now `submitGlMRReview` writes them to
   `~/.plannotator/failed-comments/{host}-{project}-mr{iid}-{ts}.json`
   in both the all-fail and partial-fail branches. The throw-vs-warn
   split is preserved deliberately: all-fail throws so the UI retries
   from a clean state, partial-fail warns so the UI doesn't resubmit
   already-posted content.

2. Split `packages/shared/pr-provider.ts` into `pr-types.ts`
   (browser-safe types + pure label/URL helpers) and `pr-provider.ts`
   (server-only dispatch that imports pr-github / pr-gitlab). The
   review-editor browser bundle previously dragged pr-gitlab.ts in as
   dead code via static imports, which silently constrained the file
   to never use Node built-ins. Adding `fs`/`os`/`path` for (1) broke
   the review build until we routed browser imports to pr-types and
   left server callers on the now server-only pr-provider facade.

Server-only `pr-provider.ts` re-exports `pr-types` so existing
server-side imports keep working unchanged.
2026-05-13 05:27:28 -07:00
Michael Ramos d21d0de074 fix: accept PLANNOTATOR_PORT=0 without spurious warning (#716)
Port 0 is the conventional "let OS pick a random port" value and is
already the local-mode default. Validation rejected it as invalid and
printed a warning, even though the fallback returned 0 anyway.

Fixes #715
2026-05-13 04:51:56 -07:00
Michael Ramos 4c8338f53f feat(ui): copyable hook path + guidance in Settings Hooks tab (#707)
* feat(ui): show copyable file path and guidance in Hooks settings tab

Always return the improvement hook file path from /api/hooks/status
(actual path when present, expected path when absent) so the UI can
display it in both states. Add CopyPathButton with tilde-shortened
display and full-path clipboard copy. When active, guide users to
edit directly or regenerate via /plannotator-compound. When absent,
show expected path and both creation options (auto-generate or manual).

* fix(ui): anchor displayPath on .plannotator instead of guessing homedir

The regex assumed home directories are always two segments deep
(/Users/x), which breaks for /root on Linux — it would capture
/root/.plannotator as the home prefix and display ~/hooks/... instead
of ~/.plannotator/hooks/..., leading users to create the file in the
wrong location.
2026-05-11 21:07:58 -07:00
Madhu C.S. 4272312eb0 Add jj evolog diff mode to code review UI (#702)
* review: add jj evolog diff mode to code review UI

Adds a new "Evolution diff" diff type for jj repos that shows what
changed between two evolutions (amendments) of the current change — i.e.
what was amended since a prior state of `@`.

Changes:
- Add `JjEvoLogEntry` type to `review-core.ts` and export from
  `shared/types.ts`
- Add `getJjEvoLogEntries()` to `jj-core.ts` — runs `jj evolog` with a
  tab-delimited template to collect commit ID, description, and age for
  each historical state of `@`
- Add `jj-evolog` to the `DiffType` union in `review-core.ts` and to the
  `JJ_DIFF_TYPES` set in `vcs-core.ts`
- Wire evolog mode into `runJjDiff()` and `getJjDiffArgs()` — defaults
  to the second evolog entry (the most recent prior state) when no
  explicit base is given; propagates `error` when there's no history
- Expose `jjEvologs` on `GitContext` and include the "Evolution diff"
  option in `diffOptions` only when two or more entries exist
- Add `EvoLogPicker.tsx`: a Radix Popover component that lists evolog
  entries and lets the user pick which prior state to compare against;
  shows commit ID, description, and age with a "default" badge on the
  auto-selected entry
- Update `FileTree.tsx` to render `EvoLogPicker` instead of
  `BaseBranchPicker` when `jj-evolog` is the active diff type
- Update `App.tsx` to thread `jjEvologs` and `detectedEvoBase` through
  to the file tree, auto-set the base to the second evolog entry on mode
  switch, and show an appropriate empty-state message
- Update `agent-review-message.ts` with an inspect hint for the evolog
  diff type
- Add unit tests covering: diff args, evolog output parsing, error
  cases, and the auto-base-selection fallback path

* review: fix evolog picker overflow and add evolog test sandbox

Fix the EvoLogPicker trigger and popover rows to handle narrow sidebar
widths — age strings now truncate instead of bleeding out of the container.

Add --with-evolog flag to the JJ manual test sandbox that amends the
working-copy change four times to create realistic evolution history.
Also writes a create-evolog.sh helper script into the sandbox for
on-demand evolog creation without the flag.

* fix: restore compare target when switching away from jj-evolog

When leaving Evolution diff mode, the evolog commit ID was left in
selectedBase and leaked into the next diff request. For jj-line this
caused jjLineBaseRevset() to treat the commit ID as a bookmark name,
producing a "didn't resolve to any revisions" error.

Now explicitly passes the restored default branch as baseOverride to
fetchDiffSwitch when leaving evolog, avoiding the React state batching
race that made the previous setSelectedBase-only approach insufficient.

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-05-11 21:07:15 -07:00
Michael Ramos fcf2ba4cf5 fix: indent loose list continuation content under parent bullet (#705)
Closes #704
2026-05-11 16:41:26 -07:00
Michael Ramos c5b8535173 chore: bump version to 0.19.14
For provenance purposes, this commit was AI assisted.
2026-05-11 12:44:14 -07:00
Michael Ramos 071083e97d chore: bump version to 0.19.13
For provenance purposes, this commit was AI assisted.
2026-05-11 11:23:51 -07:00
Michael Ramos d0bb3badda chore: bump version to 0.19.12
For provenance purposes, this commit was AI assisted.
2026-05-11 09:38:27 -07:00
Michael Ramos 1eb561551c feat: standalone skills package + HTML render-annotate mode (#687)
Add --render-html flag to plannotator annotate that renders HTML files
as-is in an iframe instead of converting to markdown. Includes annotation
support via postMessage bridge, sharing via paste service, and theme
inheritance from Plannotator's 30+ themes.

New skill: plannotator-visual-explainer — wraps nicobailon/visual-explainer
with Plannotator theme tokens, extended patterns (timelines, SVG diagrams,
code blocks, risk tables, Pierre diffs via CDN), and plan/PR-specific guidance.

All three servers (Bun, Pi, OpenCode) support the new flag.
2026-05-11 10:30:49 -04:00
Andrey Skripalschikov 50191e6c1d Add diff line background intensity option (#691) 2026-05-11 10:18:27 -04:00
Michael Ramos 347663a4de feat(pfm): code line range references, hover preview, sketch Graphviz (#692)
Code file line range references with hover preview + Graphviz improvements.

Line ranges: `file.ts:42` and `file.ts:10-20` are fully supported with
syntax-highlighted hover preview popover (150ms delay, GitHub-style
persistence). New parseCodePath() utility, server-side line suffix
stripping on both Bun and Pi servers, ambiguous picker preserves line
suffix. useCodeFilePopout moved to hooks/pfm/.

Graphviz: responsive container height from SVG aspect ratio, white
background polygon removed, default colors (black, lightgrey) replaced
with theme tokens via SVG post-processing. User-specified colors
preserved.
2026-05-11 10:13:47 -04:00
Michael Ramos f4493fc6ff Optimize plan editor rendering (#696)
- Replace blocks/frontmatter useState + useEffect with useMemo (eliminates mount cascade)
- Migrate toast notifications from React state to Sonner (App no longer re-renders for toasts)
- Extract AppHeader component with React.memo (header no longer re-renders on unrelated state changes)
- Fix color transition flash on initial load (suppress global * transition until mount settles)
- Add Tailwind @source path for new editor components directory
2026-05-11 08:55:18 -04:00
Michael Ramos 579127e2c9 feat(ui): add Hooks visibility tab to Settings dialog (#695)
Add Hooks tab to plan review Settings dialog for visibility into the
improve-context system. PFM Reminder toggle card with config persistence,
Improvement Hook status card with collapsible content preview. New
GET /api/hooks/status endpoint and pfmReminder in POST /api/config
on both Bun and Pi servers. Dev mock API wired up.
2026-05-11 08:26:58 -04:00
Michael Ramos 13c667c044 feat(hook): PFM reminder & improvement hook support across all runtimes (#689)
PFM reminder & improvement hook support across Claude Code, OpenCode, and Pi.

- Add opt-in PFM reminder (pfmReminder config flag) injected on EnterPlanMode
- Wire composeImproveContext() into all three runtimes
- Fix OpenCode system.transform array reference bug (pushes were going to dead array)
- Fix install scripts silently stripping PreToolUse/EnterPlanMode hook entry
- Isolated Pi sandbox testing (--no-extensions -e)
2026-05-11 08:14:13 -04:00
Graeme Folk b5cd3d5120 fix(review): resolve jj default target from trunk (#686)
Resolve the default JJ compare target from trunk() so review UI and diff behavior use the same concrete bookmark when JJ can identify one.
2026-05-08 19:10:58 -07:00
Andrey Skripalschikov 7329efb980 Add diff tab size option (#685) 2026-05-08 11:45:47 -07:00
Michael Ramos 36948e8572 chore: bump version to 0.19.11
For provenance purposes, this commit was AI assisted.
2026-05-07 23:24:00 -04:00
Graeme Folk 69ef11bdfb feat(review): add jj review workflows (#675)
* feat(review): add jj support for local diffs

* feat(review): add jj review workflows

* fix(review): tighten jj diff defaults

* test(review): add jj manual sandbox

* fix(review): share jj agent diff prompts

* fix(review): quote jj agent revsets

* feat(review): share jj vcs handling with pi

* fix(review): tighten jj bookmark and pi pr handling

* fix(review): tighten jj defaults and detection

* fix(review): harden jj diff and vcs detection

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-05-07 19:57:33 -07:00
Michael Ramos 0e4d301925 Review UI refinements: separator styling and header layout (#683)
* Collapse viewed files in all-files review

* Style diff hunk separators to be slimmer and subtler

Reduce separator height from 32px to 24px, use theme-integrated
semi-transparent backgrounds via --diffs-bg-separator-override,
and fade text/buttons with opacity for a less visually heavy bar.

* Refine review header layout

Move sidebar toggles to far right, options menu to their left.
Add divider between file tree button and repo label.
Remove worktree pill from header (still accessible via menu).

* Fix gutter drag selecting only one line

Switch from deprecated enableHoverUtility + renderHoverUtility to
enableGutterUtility + onGutterUtilityClick. The old API never entered
Pierre's gutterSelecting mode, so dragging the gutter button always
reported a single line. The new API receives the full drag range.

Closes #679
2026-05-07 15:54:06 -07:00
Michael Ramos 71492ec880 chore: bump version to 0.19.10
For provenance purposes, this commit was AI assisted.
2026-05-06 16:13:46 -07:00
Michael Ramos 49c55e6e25 Revert "Expose bypass clear reminder permission mode (#668)"
This reverts commit 3b88415aeb.
2026-05-06 16:10:31 -07:00
Michael Ramos cc57710df2 chore: bump version to 0.19.9
For provenance purposes, this commit was AI assisted.
2026-05-06 15:57:39 -07:00
Michael Ramos a22ee3fbe5 fix(ui): remove shimmer animation from clickable file paths (#676)
The repeating shimmer animation on code file links was distracting and
stole focus while reading plans. Closes #672.
2026-05-06 13:51:30 -07:00
Nehr 3b88415aeb Expose bypass clear reminder permission mode (#668)
* Preserve truthful approval semantics for Claude plan bypass

Thread a clear-context reminder flag through approval decisions, expose a Claude Code-only approval entry that requests bypass mode, and keep the hook response honest by emitting a reminder instead of claiming context was cleared.

Constraint: Claude Code PermissionRequest hooks have no documented clearContext response field and bypassPermissions is only a request when the mode is available.

Rejected: adding a permission-mode enum or undocumented clearContext field | those would misrepresent hook capabilities and broaden the contract.

Confidence: high

Scope-risk: moderate

Directive: Do not claim Plannotator clears context until Claude Code documents a hook field for that behavior; keep reminder copy truthful.

Tested: bun test packages/server apps/hook; bun test packages/editor/wideMode.test.ts packages/ui/hooks/useAgentSettings.test.ts; bun test; bun run build:review; bun run build:hook; bun run typecheck; git diff --check --cached.

Not-tested: Browser warning-dialog replay and interactive Claude Code smoke test.

Co-authored-by: OmX <omx@oh-my-codex.dev>

* Keep approval payloads truthful across Claude Code and Pi

Constraint: Claude Code hooks cannot clear context directly; the new action remains a truthful reminder plus bypass-mode approval.
Rejected: Sending OpenCode agent-switch state for Claude Code | it leaked build (?) UI state and misleading payload fields.
Confidence: high
Scope-risk: narrow
Directive: Keep OpenCode agent switching gated to opencode-origin approval payloads.
Tested: bun run typecheck; bun test; bun run build:review; bun run build:hook
Not-tested: Manual browser click-through of the Claude Code dropdown.

* Expose the clear-context reminder permission default

Constraint: Claude Code hooks can only emit a systemMessage nudge, not clear context directly.

Rejected: Server protocol changes | existing permissionMode plus clearContextNudge wire fields already support the behavior.

Confidence: high

Scope-risk: narrow

Directive: Keep bypassPermissionsClearReminder as a UI/storage-only synthetic mode that decomposes before /api/approve.

Tested: bun test packages/editor/approvalBody.test.ts; bun run --cwd apps/review build && bun run build:hook; bun run --cwd packages/ui typecheck; git diff --check

Not-tested: Root bun run typecheck could not run because tsc was not on PATH for the root script.

* Reject invalid persisted permission modes

Constraint: Stored browser values can be stale, corrupt, or from a future Plannotator build.

Rejected: Trusting the storage read with a type assertion | invalid values could flow into UI state and approval request construction.

Confidence: high

Scope-risk: narrow

Directive: Keep PermissionMode storage reads validated against PERMISSION_MODE_OPTIONS when adding or renaming modes.

Tested: bun test packages/editor/approvalBody.test.ts; bun run --cwd packages/ui typecheck; git diff --check

Not-tested: Full repo typecheck/build, outside this review-fix scope.

* Ensure approvals use the live permission setting

Settings persisted permission mode changes, but App kept the original mode in React state and used that stale value when building approval payloads. Push the Settings change back into App so the selected clear-reminder mode reaches the hook decision.

Constraint: Permission mode storage is cookie-backed while approval payload construction reads App state.\nRejected: Read permission cookies during approval | would couple approval construction to browser storage and duplicate Settings state.\nConfidence: high\nScope-risk: narrow\nDirective: Keep permission-mode writes synchronized with approval state when adding or changing modes.\nTested: bun test packages/editor/approvalBody.test.ts packages/ui/components/ApproveDropdown.test.tsx; bun x tsc --noEmit -p packages/ui/tsconfig.json; bun run build:hook; direct Playwright hook smoke verified bypassPermissionsClearReminder UI and hook payload.\nNot-tested: Live interactive Claude CLI session; direct hook/server simulation covered the wire output.

---------

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-05 12:27:16 -07:00
Michael Ramos ff2759b553 chore: bump version to 0.19.8
For provenance purposes, this commit was AI assisted.
2026-05-05 00:26:16 -07:00
Michael Ramos 308e272b10 feat(ui): 49 themes with matched syntax highlighting + preview mode (#664)
* feat(ui): expand theme system to 49 themes with matched syntax highlighting

Add 25 new themes sourced from shiki's bundled TextMate themes (Everforest,
Nord, Solarized, GitHub, One Dark Pro, Night Owl, Material, Vitesse, Vesper,
Poimandres, Ayu Dark, Houston, Laserwave, Andromeeda, Aurora X, Dark+/Light+,
Min, One Light, Plastic, Red, Slack, Snazzy Light, Vitesse Black). Each theme
was audited against the shiki source JSON to ensure correct color mapping.

Wire pierre/diffs syntax highlighting to match the active UI theme via a
SHIKI_THEME_MAP (35 of 49 themes get matched syntax token colors in diffs).

Add theme preview mode — a compact bottom-docked picker that lets users
browse themes while seeing the page update live behind it.

Settings panel enhancements:
- Syntax highlighting badge on themes with diff color matching
- Mode unavailability dimming (dark-only themes dim in light mode)
- Plannotator pinned first, rest alphabetical

* fix(themes): address review feedback — contrast, stale CSS, syntax mode safety

1. Material light: foreground #90A4AE → #546E7A (Blue Grey 600) for readable
   UI text (~7:1 contrast on white). Primary-foreground #FAFAFA → #263238
   for contrast on teal buttons.

2. DiffHunkPreview: add colorTheme to useEffect deps so unsafeCSS (bg/gutter
   injection) recomputes when themes change, preventing stale mixed-theme
   state in mounted tooltips during preview mode.

3. SHIKI_THEME_MAP: make mode-aware with null entries for modes lacking a
   shiki counterpart. resolveSyntaxTheme() returns undefined when the current
   mode has no match, so pierre falls back to its defaults rather than
   applying dark token colors on a light background (fixes Tokyo Night light).

* fix(review): self-review cleanup — memo, double-call, escape key

- DiffHunkPreview: extract resolveSyntaxTheme to a variable instead of
  calling it twice in the spread expression
- PierreDiffContent memo: compare syntaxTheme by dark/light string values
  instead of reference equality (resolver returns new objects each call)
- Preview mode: add Escape key handler to return to full settings

* fix(ui): consume Escape event in theme preview to prevent leaking to page
2026-05-04 23:25:30 -07:00
Michael Ramos 84a0b434f9 fix(ui): smart resolution + existence-validation for code-file paths (#654)
* fix(ui): smart resolution + existence-validation for code-file paths

The bare-prose / backtick path detector linkifies anything that looks
like a code path. Two failure modes regularly produce dead links: prose
abbreviations like `editor/App.tsx` (real file is
`packages/editor/App.tsx`) and references to files the plan proposes
but hasn't created yet. Both 404 on click with no UX cue.

Resolves abbreviated paths via a case-insensitive suffix-match against
a cached project walk (`resolveCodeFile` in `packages/shared/resolve-file.ts`),
mirroring what `resolveMarkdownFile` already does for markdown. The walk
is pre-warmed when the plan/annotate server boots and on every
`/api/doc` request, with a 30s TTL so newly-created files can resolve
mid-review. Storing the walk as a Promise makes the cache race-safe —
concurrent callers piggyback rather than starting a second walk.

A new `POST /api/doc/exists` endpoint takes a batch of candidate paths
and reports `found` / `ambiguous` / `missing` / `unavailable` per path.
On the frontend, `useValidatedCodePaths` extracts candidates from the
markdown on load and POSTs once. The renderer reads the result via
`CodePathValidationContext`: `found` opens directly with the resolved
absolute path, `ambiguous` opens a `CodeFilePicker` popover listing all
matches (common in monorepos where `App.tsx` exists in several
packages), `missing` demotes the link to plain code, and `unavailable`
falls back to the optimistic linkification we have today. While
validation is in flight, every detected path renders as a link, so
first paint is unchanged.

The detection itself gets a shape filter (`isPlausibleCodeFilePath`)
that hard-rejects shell brace expansion (`{a,b}`), glob wildcards, and
whitespace, while explicitly allowing `[` / `]` so Next.js dynamic
routes (`app/[slug]/page.tsx`) still resolve. The bare-prose regex moves
out of `InlineMarkdown.tsx` into `code-file.ts` so the renderer and the
new server-side extractor use the same source of truth, and the
extractor strips fenced code blocks, HTML comments, and URL ranges
before scanning so it only emits candidates the renderer would actually
paint.

Pi extension mirrors the Bun changes (handler upgrade, pre-warm,
`/api/doc/exists` route). When the popout's `/api/doc` request 404s the
dialog now surfaces "File not found in repo: <path>" instead of
silently swallowing the error.

Tests: `code-file.test.ts` extended with shape-filter and Next.js-route
cases; new `extract-code-paths.test.ts` covers extraction, dedup,
fenced/HTML/URL exclusion, and the URL-with-parens regression; new
`resolve-file.test.ts` covers the suffix-match strategy, leading `./`
handling, ambiguous results, and ignored-dir behavior.

* fix(ui): thread doc-base through code-path validator

Out-of-tree linked docs (and annotate-mode files outside cwd) reference
files relative to themselves. The validator was resolving against cwd
only, so those paths got marked missing and the renderer demoted them
to plain text — even though clicks still resolved correctly via base.

Also tightens the suffix-match's leading-segment strip so `../foo.ts`
no longer silently misresolves to an unrelated `foo.ts` in cwd.

Cleanup: delete unused extract-code-paths import in reference-handlers,
add the export entry to packages/shared so consumers don't rely on
Bun's lenient subpath resolution. Add TODO(security) comments at both
handleDocExists sites flagging that absolute paths bypass project-root
containment.

223 tests pass (3 new resolver cases for baseDir + ../ regression).

* refactor(editor): dedupe activeDocBaseDir; expand security TODO

Self-review fallout:

1. The doc-base expression `linkedDocHook.filepath ? dirname(...) :
   imageBaseDir` lived in two places (click-time URL builder and Viewer
   prop). If they drift, validator and click resolve against different
   bases and we silently re-introduce the demote-correct-link bug.
   Extract to a single useMemo.

2. The handleDocExists security TODO mentioned absolute paths in
   `paths[]` but I just added `base` acceptance, which has the same
   shape of leak (hostile sender supplies base=/secret/dir + relative
   path). Both vectors flagged in one TODO, mirrored Bun + Pi.

223 tests pass; both builds clean.

* fix(ui): code-file popout shows real error; misc consistency

Review fallout:

- `CodeFilePopout` hardcoded "File not found in repo" regardless of
  cause. The hook already captures the server's error string, so an
  ambiguous-path 400 (which can happen if a user clicks an optimistic
  link before validation completes) was surfacing as a misleading
  not-found message. Render the actual `error` and only show the
  planned/future-file caveat when the error matches "file not found".
- `InlineMarkdown` emitted demoted bare-prose paths as raw strings
  while every other plain-text branch in `emitPlainTextWithBareUrls`
  routes through `transformPlainText`. Cosmetic-only today since
  paths rarely contain transformable content, but the divergence
  invites copy-paste rot. Routed through the same helper.
- CLAUDE.md missed the new POST /api/doc/exists endpoint in both
  Plan Server and Annotate Server tables. Added.

223 tests pass; both builds clean.

* fix(ui): demote paths the extractor excluded from validation

When the validator is ready but a candidate path has no entry in the
validated map, the extractor intentionally excluded it — e.g. inside
an HTML comment or fenced code block. The renderer was optimistically
linking these because gateCodePath returned 'link' for missing entries.

Found during manual testing: `<!-- packages/editor/App.tsx -->` inside
a paragraph (parser doesn't recognize HTML comments as block-level)
was rendered as a clickable link. The extractor correctly stripped the
comment, but the renderer's optimistic fallback overrode that.

Also adds manual test harness: tests/manual/path-detection/ with
sandbox setup + three launcher scripts (plan mode, annotate in-tree,
annotate out-of-tree) covering ~30 test cases.

223 tests pass; both builds clean.

* fix(ui): skip HTML comments in InlineMarkdown scanner

The parser doesn't recognize <!-- --> as block-level HTML, so comments
inside paragraphs fall through to InlineMarkdown. The scanner then
finds paths inside the comment text and linkifies them.

The previous gateCodePath fix (demote when not in validated map) didn't
help here because the same path appeared elsewhere in the document —
the map had an entry from the non-comment occurrence.

Fix: match <!-- ... --> at the top of the scanner loop and skip the
entire comment. HTML comments should be invisible per CommonMark spec.

* fix: handle unavailable variant in markdown resolve narrowing

The shared ResolveResult type gained an `unavailable` variant for code
files. The markdown resolver never returns it, but TS can't narrow
past it without an explicit guard. Both Bun and Pi handlers now guard
`not_found || unavailable` before accessing `result.path`.
2026-05-04 14:21:16 -07:00
Michael Ramos 5bc57fc5bd feat(ui): shortcut registry foundation (#652)
* feat(ui): shortcut registry foundation

Salvages the foundation of #380 by @gjermundgaraba — the original PR sat
6 weeks waiting for review and was closed by the author. This lands the
engine, scope data, and auto-generated marketing docs page, deferring
per-component handler migration to follow-up PRs.

Layout:
- packages/ui/shortcuts/{core,runtime,index}.ts — engine + barrel
- packages/ui/shortcuts/plan-review/* — scopes for plan editor surfaces
- packages/ui/shortcuts/code-review/* — scopes for review editor surfaces
- packages/{editor,review-editor}/shortcuts.ts — per-app surface composition
- apps/marketing/src/{components,lib} — auto-generated docs page

App behavior is unchanged: existing ad-hoc keydown handlers keep running.
The registry is plumbing waiting for migration PRs to wire to it.

Co-authored-by: Gjermund Garaba <gjermund@garaba.net>

* feat(ui): add missing shortcuts; warn on hold-binding gap

Addresses code review on #652.

Adds five live shortcuts that were missing from the registry, so the
auto-generated /docs/reference/keyboard-shortcuts page is no longer
incomplete:

- Mod+P (print plan)            — plan review + annotate
- Mod+B (toggle file tree)      — code review (new "Layout" section)
- Mod+. (toggle review sidebar) — code review
- Mod+Shift+T (toggle demo tour, dev-only) — code review
- Mod+Shift+F (focus PR comments search)   — new code-review/prComments scope

Also whitelists `.` as a valid normalized binding token in the registry
validator (keeps catching typos like Cmd/Ctrl while allowing legitimate
punctuation keys).

Hold-binding gap: the registry's `Alt hold` advertises a shortcut that
`useShortcutScope` cannot dispatch. Today this is harmless because the
existing `useInputMethodSwitch` hook handles it bespoke. Added warning
comments at the binding declaration and next to `useDoubleTapShortcuts`
so future migrators don't fall in. A `useHoldShortcuts` hook is the
proper fix and will land with the App.tsx migration that needs it.

* feat(ui): comprehensive shortcut registry coverage

Two-Sonnet inventory audit found 19 more user-facing shortcuts not in
the registry, on top of the 5 added in the previous commit. Adding all
of them so the auto-generated /docs/reference/keyboard-shortcuts page
ships honest and the in-app help modal (once migrated to read from the
registry) is comprehensive.

Plan / annotate surface (4 added):
- Escape closes image lightbox       (Viewer)
- Mod+Enter saves annotation edit    (AnnotationPanel)
- Escape cancels annotation edit     (AnnotationPanel)
- Enter confirms image name          (ImageAnnotator name field)

Code review surface (15 added):
- v / a in single-file diff mode     (toggle viewed / stage)
- v, a, x, z, c, [, ] in all-files   (new AllFilesDiff scope)
- Mod+Enter / Escape / Tab           (new SuggestionModal scope)
- Mod+Enter / Escape                 (new AI Assistant scope: AITab + AskAIInput)
- Escape closes tour dialog          (new TourDialog scope)

Engine: extended NAMED_TOKENS with `[` and `]` for the all-files
navigation bindings.

Tests updated for the new section ordering. App behavior unchanged —
this is pure declarative metadata. The existing ad-hoc keydown handlers
keep running; the registry is plumbing for the migration follow-up PRs.

* fix(ui): suggestion modal scope advertised non-existent submit shortcut

Self-review caught: SuggestionModal.tsx only has Escape (close) and Tab
(indent) handlers — no Mod+Enter. The Mod+Enter the inventory agent
referenced lives in AnnotationToolbar's suggested-code textarea, which
is already covered by reviewAnnotationToolbarShortcuts.submitComment.

Removed the bogus submit action from the scope. Renamed cancel→close
since the modal preserves suggestion state on Escape (it's not a
discard).

* fix(review): remove broken Cmd+Shift+C copy-diff shortcut

The handler at App.tsx:695 checked `e.key === 'c'` but Shift forces
`e.key` to uppercase `'C'`, so the shortcut never fired. The in-app
help modal also advertised it incorrectly as "Toggle comment mode".

Removing the broken shortcut entirely rather than fixing the case
mismatch — the toolbar's Copy button already does the same thing,
and there's no evidence anyone was relying on the keyboard shortcut.

Three deletions:
- packages/review-editor/App.tsx — handler block
- packages/review-editor/shortcuts.ts — copyDiff registry action
- packages/ui/components/KeyboardShortcuts.tsx — help modal entry

* chore(ui): address review nits

- F3: refresh AGENTS.md scope inventory (added scopes were missing)
- F5: getShortcutPlatform() now delegates to packages/ui/utils/platform
       isMac instead of duplicating the regex+SSR guard
- F7: drop the stand-alone imageAnnotator.confirmName entry; the
       behaviour is folded into the existing save action's hint so the
       marketing docs page no longer renders two Enter rows with
       different descriptions
- F8: remove the unused listenerOptions parameter from useShortcutScope
       (no callers; inline objects in the dep array would have caused
       re-render loops if anyone tried to use it)

TODOs left for the migration follow-up:
- F2: cross-scope arbitration — dispatchShortcutEvent should bail on
       event.defaultPrevented; needs preventDefault to be claimed
       consistently across scopes first
- F6: matchesKeyToken doesn't actually match the Space token; needs a
       special case before any scope binds Space

App behaviour unchanged.

---------

Co-authored-by: Gjermund Garaba <gjermund@garaba.net>
2026-05-04 10:15:31 -07:00
Michael Ramos 525c8b05c1 fix(review): stop diff list re-rendering on every comment keystroke (#660)
* fix(review): stop diff list re-rendering on every comment keystroke (#659)

Move the useAnnotationToolbar hook + AnnotationToolbar/SuggestionModal
into a new ToolbarHost wrapper so per-keystroke form-state changes no
longer re-render AllFilesDiffView / DiffViewer (and therefore not every
LazyFileDiff and Pierre <FileDiff> beneath them). Parents talk to the
host through an imperative handle for selection/edit/token-click.

Verified with a console.count probe at the top of AllFilesDiffView:
typing 20+ chars no longer increments the render counter.

Out of scope (tracked in #659): React.memo on LazyFileDiff with stable
props, React Compiler, server-side prerendering of the review diff,
throttling capture-phase scroll listeners.

* fix(review): drop type cast on ToolbarHost mousemove handler

Widen useAnnotationToolbar's handleMouseMove signature to accept the
structural { clientX, clientY } shape — the implementation only reads
those two fields, and this lets ToolbarHost pass it directly to
window.addEventListener without an `as unknown as React.MouseEvent`
cast. Function parameters are contravariant, so any caller passing a
React.MouseEvent or native MouseEvent remains valid.

No runtime change.
2026-05-04 09:50:47 -07:00
Michael Ramos 6fc60a6a84 chore: bump version to 0.19.7
For provenance purposes, this commit was AI assisted.
2026-05-02 14:01:25 -07:00
Michael Ramos 2b144cbd2e feat(editor): auto-close left sidebar when TOC is empty (#651)
* feat(editor): auto-close left sidebar when TOC is empty

When the parsed document has no level 1-3 headings (common for annotate
and annotate-last sessions), keep the sidebar closed regardless of the
"Auto-open Sidebar" preference. The collapsed flag strip stays
available so users can still open it manually; manual re-open is not
overridden until the document changes again.

* fix(editor): hoist hasTocEntries above the effects that read it

Adding hasTocEntries to the tocEnabled sync effect's dep array while
the const itself was declared 300+ lines later threw a TDZ error at
render ("Cannot access 'Pe' before initialization" in the minified
bundle). Move the useMemo above the effects so dep-array evaluation
sees an initialized binding.
2026-05-02 12:32:36 -07:00
Michael Ramos b0ed4be063 feat(review): right-click file tree row to copy path or filename (#650)
* feat(review): right-click file tree row to copy path or filename

* feat(review): add "Copy full path" to file tree context menu

Resolves to activeWorktreePath ?? agentCwd ?? gitContext.cwd; hidden in PR-review mode where files aren't on local disk.
2026-05-02 12:28:57 -07:00
Michael Ramos 6324a0c859 chore: bump version to 0.19.6
Co-authored-by: Eric Clemmons <15182+ericclemmons@users.noreply.github.com>

For provenance purposes, this commit was AI assisted.
2026-05-01 18:38:10 -07:00
Michael Ramos 5aabace2ee feat(opencode): agent picker dropdown on Approve button (#648)
Surface agent switching directly on the Approve button as a split
dropdown for OpenCode users, so they can pick which agent to switch
to (or disable switching) without hunting through Settings.

Also fix the "approved with notes" prompt to not say "Proceed with
implementation" when agent switching is disabled.

Closes #575, closes #114, closes #106, closes #159
2026-05-01 18:05:50 -07:00
Michael Ramos 4b8ffcf0f2 fix(ui): code file and linked doc resolution in annotate-last mode (#647)
* fix(ui): code file and linked doc resolution in annotate-last mode

In annotate-last mode, imageBaseDir is set to "last-message" (a
non-path string), which gets sent as the base query param to /api/doc
and causes a 404. Guard both the code file popout and linked doc
URL builders to skip non-path imageBaseDir values and fall back to
projectRoot instead.

* fix(ui): drop projectRoot fallback to preserve server-side sandbox

Sending base=projectRoot bypasses the isWithinProjectRoot check in
/api/doc for code files (line 80) and skips containment entirely for
markdown (lines 40-57). Fall back to undefined instead, which omits
the base param and lets the server resolve paths against its own cwd
with sandbox checks intact — matching plan mode behavior.
2026-05-01 16:29:28 -07:00
Michael Ramos 5aaac6bc88 chore: bump version to 0.19.5
For provenance purposes, this commit was AI assisted.
2026-05-01 00:14:59 -07:00
Michael Ramos ddcca6e0ee feat(review): file comment button in all-files view + remap C/X shortcuts
Add file-scoped comment support to the all-files diff view via
onAddFileCommentForFile callback and CommentPopover. Remap keyboard
shortcuts: C now opens file comment, X collapses/expands files.

For provenance purposes, this commit was AI assisted.
2026-05-01 00:04:14 -07:00
Michael Ramos 0fd9826c2c feat(review): all-files diff view with unified scrolling (#640)
* feat(review): all-files diff view with lazy mounting

Renders all diffs in a single scrollable pane using individual FileDiff
components with sticky file headers, collapse toggles, and viewport-based
lazy mounting via IntersectionObserver.

For provenance purposes, this commit was AI assisted.

* feat(review): convert all-files view to dock panel + fixes

- Register AllFilesDiffView as a proper dock panel so it coexists
  with single-file tabs, AI chat, PR panels
- Remove viewMode toggle in favor of dock panel lifecycle
- Fix annotation toolbar positioning (mouse tracking)
- Fix annotation PR/scope filtering for stacked PRs
- Fix stale patch on file switch (deferred toolbar open)
- Fix conventional comments boolean comparison
- Fix SuggestionModal props to match DiffViewer pattern
- Wire reviewBase for correct hunk expansion context
- Fix pre-existing CSS bug on .has-annotations background
- Add Shift+V/A keyboard shortcuts for viewed/stage toggle
- Add [/] keyboard shortcuts for file-to-file scrolling
- Add keyboard shortcuts to settings panel
- Restore scroll-based file tree highlight via dock panel state

For provenance purposes, this commit was AI assisted.

* feat(review): sidebar toggles, keyboard shortcuts, click-drag selection

- Move sidebar tab buttons (annotations/AI/agents) to header bar
- Generalize useSidebar hook for both plan and review editors
- Remove internal tab state from ReviewSidebar, receive from parent
- Add file tree toggle button at far-left of header
- Add click-and-drag line selection in diff content area
- Change keyboard shortcuts from Shift+V/A to bare v/a/c
  v = toggle viewed (+ collapse in all-files mode)
  a = toggle git add/stage
  c = collapse/expand file (all-files mode)
- Widen DiffOptionsPopover to prevent word-alt line break
- Fix Shift+V/A targeting wrong file in all-files mode
- Default to all-files panel on initial load
- Hide dock headers when all-files panel is the only panel

For provenance purposes, this commit was AI assisted.

* feat(review): click-and-drag line selection in diff content area

Extract shared selection helpers to utils/diffSelection.ts and add
mouseup handlers to both DiffViewer and AllFilesDiffView so users can
click and drag across code lines to open the annotation toolbar —
not just the gutter.

For provenance purposes, this commit was AI assisted.

* fix(review): address AI review findings

- Gate all-files keyboard shortcuts on isActive to prevent double-toggle
  when a single-file tab is focused
- Fix getDiffSelection to iterate all diffs-container elements so
  click-drag selection works on files beyond the first
- Clear needsInitialDiffPanel after opening all-files panel to prevent
  forced reactivation on diff switches
- Clear activeFilePath and collapsedFiles when files change to prevent
  stale state across PR/diff switches
- Add file.patch to LazyFileDiff fetch deps so file contents refetch
  when the diff context changes
- Add toggleCollapse to keyboard effect dependency array

For provenance purposes, this commit was AI assisted.

* feat(review): add z to undo collapse with scroll-back

Press z to reopen the last collapsed file and scroll back to it.
Maintains a history stack so repeated z walks back through collapses.
Skips already-expanded entries. History clears on diff/PR switch.

For provenance purposes, this commit was AI assisted.

* fix(review): review findings + sidebar toggles + folder icon

- Fix stale context memo: add isAllFilesActive + handleAddAnnotationForFile
  to useMemo deps
- Fix v handler: clear activeFilePath after collapse, only collapse when
  marking viewed ON (not when toggling OFF)
- Fix click-drag copy regression: only trigger annotation toolbar for
  multi-line selections, preserve single-line/word selection for Cmd+C
- Fix LazyFileDiff: clear stale file contents before refetch
- Clean up App.tsx v/a handler: early return when isAllFilesActive
- Add Cmd+B (file tree) and Cmd+. (sidebar) keyboard shortcuts
- Add toggle menu items with kbd badges to Options dropdown
- Replace file tree icon with simple folder
- Add z (undo collapse) to keyboard shortcuts panel

For provenance purposes, this commit was AI assisted.

* chore: remove dead code from iterations

- Remove unused FileTreeIcon import and component file
- Remove unused AnnotationsIcon component from ReviewHeaderMenu
- Remove unused onClose prop and close button from FileTree

For provenance purposes, this commit was AI assisted.

* fix(review): gate v/a on diff panel, keep annotation selection in all-files

- Track isDiffPanelActive so v/a shortcuts only fire when viewing a
  single-file diff, not PR Comments/Summary/Agent panels
- Annotation selection stays in all-files mode instead of switching to
  a single-file tab — lets the scroll-to-annotation effect handle it

For provenance purposes, this commit was AI assisted.
2026-04-30 23:39:43 -07:00