github backnotprop/plannotator v0.27.15

4 hours ago

Follow @plannotator on X for updates

Missed recent releases?
Release Highlights
v0.27.14 Pi plan progress survives compaction, Codex threads across rollout files, WSL browser setting, Mod+E edit mode
v0.27.13 Open a review on a specific base (--base, --diff-type), symlink containment on /api/doc, CI flake fix, Amp decision relay
v0.27.12 Unified decision control, token hover cards, local-vs-remote diff, approval notes
v0.27.11 OpenCode server leak fix, durable local feedback archive, unknown-subcommand fix
v0.27.10 Auto-viewed files on scroll, annotation undo/redo, OpenCode 2 slash commands restored, npm 12 agent terminal fix
v0.27.9 WebMCP browser-agent tools, HTML refresh from disk, host seams, lazy renderers, Windows uninstall fix
v0.27.8 Pi keeps its prompt cache across plan transitions, thumbs-up returns to HTML annotation, embed picker seam
v0.27.7 Pi host crash fix on Windows, Call Flow tree cap, jj fork-point base, plannotator knowledge skill + llms.txt
v0.27.6 Live app annotation lands on Pi, one interaction model for HTML pages
v0.27.5 Annotate your running app, Agent TUI placement, collapsed lockfiles, VS Code theme fix
v0.27.4 Portable Guided Review exports, guides.show share links, guide CLI, jj Call Flow
v0.27.3 Folder watcher freeze fix on large repos, first SBOM-attested release pipeline

What's New in v0.27.15

Twenty-six pull requests, three of them from outside contributors and two of those first contributions. Most of the work is on the HTML annotate surface: pinpoint comments now carry a description of the element they point at, the page gets the whole viewport by default, links between local HTML files stay inside the session, and a folder review can finally see every comment it has collected. The rest is correctness work on annotation restore, on the Codex Stop hook, on the OpenCode 2 notice ordering, and on the x64 release binaries.

Four behavior changes are worth reading before you update: HTML annotate sessions now open with the floating tools hidden, the plan diff opens in Classic view again, an emoji on a GitHub alert's first line becomes that alert's icon, and Codex 0.114 through 0.116 users now get plan review on Stop where they previously got nothing.

Plannotator TUI and Herdr Annotate

A one-time panel now announces Plannotator's two terminal tools. Plannotator TUI is a standalone terminal client: annotate Markdown in the terminal (select, comment, looks good, delete), review a folder, or annotate one of your agent's recent replies, then hand the review to your agent as numbered feedback. Herdr Annotate is a Plannotator plugin for Herdr: comment on any terminal text, review Markdown documents and agent replies inside Herdr, and send the feedback straight back to the agent. Its document review runs Plannotator TUI.

The panel shows a looping demo of the real client, one headline, one sentence, and a row of actions. It appears once per browser across plan review, annotate, and code review, then never again. There are no install commands or feature lists in it; the repo pages carry those. It is also suppressed in sessions that have no server behind them, so the public share portal never shows it.

The repos are plannotator-tui and herdr-annotate.

Pinpoint comments describe the element to the agent

Pinpointing an element in a raw HTML or live app session used to export either a placeholder quote (Feedback on: "[element: Navigation]") or the element's flattened text. The bridge already knew the element's selector, tag, and text, but nothing in the export read them, so an agent receiving the feedback could not tell which element the comment was about.

Each pinpoint now captures a bounded description of the element at click time: tag, id, author-written classes, an ancestor path, ARIA role and accessible name, an allowlisted set of attributes, the rendered text, a collapsed HTML skeleton, the child count, the viewport box, the nearest landmark and heading, a component hint from data-component or data-testid, and in live app sessions the route and page title. The export prints that skeleton as a fenced block under the comment with the selector, path, role, name, attributes, and box beneath it, so the agent can find the element in the source.

The capture is deliberately narrow. Form values, inline on* handlers, style, script and template contents, and full innerHTML are never read. URLs in captured attributes lose their query and fragment, both absolute and relative, so a checkout link carrying a session token is not written into a draft or an exported comment. Everything is re-validated and re-capped when it crosses back out of the iframe, and the whole record is hard-capped at 2 KiB per comment, so a 400-row list costs the same bytes as a button. Annotations without the field export exactly as they did before.

HTML annotate: the page gets the viewport, and the chords are real

Three changes to the raw HTML surface, all from owner feedback while using it.

Tools are hidden on open. An annotated HTML page owns the full viewport now, with no floating chrome over it on a fresh session. The header eye shows them, and so does the new Mod+Shift+X chord. A reviewer who explicitly shows the tools keeps them next session, and the control that brings them back lives in the header, so a hidden state can never strand you. One consequence to know: the version diff's "Show changes" control lives in that floating cluster, so on a fresh session it is behind the eye.

Mod+Shift+A is a documented toggle. It was already wired as one in the app, but the shortcut registry and the docs described it as a re-arm only, which left "Escape dropped me to Interact, how do I get back?" unanswered. It is now described and tested as the toggle it is, and the bridge mirrors both chords from inside the sandboxed iframe, so they work whichever document has focus.

The header controls explain themselves. Back, Refresh, the eye, and the pen now carry tooltips with a description line and their keyboard shortcut instead of a bare title string.

Links between local HTML files open as linked documents

A srcdoc document has no URL of its own, so it inherits the parent page's. A plain <a href="01-entry-point.html"> inside an annotated HTML file therefore resolved against the Plannotator server, the catch-all route answered with the app's own HTML, and the entire editor rendered inside the annotated frame. Even an in-page #anchor did it.

Link clicks are now intercepted in the bridge and resolved in the parent. A relative or nested sibling opens as a linked document in place, with its annotations kept per document. An in-page fragment scrolls within the current document. A server-absolute path resolves against the directory the session opened from. A sibling markdown file opens as a markdown document. An off-origin link opens in a new tab with noopener. A file that cannot be annotated gets a toast and no navigation, and javascript:, data:, mailto: and file: are dropped. Live app sessions are untouched.

The way back is a new header control, leftmost of the group, named after the document it returns to. It claims no keyboard chord, because Alt+Left and the browser's own Back belong to you. The compact touch shell gets the same action in its Options menu. Link clicks no longer pop the sidebar open on HTML documents, since a full-screen page owns the viewport and the sidebar is not where the way back lives anymore.

All files view in the annotations panel

In a folder session the annotations panel only ever showed the open file's comments. Reviewing across several documents, there was no way to see the whole review, and a file with nothing on it showed "No annotations yet" beside a small count of comments elsewhere that you could not read.

The panel header now carries a This file | All files toggle, shown whenever feedback exists outside the open document. All files groups comments by document, open document first and the rest by path, each group collapsible and labelled relative to the session root. The groups render the same annotation cards the single-document timeline renders, so quote, comment, type, images, replies, Edit and Delete all work across files, and a cross-file delete is reflected in what gets submitted. Clicking a card navigates to that document and selects the comment there.

The default rule is the frustration this exists for: your saved preference wins, except that a document with no feedback of its own, while other documents have some, opens on All files. Arriving by clicking a card in the All files list keeps you on All files. A plan review session with linked documents open contributes the plan's own comments as a group too, labelled (this plan).

  • #1533, with the plan-document group, normalized cross-file paths, and 44px touch targets in #1537

GitHub alert titles, and the annotation restore work that followed

A GitHub alert whose first body line is a bold-only line now renders that line as the alert's title on the icon row, in place of the type word. An emoji on that line becomes the alert's icon, which is the behavior change to know about: > [!TIP] followed by > ๐Ÿ’ก **Browser quirks** now shows the lightbulb and the title rather than the default tip icon and a bold paragraph. The type word stays in the accessible name, so a screen reader still hears "Tip: Browser quirks". An alert with no title line renders exactly as before. Hosts embedding the UI can supply their own icon set through a new alertIconRenderer seam; Plannotator bundles none.

That change removed a paragraph from the rendered alert, and the annotation layer addresses blocks by position. A draft saved before the upgrade could resolve onto the wrong element, so restore verification was turned on for plan review and annotate: a restore whose painted text does not match the annotation's own quote is dropped and retried by text search, and only reported when the rescue also fails. That guard then turned out to be too strict, and the next two releases of work fixed what it broke and what it had been hiding.

Restore verification compared the stored quote against the painted text with whitespace collapsed to a single space, but a selection string carries a blank line between blocks and the painted marks are concatenated with nothing between them. Every annotation spanning two paragraphs, a heading and its paragraph, or two list items came back from a reload with no highlight at all, under a misleading toast about edited text. Whitespace-only differences are now accepted, and content drift is still rejected. Two more shapes were failing closed for their own reasons: a drag starting inside excluded chrome, such as an alert's visually hidden type word, painted only its tail, so the start of a range is now snapped onto the first annotatable position it covers; and list markers are select-none, so a selection spanning two list items resolved onto text the bullet had joined, which the verification then rejected.

The other half of this is that an annotation that genuinely cannot be re-anchored is now visible on markdown surfaces the way it already was on HTML: the comment stays in the panel, still exports, and carries an Unanchored chip so you know its highlight is gone rather than wondering where it went.

Comment box caret alignment on Windows and Linux

The comment composer paints its visible text with a mirror layer over a transparent textarea, and only the caret still comes from the textarea itself. When the textarea grew tall enough to sprout a classic vertical scrollbar, the kind Windows and Linux use, which consumes layout width, the textarea's lines wrapped earlier than the mirror's. From the first line whose wrap point differed, the caret was drawn away from the character it belonged to. macOS and iOS overlay scrollbars take no layout width, which is why nobody on a Mac ever saw it.

The overlay's right padding now tracks the measured scrollbar width, re-measured when a resize adds or drops the scrollbar rather than only on keystrokes. Where the measured width is zero, the code writes no inline style at all, so macOS, iOS, and every touch surface render a byte-identical DOM to before.

Classic is the plan diff default again

The plan diff opens in Classic view, the block-level view with old above new. The word-level Rendered view is still there next to Raw. This applies to every surface that renders the plan diff, including the annotate version diff, since they share one mode setting.

OpenCode 2: feedback no longer lands behind the session notice

On OpenCode v2, /plannotator-last printed "Plannotator session ready: " and the agent correctly stayed idle. When the reviewer clicked Send Feedback, the agent woke up and spent a whole model turn on the URL notice first, with the annotations arriving in the turn after it. A plugin's synthetic message is not a notification on OpenCode 2: it admits a pending inbox row, and declining the wake does not exempt that row from the next promotion. Since queued rows promote one at a time, oldest first, the reviewer's own feedback was what woke the session and pulled the stale notice ahead of it.

The notice and the feedback that follows it now share one promotion, so they enter the same turn. Two follow-ups hardened the tracking behind that. The first made the pending flag track reality instead of intent: it was set when the host accepted the notice and cleared only after our own prompt, so a reviewer who typed anything in the TUI while the review was open had the notice promoted out from under the flag, and feedback sent minutes later was steered into the middle of an unrelated turn. The flag is now cleared when the host reports the row left the inbox, read in both event vocabularies OpenCode 2 uses, after which feedback falls back to queued delivery. The second made the tracker's dispose() terminal, so a notice that outlives its command cannot re-open an event subscription on the host with no owner left to close it. OpenCode 1 is untouched throughout.

Codex: Stop plan review stays in the current turn

The Codex Stop hook could scrape the most recent <proposed_plan> out of the rollout even when the turn that just ended proposed no plan, which resurfaced a plan from an earlier turn and opened a second review of something already decided. Plan candidates are now confined to the turn the Stop payload identifies, and the hook fails closed when turn identity is unavailable rather than guessing from stale transcript content.

That fix assumed every Codex sends a turn id with its Stop payload. It does not. The hooks engine shipped in Codex 0.114 and the turn_id field only landed in 0.117, so on 0.114 through 0.116 plan review silently stopped: no UI, no message, exit 0. Those versions do write turn markers into the rollout, so when the payload carries no turn_id at all the hook now resolves the turn from the rollout's own last id-carrying marker and runs the unchanged machinery on it. Codex builds that do send turn_id behave byte for byte as before. A blank turn id is still refused rather than treated as absent, and a rollout with no turn marker at all still fails closed.

The deny-and-resubmit de-duplication needed the same treatment. It anchored on a <hook_prompt> user message that Codex only started writing in 0.117, so on exactly the versions the rollout fallback exists to support, a reviewer who clicked Request changes got the unchanged denied plan re-opened whenever the model replied without a fresh plan. The boundary is now whatever the previous blocking Stop left in the turn, accepting Codex's older developer-role continuation message on the rollout-fallback path only.

Pi: PLANNOTATOR_BROWSER script paths work on macOS

On macOS the Pi extension always ran open -a $PLANNOTATOR_BROWSER <url>, treating the value as an application bundle. Pointing it at a script or an executable failed with a LaunchServices error that the detached spawn swallowed, so the extension reported that the review had opened while nothing did. Pi now mirrors the branch the plannotator binary already had: a value containing a slash that does not end in .app is executed directly with the URL as its argument, and application names and .app paths keep going through open -a.

x64 release binaries run on older CPUs

The v0.27.14 Linux x64 CLI exited with SIGILL on an Ivy Bridge Xeon E3-1220 V2, because the default Bun x64 target assumes instructions those CPUs do not have. The Linux and Windows x64 CLI and paste service binaries are now compiled with Bun's baseline x64 target, which Bun recommends for pre-2013 hardware. A bounded QEMU Nehalem gate was added to the existing Linux release smoke job, checking CLI --help, an exact --version match, and paste service startup, so a non-baseline binary fails the release rather than reaching someone's machine. Asset names, checksums, provenance attestations, the installers, the Bun pin, and every other platform target are unchanged.

Additional Changes

  • Annotation panel chrome. The header decision button reads Send Feedback rather than Send Feedback ยท n, and the per-row Copy button briefly added to annotation cards is gone; cards are back to Edit and Delete on every surface. Both owner-directed (#1516, #1520)
  • @plannotator/ui package publishes. 0.38.1 on core 0.25.2 fixes an install failure for embedding hosts: 0.38.0 imported a core subpath that had never been published, and the package smoke test now catches that class of break by checking the source imports against the published core. 0.38.2 restores the alert type word in the accessible name on WebKit and relaxes the React peer range back to ^19.2.3. 0.39.0 carries the element context types and parser helpers (#1510, #1511, #1522)
  • DOM tests actually run in CI. Forty-two DOM-gated test files had drifted off the workflow's explicit list and were silently skipping. They are registered now, along with a guard test that fails whenever a DOM-gated file in the repo is not named in a DOM_TESTS step (#1537, #1538)

Install / Update

macOS / Linux:

curl -fsSL https://plannotator.ai/install.sh | bash

Windows:

irm https://plannotator.ai/install.ps1 | iex

Claude Code Plugin: Run /plugin in Claude Code, find plannotator, and click "Update now".

Pi: Update @plannotator/pi-extension to 0.27.15 and restart Pi.

OpenCode: Clear cache and restart:

rm -rf ~/.bun/install/cache/@plannotator

What's Changed

  • feat(ui): render a GitHub alert's bold-only first line as its title, with an icon seam by @backnotprop in #1509
  • fix(packages): core 0.25.2 + ui 0.38.1, and a package smoke that catches an unpublished core subpath by @backnotprop in #1510
  • fix(ui): keep the alert type word in the accessible name on every engine (ui 0.38.2) by @backnotprop in #1511
  • fix(ui): drop the annotation count pill from the header decision primary by @backnotprop in #1516
  • feat(annotate): carry agent-facing element context on HTML and live-app pinpoints by @backnotprop in #1517
  • fix(opencode): co-promote the session-URL notice with the feedback on OpenCode 2 by @backnotprop in #1518
  • fix(ui): remove the annotation panel's per-row Copy button by @backnotprop in #1520
  • chore(ui): bump @plannotator/ui to 0.39.0 by @backnotprop in #1522
  • feat(ui): one-time announcement dialog for Plannotator TUI and Herdr Annotate by @backnotprop in #1529
  • fix(codex): keep Stop plan review in the current turn by @rNoz in #1169
  • fix(pi-extension): run script/executable PLANNOTATOR_BROWSER paths directly on macOS by @punk-dev-robot in #1429
  • fix(ci): build x64 releases for baseline CPUs by @ybtam in #1514
  • fix(ui): keep the composer text overlay aligned with the textarea when a classic scrollbar appears by @backnotprop in #1530
  • feat(annotate): hide HTML tools by default, toggle chords for annotate mode and tools, shortcut tooltips on the header controls by @backnotprop in #1531
  • fix(annotate): open relative links between HTML files as linked documents instead of loading the app inside the frame by @backnotprop in #1532
  • feat(annotate): All files view in the annotations panel for folder sessions by @backnotprop in #1533
  • fix(codex): resolve the current turn from the rollout when the Stop payload carries no turn_id by @backnotprop in #1534
  • fix(opencode): track whether the session-URL notice is still pending before steering feedback by @backnotprop in #1536
  • fix(annotate): QA follow-ups for the folder, All files, and HTML surfaces by @backnotprop in #1537
  • fix(ui): keep GitHub alert titles from breaking annotation anchors and quotes by @backnotprop in #1535
  • ci: register the #1535 DOM tests in the allowlist by @backnotprop in #1538
  • fix(codex): keep deny/resubmit de-duplication working on the rollout-fallback path by @backnotprop in #1539
  • fix(opencode): make the notice tracker's dispose terminal by @backnotprop in #1540
  • fix(ui): accept whitespace-only differences in restore verification, snap excluded selection starts, and merge folder-session chrome re-stamps by @backnotprop in #1541
  • fix(ui): restore cross-block annotations over list markers and alert titles from drafts by @backnotprop in #1542
  • fix(editor): default the plan diff view to Classic by @backnotprop in #1544

New Contributors

Contributors

@rNoz returns with the Codex Stop turn scoping in #1169, the fix that stops a decided plan from being re-opened by a later turn. It is his tenth merged pull request on the project, and the two follow-ups in this release both build directly on the machinery it introduced. He also helped diagnose the Pi browser failure on @punk-dev-robot's issue.

@punk-dev-robot reported the silent PLANNOTATOR_BROWSER failure on macOS in #1391 and then fixed it in #1429, matching the branch the main binary already had rather than inventing a second behavior. First contribution.

@ybtam found that the official Linux x64 binary would not start on an Ivy Bridge Xeon, tracked it to the Bun compile target, and shipped the baseline build with a QEMU Nehalem gate in #1514 so the failure cannot ship again. First contribution.

The reports and proposals that shaped this release:

  • @de-tre reported the misaligned comment caret in #1525, with the scrollbar observation that made the root cause obvious
  • @Naasha reported the queued annotations on OpenCode v2 in #1515
  • @jpvarbed reported plan review re-opening for an already-decided plan in #1075
  • @tekumara wrote a detailed proposal and mock for a unified review navigator in #1524. It was built in full and compared side by side against the current panel, and the current Tree / Git status / Commits design was kept; the branch is available to anyone who wants to take it further
  • @JcMinarro asked for review-level comments in #1527 and, in finding that the button already existed since v0.27.12, made the case that it is placed where people do not look for it

Thank you. Plannotator gets better because you tell us where it falls short.

Full Changelog: v0.27.14...v0.27.15

Don't miss a new plannotator release

NewReleases is sending notifications on new releases.