Technical Documentation: backnotprop/plannotator
βΉοΈ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference
Overview (README.md)
Plannotator
Plannotator is a local, browser-based review surface for AI coding agents: Claude Code, Codex, Copilot CLI, Gemini CLI, OpenCode, Kiro, Droid, Amp, and Pi.
It plugs directly into your agent through its hooks and commands. When the agent proposes a plan, html, or finishes writing code, the work opens in your browser and you mark it up, comment, and send feedback directly to the agent for it to act on it.
| ### Review documents, plans, and agent messages
Annotate plans, specs, messages, html, then send the feedback to your agent.
| Demo: Plan review with Pi | |
|---|---|
| ### Code Review |
Review local changes or remote PRs. Comment on diffs, suggest code. Your comments go back to the agent. Works with Git, GitButler, Jujutsu (jj), Perforce (p4), GitHub, and GitLab. | |
Annotate HTML Artifacts
Herdr Annotate: Plannotator in the Terminal
Herdr Annotate brings Plannotator-style review to the terminal: annotate terminal text, review whole Markdown documents and your coding agent's replies inside Herdr, and send the feedback straight back to the agent as its next message. Works with Claude Code, Codex, Pi, Copilot CLI, and Droid replies. Annotations are saved in the Plannotator data directory, so terminal reviews and app reviews compound.
herdr plugin install plannotator/herdr-annotatePrefer it standalone? Plannotator TUI powers the document review and runs without Herdr: brew install plannotator/tap/plannotator-tui.
Commands
On Codex, swap the slash commands for !plannotator β¦ (e.g. !plannotator review) or the $plannotator-* skills.
Annotate
/plannotator-annotate README.md # Local markdown file
/plannotator-annotate src/ # Browse and annotate files in a folder
/plannotator-annotate https://docs.rs/β¦ # Fetch and annotate any URL
/plannotator-annotate report.html --render-html # Render HTML as-is instead of converting
/plannotator-last # Annotate the agent's last messageNeed a realistic document to try? Copy the product requirements document template and filled example as Markdown.
Code review
/plannotator-review # Review uncommitted changes
/plannotator-review <github-pr-url> # Review a GitHub pull request
/plannotator-review <gitlab-mr-url> # Review a GitLab merge request
plannotator review --gitbutler # Review an active GitButler workspace
plannotator review --patch-file reading.diff # Review a static caller-supplied unified diffGitButler users can review the whole workspace, one stack, or one branch layer. See the GitButler workflow guide.
Plan mode
No command needed. Plan mode is wired in through each harness's hooks. Any time your agent creates a plan, the markdown review surface opens for you.
CLI
plannotator sessions # List active Plannotator sessions
plannotator sessions --open 1 # Reopen a session in the browser
plannotator archive # Browse saved plan decisions read-onlyPrivacy and network behavior
Plannotator does not collect usage telemetry or analytics. Plans, diffs, annotations, drafts, history, and configuration stay local by default.
Each plan review, annotate, archive, share-portal, and code-review app surface checks GitHub for the latest Plannotator release when it loads. This sends no plan or review content and gives the Plannotator project owner no usage analytics, although GitHub receives an ordinary request. There is currently no opt-out setting for the release check. Local Git code review can also query the configured origin with git ls-remote to detect the default branch and a stale baseline; it does not send the local diff. That remote query runs when the review opens, when the page loads its diff, when you switch the diff type or base (including the "Diff out of date · Refresh" button), and when you press Fetch — never on a timer, so an idle review page makes no network calls. Turn it off entirely with plannotator review --no-git-remote-check, PLANNOTATOR_GIT_REMOTE_CHECK=0, or { "gitRemoteCheck": false } in ~/.plannotator/config.json; the compare target then comes from local refs only and the "behind GitHub" banner never appears.
Content leaves the local workflow only when a network feature needs it:
- URL annotation fetches the requested site, through Jina Reader by default for public pages or directly when Jina is disabled or unavailable.
- GitHub and GitLab review uses your authenticated CLI and Git remote to retrieve PR or MR data.
- Ask AI and review agents send the selected question and relevant plan, document, repository, or diff context to your configured provider. When Ask AI is answered by the agent session that opened Plannotator ("Ask this session"), the question goes to that session instead.
- Sharing sends the complete link to whoever or whatever service you use to deliver it. Encrypted short links upload ciphertext to the paste service.
- Workspaces is a separate hosted product, so the open source app's local-storage model does not apply to content placed there.
The privacy policy documents these boundaries and the hosted website and waitlist data.
Link sharing
Open source asynchronous link sharing remains available for compatibility but is moving to deprecated support. Workspaces is the primary direction for team sharing. No removal date has been announced.
Share a plan with a teammate and they can annotate it themselves. Import their feedback and send it straight back to your agent.
Small markdown shares are compressed into the URL fragment. The fragment is not included in the browser's request to the share portal, but it is not encrypted. Anyone or any messaging service with the complete link can read the shared content. The portal host still receives ordinary request metadata.
Large markdown and raw HTML shares use a short-link service. The share payload is encrypted with AES-256-GCM before upload, the server stores only ciphertext, and the key is kept in the URL fragment rather than sent in the paste request. Anyone with the complete link can decrypt it. Hosted pastes expire after 7 days.
Same model as PrivateBin. The paste service is self-hostable.
Sharing can be disabled entirely with PLANNOTATOR_SHARE=disabled.
Workspaces is the primary path for hosted team collaboration.
Install
One installer covers almost every agent. It installs the plannotator binary, auto-detects your installed agents, and configures hooks, skills, and slash commands for each:
# macOS / Linux / WSL
curl -fsSL https://plannotator.ai/install.sh | bash# Windows PowerShell
irm https://plannotator.ai/install.ps1 | iexThe installer downloads the binary from GitHub Releases. A full install can also contact GitHub for release resolution and agent files, Ataraxy-Labs/sem for the optional sem sidecar, and npm for Pi, selected extra skills, or the managed agent-terminal runtime. Pinning --version skips only GitHub API release resolution, not the release download. See the privacy policy for the complete network boundaries.
Want just the binary and nothing else? Pass --minimal (or export PLANNOTATOR_MINIMAL=1) to install only the plannotator binary to ~/.local/bin, skipping every skill, hook, slash command, and per-agent config:
curl -fsSL https://plannotator.ai/install.sh | bash -s -- --minimalThen finish the step for your agent:
| Agent | After the installer | Details |
|---|---|---|
| Amp | Copy plannotator.ts into ~/.config/amp/plugins/, then plugins: reload. Workflows live in the command palette. |
README |
| Claude Code | /plugin marketplace add backnotprop/plannotator, then /plugin install plannotator@plannotator. Restart Claude Code. |
README |
| Codex | Nothing. Plan review is enabled automatically via Codex's experimental Stop hook (macOS/Linux/WSL; on native Windows, Codex hooks are experimental and the installer prints manual setup steps). $plannotator-review, $plannotator-annotate, and $plannotator-last skills included. |
README |
| Copilot CLI | /plugin marketplace add backnotprop/plannotator, then /plugin install plannotator-copilot@plannotator. Restart. Plan review activates in plan mode (Shift+Tab). |
README |
| Droid | droid plugin marketplace add https://github.com/backnotprop/plannotator, then droid plugin install plannotator@plannotator. Commands only, no plan interception yet. |
README |
| Gemini CLI | Nothing. The hook, policy, and slash commands are configured automatically. Requires Gemini CLI 0.36.0+. | README |
| Kiro CLI | Nothing. Skills and an example agent are installed automatically. Try kiro-cli chat --agent plannotator. |
README |
| OpenCode | Add "plugin": ["@plannotator/opencode@latest"] to opencode.json. Restart OpenCode. |
README |
| Pi | Skip the installer. Just pi install npm:@plannotator/pi-extension. Start Pi with --plan, or toggle with /plannotator-plan-mode. |
README |
Updating: run the installer again for the binary and slash commands. It also updates an installed Claude Code plugin when the claude command is on your PATH (opt out with --skip-claude-plugin); otherwise run claude plugin marketplace update plannotator, then claude plugin update plannotator@plannotator, then restart Claude Code (refreshing the marketplace alone does not update the plugin). Pi: pi update --extensions (a plain pi update updates only Pi).
On Claude Code 2.1.287 or newer, plan review, code review and annotate don't make Claude wait: Claude ends its turn and your decision arrives later as a message. Set PLANNOTATOR_CLAUDE_MOD=0 to go back to the classic flow.
Full walkthroughs live in the installation docs.
Uninstall
The safe default removes recognized Plannotator-installed components and keeps
your local plans, history, drafts, guides, and settings:
plannotator uninstallUse --purge for a full removal of known local Plannotator data as well:
plannotator uninstall --purgePurge requires typing purge at the prompt and explains that the data is
local-only: it is not stored on a Plannotator server and cannot be recovered.
For automation, pass --yes (or -y); non-interactive removal refuses to run
without it. Use --dry-run to preview recognized work without making changes.
Host integrations are always part of uninstall. If a broken or unavailable
host prevents safe cleanup, the command names the blocking plugin manager or
configuration, gives exact manual cleanup instructions, and stops before
deleting the binary. Complete that cleanup and rerun uninstall.
These mechanics keep the ordinary confirmation default-negative, make the
irreversible outcome require a stronger explicit word, and still give package
managers and scripts a conventional non-interactive flag.
The command covers the conventional macOS, Linux, WSL, and Windows binary
locations; the managed sem sidecar and agent-terminal runtime; installer
skills, commands, hooks, policies, caches, and recognizable Amp/Kiro files; and
detected Claude Code, Copilot CLI, Droid, Pi, and VS Code installations through
their host CLIs. Shared JSONC settings are edited surgically, while strict JSON
updates preserve the file's indentation, line endings, and trailing-newline
style. Custom
or unrecognized files, separately installed optional skills, project-local
integrations, external plan-save locations, and invalid configs are preserved
(malformed host config is a fail-safe error). If cleanup reports an error,
the CLI remains available for a safe retry, and its Windows PATH entry is
retained or restored when possible. If PATH restoration itself fails, the
output gives the full CLI path for retry and asks for manual PATH repair.
For safety, purge refuses filesystem roots, the home directory, the shared
temporary directory, symlinked data directories, and non-directory data paths.
Existing paths are compared by filesystem identity, so case aliases, symlinks,
hardlinks, and bind mounts cannot bypass the root/home/ancestor checks.
That identity and every containment guard are revalidated after awaited host
commands, immediately before the synchronous data-removal block; a replaced
data directory is refused without touching either the old or replacement data.
If your dedicated data directory is symlinked, point PLANNOTATOR_DATA_DIR at
its resolved target and retry.
If you installed only the standalone Pi extension and do not have theplannotator CLI, use pi remove npm:@plannotator/pi-extension.
Claude Code: manual hook setup (without the plugin system)
Add to ~/.claude/settings.json:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "plannotator",
"timeout": 345600
}
]
}
]
}
}Pin a specific version
curl -fsSL https://plannotator.ai/install.sh | bash -s -- --version vX.Y.Z& ([scriptblock]::Create((irm https://plannotator.ai/install.ps1))) -Version vX.Y.ZTry it
The fastest way to see what Plannotator does is to invoke it yourself, right now, from your agent:
/plannotator-last # annotate the agent's last reply
/plannotator-review # review your current diff, PR-style
/plannotator-annotate report.html # annotate any file, folder, or URL(Slash commands in most agents; $plannotator-* skills in Codex, command palette in Amp.)
Plan review needs no command at all. The next time your agent proposes a plan, it opens in your browser automatically.
How it works
Plan review
Agent calls ExitPlanMode
-> PermissionRequest hook fires
-> Local server reads plan from hook input
-> Browser opens with review UI
-> You annotate and approve/deny
-> Approve: agent proceeds
-> Deny: structured feedback sent to agent
-> Agent revises, plan diff shows what changedCode review
You run /plannotator-review
-> git diff captures changes (or PR fetched by URL)
-> Browser opens with diff viewer
-> Annotate lines, stage/unstage files
-> Send feedback: returned to agent session
-> Approve: "LGTM" sentIntegrations
VS Code: Open plans in editor tabs, view diffs inline, add annotations from the editor gutter. Install from the VS Code Marketplace.
Obsidian: Auto-save approved plans to a vault with YAML frontmatter, tags from the plan title, and backlinks for graph connectivity. Configure in Plannotator's Settings panel.
Bear: Save plans as Bear notes with nested tags and project metadata.
GitHub / GitLab: Pass any PR or MR URL to /plannotator-review and review it with the full diff viewer, annotations, and file tree.
Remote / SSH / devcontainer
Plannotator auto-detects SSH sessions and switches to a fixed port. For explicit control:
export PLANNOTATOR_REMOTE=1
export PLANNOTATOR_PORT=9999 # forward this portVS Code devcontainers forward the port automatically (check the Ports tab). For raw SSH, add to ~/.ssh/config:
Host your-server
LocalForward 9999 localhost:9999Security
Every released binary ships with a SHA256 sidecar. SLSA provenance attestations are available from v0.17.2. The current release workflow also attaches a CycloneDX JSON SBOM, evaluates it with a fresh Grype database before anything is attested or published, and creates a GitHub/Sigstore SBOM attestation for the shipped binaries and npm tarballs.
The SBOM is intentionally labeled as a release-wide Syft inventory of the monorepo's locked build inputs and dependencies. It is not an exact per-binary runtime inventory: Bun standalone executables do not expose their bundled JavaScript package metadata to Syft. The canonical installation and verification docs cover the existing installer path; the exact new SBOM commands are included below and must be copied to that Mintlify page before the first SBOM-enabled release.
The release gate rejects scanner-side ignored matches and treats unknown applicability conservatively as runtime when evaluating CISA KEV and fixable Critical findings. Its explicit Grype configuration, complete JSON results, database status, and repository policy decision remain available as workflow evidence.
To verify a released Linux x64 binary, its existing provenance, and the new SBOM evidence directly:
tag=vX.Y.Z
version="${tag#v}"
mkdir -p /tmp/plannotator-release-verify
gh release download "$tag" --repo backnotprop/plannotator \
--pattern 'plannotator-linux-x64*' \
--pattern "plannotator-${version}-release-sbom.cdx.json*" \
--dir /tmp/plannotator-release-verify
(cd /tmp/plannotator-release-verify && sha256sum --check plannotator-linux-x64.sha256)
(cd /tmp/plannotator-release-verify && sha256sum --check "plannotator-${version}-release-sbom.cdx.json.sha256")
gh attestation verify /tmp/plannotator-release-verify/plannotator-linux-x64 \
--repo backnotprop/plannotator --source-ref "refs/tags/$tag" \
--signer-workflow backnotprop/plannotator/.github/workflows/release.yml \
--predicate-type https://slsa.dev/provenance/v1
gh attestation verify /tmp/plannotator-release-verify/plannotator-linux-x64 \
--repo backnotprop/plannotator --source-ref "refs/tags/$tag" \
--signer-workflow backnotprop/plannotator/.github/workflows/release.yml \
--predicate-type https://cyclonedx.org/bomThese are separate claims over the same artifact digest: provenance identifies its builder/source/workflow, while the CycloneDX predicate describes the release-wide inventory. The release runbook also canonicalizes the downloaded SBOM and attested predicate with jq -S and compares them.
To verify on install:
curl -fsSL https://plannotator.ai/install.sh | bash -s -- --verify-attestationRequires the gh CLI, but no login: the installer fetches the attestation bundle from GitHub's public attestations API and verifies it with gh attestation verify --bundle (the extraction needs node, python3, or jq on PATH; gh's authenticated fetch is the fallback). Can also be set persistently in ~/.plannotator/config.json:
{ "verifyAttestation": true }Installer verification remains opt-in and verifies SLSA build provenance; normal installation does not require gh. See the canonical installation docs for details.
Configuration
Settings are saved in cookies (not localStorage) because each hook invocation runs on a random port. You can also set options through environment variables or ~/.plannotator/config.json.
Optional Vim controls
Plan and annotate views offer a default-off Vim controls profile under
Settings β Vim. Once enabled, focus the document and use j / k
to move one rendered block at a time. After l refines into a semantic level,j / k move among sibling rows, cells, or inline targets; h moves back to
the containing target. Refining past the deepest target enters text. v
starts characterwise Visual selection andV selects whole blocks. Space opens the normal annotation toolbar; c,d, m, and t select comment, redline, markup, and label actions. The same
semantic target graph drives pointer Pinpoint and keyboard navigation. Press? in the document for the contextual key reference. Inputs, dialogs,
editors, Tab, and all pointer interactions retain their native behavior.
The document takes focus automatically when the page is otherwise neutral;
press Escape from app chrome to return to it without clicking.
An additional default-off Vim HUD toggle appears beneath Vim controls. It
uses the product-demo styling for the live target reticle and navigation
context. Its bottom-right Key panel is independently hideable while the
reticle remains active; ? still opens the complete key map on demand. The
panel shows recent handled keys, the current block/line/word/Visual phase, and
the command meaning without capturing text typed into comments or other
controls.
See Vim controls for the interaction contract and
implementation architecture.
| Variable | Description |
|---|---|
PLANNOTATOR_REMOTE |
1/true for remote mode, 0/false for local, unset for SSH auto-detection |
PLANNOTATOR_PORT |
Fixed port (default: random locally, 19432 remote) |
PLANNOTATOR_BROWSER |
Custom browser to open plans in. none (also false, 0, off, no, true, 1, :) opens no browser at all; the session URL is always printed. |
PLANNOTATOR_AI |
disabled to disable Ask AI, Review Agents, and Guided Review; the annotate agent terminal is separate |
PLANNOTATOR_SHARE |
disabled to turn off URL sharing |
PLANNOTATOR_SHARE_URL |
Custom base URL for share links (self-hosted portal) |
PLANNOTATOR_PASTE_URL |
Base URL of the paste service API |
PLANNOTATOR_ORIGIN |
Override agent detection: claude-code, amp, droid, opencode, codex, copilot-cli, gemini-cli, kiro-cli, pi |
PLANNOTATOR_JINA |
0/false to disable Jina Reader for URL annotation |
JINA_API_KEY |
Jina Reader API key for higher rate limits |
PLANNOTATOR_DATA_DIR |
Base directory for Plannotator-managed files (plans, history, drafts, config.json). Default: ~/.plannotator; if that directory doesn't exist and $XDG_DATA_HOME is set to an absolute path, $XDG_DATA_HOME/plannotator is used instead |
Plannotator-managed files live under ~/.plannotator by default. Some UI preferences are stored in functional browser cookies. To relocate the files (for example, for an XDG-clean home):
export PLANNOTATOR_DATA_DIR=~/.local/share/plannotatorDevelopment
bun install
bun run dev:hook # Plan review server
bun run dev:review # Code review editor
bun run dev:marketing # Marketing site (plannotator.ai)
bun run dev:vscode # VS Code extension (watch mode)Build
bun run build # Main targets (hook + opencode)
bun run build:hook # Single-file HTML for the hook server
bun run build:review # Code review editor
bun run build:opencode # OpenCode plugin
bun run build:vscode # VS Code extensionBuild order matters. The hook build copies pre-built HTML from apps/review/dist/. If you change UI code in packages/ui/, packages/editor/, or packages/review-editor/, rebuild the review app first:
bun run --cwd apps/review build && bun run build:hookTest the plugin locally:
claude --plugin-dir ./apps/hookFull binary build:
bun run --cwd apps/review build && bun run build:hook && \
bun build apps/hook/server/index.ts --compile --outfile ~/.local/bin/plannotatorLicense
Copyright 2025-2026 backnotprop
Dual-licensed under Apache 2.0 or MIT at your option.
Contributions are dual-licensed under the same terms unless you explicitly state otherwise.
Custom Reviews (`docs/custom-reviews.md`)
Custom Reviews
A custom review is an Agent Skill. Point Plannotator at a skill and it runs the
review using that skill's instructions. The skill becomes the review. The
default review instructions are not added, and findings come back the same way.
Turn one on
You already keep skills in your global folders (~/.claude/skills,~/.codex/skills, ~/.agents/skills). List the ones you want as reviews in~/.plannotator/review-skills.json:
{ "version": 1, "enabled": ["security-review", "api-contracts"] }The names are the skill folder names. Run a code review, pick the skill, run it.
Findings come back the way they do today.
No review-skills.json? You get the default review, unchanged.
How it works
Plannotator reads the skill's SKILL.md body at launch and uses it as the
review prompt. The skill defines the review; the default review prompt is
dropped, and the user message is trimmed to the git or PR context the agent
needs to find the changes. The read is live. Edit the skill the normal way and
the next review picks it up. Nothing is copied.
Some skills carry references/, scripts/, or assets/. For those, Plannotator
tells the agent where the skill folder is, and the agent opens those files on
demand from where they already live.
What counts as a skill
The name is the folder name. The instructions are the SKILL.md body with the
leading frontmatter block stripped off. Plannotator does not read the
frontmatter: no name, no description, no YAML.
Global skills only. A skill checked into a repo is ignored. (A pull request from
a fork could otherwise drop instructions straight into your reviewer.)
Coming from JSON profiles
The old ~/.plannotator/reviews/*.json profiles are gone. To move one over: put
its instructions text in a SKILL.md under a global skill folder, then add that
skill's name to ~/.plannotator/review-skills.json.
Vim Controls (`docs/vim-controls.md`)
Vim controls
Vim controls provide optional, fully keyboard-driven Select and Pinpoint
annotation in the plan and annotate applications.
Goal
- Add an opt-in Vim mode setting. It must default to off.
- Offer a second, separately opt-in HUD that visualizes only handled Vim
commands. - Preserve every existing pointer and keyboard interaction when Vim mode is
off. - Support keyboard navigation and annotation in both Select and Pinpoint.
- Support Markdown and raw-HTML documents.
- Route keyboard-created annotations through the same annotation pipeline as
pointer-created annotations. - Make the rendered document's semantic structureβnot DOM focusβthe primary
navigation model. - Use one semantic target graph for pointer Pinpoint and keyboard navigation.
- Cover the behavior with automated tests against representative documents.
Compatibility invariants
- Vim mode is disabled for every existing user unless they explicitly enable
it. - With Vim mode disabled, no Vim key handler may consume or alter an
unmodified key. - Vim commands never run while focus is in an input, textarea, contenteditable
region, dialog, annotation composer, label picker, image editor, or another
embedded editor. TabandShift+Tabkeep their native focus-navigation behavior.- Pointer Select, pointer Pinpoint, Alt-hold, and Alt-double-tap continue to
work as they do today. - Keyboard selection must create the same annotation data and highlight DOM
as the corresponding pointer selection. - Escape unwinds only the innermost active Vim/annotation state.
Intended interaction
Navigation model
DOCUMENT β BLOCK β INLINE TARGET β TEXT β VISUAL SELECTION
- Activating Vim controls enters BLOCK at the semantic block nearest the
viewport: a paragraph, heading, list item, code block, table, and so on. - When the page is otherwise neutral, the enabled document takes keyboard
focus automatically. From app chrome,Escapereturns focus to the document;
editors and open dialogs retain first ownership of the key. j/k: next or previous block; after refining, next or previous
semantic sibling (for example, another table row, cell, or inline target).gg/G: first or last block.l: refine into a child target; from the deepest target, enter text.h: return to the containing target.v: begin or end characterwise Visual selection.V: select the whole current block;j/kextend by whole blocks.- In text,
h/l,w/b/e,0/$, andj/kuse precise
caret motions. o: swap the fixed and moving ends of a selection.Escape: back out exactly one level.
The document element may own technical keyboard focus, but it is never drawn
as the active target. Without the optional HUD, BLOCK and INLINE use the
existing Pinpoint wash, TEXT uses a caret, and VISUAL / VISUAL BLOCK use the
browser selection. The compact status badge reports BLOCK, INLINE, NORMAL,
VISUAL, VISUAL BLOCK, or ACTION. Users who additionally enable Vim HUD get
the video treatment on the real document: a violet four-corner reticle and
target label around the live semantic block, caret, or selection, plus the
larger bottom-right command display with recent handled keys, active navigation
granularity, input method, and contextual action description. The HUD's visible
Key map controlβor ?βexpands that same bottom-right panel into a complete
legend grouped by Document, Text, Select, Annotate, and Control commands. The
group matching the current navigation level is highlighted so a learner can
tell when j / k move by blocks versus lines. Raw HTML mirrors the same
target treatment inside its sandboxed iframe while using the shared parent key
HUD and key map.
The bottom-right Key panel is independently persisted and defaults on for
backward compatibility. Its Hide control removes only that panel; the reticle,
cursor, and Vim navigation remain active. Users can restore it in
Settings β Vim, or press ? while it is hidden to temporarily open the
complete key map. Closing the map returns to the reticle-only HUD.
Pointer Pinpoint and keyboard navigation resolve through the same canonical
graph, which owns target identity, hierarchy, labels, annotation ranges, and
overlay elements. Table rows, cells, and inline formatting remain keyboard
reachable: l enters a level, j / k move among siblings, and h returns
to the parent before block navigation resumes.
Actions
Enter: use the active Markup, Comment, Redline, or Label mode.Space: open the existing annotation action toolbar.c: comment.d: redline/delete.m: markup.t: label.y: copy.Escape: cancel one layer.?: expand or collapse the contextual HUD key map.
Annotation actions apply to BLOCK/INLINE targets or an active VISUAL range.
In NORMAL text state, first create a range with v; a collapsed caret is not
silently promoted to the whole containing block.
Architecture
Markdown documents use a canonical semantic target graph shared by pointer
Pinpoint and keyboard navigation. It owns block order, hierarchy, labels,
annotation ranges, and overlay elements. Text positions are serialized as a
block ID plus a text offset, so cursor and Visual state survive highlight DOM
mutations without retaining stale text nodes.
Keyboard-created ranges enter the same @plannotator/web-highlighterfromRange() pipeline as pointer selections. Code blocks and formulas retain
their specialized annotation paths. Raw HTML keeps its range and focus state
inside the sandboxed iframe, then posts through the existing, source-validated
annotation bridge.
Vim controls are cookie-persisted, default off, and scoped to the focused
document. No global letter-key listener is installed. Disabling the setting
removes the document focus surface and leaves existing behavior unchanged.
Vim HUD is independently cookie-persisted, default off, and only records or
renders commands while enabled. Text entered into annotation composers is
neither captured nor displayed. The HUD key panel is separately
cookie-persisted, defaults on, and never controls the reticle.
Both controls live in their own Settings β Vim panel. The Shortcuts panel
is reserved for the key-binding reference.
UX smoke testing
The user-journey matrix and latest real-app run record live intests/manual/vim-ux-smoke.md. It covers
focus recovery, structural and exact-text navigation, every annotation entry
point, HUD learning, copy behavior, rapid-input races, and native-control
compatibility across Markdown and sandboxed raw HTML.
--- METRICS ---
- Files Extracted: 3
- Estimated Token Budget: ~9208 tokens
- Canonical Reference: https://codewiki.google/github.com/backnotprop/plannotator