Skip to content

feat(annotate): configurable extra markdown extensions - #1309

Merged
backnotprop merged 3 commits into
mainfrom
feat/1307-markdown-extensions
Aug 13, 2026
Merged

feat(annotate): configurable extra markdown extensions#1309
backnotprop merged 3 commits into
mainfrom
feat/1307-markdown-extensions

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

TLDR: New config-only setting markdownExtensions in ~/.plannotator/config.json lets extra file extensions (e.g. Livebook's .livemd) be annotated as Markdown. Closes #1307. AI-assisted.

Summary

  • { "markdownExtensions": [".livemd"] } in ~/.plannotator/config.json accepts the listed extensions anywhere annotate accepts .md: the annotate CLI and skills, folder file browser, /api/doc and /api/doc/exists, frontmatter stripping, and wiki-link/relative-link navigation in annotate sessions.
  • @plannotator/core stays browser-safe and zero-dep: the built-in lists became pure builders taking an optional extras list (default empty, byte-identical behavior). The config is read once per process through the existing loadConfig() path in a new packages/shared/markdown-extensions.ts seam.
  • Both runtimes covered: Bun server, Pi mirror (vendored via vendor.sh), and the OpenCode command parser.
  • The annotate renderer linkifies [link](doc.livemd) and [[doc.livemd]] for configured extensions.

Validation and security

  • Entries must be dot-led; lowercased and trimmed; rejected on path separators, globs, whitespace, dotless or empty forms; capped at 24 chars and 32 entries; deduped against built-ins.
  • .env is explicitly denylisted and cannot be registered (its exclusion from annotate is deliberate: annotate history copies file contents). Verified end to end with a config listing both .livemd and .env.
  • The 2MB annotate cap and version history apply to configured extensions exactly as they do to .md.

Deliberately out of scope

  • Pi plan-write allowlist (ALLOWED_PLAN_EXTENSIONS) is unchanged; noted in AGENTS.md.
  • Edit Mode in-place saving still covers .md/.mdx/.txt only; configured extras open read-annotate.
  • Plan-mode sessions do not ship the list; linkification applies in annotate sessions.

Verification

  • bun run typecheck passes
  • New and existing tests pass across packages/shared, packages/core, packages/ui, packages/editor, apps/hook/server, packages/server, apps/pi-extension, apps/opencode-plugin (7 pre-existing environmental failures identical on baseline)
  • All new tests sandbox PLANNOTATOR_DATA_DIR; none touch the real user config

Thanks @sgiath for the detailed report and for tracing both allowlists in #1307.

Adds a config-only `markdownExtensions` key to ~/.plannotator/config.json,
e.g. { "markdownExtensions": [".livemd"] } for Livebook notebooks. A listed
extension is accepted everywhere .md is on the annotate path: CLI target
resolution, folder discovery and the file browser, /api/doc plus relative and
wiki-link navigation between sibling docs, the 2MB size cap, and per-file
version history. Listed extensions render as markdown with frontmatter
stripped, never as raw HTML, and they only widen the accepted set.

Design:
- packages/core/annotatable.ts stays browser-safe and zero-dep. Its regexes
  and predicates now take an optional, defaulted-empty list of extra
  extensions, plus a normalizer and regex builders.
- packages/shared/markdown-extensions.ts is the node-side seam: it reads
  config.json once per process through the existing loadConfig() and threads
  the normalized list into those pure functions. resolve-file re-exports the
  config-aware predicates so both runtimes pick them up; the Bun server, the
  Pi mirror, the OpenCode plugin and the CLI all go through them.
- The annotate /api/plan payload ships the resolved list so the renderer can
  linkify links to sibling documents (module-level UI registry, empty by
  default, so nothing changes without config).

Validation: entries must be dot-led, lowercase-normalized, and free of path
separators, globs and whitespace. Invalid entries are dropped silently,
built-ins are deduplicated, and `.env` is denylisted so config can never
register it (annotate copies file contents into the data dir).

Deliberately unchanged: the Pi plan-write allowlist (ALLOWED_PLAN_EXTENSIONS
in tool-scope.ts) and Edit Mode source save (SOURCE_SAVE_FILE_REGEX), which
keep their own narrower allowlists.
Review follow-ups on #1309:
- deny the whole dotenv family (.prod.env, .env.local, ...) in
  normalizeMarkdownExtensions, not just the exact .env name
- resolve config.json path per call instead of at module scope so
  PLANNOTATOR_DATA_DIR sandboxing works in single-process test runs
- stop resolve-file.test.ts reading the real user config: pure
  predicate imports plus pinned empty extras on every resolve call
- add the config.json -> memo -> predicate integration test using
  resetMarkdownExtensionsCache under a temp data dir
The read-only GET only probes the node runtime while Call flow is
enabled. The stale-read test relied on earlier tests' settings POSTs
leaking callFlow=true through the process-frozen config path; with lazy
config resolution each sandbox is genuinely isolated, so the test now
enables Call flow in its own data dir. Locally the dependency was
masked by an fnm-shimmed sem sidecar spawning node coincidentally.
@backnotprop
backnotprop merged commit d2d2dba into main Aug 13, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow configuring extra extensions as Markdown (e.g. .livemd)

1 participant