Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a56f219
feat: standalone skills package + HTML render-annotate mode
backnotprop May 8, 2026
7dd0a30
fix: HtmlViewer action bar, width control, and type errors
backnotprop May 8, 2026
88f799d
fix: sandbox isolation and draft key collision for render-html
backnotprop May 9, 2026
4862cbc
chore: tighten render-html contract and remove dead code
backnotprop May 9, 2026
7ef47a6
feat: add sharing support for HTML render mode
backnotprop May 9, 2026
7fe04ce
fix: add shortShareUrl to callCallback dependency array
backnotprop May 9, 2026
5c717c5
feat: add plannotator-visual-explainer skill
backnotprop May 10, 2026
ca2644d
rename: plannotator-plan → plannotator-visual-plan
backnotprop May 11, 2026
29040fd
chore: remove bun build cache, add to gitignore
backnotprop May 11, 2026
779e1f9
fix: update visual-explainer delivery to use plannotator annotate --r…
backnotprop May 11, 2026
a1b4de0
fix: break iframe resize feedback loop by observing body and deduping…
backnotprop May 11, 2026
8d025d1
feat: add extended patterns and design philosophy to visual-explainer…
backnotprop May 11, 2026
7246ff3
feat: add full SVG diagram patterns to visual-explainer extended patt…
backnotprop May 11, 2026
4c0218e
fix: move ResizeObserver to DOMContentLoaded so bridge IIFE doesn't d…
backnotprop May 11, 2026
0dae090
fix: update pi-extension build to reference skills at repo root
backnotprop May 11, 2026
6bc6fa1
revert: move skills back to apps/skills/
backnotprop May 11, 2026
d4b9e38
fix: correct tree indentation in AGENTS.md project structure
backnotprop May 11, 2026
64649b5
fix: address review findings for HTML render-annotate
backnotprop May 11, 2026
746c718
refactor: consolidate into single plannotator-visual-explainer skill
backnotprop May 11, 2026
6f26674
feat: use Pierre diffs via CDN for diff rendering in visual-explainer
backnotprop May 11, 2026
1d83713
fix: preserve incoming short URL before clearing browser history
backnotprop May 11, 2026
06715ae
fix: prevent stale-clearing effect from wiping preserved short URL on…
backnotprop May 11, 2026
d0544cc
fix: remove +32 padding that caused iframe resize loop with min-heigh…
backnotprop May 11, 2026
532f905
merge: resolve conflicts with main (AppHeader extraction, sonner toast)
backnotprop May 11, 2026
7f88017
fix: skip markdown fallback read for render-html in Pi extension
backnotprop May 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ opencode.json
plannotator-local
# Local research/reference docs (not for repo)
/reference/
*.bun-build
22 changes: 16 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@ plannotator/
│ │ ├── index.html
│ │ ├── index.tsx
│ │ └── vite.config.ts
│ └── vscode-extension/ # VS Code extension — opens plans in editor tabs
│ ├── bin/ # Router scripts (open-in-vscode, xdg-open)
│ ├── src/ # extension.ts, cookie-proxy.ts, ipc-server.ts, panel-manager.ts, editor-annotations.ts, vscode-theme.ts
│ └── package.json # Extension manifest (publisher: backnotprop)
│ ├── vscode-extension/ # VS Code extension — opens plans in editor tabs
│ │ ├── bin/ # Router scripts (open-in-vscode, xdg-open)
│ │ ├── src/ # extension.ts, cookie-proxy.ts, ipc-server.ts, panel-manager.ts, editor-annotations.ts, vscode-theme.ts
│ │ └── package.json # Extension manifest (publisher: backnotprop)
│ └── skills/ # Agent skills (agentskills.io format)
│ ├── plannotator-review/ # Lightweight: opens review UI
│ ├── plannotator-annotate/ # Lightweight: opens annotate UI
│ ├── plannotator-last/ # Lightweight: annotates last message
│ ├── plannotator-compound/ # Research analysis agent (map-reduce over denied plans)
│ ├── plannotator-setup-goal/ # Goal package scaffolder for /goal workflows
│ └── plannotator-visual-explainer/ # Visual HTML generator (plans, diagrams, PR explainers) with Plannotator theming
├── packages/
│ ├── server/ # Shared server implementation
│ │ ├── index.ts # startPlannotatorServer(), handleServerReady()
Expand Down Expand Up @@ -176,7 +183,7 @@ OpenCode/Pi: event handler intercepts command
↓
Input type detected:
.md/.mdx → file read from disk
.html/.htm → file read, converted to markdown via Turndown
.html/.htm → file read, converted to markdown via Turndown (or rendered as-is with --render-html)
https:// → fetched via Jina Reader (default) or fetch+Turndown (--no-jina)
folder/ → file browser opened, files converted on demand
↓
Expand Down Expand Up @@ -275,7 +282,7 @@ During normal plan review, an Archive sidebar tab provides the same browsing via

| Endpoint | Method | Purpose |
| --------------------- | ------ | ------------------------------------------ |
| `/api/plan` | GET | Returns `{ plan, origin, mode: "annotate", filePath, sourceInfo?, gate }` |
| `/api/plan` | GET | Returns `{ plan, origin, mode: "annotate", filePath, sourceInfo?, gate, renderAs?, rawHtml? }` |
| `/api/feedback` | POST | Submit annotations (body: feedback, annotations) |
| `/api/approve` | POST | Approve without feedback (review-gate UX, `--gate`) |
| `/api/exit` | POST | Close session without feedback |
Expand Down Expand Up @@ -428,6 +435,9 @@ interface SharePayload {
a: ShareableAnnotation[]; // Compact annotations
g?: ShareableImage[]; // Global attachments
d?: (string | null)[]; // diffContext per annotation, parallel to `a`
s?: (string | undefined)[]; // source per annotation (external tool identifier), parallel to `a`
h?: string; // Raw HTML content (--render-html mode)
r?: 'html'; // Render mode flag (omitted = markdown)
}

type ShareableAnnotation =
Expand Down
17 changes: 14 additions & 3 deletions apps/hook/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ const hookIdx = args.indexOf("--hook");
const hookFlag = hookIdx !== -1;
if (hookFlag) args.splice(hookIdx, 1);
if (hookFlag) gateFlag = true;
const renderHtmlIdx = args.indexOf("--render-html");
const renderHtmlFlag = renderHtmlIdx !== -1;
if (renderHtmlFlag) args.splice(renderHtmlIdx, 1);

// Stdout matrix for annotate / annotate-last / copilot annotate-last (#570).
//
Expand Down Expand Up @@ -590,6 +593,7 @@ if (args[0] === "sessions") {
}

let markdown: string;
let rawHtml: string | undefined;
let absolutePath: string;
let folderPath: string | undefined;
let annotateMode: "annotate" | "annotate-folder" = "annotate";
Expand Down Expand Up @@ -649,11 +653,16 @@ if (args[0] === "sessions") {
process.exit(1);
}
const html = await htmlFile.text();
markdown = htmlToMarkdown(html);
if (renderHtmlFlag) {
rawHtml = html;
markdown = "";
} else {
markdown = htmlToMarkdown(html);
sourceConverted = true;
}
absolutePath = resolvedArg;
sourceInfo = path.basename(resolvedArg);
sourceConverted = true;
console.error(`Converted: ${absolutePath}`);
console.error(`${renderHtmlFlag ? "Raw HTML" : "Converted"}: ${absolutePath}`);
} else {
// Single markdown file annotation mode
// Strip-first with literal-@ fallback (scoped-package-style names).
Expand Down Expand Up @@ -696,6 +705,8 @@ if (args[0] === "sessions") {
shareBaseUrl,
pasteApiUrl,
gate: gateFlag,
rawHtml,
renderHtml: renderHtmlFlag,
htmlContent: planHtmlContent,
onReady: async (url, isRemote, port) => {
handleAnnotateServerReady(url, isRemote, port);
Expand Down
16 changes: 12 additions & 4 deletions apps/opencode-plugin/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,15 @@ export async function handleAnnotateCommand(
// --json is accepted silently (OpenCode writes to session, not stdout).
// parseAnnotateArgs strips leading @ on filePath (reference-mode convention).
// `rawFilePath` preserves it for the scoped-package markdown fallback.
const { filePath, rawFilePath, gate } = parseAnnotateArgs(rawArgs);
const { filePath, rawFilePath, gate, renderHtml: renderHtmlFlag } = parseAnnotateArgs(rawArgs);

if (!filePath) {
client.app.log({ level: "error", message: "Usage: /plannotator-annotate <file.md | file.html | https://... | folder/> [--gate] [--json]" });
return;
}

let markdown: string;
let rawHtml: string | undefined;
let absolutePath: string;
let folderPath: string | undefined;
let annotateMode: "annotate" | "annotate-folder" = "annotate";
Expand Down Expand Up @@ -240,11 +241,16 @@ export async function handleAnnotateCommand(
return;
}
const html = await Bun.file(resolvedArg).text();
markdown = htmlToMarkdown(html);
if (renderHtmlFlag) {
rawHtml = html;
markdown = "";
} else {
markdown = htmlToMarkdown(html);
sourceConverted = true;
}
absolutePath = resolvedArg;
sourceInfo = path.basename(resolvedArg);
sourceConverted = true;
client.app.log({ level: "info", message: `Converted: ${absolutePath}` });
client.app.log({ level: "info", message: `${renderHtmlFlag ? "Raw HTML" : "Converted"}: ${absolutePath}` });
} else {
// Markdown file annotation
client.app.log({ level: "info", message: `Opening annotation UI for ${filePath}...` });
Expand Down Expand Up @@ -280,6 +286,8 @@ export async function handleAnnotateCommand(
folderPath,
sourceInfo,
sourceConverted,
rawHtml,
renderHtml: renderHtmlFlag,
sharingEnabled: await getSharingEnabled(),
shareBaseUrl: getShareBaseUrl(),
pasteApiUrl: getPasteApiUrl(),
Expand Down
14 changes: 11 additions & 3 deletions apps/pi-extension/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ export default function plannotator(pi: ExtensionAPI): void {
// accepted (Pi writes back via sendUserMessage, not stdout).
// `rawFilePath` keeps any leading `@` for the literal-@ fallback
// (scoped-package-style names).
const { filePath, rawFilePath, gate } = parseAnnotateArgs(args ?? "");
const { filePath, rawFilePath, gate, renderHtml: renderHtmlFlag } = parseAnnotateArgs(args ?? "");
if (!filePath) {
ctx.ui.notify("Usage: /plannotator-annotate <file.md | file.html | https://... | folder/> [--gate] [--json]", "error");
return;
Expand All @@ -507,6 +507,7 @@ export default function plannotator(pi: ExtensionAPI): void {
}

let markdown: string;
let rawHtml: string | undefined;
let absolutePath: string;
let folderPath: string | undefined;
let mode: "annotate" | "annotate-folder" | undefined;
Expand Down Expand Up @@ -570,9 +571,14 @@ export default function plannotator(pi: ExtensionAPI): void {
return;
}
const html = readFileSync(absolutePath, "utf-8");
markdown = htmlToMarkdown(html);
if (renderHtmlFlag) {
rawHtml = html;
markdown = "";
} else {
markdown = htmlToMarkdown(html);
sourceConverted = true;
}
sourceInfo = basename(absolutePath);
sourceConverted = true;
ctx.ui.notify(`Opening annotation UI for ${filePath}...`, "info");
} else {
markdown = readFileSync(absolutePath, "utf-8");
Expand All @@ -593,6 +599,8 @@ export default function plannotator(pi: ExtensionAPI): void {
sourceInfo,
sourceConverted,
gate,
rawHtml,
renderHtmlFlag,
);
ctx.ui.notify("Annotation opened. You can keep chatting while it runs.", "info");
void session
Expand Down
6 changes: 5 additions & 1 deletion apps/pi-extension/plannotator-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,13 +471,15 @@ export async function startMarkdownAnnotationSession(
sourceInfo?: string,
sourceConverted?: boolean,
gate?: boolean,
rawHtml?: string,
renderHtml?: boolean,
): Promise<BrowserDecisionSession<{ feedback: string; exit?: boolean; approved?: boolean }>> {
if (!ctx.hasUI || !planHtmlContent) {
throw new Error("Plannotator annotation browser is unavailable in this session.");
}

let resolvedMarkdown = markdown;
if (!resolvedMarkdown.trim() && existsSync(filePath)) {
if (!renderHtml && !resolvedMarkdown.trim() && existsSync(filePath)) {
try {
const fileStat = statSync(filePath);
if (!fileStat.isDirectory()) {
Expand All @@ -497,6 +499,8 @@ export async function startMarkdownAnnotationSession(
sourceInfo,
sourceConverted,
gate,
rawHtml,
renderHtml,
htmlContent: planHtmlContent,
sharingEnabled: process.env.PLANNOTATOR_SHARE !== "disabled",
shareBaseUrl: process.env.PLANNOTATOR_SHARE_URL || undefined,
Expand Down
6 changes: 5 additions & 1 deletion apps/pi-extension/server/serverAnnotate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export async function startAnnotateServer(options: {
sourceInfo?: string;
sourceConverted?: boolean;
gate?: boolean;
rawHtml?: string;
renderHtml?: boolean;
}): Promise<AnnotateServerResult> {
// Side-channel pre-warm so /api/doc/exists POSTs land on warm cache.
void warmFileListCache(process.cwd(), "code");
Expand Down Expand Up @@ -77,7 +79,7 @@ export async function startAnnotateServer(options: {
const draftSource =
options.mode === "annotate-folder" && options.folderPath
? `folder:${resolvePath(options.folderPath)}`
: options.markdown;
: options.renderHtml && options.rawHtml ? options.rawHtml : options.markdown;
const draftKey = contentHash(draftSource);

// Detect repo info (cached for this session)
Expand All @@ -99,6 +101,8 @@ export async function startAnnotateServer(options: {
sourceInfo: options.sourceInfo,
sourceConverted: options.sourceConverted ?? false,
gate: options.gate ?? false,
renderAs: options.renderHtml && options.rawHtml ? 'html' : 'markdown',
...(options.renderHtml && options.rawHtml ? { rawHtml: options.rawHtml } : {}),
sharingEnabled,
shareBaseUrl,
pasteApiUrl,
Expand Down
124 changes: 124 additions & 0 deletions apps/skills/plannotator-visual-explainer/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
name: plannotator-visual-explainer
description: >
Generate beautiful, self-contained HTML visualizations themed with Plannotator's design system.
Wraps the visual-explainer skill by nicobailon with Plannotator theme token integration. Use for
architecture diagrams, diff reviews, plan reviews, data tables, slide decks, project recaps, or
any visual explanation of technical concepts — whenever you want output styled consistently with
Plannotator's UI and compatible with --render-html annotation. Triggers on the same prompts as
visual-explainer (diagrams, architecture overviews, visual plans, diff reviews) but produces
output that uses Plannotator CSS custom properties instead of custom palettes.
---

# Plannotator Visual Explainer

This skill wraps [visual-explainer](https://github.com/nicobailon/visual-explainer) by Nico Bailon with Plannotator theme integration and additional component patterns. You follow visual-explainer's workflow, references, templates, and anti-slop rules — with Plannotator's color/typography tokens and extended patterns for plans and technical documents.

## Setup

Before generating, ensure `visual-explainer` is available:

1. Check if the skill exists at any of these paths (in order):
- `~/.claude/skills/visual-explainer/SKILL.md`
- `~/.agents/skills/visual-explainer/SKILL.md`
- `~/.codex/skills/visual-explainer/SKILL.md`

2. If not found, install it:
```bash
npx skills add nicobailon/visual-explainer -g --yes
```

3. Read the visual-explainer `SKILL.md` to absorb its full workflow, diagram type routing, structure rules, and anti-slop guidelines. Read its `references/` and `templates/` as directed by its workflow.

## Theme Override

Instead of visual-explainer's custom palettes and font pairings, use Plannotator's semantic theme tokens. Read `references/theme-override.md` for the exact CSS custom properties and mapping table. Apply these **after** reading visual-explainer's references — they replace only the color and typography layer.

## Extended Patterns

Plannotator adds component patterns that complement visual-explainer's toolkit. Read `references/extended-patterns.md` for timelines, inline SVG diagrams, code blocks with syntax highlighting, risk tables, and open question callouts. Use these alongside Nico's `.ve-card`, `.kpi-card`, `.pipeline` components — they share the same theme tokens.

## Design Philosophy: Use the Power of HTML

The point of generating HTML instead of markdown is spatial layout. Don't pack every piece of information into dense cards. Let the page breathe.

- **Whitespace is a feature.** Generous padding, large section gaps, breathing room between cards. If a section feels cramped, it needs more space, not smaller text.
- **One idea per viewport.** The reader should be able to absorb one concept at a time as they scroll. A hero section, then a diagram, then a detail grid — not all three crammed together.
- **Visual weight signals importance.** Hero sections dominate (large type, accent-tinted backgrounds, more padding). Supporting details are compact and can collapse. Not everything deserves equal treatment.
- **Show, don't describe.** A timeline shows sequencing. A diagram shows relationships. A before/after grid shows change. A code block shows the interface. Use the right visual element — don't describe things in prose that a component could show directly.
- **Timelines show sequence without estimates.** Show the phases and their dependencies, but do not attach hour/day/week estimates. AI consistently misjudges timing. Showing phases in order communicates sequencing; attaching numbers communicates false precision.

## Workflow

1. **Read** visual-explainer's SKILL.md (full workflow, diagram types, quality checks)
2. **Read** the relevant visual-explainer references and templates for your content type
3. **Read** `references/theme-override.md` for Plannotator color/typography tokens
4. **Read** `references/extended-patterns.md` for additional components (timelines, code blocks, risk tables, SVG diagrams)
5. **Generate** following visual-explainer's structure and rules, with Plannotator tokens and extended patterns. Use Nico's component classes (`.ve-card`, `.ve-card--hero`, `.kpi-card`, `.pipeline`, etc.) for cards and layout. Use the extended patterns for timelines, code blocks, risk tables, and SVG diagrams.
6. **Deliver** via Plannotator's annotation UI:

**If the output is a plan or proposal** (something the user should approve/deny):
```bash
plannotator annotate <file> --render-html --gate
```

**If the output is a visual explainer, diagram, or informational page:**
```bash
plannotator annotate <file> --render-html
```

Always use `--render-html` so the HTML renders as-is in the Plannotator UI with theme inheritance and annotation support. Do NOT use `open` or `xdg-open` directly.

## What visual-explainer provides (do not duplicate)

All of these come from visual-explainer — read them there, don't reinvent them:
- Diagram type routing (architecture, flowchart, sequence, ER, state, mind map, etc.)
- Mermaid integration (theming, zoom controls, scaling, layout direction)
- CSS structural patterns (ve-card, grids, connectors, depth tiers, collapsibles)
- Slide deck mode (viewport-snapping presentations)
- Data table patterns (sticky headers, status indicators, responsive scrolling)
- Anti-slop rules (forbidden fonts, colors, animations, patterns)
- Quality checks (squint test, swap test, overflow protection)
- Animation guidelines (staggered entrance, reduced-motion support)

## Plan-specific guidance

When the output is an implementation plan, design doc, or proposal:

**Adapt the visual vocabulary to the task:**
- **Backend/API work**: Lead with data flow diagrams, schemas, API signatures
- **Frontend/UI work**: Lead with mockups, component hierarchy, state flow
- **Infrastructure/DevOps**: Lead with architecture diagrams, deployment flow
- **Refactoring**: Lead with before/after diagrams showing structural change
- **Cross-cutting features**: Lead with a system map showing all touchpoints

**Section menu — pick what fits:**
Solution overview, architecture/data flow diagram, UI mockups, key code, integration points, risks & mitigations, open questions, considerations & rationale, reusability & code quality. Not every plan needs every section — choose what serves the content.

**What NOT to include in plans:**
- Time estimates (timelines showing sequence are fine, hour/day estimates are not)
- Boilerplate sections that would just say "N/A"
- Exhaustive file lists — show the important files, not every file touched

**Quality bar for plans:** The plan should answer "what are we building, why, and how" within 30 seconds of reading.

## PR explainer guidance

When the output is a PR walkthrough, diff review, or code change explainer:

- **TL;DR first** — a bordered card summarizing what the PR does and why, so readers who skim get the gist
- **Risk map** — visual chips showing which files need careful review vs. which are mechanical
- **Inline diffs** — use the diff rendering pattern from `references/extended-patterns.md` for important hunks (not every hunk)
- **File-by-file commentary** — collapsible cards per file with a "why" paragraph explaining the purpose of changes
- **"Where to focus"** — numbered callouts telling reviewers exactly what to look at and why
- **Before/after comparison** — two-column grid for behavior changes

See `references/extended-patterns.md` for diff rendering, review comment bubbles, and file badge patterns.

## What this skill adds

- Plannotator theme tokens (colors, typography, radii) — see `references/theme-override.md`
- Extended component patterns (timelines, code blocks, risk tables, SVG diagrams, open questions) — see `references/extended-patterns.md`
- Plan-specific guidance (section menu, adaptation by task type, quality bar)
- `--render-html` delivery with annotation support and theme inheritance
- Design philosophy emphasizing spatial layout, breathing room, and visual hierarchy
Loading
Loading