The Pi extension is distributed and executed as raw TypeScript through
Pi's jiti loader. Phantom .js paths force jiti through its last-resort
fallback, while extensionless paths still require probing. Exact .ts
paths avoid both and match the files that actually ship.
- rewrite relative .js and extensionless specifiers in hand-written
sources (the existing noEmit bundler tsconfig permits .ts imports)
- make vendor.sh emit .ts in explicit rewrite rules and normalize both
source styles across verbatim-copied generated modules
- guard hand-written and generated sources against either inexact form
Extension module import: 43.5ms -> 15ms median (PI_TIMING, n=12
interleaved). bun test: 73/73 (pi-extension), 456/456 (server); all
seven TypeScript project checks and the packed-artifact audit pass.
Co-authored-by: dca123 <dca123@users.noreply.github.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Clipboard Copy paths (ExportModal Copy and the annotation panel quick
copy) unconditionally wrapped exported annotations with the plan-deny
template, so annotate sessions copied text starting with "YOUR PLAN WAS
NOT APPROVED." while Send Feedback used the annotate template (including
custom prompts.annotate.* config).
The annotate servers (Bun and Pi) now ship the resolved, unsubstituted
copy-wrapper templates in the /api/plan payload (feedbackTemplates), and
the plan editor wraps copied annotations mode-aware: server template when
present, browser-safe default annotate template otherwise, plan-deny only
in actual plan review. Send Feedback behavior is unchanged.
Closes#1107
* fix(ui): parse YAML block scalars in frontmatter
Folded (>) and literal (|) values were stored as the bare indicator
(e.g. ">-") with their body dropped, so multi-line skill descriptions
rendered empty in the plan/annotate viewer.
* fix: strip trailing CR from block scalar body lines (CRLF sources)
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Guide section file chips scrolled directly to the target diff card, so a
file collapsed by marking it viewed landed on its bare header with no code.
Route the chip click through the existing guideRevealFile channel (the same
path used by sidebar annotation clicks and AI line citations) via a new
onGuideRevealFile setter on ReviewState, so the target expands, focuses,
and scrolls in the same order as the other two jump paths.
~/.plannotator/config.json is hand-edited, so boolean settings often
arrive as quoted strings ("cursorSandbox": "false"). The resolvers
passed the raw JSON value through, so a quoted "false" reached strict
=== false checks downstream as a string and silently kept the default
behavior. Concretely, the Cursor sandbox opt-out added for NixOS users
in #1095 did nothing when set via config.json with quotes, even though
the equivalent env var accepts the string form.
Add a coerceConfigBoolean helper and apply it to the config-file branch
of resolveUseGlimpse, resolveAnnotateHistory, resolveUseJina, and
resolveCursorSandbox. Real booleans pass through, "true"/"false"/
"1"/"0" strings (any case) coerce, and anything else falls back to
the default. Env-var branches and their precedence are unchanged.
resolveSharingEnabled is left alone: it is keyed on the string sentinel
"disabled" and already fails safe.
Pi picks the fix up automatically via apps/pi-extension/vendor.sh.
* feat(annotate): accept common plain-text config formats (.yaml, .json, .toml, …)
Annotate previously rejected every file that wasn't .md/.mdx/.txt (or
.html/.htm), even though the pipeline reads files as UTF-8 text and
renders anything. Widen the accepted set to unambiguously plain-text
config/data formats: .yaml .yml .json .jsonc .json5 .toml .ini .cfg
.conf .properties .csv .tsv .log .xml .env.example. They render exactly
the way .txt renders today.
- New single source of truth: packages/core/annotatable.ts
(ANNOTATABLE_TEXT_REGEX / ANNOTATABLE_DOC_REGEX + predicates),
re-exported through @plannotator/shared/resolve-file and vendored into
the Pi extension.
- .env stays excluded (commonly holds secrets; annotate history copies
file contents into the data dir). Source-code extensions stay with
code review.
- Single-file accept + bare-filename fuzzy search widen in
resolveMarkdownFile; folder discovery and the file-browser listing
widen in all three runtimes (hook CLI, OpenCode, Pi).
- /api/doc gains a `doc=1` param set by the file browser so extensions
that overlap CODE_FILE_REGEX (.yaml/.json/.toml/.ini/.xml) render as
annotatable documents there while code-file links inside documents
keep the syntax-highlighted popout.
- Error messages now list the wider set; docs updated (AGENTS.md,
marketing annotate page).
Closes#1029
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
* fix(annotate): frontmatter, size caps, edit-guard, and skill docs from review
Review fixes for #1099:
- Frontmatter: `--- … ---` stripping is a markdown convention; for
non-markdown annotatable sources (multi-document YAML, .txt starting
with ---) the delimiters are real content. parseMarkdownToBlocks gains
a { frontmatter } option and the editor keys it off the active
document's path via shouldStripFrontmatter() (strip for .md/.mdx and
pathless/converted sources; keep raw for other annotatable text).
- Size caps: new shared MAX_ANNOTATABLE_FILE_BYTES (2MB — same limit the
code-file popout always had) now guards the annotate CLI single-file
read in all three runtimes and the /api/doc document branches in both
servers. Also applies to .md/.txt (behavior change for pathological
inputs; previously unbounded).
- Editing guard: mid-edit file opens gate on isSourceSaveFilePath
(.md/.mdx/.txt) instead of the wider annotatable set — config files
are view-only, so switching to one mid-edit no longer silently
downgrades "Done editing" to feedback-only edits.
- Skill docs: plannotator-annotate SKILL.md (core + Kiro) now mention
the plain-text config formats.
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
Apply global scope consistently across the shell, PowerShell, and cmd installers and their public install-later documentation. Add parity coverage so every supported command remains explicitly global.
* fix(agents): align Codex model catalog and reasoning efforts with Codex CLI 0.144
- Remove gpt-5.3-codex from the Codex model picker: saved picks of it are
already force-migrated to the fallback (ChatGPT-account Codex rejects it),
so offering it was incoherent. The 5.2/5.1 family stays — those models
remain available to API-key Codex users.
- Per-model reasoning-effort gating: the catalog (now in
packages/ui/utils/codexModels.ts, re-exported from AgentsTab) carries each
model's supported efforts and default effort from the CLI catalog. The
reasoning pickers only offer the selected model's efforts, including the
new max/ultra tiers on the GPT-5.6 family, and useAgentSettings clamps an
unsupported saved effort to the model's catalog default before it reaches
the pickers or a launch payload.
- Drop `minimal` everywhere: no current Codex model supports it. Saved
perModel reasoning entries migrate minimal -> low, and the guide repair
launch no longer hardcodes it.
- Ask AI: bump the codex-app-server static fallback model and DEFAULT_MODEL
from gpt-5.4 to gpt-5.6-sol (runtime model/list discovery still replaces
the fallback and already carries per-model efforts; added Max/Ultra labels).
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
* fix(agents): prune API-shut-down Codex models per OpenAI deprecations
OpenAI's API deprecations page schedules an API-level shutdown on
2026-07-23 for gpt-5.2-codex (-> gpt-5.5), gpt-5.1-codex-max (-> gpt-5.5),
and gpt-5.1-codex-mini (-> gpt-5.4-mini) — dead for ALL auth modes, not
just ChatGPT accounts — so they leave the picker and saved picks migrate:
the first two to the surface fallback (already gpt-5.5), codex-mini to
gpt-5.4-mini with its perModel preference carried under the new key (same
pattern as the gpt-5.6 -> gpt-5.6-sol rename).
gpt-5.2 stays selectable with no migration: it was retired from the
ChatGPT product (steered to 5.5) but the API still serves it, so API-key
Codex users retain it.
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
* fix(review): collapse viewed guided files
* fix(review): expand a collapsed guide file when a sidebar jump targets it
The per-file viewed-collapse unmounts the diff body, so an annotation click
or AI line citation targeting a collapsed file would scroll to the section
but silently fail to reveal the line. Mirror GuideSectionCard's token-keyed
reveal effect at the file level.
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
New resolution order for the Plannotator data directory:
1. PLANNOTATOR_DATA_DIR (unchanged, top priority, ~ expansion)
2. ~/.plannotator when it already exists (legacy default — existing
installs never move)
3. $XDG_DATA_HOME/plannotator when XDG_DATA_HOME is set to a
non-empty absolute path
4. ~/.plannotator (default for everyone else)
This is git's legacy-first pattern: the XDG branch only fires for fresh
installs whose user has explicitly set XDG_DATA_HOME. The XDG spec's
implicit ~/.local/share default is deliberately NOT applied, and the
directory stays monolithic (no config/data/cache split).
Mirrored in every private copy of the resolver: the Amp plugin,
scripts/install.sh, scripts/install.ps1, and scripts/install.cmd. The
Pi runtime picks the change up automatically via vendor.sh. Docs updated
in README.md, AGENTS.md/CLAUDE.md, and the marketing env-var reference.
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
The Cursor review engine hardcodes `--sandbox enabled` when launching the
`agent` CLI. On systems where Cursor's sandbox cannot start (NixOS,
AppArmor-restricted Linux) that hard-fails every job with "Sandbox mode is
enabled but not available on this system", and the flag overrides the
user's own `agent sandbox disable` configuration.
Default is unchanged: review jobs still pass `--sandbox enabled` as part
of their read-only posture. Setting PLANNOTATOR_CURSOR_SANDBOX=0 (or
`{ "cursorSandbox": false }` in ~/.plannotator/config.json; the env var
wins) omits the flag pair entirely — never `--sandbox disabled` — so the
user's own Cursor Agent sandbox configuration governs. Resolution follows
the established env-plus-config pattern via resolveCursorSandbox() in
packages/shared/config.ts, applied at the buildMarkerCommand call sites in
both the Bun server (review.ts, guide-review.ts) and the Pi server
(serverReview.ts; guide-review is vendored, with the new ../config import
rewritten in vendor.sh).
Closes#1094
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
plannotator annotate ../docs/plan.md failed with a misleading 'File type
not supported: .md' because resolveMarkdownFile rejected any relative path
resolving outside the project root, even when the file existed and was a
supported type. The CLI then found the file on disk and mislabeled the
resolver miss as a type error.
An explicit path the user types (one containing a separator, including ../)
is now honored when it exists, matching the trust already given to absolute
paths. Bare filenames stay restricted to the in-root fuzzy search, so a
stray notes.md cannot resolve into a parent directory.
Closes#1085.
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
Workspace discovery follows symlinks since #1060, which means the walk can
leave the workspace root by design. A link into a huge unrelated tree was
enumerated synchronously before the server bound its port, hanging startup
with no output. The walk now shares the PLANNOTATOR_FILE_BROWSER_MAX_FILES
budget: it visits at most that many directories and returns what it found.
Symlinked repos are still discovered under the default budget.
Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
Adds a first-class artifact gallery and annotation workflow for GitHub pull requests and GitLab merge requests, including images, GIFs, video timestamps, rendered HTML, Markdown, provenance-aware feedback, authenticated provider resources, and reliable CDN/media handling.
Adds current-architecture GitButler workspace, stack, and branch review support across Bun and Pi while preserving the existing Git, JJ, and P4 paths.
Co-authored-by: Dan Susman <56033661+dansusman@users.noreply.github.com>
* feat(server): support bounded port ranges
* fix(server): harden bounded port retries
* fix(server): preserve non-range port behavior
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
* fix(server): return JSON 404 for unknown /api/* routes instead of HTML
All three servers (plan, review, annotate) serve the SPA HTML for any
unmatched route. This means hitting /api/nonexistent returns the full
HTML page with 200 OK — confusing for API clients and wasteful.
Now unknown /api/* paths get a proper JSON 404 response before the SPA
fallback kicks in. Non-API paths (like /some/random/path) still serve
HTML for SPA routing as before.
Tested on all three server types.
* fix(server): guard unknown API routes across runtimes
---------
Co-authored-by: buihongduc132 <buihongduc132@gmail.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Use one shared Light/Dark/System registry across review, plan, annotate, Settings, and published UI consumers. Preserve the public Mode contract, scope wider menu geometry to theme pickers, and keep constrained palettes coherent in rendering and previews.
Co-authored-by: Vladyslav Hrabovyi <vladyslav.hrabovyii@gmail.com>
Co-authored-by: sasha <kredich05@gmail.com>