github backnotprop/plannotator v0.18.0

7 hours ago

Follow @plannotator on X for updates


Missed recent releases?
Release Highlights
v0.17.10 HTML and URL annotation, loopback binding by default, Safari scroll fix, triple-click fix, release pipeline smoke tests
v0.17.9 Hotfix: pin Bun to 1.3.11 for macOS binary codesign regression
v0.17.8 Configurable default diff type, close button for sessions, annotate data loss fix, markdown rendering polish
v0.17.7 Fix "fetch did not return a Response" error in OpenCode web/serve modes
v0.17.6 Bun.serve error handlers for diagnostic 500 responses, install.cmd cache fix
v0.17.5 Fix VCS detection crash when p4 not installed, install script cache path fix
v0.17.4 Vault browser merged into Files tab, Kanagawa themes, Pi idle session tool fix
v0.17.3 Sticky lane repo/branch badge overflow fix
v0.17.2 Supply-chain hardening, sticky toolstrip and badges, overlay scrollbars, external annotation highlighting, Conventional Comments
v0.17.1 Pi PR review parity, parseRemoteUrl rewrite, cross-repo clone fixes, diff viewer flash fix
v0.17.0 AI code review agents, token-level annotation, merge-base diffs
v0.16.7 Gemini CLI plan review, install script skills directory fix

What's New in v0.18.0

v0.18.0 adds focus & wide modes for annotate, first-class OpenCode detection, word-level inline plan diffs, content negotiation for URLs that publish Markdown (via Cloudflare), and inline color swatches in the plan viewer. 13 PRs, 7 from external contributors — 6 of them first-timers.

Word-Level Inline Plan Diff

The old plan diff stacked the full old block above the full new block whenever a paragraph was modified. A single word change showed the same paragraph twice with no visual cue to where the edit actually happened. Readers ended up comparing two nearly identical blocks line by line to find the delta.

The new default Rendered mode performs a second-pass word diff on modified blocks and highlights only the changed tokens inline. A one-word reword now reads as a single paragraph with <ins> and <del> markers on exactly the changed words. Inline code spans, markdown links, and fenced code blocks are preserved as atomic units through a sentinel substitution pass, so diff markers can't split them.

A third mode switcher tab, "Classic," keeps the legacy block-level stacked rendering for users who prefer it. Raw git-style output is unchanged. Modified blocks are click-to-annotate directly, with both the old and new content captured in the exported feedback so comments on struck-through words keep their context.

Amber borders on modified blocks complete the green/red/yellow convention used by GitHub and VS Code.

Wide and Focus Modes

Wide markdown tables were unreadable because both side panels (TOC on the left, annotations on the right) stayed fixed while the reader width was capped. Tables wrapped awkwardly or required horizontal scrolling inside a narrow column.

Two new toggles sit above the document and next to the lightning-bolt action:

  • Wide hides both panels and removes the reader width cap. Wide tables and code fences get the full document area.
  • Focus hides both panels but keeps the normal reader width. Distraction-free reading without stretching the content.

Enabling either mode collapses the left sidebar, hides the annotations panel and resize handle, and toggles the width cap accordingly. Exiting restores the exact previous layout, including which sidebar tab was open. Opening any sidebar or annotations panel automatically exits.

Available in plan review, annotate, and linked-doc overlays. Archive mode and plan-diff view keep the standard layout.

First-Class OpenCode Detection

The origin detection chain in the hook server didn't include OpenCode. Every OpenCode invocation fell through to the claude-code default, which loaded the wrong UI variant: missing agent-switch toggle, wrong agent badge. The opencode origin key was already defined in AGENT_CONFIG with its badge styling in place, but the detection side was never wired up.

OpenCode is now detected via OPENCODE=1, the canonical runtime flag set unconditionally by the OpenCode binary. The full priority order is:

PLANNOTATOR_ORIGIN > Codex > Copilot CLI > OpenCode > Claude Code (default)

The PLANNOTATOR_ORIGIN environment variable was documented in the source but never read. It now functions as an explicit override at the top of the chain, validated against AGENT_CONFIG so invalid values fall through to env-based detection instead of breaking.

Content Negotiation for Markdown-Serving URLs

When you run plannotator annotate https://..., the tool goes through Jina Reader (or Turndown as a fallback) to convert HTML to markdown. But a growing number of sites — including Cloudflare's developer docs — now publish Markdown directly when you ask for it. Routing those through an HTML-to-markdown converter is wasteful and loses fidelity.

URL annotation now tries Accept: text/markdown, text/html;q=0.9 first, with a 5-second timeout. If the server returns content-type: text/markdown, the response is used directly — one fetch, no conversion. If the server returns HTML or the request fails, it falls through silently to the existing Jina/Turndown pipeline. Local URLs skip negotiation entirely.

A new content-negotiation source type is recorded on the result so the UI can indicate which path produced the content.

Hex Color Swatches in the Plan Viewer

Frontend plans reference hex color values constantly — design tokens, Tailwind overrides, CSS variable assignments, component palette decisions. Reviewers had to mentally decode every #ff6600 or open a color picker to follow the author's intent.

The plan viewer now renders a small filled swatch inline, immediately to the left of the hex code. The swatch is a 14×14 rounded square matching the referenced color. Supports 3-, 4-, 6-, and 8-digit hex with a negative lookahead that excludes URL anchors, CSS id selectors, and any identifier that continues with word characters.

The color value is constrained by the regex before reaching React, and rendered via the React style object — not cssText — so there's no CSS injection path. 19 tests cover valid patterns, false-positive guards, and injection attempts.

Self-Hosted Paste Service Support

Short-link sharing for larger plans routes through a paste service at plannotator-paste.plannotator.workers.dev. Self-hosted deployments had no way to point at their own paste service — the URL was hardcoded in the OpenCode plugin.

The PLANNOTATOR_PASTE_URL environment variable now configures a custom paste endpoint. The OpenCode plugin reads it via a new getPasteApiUrl dependency that flows through command handlers (annotate, annotate-last, archive) and the review server. The Landing component accepts a shareBaseUrl prop with a fallback to the default. CORS documentation in the paste service now includes explicit guidance for self-hosters.

Backward compatible: unset PLANNOTATOR_PASTE_URL continues to use the hosted default.

OpenCode Review: Reuse the Existing Local Server

On subsequent review commands, the OpenCode AI review path tried to start a second opencode serve and collided with the existing local server on port 4096. The first opencode serve wasn't being cleaned up, so port conflicts were guaranteed on the second invocation.

The review flow now attaches to the default local OpenCode server at 127.0.0.1:4096 if one is already running. If nothing is listening, it spawns a new instance as before. No extra lifecycle management, no extra ports — just reuse what's already there.

The PR also fixes two local-testing issues uncovered along the way: the source-loaded OpenCode plugin was resolving bundled HTML from the wrong directory, and the sandbox + postinstall paths were not using the documented plugins/ and commands/ directories.

Additional Changes

  • ~ expansion in user-entered file paths — The shared path resolver now expands home-relative ~ in annotate entrypoints and the Bun and Pi reference handlers, so file, folder, vault, and linked-document paths all handle ~ consistently. #572 by @AlexanderKolberg
  • Thumbs-up quick label on the annotation toolbar — A one-click "Looks good" 👍 button sits before the existing quick labels menu, with green hover styling to match the semantic. #588 by @backnotprop
  • Save as PDF discoverability — The action menu label is now "Print / Save as PDF" with a subtitle explaining how to choose Save as PDF in the system print dialog. No new print pipeline — just making the existing capability findable. #587 by @backnotprop
  • Disable auto-invocation of plannotator slash commands in Claude Code — The four plannotator Claude Code command definitions (annotate, archive, last, review) now carry disable-model-invocation: true, preventing the model from running them automatically. #586 by @backnotprop
  • Stop forcing an agent cycle in OpenCodeagent_cycle assumed only a build and plan agent and broke when users had other agents defined. Removed. #564 by @andreineculau
  • RSS feed link in the marketing layout — The blog's RSS feed is now advertised in the shared <head> so feed readers and browsers can discover it automatically. #573 by @dotemacs

Install / Update

macOS / Linux:

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

Windows PowerShell:

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

Pin a specific version:

curl -fsSL https://plannotator.ai/install.sh | bash -s -- --version v0.18.0

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

Copilot CLI:

/plugin marketplace add backnotprop/plannotator
/plugin install plannotator-copilot@plannotator

Gemini CLI: The install script auto-detects ~/.gemini and configures hooks, policy, and slash commands.

OpenCode: Clear cache and restart:

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

Then in opencode.json:

{
  "plugin": ["@plannotator/opencode@latest"]
}

Pi: Install or update the extension:

pi install npm:@plannotator/pi-extension

VS Code Extension: Install from the VS Code Marketplace.


What's Changed

New Contributors

Contributors

@Pran-Ker shipped inline hex color swatches in the plan viewer, with a carefully constrained regex, a negative lookahead to avoid URL anchors and CSS selectors, and 19 tests including explicit injection guards.

@andreineculau removed the agent_cycle call that assumed everyone had only build and plan agents in OpenCode, fixing a bug introduced by #40.

@oorestisime fixed the OpenCode review port collision by reusing the existing local opencode serve at 127.0.0.1:4096 instead of spawning a second one, and cleaned up two local-testing path issues along the way.

@AlexanderKolberg added ~ home-directory expansion to the shared path resolver so annotate entrypoints and the Bun and Pi reference handlers all treat ~/file.md the same way.

@dotemacs added the RSS autodiscovery <link> to the marketing site layout so feed readers and browsers can pick up the blog feed automatically.

@dgrissen2 returned with annotate wide mode — a toggle that collapses both side panels and removes the reader width cap, gated to annotate sessions only, with layout restoration on exit. This follows their prior work on linked-doc navigation, image lightboxing, smart file resolution, and the purple P favicon.

@HeikoAtGitHub wired OpenCode into the origin detection chain (via OPENCODE=1) and activated the PLANNOTATOR_ORIGIN override that had been documented but never read, with seven headless detection tests covering the new priority order.

Community issue reporters:

  • @pbowyer filed #560 with a detailed request for word-level diffs and diff display options — that issue directly shaped the design of the new Rendered/Classic/Raw mode switcher.
  • @ndesjardins-comact reported #580, the hardcoded share URL blocking custom-domain usage, which drove the PLANNOTATOR_PASTE_URL work.
  • @alexey-igrychev reported both #513 (the opencode serve port collision) and #514 (empty response bubbles in the OpenCode AI tab).

Full Changelog: v0.17.10...v0.18.0

Don't miss a new plannotator release

NewReleases is sending notifications on new releases.