Injects the version from package.json at compile time via Bun's --define
so compiled binaries report the correct version (e.g. `plannotator 0.19.16`).
Uncompiled dev runs fall back to `plannotator dev`.
* 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.
- Accept PLANNOTATOR_PORT=0 in Pi network.ts (matches Bun server)
- Add PLANNOTATOR_ORIGIN and PLANNOTATOR_VERIFY_ATTESTATION to env vars docs
- Fix port range from 1-65535 to 0-65535
- Add jj diff modes, commit-based base, and expanded API table to code review docs
For provenance purposes, this commit was AI assisted.
* 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.
* 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>
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
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.
OpenCode's command dispatcher appends `arguments` to the `.md` body and
runs `resolvePromptParts()` over the combined string, which auto-attaches
any file path it finds as a `FilePart`. With `/plannotator-annotate
/path/to/huge.md`, that meant the agent received the file's content as a
user message before the annotation UI even opened — blowing the context
on large files (GLM-5 auto-compact reported in #713).
Move `plannotator-annotate`, `plannotator-review`, and `plannotator-archive`
from the post-hoc `event` handler to `command.execute.before`, matching
the pattern `plannotator-last` already used. The hook clears `output.parts`
in place so the agent never receives the command turn; handlers then run
the UI and inject feedback via `client.session.prompt` as a separate turn.
Empty the bodies of the three `.md` files for defense in depth — only the
frontmatter is needed for OpenCode to register the slash command.
Also fixes a latent bug in the `plannotator-last` path: `output.parts = []`
reassigns the throwaway wrapper object's property but doesn't touch the
`parts` array the caller in `prompt.ts:1944` uses directly. Switched to
`output.parts.length = 0` to mutate in place. `plannotator-last` only
escaped notice because its parts array was always a single benign text
part.
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
* 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.
* 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>
The workflow-level permissions block granted id-token:write to every
job, including detect-changes which only reads git diffs. Move the
OIDC capability to only deploy-marketing and deploy-portal (which
need it for aws-actions/configure-aws-credentials). deploy-paste
uses a Cloudflare API token and doesn't need OIDC at all.
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.
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.
- 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
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.
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)
* 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
Pi snapshots the model at the start of each agent.prompt() call, so
calling pi.setModel() mid-loop has no effect until the next user turn.
Return terminate: true from the approval tool to end the current loop,
then use an agent_end handler with deferred sendUserMessage to start a
fresh turn that picks up the executing model.
Closes#674
## Summary
- configure Pi package entries to keep extension commands while disabling bundled package skills when shared Plannotator skills are installed globally
- preserve Pi bundled skills when global shared skills are unavailable
- write Pi settings without a UTF-8 BOM on Windows
## Tests
- bash -n scripts/install.sh
- bun test scripts/install.test.ts
- git diff --check
Registers submit_plan tool and slash commands without modifying prompts
or agent permissions. Fills the gap between manual (commands only) and
plan-agent (full automation) for users who want to manage prompts and
permissions themselves.
* 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>
* chore: support bun link for local CLI development
* move Local Development section to end of README as Development
For provenance purposes, this commit was AI assisted.
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>