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>
* 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>
* Add Amp plugin integration
* Use official Amp logo on landing page
* Tighten landing agent selector
* Default landing selector to Claude Code
* Stabilize server ready handoff test
* Create ready handoff directory before writing
* Stabilize server ready handoff tests
* Fix Amp command cancellation and cwd
* Preserve Plannotator browser handling for Amp
* Fix Amp review edge cases
* Add PowerShell installer smoke coverage
* 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>
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
* 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
* 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
* 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.
Claude Code's agent view (and similar background/headless environments) sets
$BROWSER=true to mean "do not launch a browser." The previous logic took that
literally and ran `true http://localhost:NNNN` via `Bun.$`, which exits 0 without
opening anything. The server then sits forever in waitForDecision() with no
visible UI and no way for the user to recover the URL.
Detect the documented no-op sentinels (`true`, `false`, `none`, `:`, `0`, `1`)
on both PLANNOTATOR_BROWSER and BROWSER and treat them as if the variable were
unset. This lets shouldTryRemoteBrowserFallback() reach the VS Code IPC
fallback in remote sessions and lets openBrowser() fall through to the
platform default when nothing else is configured.
Refs #154.
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.
* 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.
* 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.
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>
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