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.
- When a file exists but has an unsupported type (.cs, .ts, .py, etc.),
show 'File type not supported' instead of misleading 'File not found'
- Display supported file types (.md, .mdx, .html, .htm)
- Suggest using 'plannotator review' for code files
- Maintains backward compatibility for actual missing files
Fixes#757
Co-authored-by: ishowman <ishowman@users.noreply.github.com>
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).
The release/look-and-feel dialog previously rendered only in the plan
editor, so code-review-first users never saw the 0.20.0 announcement.
Render the same LookAndFeelAnnouncementDialog in review-editor, wired to
the shared gridEnabled config. Seen-state is a host-scoped cookie, so it
appears once across both apps. Gated ahead of the diff-type setup dialog
so the two never stack.
The Ask AI sidebar (claude-agent-sdk provider) was stale — topped out at
Opus 4.7. Add Fable 5 and Opus 4.8 (+1M) to match the review dropdown.
Sonnet 4.6 stays the default.
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.
- 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.
* 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.
* feat(review): add semantic diff overview
* fix(review): address semantic diff review findings
* fix(review): harden semantic diff fallback
* fix(review): avoid repo-local sem execution
* fix(review): normalize semantic diff cwd
* test(review): cover semantic diff local cwd
* fix(gitlab): fetch raw MR diffs
* fix(review): harden sem path resolution
* docs(review): add semantic diff handoff
* feat(review): semantic diff cards, header sem badges, jump-to-line
- Restyle the semantic panel as real bordered cards (shadcn surface) instead
of ASCII box-drawing; centered column, grid-aligned entity rows.
- Hide orphan (module-level) changes from the rows, matching sem's default;
the count still surfaces in the summary line.
- Add a 'sem · N' hover popover to each diff file header showing that file's
semantic changes, reusing the same row component as the panel.
- Clicking an entity (panel or popover) scrolls the diff to the lines via
pierre's [data-selected-line]; centers only when off-screen so manual
drag-selection isn't disturbed.
- Extract shared rows/helpers into semanticDiffShared; share one cached
/api/semantic-diff fetch across header badges.
* feat(review): land on All files by default
Semantic diff stays available via the file-tree nav entry and header badges,
but it's no longer the initial landing view.
* chore(review): drop dead change-symbol entries, log badge fetch failures
- Remove unreachable 'moved'/'renamed' entries from the changeSymbols table
(getChangeSymbol early-returns for those before the lookup).
- Log a console.error once per patch when the file-header badge's semantic
diff fetch fails or returns a non-ok status, so a systemic failure leaves a
trace instead of every badge silently showing nothing.
* feat(review): flatten semantic diff panel into grouped list
- Dissolve the per-file bordered cards into flat sections: the file path is now
a quiet underlined header (single hairline) with entity rows flush beneath,
hierarchy from whitespace + hover tint instead of boxes.
- Split the path into muted directory + emphasized filename for faster scanning.
- Nudge the add/remove glyphs (⊕/⊖) ~15% larger, line-height pinned so rows
don't grow; modified/rename/reorder glyphs unchanged.
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.
Two v0.19.28 release-gate follow-ups.
1. install.ps1 prompt timeout: PowerShell prompts had no timeout (only the
IsInputRedirected gate), so an attached-but-unattended Windows console
(PsExec/provisioner first-run or -Reconfigure) could hang. Added
Read-LineWithTimeout (standard KeyAvailable polling) -> same bounded read +
safe-"no" fallback install.sh already had; default 30s,
PLANNOTATOR_PROMPT_TIMEOUT=0 waits forever. Real humans answer in time;
-NonInteractive / redirected runs never prompt; the checkbox picker is only
reachable after a bounded "yes", so it can't hang unattended.
2. Sweep stale OpenCode archive stub: the /plannotator-archive removal (#873)
cleaned the skill scopes but not the OpenCode commands dir, leaving an
npm-postinstall upgrader with a dead plannotator-archive.md (empty no-op).
Swept in all three installers' cleanup.
install.test.ts: assert the OpenCode stub cleanup (sh/ps1/cmd) and the ps1
prompt timeout. 72 pass.
Two agent command-surface cleanups. Both remove only the command entry points; all underlying infrastructure stays.
1. /plannotator-status (Pi): removed — it echoed phase/plan-file/progress on
demand, but that state is already shown ambiently (status bar + live
checklist widget). The phase/checklist state machine is untouched.
2. /plannotator-archive (all agents): removed the command/skill entry points
across every surface — Claude/Codex/Kiro skills, Pi, OpenCode (handler +
dispatch + cli-bridge + embedded + stub), Droid, the Kiro agent prompt, all
three installers, docs, marketing, and the CI deprecated-command guard. The
installers also gained a stale-skill cleanup so upgraders drop a previously
installed plannotator-archive skill.
Kept (infrastructure) — archive browsing stays available in-review via the
sidebar: the `plannotator archive` CLI subcommand (apps/hook/server), the
mode:"archive" server path + /api/archive endpoints, ArchiveBrowser/useArchive,
the sidebar Archive tab, sessions.ts "archive" mode, and ~/.plannotator/plans
storage.
Verified: bun test scripts/install.test.ts → 72 pass; pi-extension typecheck +
build:opencode pass; repo-wide residual scan clean; KEEP-set integrity
confirmed; one orphaned import (opencode commands.ts) caught in self-review and
removed.
Follow-up to #850 (unreleased), fixing two pre-release regressions vs v0.19.27.
- Claude Code: add apps/skills/claude/* skills using `!`plannotator <sub> $ARGUMENTS``
+ allowed-tools + disable-model-invocation, so /plannotator-review|annotate|last|
archive auto-run with no permission prompt and pass arguments through — matching
the old slash commands. Codex keeps the prose apps/skills/core/*. Installers route
~/.claude/skills <- apps/skills/claude and ~/.agents/skills <- apps/skills/core.
- Installers: gate the wizard so an unattended-but-open /dev/tty can't hang
(PLANNOTATOR_PROMPT_TIMEOUT, default 30s; timeout/EOF resolves to safe "no").
Don't gate on $CI (explicit --reconfigure/--extras must win). Don't persist
timed-out wizard answers as install-prefs. Keep ask_yes_no's read in a tested
context so set -e never aborts on a timeout.
72 installer tests pass.
* feat: single-source skills into core/extra, replace Claude commands with skills, de-hardcode installers
- apps/skills/core/{review,annotate,last,archive}: single authoritative
source for the always-installed skills (archive is new); all carry
disable-model-invocation + agents/openai.yaml sidecars
- apps/skills/extra/{compound,setup-goal,visual-explainer}: no longer
default-installed (except Kiro); installers print an
`npx skills add backnotprop/plannotator/apps/skills/extra` suggestion
- Claude Code: apps/hook/commands/ deleted, command heredocs removed;
core skills in ~/.claude/skills are the slash commands now
- Installers: OpenCode/Gemini command files copied from an extended
sparse checkout instead of heredocs; install.cmd gains the previously
missing OpenCode command install; aggressive cleanup of legacy
~/.claude/commands and ~/.codex/skills artifacts
- Codex: core skills install to ~/.agents/skills (official path);
~/.codex/skills install removed
- Pi: extension no longer bundles skills; #670 settings filter removed
* fix: review findings — old-tag soft guards, cmd replace-not-merge, plugin-update hint, frontmatter test
- install.sh: a --version tag predating apps/skills/core no longer aborts
the whole copy subshell (which also skipped OpenCode/Gemini commands);
core skills now soft-skip with an accurate message, matching ps1/cmd
- install.sh: subshell failure message no longer claims "git required"
when git was present (clone/network errors get their own wording)
- install.cmd: pre-remove skill dirs before xcopy so upgrades replace
rather than merge (stale files from renamed/deleted skill files no
longer linger; parity with sh/ps1)
- all installers + docs: tell upgraders to run /plugin marketplace update
so the plugin's old namespaced plannotator:* commands disappear (#817)
- install.test.ts: assert every core SKILL.md sets
disable-model-invocation: true — the load-bearing line that keeps core
skills out of Pi's system prompt (#842 regression guard)
* test: pin old-tag soft-guard behavior, dedupe core-skill list in tests
* fix: interrogation review findings — cross-installer diagnostic parity
- install.ps1/install.cmd: emit the "predates the core/extra skill
layout" diagnostic on old pinned tags instead of silently skipping
core skills (parity with install.sh)
- install.ps1: clone/network failure no longer claims "git required"
(git was already verified present); the outer catch now reports the
actual exception
- install.sh: "Installed OpenCode/Gemini commands" echoes are guarded
on the copy actually having a source, so old pinned tags don't print
false success (ps1/cmd already gated this way)
- AGENTS.md: opencode-plugin commands/ comment now reflects all four
command stubs
- install.test.ts: shared test asserts the soft-skip diagnostic exists
in all three installers and pins ps1's honest failure wording
* fix: respect CODEX_HOME for Codex home directory (#852)
Codex stores config and state under $CODEX_HOME when set, falling back
to ~/.codex (developers.openai.com/codex/config-advanced). Plannotator
hardcoded ~/.codex in two places:
- runtime: codex-session.ts scanned ~/.codex/sessions for rollout
files, so `plannotator last` failed with "No rendered assistant
message found" when CODEX_HOME pointed elsewhere. Now resolved the
same way copilot-session.ts handles COPILOT_HOME and session-log.ts
handles CLAUDE_CONFIG_DIR.
- installers: detection, config.toml/hooks.json paths, manual-setup
instructions, and the stale-skills cleanup now derive from
CODEX_HOME in all three scripts.
Tests: codex-session.test.ts covers rollout discovery under a
CODEX_HOME temp dir; install.test.ts asserts all three installers
respect the variable and that the fallback is the only hardcoded
~/.codex path left in install.sh.
* fix: hard-fail skill install, guard command cleanup, one-time extras migration
External review triage on PR #850 surfaced two real installer issues:
P1 — commands deleted before replacement: the Claude command cleanup
ran before the git-gated skill install, so a missing git, a failed
clone, or an old pinned tag deleted the user's slash commands and
installed nothing (a regression — the old installer needed no git).
Now:
- missing git is a hard failure before anything is touched ("install
git, then run this installer again")
- a failed fetch is a hard failure ("something went wrong — run the
installer again") instead of a silent skip
- the legacy command cleanup runs AFTER the install and only removes a
command file when its same-name replacement skill exists on disk
- old pinned tags keep the soft-skip (no deletion happens, commands
survive, CI e2e against old tags stays green)
P2 — recurring extras deletion: the extras cleanup ran on every
invocation, deleting copies users reinstalled via the suggested
`npx skills add` (the copies are byte-identical, so only provenance
can tell them apart). The cleanup is now a one-time migration recorded
in a migrations ledger under the Plannotator data dir
(<PLANNOTATOR_DATA_DIR|~/.plannotator>/migrations/), the same
record-what-you-did pattern package managers use.
All three installers (sh/ps1/cmd) updated in parity; tests pin the
guard condition, the ledger gating, and the hard-fail messages.
* test: tripwire — install.cmd must never contain /dev/null redirects
* fix: every skill sets disable-model-invocation — no exceptions
Maintainer rule: all Plannotator skills are user-invoked, never
model-auto-invoked. setup-goal (missing since #665) and the three Kiro
skills now carry the flag. The frontmatter test scans every SKILL.md in
apps/skills/core, apps/skills/extra, and apps/kiro-cli/skills
dynamically — with a floor of 10 — so a future skill cannot ship
without it.
* docs: git is a hard installer requirement; clarify post-gate sections complete on re-run
* docs: align ps1/cmd comments with hard-fail semantics
* feat: guided install — extras opt-in via skills CLI, model-invocation picker
Interactive terminals get a two-question wizard on first run:
1. Install the extra skills? Yes delegates to `npx skills add
backnotprop/plannotator/apps/skills/extra` (its UI picks the agents),
wired to /dev/tty so piped curl|bash installs still work. Skipped
when extras already exist on disk.
2. Make any skills callable by the model? Yes opens a space-toggle
checkbox (sh/ps1) or numbered toggles (cmd), listing all skills if
extras were chosen, core-only otherwise. Chosen skills get
disable-model-invocation stripped from their INSTALLED copies and the
Codex sidecar's allow_implicit_invocation flipped — re-applied every
run since installs replace skill folders. Repo sources stay locked.
Answers persist to <data dir>/install-prefs (shared format across all
three installers) and re-runs reuse them silently; --reconfigure
re-opens the wizard. Automation is untouched: no terminal means no
prompts and today's defaults; --extras/--no-extras/--model-invocable/
--non-interactive give scripts explicit control.
* fix: self-review of guided install — cmd pipe expansion bug, flag/wizard interplay
- install.cmd: the checkbox preselection used `echo !var! | findstr` —
each side of a cmd pipe runs in a child WITHOUT delayed expansion, so
the saved choices passed through as literal !var! text and
preselection never matched. Replaced with a substring-replace
containment test (no pipe).
- all three: a wizard question whose answer was already provided by a
CLI flag (--extras/--no-extras/--model-invocable) is no longer asked
and then silently overridden — the flag pre-answers it.
- install.cmd: unknown-option usage line now lists the wizard flags.
* feat: guided install question 3 — install Glimpse (native window)
glimpseui (third-party npm package, PR #840) gives Plannotator a native
WebView window instead of a browser tab; the runtime already
auto-detects it on PATH, so a global install is all that's needed.
- Wizard asks "Install Glimpse?" (default yes) after the skills
questions; skipped when glimpseui is already on PATH
- Yes runs `npm install -g glimpseui` (bun fallback on sh/ps1; printed
instruction when neither exists) — wizard or explicit flag only,
silent re-runs never install software
- --glimpse / --no-glimpse flags for automation; choice persisted to
install-prefs like the others
- docs + tests updated (glimpse detection, install command, flags, and
persist-condition assertions across all three installers)
* fix: self-review of Glimpse question — cmd bun fallback, stale usage text
* fix: merge-window hardening — guard Codex cleanup, remove old-installer junk dirs
plannotator.ai serves install.sh live from main (public/ symlink,
deployed on push), while the script fetches repo files at the LATEST
RELEASE TAG. Between merging the core/extra restructure and cutting the
release that ships it, the live script runs against the old-layout tag.
Two hazards in that window:
1. The Codex stale-skill cleanup removed working ~/.codex/skills with
no successor installed (core skills soft-skip on old tags). Now the
cleanup runs AFTER the install and removes a core skill only once
its replacement exists in ~/.agents/skills — same guard the Claude
command cleanup uses. The compound/setup-goal stale copies stay
unconditional (never Codex's to begin with).
2. The reverse combo (cached OLD script + NEW release tag) wholesale-
copies apps/skills/* and leaves junk core/ and extra/ directory
copies in ~/.claude/skills. Never valid skill names — all three
installers now remove them on every run.
* fix: glimpseui is a devDependency — consumers never use it from node_modules
PR #840 added glimpseui to dependencies in @plannotator/server and
@plannotator/pi-extension, but nothing imports it: both runtimes detect
the CLI on PATH (Bun.which / a manual PATH walk) and spawn it. The dep
only ever mattered in repo development, where `bun run` prepends
node_modules/.bin to PATH. For consumers it was inert download weight —
OpenCode plugin installs and `pi install` pulled a third-party package
that could never be detected (Pi's loader does not expose
node_modules/.bin; verified). Moved to devDependencies in both: dev
flows keep working, published packages stop shipping it. The sanctioned
end-user path is the guided installer's global `npm install -g
glimpseui`.
* fix: clean stale plugin command files from the installed plugin checkout (#817)
The installer already manages hooks.json inside
~/.claude/plugins/marketplaces/plannotator/apps/hook/, so the earlier
"don't reach into plugin storage" rationale for leaving the old
namespaced plannotator:* command files there was inconsistent. All
three installers now remove them — same replacement-skill guard as the
bare ~/.claude/commands cleanup — making the #817 duplicate menu
entries die on a single installer run + restart instead of waiting for
/plugin marketplace update. Hints/docs reworded accordingly.
* Revert "fix: clean stale plugin command files from the installed plugin checkout (#817)"
This reverts commit 3df37da92c.
* fix: Windows CI — assert the new no-commands contract; gate cmd wizard on a real console
The cmd e2e CI step still asserted the OLD contract (installer writes
~/.claude/commands/plannotator-*.md with the ! prefix). Commands are
dead; the step now guards the NEW contract: a fresh install must write
NO plannotator command files. The Gemini TOML assertions stay — they
now verify the verbatim checkout copy delivers intact files (the tag
the e2e pins, v0.17.1, contains apps/gemini/commands).
The failure also exposed that install.cmd ran the wizard on redirected
stdin (set /p falls through to defaults at EOF) — and Q3's default
being yes meant CI silently ran `npm install -g glimpseui`. cmd now
probes for a real console via `timeout /t 0` (errors when stdin is
redirected), matching sh's /dev/tty and ps1's IsInputRedirected gates:
no console, no wizard, no wizard-only installs.
On Windows, Bun.which("glimpseui") resolves to an npm script shim (.cmd / extensionless shebang), which spawn() cannot launch without a shell: it throws EINVAL on .cmd and ENOENT on the shim, so openGlimpse() always returned false and plannotator fell back to the browser. shell:true is not viable because it severs the stdin HTML pipe (glimpse.mjs then exits 1). Resolve the package entry and run it with node directly instead.
Add glimpse config option to PlannotatorConfig and resolveUseGlimpse()
resolver. Wire into both Bun and Pi browser-opening paths. Document
PLANNOTATOR_GLIMPSE, PLANNOTATOR_GLIMPSE_WIDTH, and
PLANNOTATOR_GLIMPSE_HEIGHT in env var reference and AGENTS.md.
For provenance purposes, this commit was AI assisted.
Move messageAnnotationEntries from a reactive useMemo to an on-demand
useCallback. The expensive parseMarkdownToBlocks calls for all messages
now only run when the user submits, downloads, or copies feedback —
not on every annotation change. Derive annotatedMessageIds from the
lightweight counts map instead of the full parsed entries.
For provenance purposes, this commit was AI assisted.
* 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>
* feat: open plannotator in glimpse
* fix: harden Glimpse browser integration
* chore: refresh lockfile after rebase
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
* 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).
Universal, auto-detected Kiro CLI support — no flag, no separate installer.
When ~/.kiro exists (or kiro-cli is on PATH), the installer copies Plannotator
skills and a custom agent into ~/.kiro, the same convention used for Codex and
Gemini.
- packages/shared: add the kiro-cli agent origin (badge, prompt runtime,
PLAN_TOOL_NAMES). Origin is cosmetic for Kiro (no dedicated AI provider).
- apps/kiro-cli: 3 origin-baked skills (review/annotate/archive) + an example
custom agent that wires every skill via skill:// resources and a
plannotator-scoped shell tool. setup-goal + visual-explainer install from
apps/skills (no content duplication).
- scripts/install.sh: auto-detect ~/.kiro, sparse-checkout apps/kiro-cli,
install 3 kiro + 2 shared skills + the agent (never clobbering an existing
one); covered by scripts/install.test.ts.
- scripts/install.ps1 + install.cmd: Windows parity mirroring the Codex
pattern. Statically verified only; not yet runtime-tested on Windows.
- docs: installation + Kiro guides, AGENTS.md, env-var reference updated.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
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
Replace the warm-hue (55) walnut dark palette with the Plandromeda
palette — Andromeda's soft darkness at oklch hue 260. Brighter
primaries, richer accents, consistent blue undertone across the page.
Light mode unchanged (already identical to Plandromeda light).
* 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