mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
feat/opencode2-native-commands
370 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
afac44b940 |
feat(review): migrate all-files code review to Pierre CodeView (#885)
Replaces the all-files renderer with Pierre's virtualized CodeView (one scroller, identical look, scales to huge diffs); deletes the legacy AllFilesDiffView/LazyFileDiff and their flag; single-file panels stay on FileDiff. @pierre/diffs pinned exact 1.2.8 (1.2.9's tree is broken on npm — see #880). Also ships: diff staleness detection ('Diff out of date · Refresh' toolbar notice backed by shared per-VCS fingerprints + GET /api/diff/fresh on both Bun and Pi servers), a stale-content guard so files edited mid-review degrade to raw-patch view instead of breaking virtualization, the Ask AI wrong-file fix (pre-existing), and toolbar/header UI polish (global settings cog, aligned sem badges, no all-files split dragger). Hardened through five review waves; tested with 1384 passing tests incl. real-git fingerprint and patch-consistency suites. Known minor gaps documented in the PR. |
||
|
|
9b936aa299 |
fix(markdown): keep custom angle autolinks literal (#867)
Co-authored-by: ishowman <ishowman@users.noreply.github.com> |
||
|
|
4e841e2124 |
fix(deps): pin @pierre/diffs to exact 1.1.20
Upstream @pierre/diffs@1.2.x depends on @pierre/theming@0.0.1, which is not published on npm, so any fresh install resolving our ^1.1.12 range floated to 1.2.9 and failed with a 404. Our lockfile masked this for repo builds, but npm consumers of @plannotator/pi-extension resolve fresh and hit it (#880). Pin every declaration to exact 1.1.20, whose dependency tree is fully resolvable (@pierre/theme@0.0.28). |
||
|
|
cbf891ba40 |
feat(review): add Claude Fable 5 to the code-review model dropdown
Fable 5 dropped today; add it to the top of CLAUDE_MODELS (the review agents model picker) alongside the existing Opus 4.8 entry. No [1m] variant — Fable 5 has no separate 1M-context toggle. Server forwards --model verbatim, so no allowlist change needed. |
||
|
|
6573f2f519 |
chore(release): fix README command count + point dialog at v0.20.0 notes
- apps/hook/README.md: 'four' -> 'three' slash commands (archive row was removed in #873 but the count wasn't reverted). - LookAndFeelAnnouncementDialog: 'Full release notes' now deep-links to the v0.20.0 release tag instead of the generic releases list. |
||
|
|
0be4295a2e |
Plan-look chooser + 0.20.0 release dialog (with GitLab / sem / install fixes) (#879)
* feat(plan): default to the grid look + look-and-feel image chooser - Flip gridEnabled default back to true (classic grid / floating-card look), reverting #863's flat-by-default so existing users without an explicit choice return to the grid view they're used to. - Rewrite the look-and-feel announcement into a two-image chooser: Grid (classic, now the default) vs Clean (the new flat look), each a clickable screenshot that hover-zooms to preview — mirroring the code-review DiffTypeSetupDialog pattern. - Bump the announcement version (1 -> 2) so existing users are re-offered the choice once. - Add look-grid.png / look-flat.png preview assets (resized app screenshots). * fix(review): restore GitLab /diffs fallback for MR review #871's pr-gitlab refactor moved to the raw_diffs endpoint and deleted the paginated /diffs path, hard-breaking MR review on self-hosted GitLab too old to expose raw_diffs (opaque "Failed to fetch MR diff") and silently rendering zero files when raw_diffs returns empty for oversized MRs. Keep raw_diffs as the primary path (it preserves binary markers + collapsed content), but fall back to the restored paginated /diffs + reconstructPatch when raw_diffs fails or is empty, and throw a clear "diff is empty / too large" error if both come back empty. Restores parsePaginatedArray / reconstructPatch and adds tests for the fallback paths. * fix(install): bound the optional sem sidecar download The semantic-diff 'sem' download (#871) ran curl / Invoke-WebRequest with no timeout. The sidecar installs after plannotator itself, so a slow or hung fetch could wedge an otherwise-complete install. Add --connect-timeout 10 --max-time 120/60 (sh, cmd) and -TimeoutSec 120/60 (ps1) so it times out and skips gracefully, and document the PLANNOTATOR_SKIP_SEM_INSTALL opt-out in the installer help. * chore(install): remove the Glimpse install option from the installers The installers offered to `npm install -g glimpseui` (a wizard prompt + --glimpse/--no-glimpse flags + saved pref). Drop all of it across install.sh, install.ps1, and install.cmd: flags, usage text, the wizard question, the glimpse_present detection, the saved-pref read/write, the persistence clauses, and the install block. The guided wizard is now two questions (extras, model-invocable). Runtime Glimpse support is unchanged: the app still opens in the native window if glimpseui is on PATH (packages/server/browser.ts), still gated by PLANNOTATOR_GLIMPSE. Updated install.test.ts (asserts the installers are glimpse-free) and the installation docs. * feat(plan): 0.20.0 release announcement dialog Turn the first-run look chooser into a two-page 0.20.0 announcement. - Page 1: header with a "Full release notes" link, a four-up feature grid (fresh look, semantic review, multi-repo review, leaner install), and the grid/clean plan-look chooser. - Page 2: "Workspaces are coming" teaser with the waitlist image + link. - Footer actions grouped bottom-right on both pages (shimmering "Workspaces are coming" teaser via TextShimmer + the primary action) so focus stays in one corner across the page turn. - Reshoot the grid/clean chooser screenshots on a clean prose plan and add the workspaces teaser image. * feat(plan): add full-page HTML feature card, lead with leaner install Add a fifth what's-new card for --render-html (annotate HTML reports and explainers rendered full-screen) and move Leaner install to the front of the grid. |
||
|
|
c23df4db43 |
UI 2.0 visual refresh + HTML-render annotate (strictly UI, off main) (#863)
Extracts the UI 2.0 visual refresh and the HTML-render annotate feature onto main, standalone (no daemon). Faithful copy of feat's UI/HTML logic with the standalone transport kept. |
||
|
|
13a6adde18 |
Add full-file diff context setting (#858)
Co-authored-by: Nikolai Perlt <perlt@Nikolais-MacBook-Pro.local> |
||
|
|
be2c81fa3b |
annotate-last: pick which message to annotate (fixes #800) (#809)
* feat: message picker for annotate-last (#800) When running /plannotator-last after /rewind, the newest transcript entry is no longer the message the user intended to annotate, and there was no affordance to pick a different one. Adds a picker UI that surfaces the recent assistant messages so the user can choose which one to annotate: - A "Message N of M" button in the Viewer's sticky-top action bar (alongside Copy / Global comment / Attachments), so it stays accessible while scrolling. - A "Messages" tab in the left sidebar with the full list (newest-first, preview + timestamp, default ★), mirroring the existing Files / Versions / Archive tab pattern. Wired for Claude Code, Codex, and Droid (all share apps/hook/server). OpenCode, Pi, and Copilot still get the original single-message behavior — they don't emit recentMessages, so the picker affordances hide cleanly. Default selection (index 0) matches today's "last message" behavior, so users who don't interact with the picker see no change. * feat: extend annotate-last picker to Copilot and OpenCode The picker UI from #800 was wired for Claude / Codex / Droid only. Pull Copilot and OpenCode onto the same shape so users on those harnesses also get the recent-messages picker when annotating the last assistant message. - Copilot: replace getLastCopilotMessage with getRecentCopilotMessages, walking events.jsonl newest-first up to 25 assistant.message events. - OpenCode: rewrite the session walk to collect up to 25 messages (newest first) instead of bailing on the first hit; normalize the SDK time.created (ms epoch) to ISO to match the shared picker contract. - Both pass recentMessages to startAnnotateServer only when length > 1, matching the existing Claude/Codex/Droid behavior. Also trims a leftover narrating comment in MessagesBrowser and refreshes the stale Copilot session-parser header. Pi parity follows in the next commit (needs round-trip of the picker selection through /api/feedback so its post-submit anchoring quotes the right message). * feat(pi): wire annotate-last picker with feedback round-trip Extends the picker UI (#800) to Pi and fixes a Pi-specific anchoring bug the picker would otherwise introduce. Picker plumbing - assistant-message: getRecentAssistantMessages walks the active branch newest-first, returning { messageId, text, timestamp? } in the same shape the other harnesses produce. - Plumbed through plannotator-browser / plannotator-events so the Bun server's recentMessages option is populated when the branch has more than one assistant message. Anchoring fix - Pi quotes the targeted assistant message back to the agent because its UX is async — the conversation may have moved on by feedback time. With the picker, that target is no longer guaranteed to be the snapshot taken when the UI opened. The editor now sends the user's selectedMessageId with /api/feedback; Pi looks it up in the current branch via findAssistantMessageByEntryId and quotes that message instead. Falls back to the original snapshot if the entry is gone. - The round-trip field is optional and only meaningful in annotate-last mode; other harnesses (and other modes) ignore it. Timestamp safety - Pi's SDK currently types SessionEntryBase.timestamp as string, but the picker contract everywhere else is ISO. Treat the value as unknown and normalize string/number(ms)/Date to ISO; drop anything else, rather than blind-casting and risking silent drift if the SDK changes. * chore: strip issue-number references from comments Comments shouldn't rely on external references — issue numbers age out of context, link rot is a thing, and a reader shouldn't need to open GitHub to understand why a line exists. Strip the `(#800)` and `(#570)` parentheticals from comments and doc strings across the picker and review-gate code; the surrounding "why" content is preserved. * fix: prevent removeChild crash when switching annotate-last messages Switching the picked message remounted nothing, so React reconciled new content against DOM that web-highlighter had mutated with <mark> nodes, throwing removeChild. Drive the Viewer key (and StickyHeaderLane's remount token) off a shared viewerContentKey so a message switch fully remounts the Viewer and re-anchors the sticky-header observer. Also cap MessagesBrowser row previews via previewText() and drop the redundant 'block' class that was overriding line-clamp-2. * feat: persist annotate-last feedback across messages --------- Co-authored-by: Michael Ramos <mdramos8@gmail.com> |
||
|
|
96aad592af |
Open Plannotator in Glimpse when available (#840)
* feat: open plannotator in glimpse * fix: harden Glimpse browser integration * chore: refresh lockfile after rebase --------- Co-authored-by: Michael Ramos <mdramos8@gmail.com> |
||
|
|
cbc6186a15 |
fix(ui): align list markers to first line in clean diff view (#838)
* fix(ui): align list marker to first line in clean diff view * refactor(ui): extract list-item marker and body into ListItemBody Three render paths (BlockRenderer and two in PlanCleanDiffView) duplicated the same marker-plus-paragraph scaffold. A recent alignment fix had to be applied to each surface independently, which exposed the duplication. The shared structure now lives in ListItemBody; call sites keep their own row wrapper with surface-specific concerns (indent, data attributes, hover props, interactivity). |
||
|
|
ffe2be504f |
feat: improve update notification with toast, shimmer, and prominent button
Add a sonner toast when a new version is detected, apply TextShimmer to the Options button label, and restyle the copy-update button as a filled primary action. Applied to both plan and review editors. For provenance purposes, this commit was AI assisted. |
||
|
|
2a8eb9d647 |
Fix flickering AI-tip input in label settings (#830)
The "Add AI instruction tip" editor in Settings > Labels flickered intensely on open. Its `tip-slide-open` keyframe animated `max-height` (a layout property) to 60px -- larger than the input's natural height -- with no animation-fill-mode. Inside the OverlayScrollbars viewport and a flex `min-h-0` container this formed a layout feedback loop that restarted the animation ~40x/second, jittering the editor height. Animate `opacity` and `transform` instead, so no layout property is animated. Measured on the live demo, animation restarts and height reversals dropped from ~17/10 to 0, with height stable at its natural value. Fixes #829 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
b00ddfa16d |
Fix Mermaid block sizing, zoom retention, and pinpoint drag (#819)
* Pin Mermaid SVG sizing attrs into markup so they survive re-injection Mermaid 11.x emits the root <svg> with style="max-width: <natural-px>;" and no preserveAspectRatio / height attributes. The component fixed this via imperative setAttribute in a useEffect, but React's repeated dangerouslySetInnerHTML re-injections (4-5 times during mount) wipe the imperative attrs because the useEffect's `svg` state dep does not change. Result: short flowcharts (e.g. `X --> Y --> Z`) render at their natural 85x66 px "dot" size, long flowcharts overflow vertically past their container (~1667 px), and drag feels broken because pan-on-clipped-svg has nowhere to go. Bake max-width:none, preserveAspectRatio="xMidYMid meet", and height="100%" into the SVG markup string itself before setSvg(). Since the attributes live in the markup, every re-injection carries them along. The imperative useEffect is left in place as defense-in-depth. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Memoize MermaidBlock to preserve zoomed viewBox across pinpoint hover After zooming a Mermaid diagram and toggling pinpoint mode on, hovering the viewer would reset the visible viewport back to the full diagram. The zoom ref / badge stayed at e.g. 150%, but the SVG snapped back to its natural extents. Root cause: the pinpoint hook calls setHoverTarget on every mousemove, which re-renders the parent Viewer, which re-runs MermaidBlock's dangerouslySetInnerHTML and replaces the live SVG with the cached markup string. The prior commit's markup normalization pins three sizing attrs, but viewBox is not in the markup -- zoom mutates viewBox via setAttribute, so each re-injection drops the zoomed value. Wrap MermaidBlock with React.memo and a custom comparator on block.id + block.content (matching HtmlBlock's convention). Upstream blocks come from useMemo([markdown]) in the editor App, so referential equality holds whenever content is unchanged -- memo skips the re-render and the live SVG keeps its zoomed viewBox across pinpoint mousemove storms. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Skip pinpoint targeting inside Mermaid grab area to keep drag clean In pinpoint mode, releasing a Mermaid pan-drag created a whole-block annotation. mouseup is followed by a click event, and the pinpoint overlay's click handler caught it and called highlighter.fromRange() on the entire Mermaid block. Separate annoyance: the dashed hover border drawn by the pinpoint overlay was visually distracting while the user was clearly trying to drag the diagram. Add data-pinpoint-ignore="" to the grab container div. usePinpoint's existing SKIP_SELECTORS in blockTargeting.ts already matches that attribute, so this is a one-attribute opt-out using a public extension point -- no changes to the pinpoint hook itself. Trade-off: Mermaid blocks no longer support whole-block-annotation via pinpoint. Annotations on diagrams must go through standard selection. This is the deliberate behavior chosen for diagrams where drag/zoom ergonomics matter more than whole-block targeting. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Extract normalizeMermaidSvgMarkup to a pure module The MermaidBlock test imported the component, which runs mermaid.initialize() at module load. That throws 'Unsupported color format: "#333"' in the headless CI environment, failing the test on import before any assertion runs. Move the pure SVG-normalization helper into mermaidSvg.ts (no React, no mermaid import) and have both the component and the test import it from there. App behavior is unchanged; the test no longer loads mermaid. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Michael Ramos <mdramos8@gmail.com> |
||
|
|
e0aee7451b |
feat: add PLANNOTATOR_DATA_DIR env var to customize data directory (#795)
* feat: add PLANNOTATOR_DATA_DIR env var to customize data directory * fix: update missed hardcoded paths to use PLANNOTATOR_DATA_DIR OpenCode plugin and VS Code extension still used hardcoded ~/.plannotator paths, causing the IPC registry and plan backing file to diverge from the server when PLANNOTATOR_DATA_DIR is set. Also exports data-dir from @plannotator/shared and documents the new env var in AGENTS.md. Co-authored-by: Chris Werner Rau <14326070+cwrau@users.noreply.github.com> Co-authored-by: João O. Santos <34689526+Joao-O-Santos@users.noreply.github.com> * fix: vendor data-dir.ts into Pi extension and rewrite imports The Pi extension copies shared/server modules into generated/ at build time. Without vendoring data-dir.ts and rewriting the parent-relative imports, typecheck fails on all generated files that import getPlannotatorDataDir. * refactor: eliminate duplicated data-dir logic and clean up call sites - VS Code extension: replace inlined getPlannotatorDataDir() copy with import from the canonical packages/shared/data-dir.ts (esbuild bundles it, so no runtime dependency needed) - storage.ts: hoist repeated getPlannotatorDataDir() calls to a module-level DATA_DIR constant, matching the pattern config.ts uses - data-dir.ts: remove inaccurate docstring claim about relative path resolution (the code does not call resolve()) - improvement-hooks.ts: hoist to DATA_DIR constant, clarify comments on the two-level hook lookup (hooks/ subdir vs root fallback) * fix: resolve relative PLANNOTATOR_DATA_DIR to absolute path A relative value like ./data would break readArchivedPlan's path traversal guard, which compares a resolve()'d absolute path against the still-relative planDir prefix. Always return an absolute path so all callers get consistent path shapes. * fix: use @plannotator/shared/data-dir imports in server package Switch from relative ../shared/data-dir imports to the package export, matching the convention every other server file follows. Update Pi vendor script sed rules to match the new import style. * fix: use package imports in server and respect data dir in compound skill Server modules: switch from relative ../shared/data-dir imports to @plannotator/shared/data-dir, matching the convention every other server file follows. Update Pi vendor script sed rules to match. Compound skill: update hardcoded ~/.plannotator paths to check PLANNOTATOR_DATA_DIR first, so the skill reads plans and writes the improvement hook to the correct location when users set a custom data directory. Co-authored-by: Chris Werner Rau <14326070+cwrau@users.noreply.github.com> Co-authored-by: João O. Santos <34689526+Joao-O-Santos@users.noreply.github.com> * fix: remove remaining hardcoded ~/.plannotator assumptions - Settings UI: replace hardcoded path in label and placeholder with generic text that doesn't assume a specific data directory - quickLabels: update agent tip to reference PLANNOTATOR_DATA_DIR so the agent checks the correct plans directory - codex-review: hoist getPlannotatorDataDir() to module-level DATA_DIR constant, eliminating redundant per-call resolution in debugLog() - Tests: make submit-plan and storage tests resilient to PLANNOTATOR_DATA_DIR being set in the environment - Install scripts (sh, ps1, cmd): check PLANNOTATOR_DATA_DIR before falling back to ~/.plannotator for config.json attestation lookup * fix: expand tilde in install script and update test assertions install.sh: PLANNOTATOR_DATA_DIR set to ~/... stays literal inside double quotes, so the config file check silently failed. Add case statement to expand ~ the same way the runtime data-dir.ts does. install.test.ts: update three assertions that checked for hardcoded ~/.plannotator paths — now verify PLANNOTATOR_DATA_DIR awareness instead. * docs: add PLANNOTATOR_DATA_DIR to env var reference with VS Code note Document the new env var on the marketing site's environment variables reference page. Include a footnote about ensuring VS Code inherits the variable when launched from the Dock. --------- Co-authored-by: Michael Ramos <mdramos8@gmail.com> Co-authored-by: Chris Werner Rau <14326070+cwrau@users.noreply.github.com> Co-authored-by: João O. Santos <34689526+Joao-O-Santos@users.noreply.github.com> |
||
|
|
92efe6fdef |
Add Auto Mode option to Permission Mode dropdown (#796)
Claude Code shipped Auto Mode in March 2026 as a new permission mode value (`auto`) — autonomous execution gated by a Sonnet 4.6 safety classifier. It sits between `acceptEdits` and `bypassPermissions` in risk profile. Extend the `PermissionMode` union and `PERMISSION_MODE_OPTIONS` array with the `auto` entry. The Settings dropdown and first-time setup modal both iterate the options array, so no UI component changes are needed. The hook stdout payload (`updatedPermissions[].mode`) is already generic over mode value, so server-side plumbing also requires no changes. Refs: - https://www.anthropic.com/engineering/claude-code-auto-mode - https://code.claude.com/docs/en/permission-modes |
||
|
|
dbe2d6e4cb |
Replace update banner with passive dot indicator in Options menu (#793)
* Replace persistent update banner with passive dot indicator in Options menu (#790) The floating update banner that appeared every session is removed. Update availability is now surfaced as a small dot on the Options button that auto-dismisses (cookie-persisted per version) when the menu is opened. The version section inside the menu shows install command and release notes when an update exists. * fix: restore ActionMenuSectionLabel import in both header menus * Add TextShimmer to update notice and simplify menu version section - Add reusable TextShimmer component (motion-primitives adaptation) - Add reusable BorderTrail component for future use - Shimmer "New update available!" text in the Options menu - Simplify button to plain text link in emerald - Remove Project repo link, make "Plannotator" header link to repo |
||
|
|
05392eb99e |
Fix copy (Cmd+C) broken in Safari on plan viewer (#785)
* Fix copy (Cmd+C) broken in Safari on plan viewer Safari rejects async navigator.clipboard.writeText() outside the immediate user-gesture window. The old keydown handler broke the gesture chain with an await, and web-highlighter already clears the DOM selection on mouseup so native copy had nothing to grab. Switch Viewer to the synchronous copy event + clipboardData.setData() which works across all browsers. Add an execCommand fallback to the AnnotationToolbar copy button for extra safety. * Remove copy button from annotation toolbar Cmd+C now works reliably via the native copy event, so the toolbar copy button is redundant. Code block and table copy buttons are kept. * Show copy button on touch devices only Touch users have no Cmd+C fallback since web-highlighter clears the native selection. Detect (pointer: coarse) and conditionally render the copy button + execCommand fallback for iPad/phone users. * Reset copied state when selection changes on touch devices * Keep copy button on code block hover toolbar for desktop users * Scope copy handler to viewer container Prevents the copy handler from hijacking Cmd+C when the target is outside the viewer (sidebar, annotations panel, code popout, etc.). * Remove overly aggressive container scope check from copy handler * Add copy event handler to HtmlViewer for Safari support * Keep copy button visible in HtmlViewer toolbar The iframe copy event can't reach the parent document handler, so the toolbar button is the only copy path for HTML annotation mode. * Use hideCopyButton prop instead of overloading copyText Separates "what text to copy" from "whether to show the button." Only Viewer's text selection toolbar hides it (desktop only, has keyboard handler). HtmlViewer and code block toolbars always show it. * Memoize touch device check in Viewer |
||
|
|
2a5528693f |
Add Ask AI to plan and annotate reviews (#763)
* Add Ask AI to plan and annotate reviews - mount shared AI runtime on plan, annotate, review, and Pi servers - add shared document chat UI and comment-popover Ask AI entry point - default providers from detected agent origin and document first-run announcement - reuse shared code-review AI hook and document setup/docs * Address Ask AI review followups Stabilize AI chat session resets and callbacks, preserve per-origin provider defaults, share AI chat formatting utilities, and avoid blocking startup on model discovery. * Fix AI permission details and Pi CLI lookup Show formatted tool input in document AI permission cards and make Pi AI provider CLI detection work on Windows. * Fix Pi model discovery in AI capabilities Keep AI runtime startup non-blocking while making the capabilities endpoint await pending Pi/OpenCode model discovery before returning provider metadata. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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> |
||
|
|
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. |
||
|
|
fcf2ba4cf5 |
fix: indent loose list continuation content under parent bullet (#705)
Closes #704 |
||
|
|
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. |
||
|
|
50191e6c1d | Add diff line background intensity option (#691) | ||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
7329efb980 | Add diff tab size option (#685) | ||
|
|
36948e8572 |
chore: bump version to 0.19.11
For provenance purposes, this commit was AI assisted. |
||
|
|
49c55e6e25 |
Revert "Expose bypass clear reminder permission mode (#668)"
This reverts commit
|
||
|
|
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. |
||
|
|
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> |
||
|
|
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 |
||
|
|
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`.
|
||
|
|
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> |
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
108a61b7d8 |
feat(ui): detect code file paths in backtick spans and bare prose (#639)
Inline code like `src/utils/foo.ts` and bare prose paths like packages/server/index.ts are now clickable, opening the code file popout viewer. Adds a subtle CSS shimmer and hover-to-primary transition so users discover the interactivity without disrupting the familiar inline-code look. - Branch the backtick handler in InlineMarkdown to check isCodeFilePath() - Extend emitPlainTextWithBareUrls() to also detect bare file paths - Add isCodeFilePathStrict() requiring a / separator for bare prose - Extract CodeFileIcon component to deduplicate the SVG across 3 sites - Add code-file-link shimmer animation in theme.css with reduced-motion support For provenance purposes, this commit was AI assisted. |
||
|
|
a11bf802bd |
feat(ui): code file viewer with syntax highlighting and annotations (#634)
* feat(ui): extract reusable PopoutDialog, fix backdrop blur
The table popout lost its backdrop blur when we switched to modal={false}
to keep annotation toolbars interactive. Radix ignores Dialog.Overlay in
non-modal mode, so replace it with a plain div backdrop that works
regardless. Extract the dialog shell (backdrop, close button, portal,
annotation-aware dismiss) into a reusable PopoutDialog component for
upcoming use cases. Add a demo table to the dev plan content.
* Add read-only code file popout
* Add code file annotation support
* Fix code selection popover position
* fix(editor): restore global-attachment-only drafts
The save condition was broadened to persist drafts with only global
attachments, but the restore handler still skipped applying when both
annotation arrays were empty — silently dropping the attachments.
* fix(ui): import SelectedLineRange from @pierre/diffs base package
SelectedLineRange is not re-exported from @pierre/diffs/react —
import it from the base @pierre/diffs entry point instead.
* chore: add TODO for bot callback + code annotation limitation
|
||
|
|
b4b1d432c8 |
feat(review): diff display options — hide whitespace + quick-settings popover (#631)
* feat(review): add hide whitespace setting for diffs Adds a "Hide Whitespace" toggle that suppresses whitespace-only changes in diffs, matching GitHub's ?w=1 behavior. Uses parseDiffFromFile with ignoreWhitespace when full file contents are available. Includes demo data with a whitespace-heavy file (settings.ts) and a vite dev server plugin to serve file contents in demo mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(review): add quick-settings popover on file header Adds a gear icon to the file header that opens a compact Radix popover with all diff display options (style, overflow, indicators, inline diff, line numbers, background, hide whitespace). Provides quick access to settings without opening the full settings dialog. Exports option constants from Settings.tsx for reuse. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
a68463b6a4 |
feat(review): add "All files" diff type (#629)
* feat(review): add "All files" diff type for reviewing entire codebases Adds an "all" diff type that diffs the empty tree against HEAD, showing every tracked file as an addition. Solves the case where a repo has only committed changes and no working tree diffs — previously there was no way to launch a review at all. For provenance purposes, this commit was AI assisted. * fix(review): add "All files" to Settings panel diff type options The Settings panel (gear icon) had its own DEFAULT_DIFF_TYPE_OPTIONS list that was missing the new "all" diff type, so users couldn't select or see it as the default from the settings UI. For provenance purposes, this commit was AI assisted. * fix(review): rename "All files" to "All files (HEAD)" for clarity Makes it clear this mode shows committed state at HEAD, not the working tree. Updated tooltip to distinguish from "Committed changes" (which diffs against a base branch). For provenance purposes, this commit was AI assisted. |
||
|
|
93c844021d |
feat(review): hide/de-emphasize merged PRs in stacked PR selector (#626)
* feat(review): hide/de-emphasize merged PRs in stacked PR selector (#625) Fetch PR state from GitHub GraphQL and surface it in the stack tree popover. Adds a cookie-persisted "Hide merged" toggle: when on, merged nodes are removed with a summary count; when off, they render dimmed with strikethrough and a "merged" badge. Also reduces tree indentation to 2px/level to prevent overflow on deep stacks. Closes #625 For provenance purposes, this commit was AI assisted. * feat(review): add hide-merged toggle to PR selector dropdown Reuses the same micro-toggle pattern from the stack tree popover. Adds a headerContent slot to SearchableSelect and filters merged PRs from the list when the toggle is on. Separate cookie persistence from the stack tree toggle. For provenance purposes, this commit was AI assisted. * fix(review): filter closed PRs alongside merged, improve count labels Toggle ON now keeps only open PRs (filters both merged and closed). Labels show "N open · N hidden" when filtering, "N open, N total" otherwise. For provenance purposes, this commit was AI assisted. |
||
|
|
03d4e8b214 |
fix: widen diff type dialog with 60/40 split layout and hover-to-zoom image
Makes the image preview visible by splitting the dialog into options (left) and a toolbar screenshot (right) with a CSS hover-to-zoom effect so users can clearly see where the dropdown lives. For provenance purposes, this commit was AI assisted. |
||
|
|
aaad89ee3f |
fix: re-show diff type setup dialog with clearer options and toolbar hint
Version-gates the cookie so all users see the dialog again. Reorders options to match Settings, adds git command hints to descriptions, and includes a screenshot showing the toolbar dropdown so users know they can switch views at any time during a review. For provenance purposes, this commit was AI assisted. |
||
|
|
fdc4bc4656 |
feat(plan,annotate): include source line numbers in exported feedback (#623)
Each annotation in exported plan/annotate feedback now carries source line numbers — single-line blocks show `(line N)`, multi-line blocks show `(lines N–M)`. Diff-context and global comments stay lineless. When the document was produced by Turndown/Jina (HTML file or URL), the export carries a caveat that line numbers refer to the converted markdown rather than the original source. Key implementation details: - extractFrontmatter() returns contentStartLine so block line numbers account for stripped YAML headers - blockEndLine() computes end lines per block type, with code blocks, directives, and alerts accounting for stripped wrapper lines - isConvertedSource() helper in url-to-markdown.ts centralizes the source-type check across all entry points - sourceConverted threaded from all CLIs through annotate servers to the /api/plan payload; isConverted added to /api/doc responses - Per-document conversion tracking in useLinkedDoc ensures the correct flag is used when viewing linked HTML docs Supersedes #621. For provenance purposes, this commit was AI assisted. |
||
|
|
7ab2d8f868 |
fix: remove ghost dot on Options menu (new-settings-hint system)
The version-gated hint dot was permanently visible for users who never matched the cookie version, with no way to dismiss it. Removes the entire system: utility, state, prop plumbing, and pulsing dot JSX. For provenance purposes, this commit was AI assisted. |
||
|
|
bb404f8d14 |
feat: stacked PR review — PR switching, scope toggling, multi-PR posting (#620)
* feat(shared): add isSameProject, PR stack types, and PR list provider Extends PRRef/PRMetadata with defaultBranch, PRStackInfo, PRStackTree, PRStackNode, PRDiffScope, and PRListItem types. Adds isSameProject() for owner/repo validation on PR switching. Adds fetchPRStack() and fetchPRList() dispatch functions (GitHub-only for now, GitLab stubs). Includes 9 new tests for isSameProject covering GitHub, GitLab, and cross-platform scenarios. For provenance purposes, this commit was AI assisted. * feat(shared): add GitHub PR stack tree walking and PR list fetching Implements fetchGhPRStack() which walks up/down the PR stack via GraphQL, resolving numbers and titles for each node in the chain. Collapses queryPRsByHead/queryPRsByBase into a single queryPRsByRef helper. Adds fetchGhPRList() using gh pr list. Fixes GHE support by removing hostnameArgs from fetchGhPRList (--repo already handles GHE). Filters jq "null" string from defaultBranch detection. For provenance purposes, this commit was AI assisted. * feat(shared): fetch defaultBranch for GitLab MRs Queries the project's default_branch via glab API so getPRStackInfo can detect stacked MRs on GitLab. Best-effort — caught errors fall back to undefined. For provenance purposes, this commit was AI assisted. * feat(shared): add PR stack detection and full-stack diff module New pr-stack module with: - getPRStackInfo(): detects stacked PRs from baseBranch vs defaultBranch - getPRDiffScopeOptions(): generates layer/full-stack scope options - runPRFullStackDiff(): computes diff from default branch to HEAD - resolvePRFullStackBaseRef(): resolves origin/main or local main - checkoutPRHead(): fetches and checks out a PR head in a worktree - buildMinimalStackTree(): builds UI tree from stack info Includes 13 tests covering ref resolution, branch fallbacks, and GitLab ref formats. For provenance purposes, this commit was AI assisted. * feat(shared): add worktree pool for per-PR agent isolation Creates a session-scoped pool of git worktrees — each PR visited during a stacked review gets its own isolated checkout. Agents run in their PR's worktree undisturbed by PR switches. Handles deduplication of concurrent ensure() calls for the same PR. Includes 11 tests covering caching, cross-repo restrictions, GitLab ref formats, and cleanup. For provenance purposes, this commit was AI assisted. * feat(shared): add diffScope/prUrl to agent jobs, branch diff type Adds prUrl and diffScope optional fields to AgentJobInfo so agent findings carry the PR and scope context they were launched under. Exports new pr-stack and worktree-pool modules from package.json. Adds 'branch' to DefaultDiffType union for branch diff as default. For provenance purposes, this commit was AI assisted. * feat(ui): add PR annotation fields, Popover, and SearchableSelect Extends CodeAnnotation with prUrl, prNumber, prTitle, prRepo, and diffScope fields for stacked PR attribution. Adds shared Popover wrapper around radix-ui. Adds SearchableSelect for filterable dropdown lists (used by PR selector). For provenance purposes, this commit was AI assisted. * feat(ui): add branch diff as default option, new Git settings tab Adds 'Branch' as a fourth default diff type option in both the first-run dialog and settings panel. Moves the default diff type setting from the Display tab to a new Git tab in review mode. Updates config store validators to accept 'branch'. For provenance purposes, this commit was AI assisted. * feat(server): add prUrl/diffScope plumbing to agent jobs and prompts Threads prUrl and diffScope through the agent job lifecycle so findings carry the PR and scope they were generated under. Adds full-stack prompt branch to codex-review and tour-review — when in full-stack mode, the diff is inlined in the prompt instead of telling the agent to run git diff. Re-exports isSameProject and new PR provider functions from server/pr.ts. For provenance purposes, this commit was AI assisted. * feat(server): add stacked PR support to Bun review server Adds PR switching, layer/full-stack scope toggling, PR list caching, worktree pool integration, and multi-PR platform posting to the Bun review server. Key additions: - /api/pr-diff-scope: switch between layer and full-stack diffs - /api/pr-list: cached PR list for the current repo - /api/pr-switch: in-place navigation between PRs in a stack - /api/pr-action: targetPrUrl support for multi-PR posting - /api/file-content: full-stack branch for hunk expansion - prSwitchCache/prStackTreeCache for session-scoped caching - diffScope tagging on agent job completion - Scope guard: returns 400 on full-stack diff failure instead of overwriting the working diff with empty content For provenance purposes, this commit was AI assisted. * feat(ai): pass cwd to Claude agent SDK for worktree support Forwards the working directory to the Claude agent provider so agents run in the correct worktree when reviewing stacked PRs. For provenance purposes, this commit was AI assisted. * feat(pi): add stacked PR support to Pi server (Bun parity) Mirrors all stacked PR features from the Bun server: - PR switching, scope toggling, PR list, multi-PR posting - prSwitchCache/prStackTreeCache with initial PR seeding - diffScope/prUrl plumbing in agent jobs - Worktree pool creation and lifecycle - Full-stack file-content resolution matching Bun's guard structure - targetPrUrl support on /api/pr-action Hoists worktreePool declaration to outer scope in plannotator-browser to fix TS18004 scoping error. Updates vendor.sh for new shared modules. For provenance purposes, this commit was AI assisted. * feat(hook): create worktree pool for PR review sessions Creates a worktree pool when opening a PR review with --local, seeding it with the initial PR's checkout. Integrates pool cleanup into server shutdown. Passes the pool to startReviewServer for agent isolation during PR switching. For provenance purposes, this commit was AI assisted. * feat(review-editor): add hooks for PR stack, context, and annotations - useAnnotationFactory: stamps prUrl/prNumber/prTitle/prRepo/diffScope onto annotations, only when viewing a stacked PR - usePRStack: handles scope selection and PR switching with loading state - usePRContext: adds URL-change detection to prevent stale-fetch race when switching PRs (discards in-flight responses for previous PR) For provenance purposes, this commit was AI assisted. * feat(review-editor): add stacked PR UI components - PRSelector: searchable dropdown for switching between PRs in a repo - PRSwitchOverlay: loading animation during PR switch - StackedPRLabel: stack tree popover with scope selector and PR navigation - ReviewSubmissionDialog: multi-PR submission dialog with per-target status, orphaned findings section with copy-as-markdown, and partial failure retry For provenance purposes, this commit was AI assisted. * feat(review-editor): multi-PR export with heading hierarchy Updates exportReviewFeedback for multi-PR sessions: - Groups annotations by prUrl, then by file within each PR - Uses proper heading hierarchy (## for files, ### for annotations in multi-PR mode) - Detects single-PR mismatch (annotations from a different PR than the current view) and uses annotation-level PR context - Adds diffScope labels per PR group when present Includes 5 new tests: multi-PR headings, single-PR mismatch, diffScope labels, and non-stacked annotation handling. For provenance purposes, this commit was AI assisted. * feat(review-editor): integrate stacked PR into sidebar, diff panel, and agents - ReviewSidebar: groups annotations by PR in multi-PR sessions, shows PR headers with annotation counts - ReviewDiffPanel: filters annotations by prUrl and diffScope so only matching annotations appear in the diff gutter - ReviewStateContext: adds prDiffScope to shared review state - ReviewAgentJobDetailPanel: shows diffScope in job detail - PRSummaryTab: shows stack info in PR summary - index.css: PR switch shimmer and overlay animations For provenance purposes, this commit was AI assisted. * feat(review-editor): wire stacked PR into main review app Integrates all stacked PR features into the review editor: - PR stack state management (prStackInfo, prStackTree, prDiffScope) - applyPRResponse: shared handler for PR switch and scope toggle, preserves active file index on scope changes - Multi-PR platform posting via Promise.allSettled with parallel requests, partial failure retry, and per-target status tracking - ReviewSubmissionDialog replaces inline dialog JSX - useAnnotationFactory stamps PR context onto annotations - keepalive on /api/feedback to survive tab closure - Proper try/catch/finally on handlePlatformAction For provenance purposes, this commit was AI assisted. * docs: add stacked PR review documentation Updates AGENTS.md, code-review command docs, and AI code review guide with stacked PR review capabilities. For provenance purposes, this commit was AI assisted. * fix(server): stamp prNumber/prTitle/prRepo on agent findings Agent annotations only had prUrl and diffScope, missing prNumber, prTitle, and prRepo. When agent findings were the only annotations for a PR target in the submission dialog, the target rendered as #0 with no title. Now resolves full PR context from prSwitchCache at job completion and stamps all five fields. Both Bun and Pi. For provenance purposes, this commit was AI assisted. * feat(ui): rename diff options — "Committed" replaces "Branch" / "Current PR Diff" Consolidates two confusing committed-diff options into one: - Settings/first-run: "Committed" — "Everything you've committed on this branch" - Mid-session switcher: "Committed changes" (replaces both "vs main" and "Current PR Diff") Uses merge-base under the hood (matches GitHub PR behavior). Removes the two-dot branch diff from the UI — it stays in the runtime DiffType union for backwards compat. Old "branch" values in config/cookies are silently upgraded to merge-base. Git settings tab now uses radio cards with descriptions instead of a cramped segmented control. First-run dialog descriptions rewritten in plain language — no git commands. For provenance purposes, this commit was AI assisted. * fix(review-editor): rename client-side "PR Diff" labels to "Committed changes" DiffTypePicker.tsx had a hardcoded "PR Diff" override for merge-base when the base picker is present. exportFeedback.ts also used "PR Diff" in export labels. Both now say "Committed changes" to match the server-side label and settings UI. For provenance purposes, this commit was AI assisted. * fix(server): discover stack UI for root PRs targeting the default branch Root PRs (baseBranch === defaultBranch) were excluded from stack detection because getPRStackInfo returned null. Now the server always fetches the stack tree in PR mode. If the tree reveals descendant PRs, prStackInfo is retroactively set with source "tree-discovered", enabling the stack UI, scope selector, and PR navigation from the root of a stack. Both Bun and Pi servers updated. Adds "tree-discovered" to the PRStackInfo source union. For provenance purposes, this commit was AI assisted. * fix(review-editor): derive diff scope from annotations, not UI state The export function now reads diffScope from annotations instead of the prReviewScope parameter. Fixes two issues: 1. Agent job "Copy All" showed the wrong scope when the user switched between layer/full-stack after launching the agent 2. Mixed-scope annotations produced a confusing "layer, full-stack" comma-joined label instead of grouping by scope Extracts renderScopedGroups helper for scope-aware grouping — used by both single-PR and multi-PR export paths. When annotations share one scope, it appears in the header. When mixed, annotations are grouped under ## Layer / ## Full-stack headings. Includes 4 new tests: uniform scope derivation, mixed scope grouping, single scope header, and prReviewScope override prevention. For provenance purposes, this commit was AI assisted. * fix(server): add tree-discovered stack fallback to pr-switch handler The initial-load path upgrades prStackInfo for root PRs when the stack tree reveals descendants, but the pr-switch handler was missing this logic. The server now sends correct prStackInfo after switching to a root-of-stack PR. Both Bun and Pi. Also removes stale prReviewScope dependency from agent job panel's copyAllText useMemo. For provenance purposes, this commit was AI assisted. * fix: extract resolveStackInfo helper, fix stack UI on non-stacked PRs Extracts the tree-discovered stack fallback into resolveStackInfo() in pr-stack.ts — eliminates 4 copies of the same logic across Bun startup, Bun pr-switch, Pi startup, and Pi pr-switch. Fixes StackedPRLabel showing on every PR: the check now counts non-default-branch nodes (> 1) instead of all nodes (> 1). Without this, every PR showed a "Stack (1 PR)" popover because the tree always has at least [defaultBranch, currentPR]. For provenance purposes, this commit was AI assisted. * fix(review-editor): don't re-open already-succeeded PR tabs on retry On partial failure retry, openUrls was pre-seeded with URLs from previously succeeded targets, causing those PR pages to re-open in the browser alongside newly succeeded ones. Now starts empty — only URLs from the current attempt are opened. For provenance purposes, this commit was AI assisted. * refactor(review-editor): extract PR session state into usePRSession hook Consolidates 5 independent useState calls (prMetadata, prStackInfo, prStackTree, prDiffScope, prDiffScopeOptions) into a single usePRSession hook with atomic updatePRSession callback. Replaces two identical 5-line setter blocks (initial load and applyPRResponse) with single updatePRSession calls. All ~60 consumer sites unchanged — same variable names via destructuring. For provenance purposes, this commit was AI assisted. |