vplan 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/index.ts +2 -1
- package/dist/index.js +33 -13
- package/dist/index.js.map +1 -1
- package/package.json +18 -14
- package/runtime/components/Chart.tsx +30 -4
- package/runtime/components/Questions.tsx +3 -6
- package/runtime/theme.css +80 -41
package/core/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ export const calloutSchema = z.object({
|
|
|
56
56
|
})
|
|
57
57
|
|
|
58
58
|
export const questionsSchema = z.object({
|
|
59
|
+
title: z.string().default('Open questions'),
|
|
59
60
|
items: z.array(z.string().min(1)).min(1, 'questions needs at least one item'),
|
|
60
61
|
})
|
|
61
62
|
|
|
@@ -118,7 +119,7 @@ export const CATALOG: readonly CatalogEntry[] = [
|
|
|
118
119
|
{
|
|
119
120
|
name: 'Questions',
|
|
120
121
|
summary:
|
|
121
|
-
'Open questions you
|
|
122
|
+
'Open questions you want the reader to weigh in on before building, as a highlighted panel. Title defaults to "Open questions"; override with title.',
|
|
122
123
|
staticEnums: {},
|
|
123
124
|
example:
|
|
124
125
|
'<Questions items={["Should refresh tokens rotate on every use?", "Is a 15-minute access-token TTL acceptable?"]} />',
|
package/dist/index.js
CHANGED
|
@@ -6,16 +6,23 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "vplan",
|
|
9
|
-
version: "0.
|
|
10
|
-
description: "Render
|
|
9
|
+
version: "0.2.0",
|
|
10
|
+
description: "Render an AI agent's plans as visual MDX pages instead of walls of text",
|
|
11
11
|
author: "Brandon Burrus <brandon@burrus.io>",
|
|
12
12
|
license: "MIT",
|
|
13
|
+
repository: {
|
|
14
|
+
type: "git",
|
|
15
|
+
url: "git+https://github.com/brandonburrus/visualplan.git",
|
|
16
|
+
directory: "packages/cli"
|
|
17
|
+
},
|
|
18
|
+
homepage: "https://github.com/brandonburrus/visualplan#readme",
|
|
19
|
+
bugs: "https://github.com/brandonburrus/visualplan/issues",
|
|
13
20
|
type: "module",
|
|
14
21
|
engines: {
|
|
15
22
|
node: ">=20.0.0"
|
|
16
23
|
},
|
|
17
24
|
bin: {
|
|
18
|
-
vplan: "
|
|
25
|
+
vplan: "dist/index.js"
|
|
19
26
|
},
|
|
20
27
|
files: [
|
|
21
28
|
"dist",
|
|
@@ -51,10 +58,6 @@ var package_default = {
|
|
|
51
58
|
vite: "^8.0.16",
|
|
52
59
|
"vite-plugin-singlefile": "^2.3.3",
|
|
53
60
|
zod: "^4.4.3"
|
|
54
|
-
},
|
|
55
|
-
devDependencies: {
|
|
56
|
-
"@visualplan/core": "workspace:*",
|
|
57
|
-
"@visualplan/runtime": "workspace:*"
|
|
58
61
|
}
|
|
59
62
|
};
|
|
60
63
|
|
|
@@ -109,6 +112,7 @@ var calloutSchema = z.object({
|
|
|
109
112
|
type: z.enum(CALLOUT_TYPE_VALUES).default("note")
|
|
110
113
|
});
|
|
111
114
|
var questionsSchema = z.object({
|
|
115
|
+
title: z.string().default("Open questions"),
|
|
112
116
|
items: z.array(z.string().min(1)).min(1, "questions needs at least one item")
|
|
113
117
|
});
|
|
114
118
|
var checklistSchema = z.object({
|
|
@@ -153,7 +157,7 @@ var CATALOG = [
|
|
|
153
157
|
},
|
|
154
158
|
{
|
|
155
159
|
name: "Questions",
|
|
156
|
-
summary:
|
|
160
|
+
summary: 'Open questions you want the reader to weigh in on before building, as a highlighted panel. Title defaults to "Open questions"; override with title.',
|
|
157
161
|
staticEnums: {},
|
|
158
162
|
example: '<Questions items={["Should refresh tokens rotate on every use?", "Is a 15-minute access-token TTL acceptable?"]} />'
|
|
159
163
|
},
|
|
@@ -189,11 +193,14 @@ async function checkPlan(mdxPath) {
|
|
|
189
193
|
});
|
|
190
194
|
} catch (error) {
|
|
191
195
|
const vfileError = error;
|
|
196
|
+
const message = vfileError.reason ?? vfileError.message ?? "MDX failed to compile";
|
|
197
|
+
const placeStart = vfileError.place?.start ?? vfileError.place;
|
|
198
|
+
const fromMessage = message.match(/\((\d+):(\d+)/);
|
|
192
199
|
return [
|
|
193
200
|
{
|
|
194
|
-
line: vfileError.line ?? 1,
|
|
195
|
-
column: vfileError.column ?? 1,
|
|
196
|
-
message
|
|
201
|
+
line: vfileError.line ?? placeStart?.line ?? (fromMessage ? Number(fromMessage[1]) : 1),
|
|
202
|
+
column: vfileError.column ?? placeStart?.column ?? (fromMessage ? Number(fromMessage[2]) : 1),
|
|
203
|
+
message
|
|
197
204
|
}
|
|
198
205
|
];
|
|
199
206
|
}
|
|
@@ -389,7 +396,20 @@ function baseConfig(paths, mdxPath) {
|
|
|
389
396
|
root: paths.runtimeDir,
|
|
390
397
|
configFile: false,
|
|
391
398
|
logLevel: "silent",
|
|
392
|
-
resolve: {
|
|
399
|
+
resolve: {
|
|
400
|
+
alias: {
|
|
401
|
+
"virtual:plan": mdxPath,
|
|
402
|
+
"@visualplan/core": paths.coreEntry,
|
|
403
|
+
// The plan .mdx lives anywhere on disk, often outside any node project, but
|
|
404
|
+
// @mdx-js/rollup makes it import react/jsx-runtime and @mdx-js/react. Those
|
|
405
|
+
// are attributed to the plan's own directory, which usually has no
|
|
406
|
+
// node_modules, so resolve them from the CLI's install instead. Without this
|
|
407
|
+
// a plan in a bare directory fails with "failed to resolve react/jsx-runtime".
|
|
408
|
+
"react/jsx-runtime": require2.resolve("react/jsx-runtime"),
|
|
409
|
+
"react/jsx-dev-runtime": require2.resolve("react/jsx-dev-runtime"),
|
|
410
|
+
"@mdx-js/react": require2.resolve("@mdx-js/react")
|
|
411
|
+
}
|
|
412
|
+
},
|
|
393
413
|
esbuild: { jsx: "automatic", jsxImportSource: "react" },
|
|
394
414
|
plugins: [mdxPlugin()],
|
|
395
415
|
// The runtime, core, and the user's plan span sibling dirs (and a hoisted
|
|
@@ -462,7 +482,7 @@ async function runRender(file, options) {
|
|
|
462
482
|
}
|
|
463
483
|
|
|
464
484
|
// src/index.ts
|
|
465
|
-
var program = new Command("vplan").description("Render
|
|
485
|
+
var program = new Command("vplan").description("Render an AI agent's plans as visual MDX pages instead of walls of text").version(package_default.version);
|
|
466
486
|
program.command("render", { isDefault: true }).description("Compile a plan .mdx to a self-contained HTML page (default command)").argument("<file>", "the plan .mdx file to render").option("--watch", "start a hot-reloading dev server instead of writing a file").option("--out <path>", "output HTML path (defaults to <file>.plan.html)").option("--no-open", "do not open the result in a browser").action((file, options) => runRender(file, options));
|
|
467
487
|
program.command("check").description("Validate a plan .mdx (compile + component checks) without rendering").argument("<file>", "the plan .mdx file to validate").action((file) => runCheck(file));
|
|
468
488
|
program.command("components").description("Print the available plan components and their props").action(() => runComponents());
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../package.json","../src/commands/check.ts","../src/build/check.ts","../../core/src/index.ts","../src/commands/components.ts","../src/commands/render.ts","../src/build/compile.ts","../src/build/remark-mermaid.ts"],"sourcesContent":["import { Command } from 'commander'\nimport packageJson from '../package.json' with { type: 'json' }\nimport { runCheck } from './commands/check.js'\nimport { runComponents } from './commands/components.js'\nimport { runRender, type RenderOptions } from './commands/render.js'\n\nconst program = new Command('vplan')\n .description('Render Claude plans as visual MDX pages instead of walls of text')\n .version(packageJson.version)\n\nprogram\n .command('render', { isDefault: true })\n .description('Compile a plan .mdx to a self-contained HTML page (default command)')\n .argument('<file>', 'the plan .mdx file to render')\n .option('--watch', 'start a hot-reloading dev server instead of writing a file')\n .option('--out <path>', 'output HTML path (defaults to <file>.plan.html)')\n .option('--no-open', 'do not open the result in a browser')\n .action((file: string, options: RenderOptions) => runRender(file, options))\n\nprogram\n .command('check')\n .description('Validate a plan .mdx (compile + component checks) without rendering')\n .argument('<file>', 'the plan .mdx file to validate')\n .action((file: string) => runCheck(file))\n\nprogram\n .command('components')\n .description('Print the available plan components and their props')\n .action(() => runComponents())\n\nprogram.parseAsync(process.argv).catch((error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`)\n process.exitCode = 1\n})\n","{\n \"name\": \"vplan\",\n \"version\": \"0.1.0\",\n \"description\": \"Render Claude's plans as visual MDX pages instead of walls of text\",\n \"author\": \"Brandon Burrus <brandon@burrus.io>\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20.0.0\"\n },\n \"bin\": {\n \"vplan\": \"./dist/index.js\"\n },\n \"files\": [\n \"dist\",\n \"runtime\",\n \"core\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx src/index.ts\",\n \"typecheck\": \"tsc --noEmit\",\n \"vendor\": \"node scripts/vendor.mjs\",\n \"prepack\": \"node scripts/vendor.mjs && tsup\"\n },\n \"dependencies\": {\n \"@mdx-js/mdx\": \"^3.1.1\",\n \"@mdx-js/react\": \"^3.1.1\",\n \"@mdx-js/rollup\": \"^3.1.1\",\n \"@tabler/icons-react\": \"^3.44.0\",\n \"beautiful-mermaid\": \"^1.1.3\",\n \"commander\": \"^15.0.0\",\n \"open\": \"^11.0.0\",\n \"react\": \"^19.2.7\",\n \"react-dom\": \"^19.2.7\",\n \"recharts\": \"^3.8.1\",\n \"rehype-expressive-code\": \"^0.43.1\",\n \"remark-frontmatter\": \"^5.0.0\",\n \"remark-gfm\": \"^4.0.1\",\n \"remark-mdx\": \"^3.1.1\",\n \"remark-mdx-frontmatter\": \"^5.2.0\",\n \"remark-parse\": \"^11.0.0\",\n \"unified\": \"^11.0.5\",\n \"unist-util-visit\": \"^5.1.0\",\n \"vite\": \"^8.0.16\",\n \"vite-plugin-singlefile\": \"^2.3.3\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@visualplan/core\": \"workspace:*\",\n \"@visualplan/runtime\": \"workspace:*\"\n }\n}\n","import { resolve } from 'node:path'\nimport { type CheckIssue, checkPlan } from '../build/check.js'\n\n/** Print check issues in an editor-clickable `file:line:column message` format. */\nexport function printIssues(file: string, issues: CheckIssue[]): void {\n for (const issue of issues) {\n process.stderr.write(`${file}:${issue.line}:${issue.column} ${issue.message}\\n`)\n }\n}\n\n/** `vplan check <file>` — validate a plan's MDX without rendering it. */\nexport async function runCheck(file: string): Promise<void> {\n const issues = await checkPlan(resolve(file))\n if (issues.length === 0) {\n process.stdout.write(`${file} is valid\\n`)\n return\n }\n printIssues(file, issues)\n process.stdout.write(`\\n${issues.length} issue(s) found\\n`)\n process.exitCode = 1\n}\n","import { readFile } from 'node:fs/promises'\nimport { compile } from '@mdx-js/mdx'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkGfm from 'remark-gfm'\nimport remarkMdxFrontmatter from 'remark-mdx-frontmatter'\nimport remarkMdx from 'remark-mdx'\nimport remarkParse from 'remark-parse'\nimport { unified } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport { CATALOG } from '@visualplan/core'\n\nexport interface CheckIssue {\n line: number\n column: number\n message: string\n}\n\ninterface JsxAttribute {\n type: string\n name?: string\n value?: unknown\n}\n\ninterface JsxNode {\n type: string\n name?: string | null\n attributes?: JsxAttribute[]\n position?: { start: { line: number; column: number } }\n}\n\nconst COMPONENT_NAMES = CATALOG.filter(entry => /^[A-Z][A-Za-z0-9]*$/.test(entry.name)).map(\n entry => entry.name,\n)\n\nconst ENUMS_BY_COMPONENT = new Map(CATALOG.map(entry => [entry.name, entry.staticEnums]))\n\n/** Validate a plan's MDX: real compile errors plus static enum / unknown-component checks. */\nexport async function checkPlan(mdxPath: string): Promise<CheckIssue[]> {\n const source = await readFile(mdxPath, 'utf8')\n const issues: CheckIssue[] = []\n\n try {\n await compile(source, {\n remarkPlugins: [\n remarkFrontmatter,\n [remarkMdxFrontmatter, { name: 'frontmatter' }],\n remarkGfm,\n ],\n })\n } catch (error) {\n const vfileError = error as {\n line?: number\n column?: number\n reason?: string\n message?: string\n }\n return [\n {\n line: vfileError.line ?? 1,\n column: vfileError.column ?? 1,\n message: vfileError.reason ?? vfileError.message ?? 'MDX failed to compile',\n },\n ]\n }\n\n const tree = unified().use(remarkParse).use(remarkFrontmatter).use(remarkMdx).parse(source)\n\n visit(tree, node => {\n const element = node as unknown as JsxNode\n if (element.type !== 'mdxJsxFlowElement' && element.type !== 'mdxJsxTextElement') return\n const name = element.name\n if (!name) return\n const at = element.position?.start ?? { line: 1, column: 1 }\n\n if (!COMPONENT_NAMES.includes(name)) {\n issues.push({\n line: at.line,\n column: at.column,\n message: `Unknown component <${name}>. Valid components: ${COMPONENT_NAMES.join(', ')}.`,\n })\n return\n }\n\n const enums = ENUMS_BY_COMPONENT.get(name) ?? {}\n for (const [prop, allowed] of Object.entries(enums)) {\n const attribute = element.attributes?.find(\n candidate => candidate.type === 'mdxJsxAttribute' && candidate.name === prop,\n )\n if (attribute && typeof attribute.value === 'string' && !allowed.includes(attribute.value)) {\n issues.push({\n line: at.line,\n column: at.column,\n message: `<${name}> prop ${prop}=\"${attribute.value}\" is invalid. Valid: ${allowed.join(', ')}.`,\n })\n }\n }\n })\n\n return issues\n}\n","import { z } from 'zod'\n\n/**\n * Single source of truth for the VisualPlan component vocabulary.\n *\n * This module is imported by BOTH the browser runtime (for render-time zod\n * validation) and the Node CLI (for static `check` and the `components`\n * catalog printer), so it must stay free of any React, recharts, or mermaid\n * imports. Keep it isomorphic.\n */\n\nexport const STATUS_VALUES = ['planned', 'active', 'done'] as const\nexport const CHANGE_VALUES = ['add', 'modify', 'delete', 'move'] as const\nexport const CHART_TYPE_VALUES = ['bar', 'line', 'pie'] as const\nexport const CALLOUT_TYPE_VALUES = ['note', 'risk', 'decision', 'warn'] as const\n\nexport const phaseSchema = z.object({\n title: z.string().min(1, 'title is required'),\n status: z.enum(STATUS_VALUES).default('planned'),\n})\n\nexport const fileTreeSchema = z.object({\n files: z\n .array(\n z.object({\n path: z.string().min(1, 'each file needs a path'),\n change: z.enum(CHANGE_VALUES),\n }),\n )\n .min(1, 'files must list at least one entry'),\n})\n\nexport const chartSchema = z.object({\n type: z.enum(CHART_TYPE_VALUES),\n title: z.string().optional(),\n data: z\n .array(z.object({ label: z.string(), value: z.number() }))\n .min(1, 'data must have at least one point'),\n})\n\nexport const compareSchema = z.object({\n options: z\n .array(\n z.object({\n name: z.string().min(1, 'each option needs a name'),\n pros: z.array(z.string()).default([]),\n cons: z.array(z.string()).default([]),\n pick: z.boolean().optional(),\n }),\n )\n .min(2, 'compare needs at least two options'),\n})\n\nexport const calloutSchema = z.object({\n type: z.enum(CALLOUT_TYPE_VALUES).default('note'),\n})\n\nexport const questionsSchema = z.object({\n items: z.array(z.string().min(1)).min(1, 'questions needs at least one item'),\n})\n\nexport const checklistSchema = z.object({\n title: z.string().optional(),\n items: z\n .array(\n z.object({\n text: z.string().min(1, 'each item needs text'),\n done: z.boolean().default(false),\n }),\n )\n .min(1, 'checklist needs at least one item'),\n})\n\n/** Describes a component for the `components` printer and the static checker. */\nexport interface CatalogEntry {\n name: string\n summary: string\n /** Props the CLI can statically validate from MDX source (string-literal enums). */\n staticEnums: Record<string, readonly string[]>\n example: string\n}\n\nexport const CATALOG: readonly CatalogEntry[] = [\n {\n name: 'Phase',\n summary: 'A collapsible plan stage with a status badge. Wraps markdown.',\n staticEnums: { status: STATUS_VALUES },\n example: '<Phase title=\"Build the API\" status=\"active\">\\n 1. Define routes\\n</Phase>',\n },\n {\n name: 'FileTree',\n summary:\n 'A nested directory tree of file changes, built from the paths, with add/modify/delete/move markers.',\n staticEnums: {},\n example:\n '<FileTree files={[{ path: \"src/api/routes.ts\", change: \"add\" }, { path: \"src/api/db.ts\", change: \"modify\" }, { path: \"src/legacy.ts\", change: \"delete\" }]} />',\n },\n {\n name: 'Chart',\n summary: 'A bar/line/pie chart for estimates or metrics.',\n staticEnums: { type: CHART_TYPE_VALUES },\n example:\n '<Chart type=\"bar\" title=\"Effort (days)\" data={[{ label: \"API\", value: 3 }, { label: \"UI\", value: 2 }]} />',\n },\n {\n name: 'Compare',\n summary: 'Side-by-side option cards for weighing approaches.',\n staticEnums: {},\n example:\n '<Compare options={[{ name: \"Postgres\", pros: [\"ACID\"], cons: [\"ops\"], pick: true }, { name: \"SQLite\", pros: [\"simple\"], cons: [\"scale\"] }]} />',\n },\n {\n name: 'Callout',\n summary: 'A highlighted note/risk/decision/warning block. Wraps markdown.',\n staticEnums: { type: CALLOUT_TYPE_VALUES },\n example: '<Callout type=\"risk\">\\n Migration locks the table for ~2s.\\n</Callout>',\n },\n {\n name: 'Questions',\n summary:\n 'Open questions you (Claude) want the reader to weigh in on before building, as a highlighted panel.',\n staticEnums: {},\n example:\n '<Questions items={[\"Should refresh tokens rotate on every use?\", \"Is a 15-minute access-token TTL acceptable?\"]} />',\n },\n {\n name: 'Checklist',\n summary: 'Acceptance criteria / definition of done, with done and todo states.',\n staticEnums: {},\n example:\n '<Checklist title=\"Done when\" items={[{ text: \"Returns 429 over the limit\", done: true }, { text: \"Dashboards live\" }]} />',\n },\n {\n name: 'mermaid (code fence)',\n summary:\n 'A flowchart, sequence, state, class, ER, or XY-chart diagram. Write a ```mermaid fenced block. (gantt/pie are not supported by the renderer.)',\n staticEnums: {},\n example: '```mermaid\\nflowchart LR\\n A[Client] --> B[API] --> C[(DB)]\\n```',\n },\n]\n","import { CATALOG } from '@visualplan/core'\n\n/** `vplan components` — print the component vocabulary cheat-sheet. */\nexport function runComponents(): void {\n const lines: string[] = [\n 'VisualPlan components — use these directly in a plan .mdx (no imports):',\n '',\n ]\n for (const entry of CATALOG) {\n lines.push(`${entry.name}`)\n lines.push(` ${entry.summary}`)\n const enums = Object.entries(entry.staticEnums)\n for (const [prop, values] of enums) {\n lines.push(` ${prop}: ${values.join(' | ')}`)\n }\n lines.push(' example:')\n for (const exampleLine of entry.example.split('\\n')) {\n lines.push(` ${exampleLine}`)\n }\n lines.push('')\n }\n lines.push('Start the plan with a `# Title` heading. Do not use YAML frontmatter.')\n process.stdout.write(`${lines.join('\\n')}\\n`)\n}\n","import { basename, dirname, extname, join, resolve } from 'node:path'\nimport open from 'open'\nimport { checkPlan } from '../build/check.js'\nimport { renderToFile, startDevServer } from '../build/compile.js'\nimport { printIssues } from './check.js'\n\nexport interface RenderOptions {\n watch?: boolean\n out?: string\n open?: boolean\n}\n\nfunction defaultOutPath(absMdx: string): string {\n const stem = basename(absMdx, extname(absMdx))\n return join(dirname(absMdx), `${stem}.plan.html`)\n}\n\n/** `vplan render <file>` — validate, then build a static page or start a watch server. */\nexport async function runRender(file: string, options: RenderOptions): Promise<void> {\n const absMdx = resolve(file)\n\n const issues = await checkPlan(absMdx)\n if (issues.length > 0) {\n printIssues(file, issues)\n process.exitCode = 1\n return\n }\n\n if (options.watch) {\n const server = await startDevServer(absMdx)\n process.stdout.write(\n `VisualPlan watching ${file}\\n ${server.url}\\n (edit the file to hot-reload; Ctrl+C to stop)\\n`,\n )\n if (options.open !== false) await open(server.url)\n return\n }\n\n const out = options.out ? resolve(options.out) : defaultOutPath(absMdx)\n await renderToFile(absMdx, out)\n process.stdout.write(`Rendered ${out}\\n`)\n if (options.open !== false) await open(out)\n}\n","import { cp, mkdtemp, rm } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { tmpdir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport mdx from '@mdx-js/rollup'\nimport rehypeExpressiveCode, { type RehypeExpressiveCodeOptions } from 'rehype-expressive-code'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkGfm from 'remark-gfm'\nimport remarkMdxFrontmatter from 'remark-mdx-frontmatter'\nimport { build, createServer, type InlineConfig, type Plugin } from 'vite'\nimport { viteSingleFile } from 'vite-plugin-singlefile'\nimport { remarkMermaid } from './remark-mermaid.js'\n\nconst expressiveCodeOptions: RehypeExpressiveCodeOptions = {\n themes: ['github-dark', 'github-light'],\n useDarkModeMediaQuery: true,\n // The copy-button script does not execute reliably in our client-rendered SPA;\n // frames (titles) are CSS-only, so keep those and drop the interactive button.\n frames: { showCopyToClipboardButton: false },\n // Match the flat ink design: our borders/radius/surfaces/fonts, no shadow, no\n // colored tab accent. Values are CSS vars so the frame chrome tracks light/dark too.\n styleOverrides: {\n borderRadius: '10px',\n borderColor: 'var(--vp-border)',\n codeBackground: 'var(--vp-surface)',\n codeFontFamily: 'var(--vp-mono)',\n codeFontSize: '0.8rem',\n codeLineHeight: '1.6',\n codePaddingBlock: '0.9rem',\n codePaddingInline: '1rem',\n uiFontFamily: 'var(--vp-font)',\n uiFontSize: '0.78rem',\n frames: {\n frameBoxShadowCssValue: 'none',\n // A flat filename header on the same surface as the code, separated by one\n // border. No editor-tab metaphor, no colored indicator line.\n editorBackground: 'var(--vp-surface)',\n editorTabBarBackground: 'var(--vp-surface)',\n editorTabBarBorderBottomColor: 'var(--vp-border)',\n editorActiveTabBackground: 'var(--vp-surface)',\n editorActiveTabForeground: 'var(--vp-muted)',\n editorActiveTabBorderColor: 'transparent',\n editorActiveTabIndicatorTopColor: 'transparent',\n editorActiveTabIndicatorBottomColor: 'transparent',\n editorTabsMarginInlineStart: '0',\n terminalBackground: 'var(--vp-surface)',\n terminalTitlebarBackground: 'var(--vp-surface)',\n terminalTitlebarForeground: 'var(--vp-muted)',\n terminalTitlebarBorderBottomColor: 'var(--vp-border)',\n },\n },\n}\n\nconst require = createRequire(import.meta.url)\n\ninterface RuntimePaths {\n /** Directory Vite roots at: holds index.html plus the runtime source. */\n runtimeDir: string\n /** Core catalog source, aliased to `@visualplan/core` so the runtime import resolves. */\n coreEntry: string\n}\n\n/**\n * Locate the runtime source and the core catalog in both layouts:\n * - Published: both are vendored next to dist/ (`<pkg>/runtime`, `<pkg>/core`).\n * - Dev (workspace): they are sibling packages, resolved via node module resolution.\n * The core path is aliased to `@visualplan/core` in the Vite build, so the runtime's\n * import resolves identically whether the CLI is installed or run from the monorepo.\n */\nfunction findRuntimePaths(): RuntimePaths {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let depth = 0; depth < 6; depth++) {\n const runtimeDir = join(dir, 'runtime')\n const coreEntry = join(dir, 'core', 'index.ts')\n // Require BOTH vendored siblings: the monorepo also has a `runtime/index.html`\n // (under packages/), but its core lives at core/src/index.ts, so only the true\n // vendored layout has core/index.ts next to runtime/.\n if (existsSync(join(runtimeDir, 'index.html')) && existsSync(coreEntry)) {\n return { runtimeDir, coreEntry }\n }\n dir = dirname(dir)\n }\n const runtimeDir = dirname(require.resolve('@visualplan/runtime/package.json'))\n const coreDir = dirname(require.resolve('@visualplan/core/package.json'))\n return { runtimeDir, coreEntry: join(coreDir, 'src', 'index.ts') }\n}\n\nfunction mdxPlugin(): Plugin {\n return {\n enforce: 'pre',\n ...mdx({\n providerImportSource: '@mdx-js/react',\n remarkPlugins: [\n remarkFrontmatter,\n [remarkMdxFrontmatter, { name: 'frontmatter' }],\n remarkGfm,\n // Must run before rehype-expressive-code so mermaid never reaches the highlighter.\n remarkMermaid,\n ],\n rehypePlugins: [[rehypeExpressiveCode, expressiveCodeOptions]],\n }),\n }\n}\n\nfunction baseConfig(paths: RuntimePaths, mdxPath: string): InlineConfig {\n return {\n root: paths.runtimeDir,\n configFile: false,\n logLevel: 'silent',\n resolve: { alias: { 'virtual:plan': mdxPath, '@visualplan/core': paths.coreEntry } },\n esbuild: { jsx: 'automatic', jsxImportSource: 'react' },\n plugins: [mdxPlugin()],\n // The runtime, core, and the user's plan span sibling dirs (and a hoisted\n // node_modules) in the monorepo, so the dev server cannot use a single allow\n // root. This is a local tool rendering the user's own file, so fs is unrestricted.\n server: { fs: { strict: false }, open: false },\n }\n}\n\n/** Compile an MDX plan to a single self-contained HTML file at `outPath`. */\nexport async function renderToFile(mdxPath: string, outPath: string): Promise<void> {\n const paths = findRuntimePaths()\n const absMdx = resolve(mdxPath)\n const outDir = await mkdtemp(join(tmpdir(), 'visualplan-build-'))\n try {\n const config = baseConfig(paths, absMdx)\n await build({\n ...config,\n plugins: [...(config.plugins ?? []), viteSingleFile()],\n build: {\n outDir,\n emptyOutDir: true,\n rollupOptions: { input: join(paths.runtimeDir, 'index.html') },\n },\n })\n await cp(join(outDir, 'index.html'), resolve(outPath))\n } finally {\n await rm(outDir, { recursive: true, force: true })\n }\n}\n\nexport interface DevServer {\n url: string\n close: () => Promise<void>\n}\n\n/** Start a hot-reloading dev server for an MDX plan and return its local URL. */\nexport async function startDevServer(mdxPath: string): Promise<DevServer> {\n const paths = findRuntimePaths()\n const absMdx = resolve(mdxPath)\n const server = await createServer(baseConfig(paths, absMdx))\n await server.listen()\n const url =\n server.resolvedUrls?.local[0] ?? `http://localhost:${server.config.server.port ?? 5173}`\n return {\n url,\n close: () => server.close(),\n }\n}\n","import { visit } from 'unist-util-visit'\n\ninterface MdastCode {\n type: 'code'\n lang?: string | null\n value: string\n}\n\ninterface MdastParent {\n children: unknown[]\n}\n\n/**\n * Convert ```mermaid fenced code blocks into `<Mermaid chart=\"...\" />` MDX JSX\n * elements. This runs in the remark (mdast) stage, BEFORE rehype-expressive-code,\n * so the code highlighter never sees mermaid blocks and the diagram renders via the\n * Mermaid component instead.\n */\nexport function remarkMermaid() {\n return (tree: unknown) => {\n visit(\n tree as never,\n 'code',\n (node: MdastCode, index: number | undefined, parent: MdastParent | undefined) => {\n if (node.lang !== 'mermaid' || !parent || index === undefined) return\n parent.children[index] = {\n type: 'mdxJsxFlowElement',\n name: 'Mermaid',\n attributes: [{ type: 'mdxJsxAttribute', name: 'chart', value: node.value }],\n children: [],\n }\n },\n )\n }\n}\n"],"mappings":"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","names":["dirname","join","resolve","resolve","remarkFrontmatter","remarkGfm","remarkMdxFrontmatter","visit","require","runtimeDir","remarkFrontmatter","remarkMdxFrontmatter","remarkGfm","resolve","join","dirname","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../package.json","../src/commands/check.ts","../src/build/check.ts","../../core/src/index.ts","../src/commands/components.ts","../src/commands/render.ts","../src/build/compile.ts","../src/build/remark-mermaid.ts"],"sourcesContent":["import { Command } from 'commander'\nimport packageJson from '../package.json' with { type: 'json' }\nimport { runCheck } from './commands/check.js'\nimport { runComponents } from './commands/components.js'\nimport { runRender, type RenderOptions } from './commands/render.js'\n\nconst program = new Command('vplan')\n .description(\"Render an AI agent's plans as visual MDX pages instead of walls of text\")\n .version(packageJson.version)\n\nprogram\n .command('render', { isDefault: true })\n .description('Compile a plan .mdx to a self-contained HTML page (default command)')\n .argument('<file>', 'the plan .mdx file to render')\n .option('--watch', 'start a hot-reloading dev server instead of writing a file')\n .option('--out <path>', 'output HTML path (defaults to <file>.plan.html)')\n .option('--no-open', 'do not open the result in a browser')\n .action((file: string, options: RenderOptions) => runRender(file, options))\n\nprogram\n .command('check')\n .description('Validate a plan .mdx (compile + component checks) without rendering')\n .argument('<file>', 'the plan .mdx file to validate')\n .action((file: string) => runCheck(file))\n\nprogram\n .command('components')\n .description('Print the available plan components and their props')\n .action(() => runComponents())\n\nprogram.parseAsync(process.argv).catch((error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`)\n process.exitCode = 1\n})\n","{\n \"name\": \"vplan\",\n \"version\": \"0.2.0\",\n \"description\": \"Render an AI agent's plans as visual MDX pages instead of walls of text\",\n \"author\": \"Brandon Burrus <brandon@burrus.io>\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/brandonburrus/visualplan.git\",\n \"directory\": \"packages/cli\"\n },\n \"homepage\": \"https://github.com/brandonburrus/visualplan#readme\",\n \"bugs\": \"https://github.com/brandonburrus/visualplan/issues\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20.0.0\"\n },\n \"bin\": {\n \"vplan\": \"dist/index.js\"\n },\n \"files\": [\n \"dist\",\n \"runtime\",\n \"core\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx src/index.ts\",\n \"typecheck\": \"tsc --noEmit\",\n \"vendor\": \"node scripts/vendor.mjs\",\n \"prepack\": \"node scripts/vendor.mjs && tsup\"\n },\n \"dependencies\": {\n \"@mdx-js/mdx\": \"^3.1.1\",\n \"@mdx-js/react\": \"^3.1.1\",\n \"@mdx-js/rollup\": \"^3.1.1\",\n \"@tabler/icons-react\": \"^3.44.0\",\n \"beautiful-mermaid\": \"^1.1.3\",\n \"commander\": \"^15.0.0\",\n \"open\": \"^11.0.0\",\n \"react\": \"^19.2.7\",\n \"react-dom\": \"^19.2.7\",\n \"recharts\": \"^3.8.1\",\n \"rehype-expressive-code\": \"^0.43.1\",\n \"remark-frontmatter\": \"^5.0.0\",\n \"remark-gfm\": \"^4.0.1\",\n \"remark-mdx\": \"^3.1.1\",\n \"remark-mdx-frontmatter\": \"^5.2.0\",\n \"remark-parse\": \"^11.0.0\",\n \"unified\": \"^11.0.5\",\n \"unist-util-visit\": \"^5.1.0\",\n \"vite\": \"^8.0.16\",\n \"vite-plugin-singlefile\": \"^2.3.3\",\n \"zod\": \"^4.4.3\"\n }\n}\n","import { resolve } from 'node:path'\nimport { type CheckIssue, checkPlan } from '../build/check.js'\n\n/** Print check issues in an editor-clickable `file:line:column message` format. */\nexport function printIssues(file: string, issues: CheckIssue[]): void {\n for (const issue of issues) {\n process.stderr.write(`${file}:${issue.line}:${issue.column} ${issue.message}\\n`)\n }\n}\n\n/** `vplan check <file>` — validate a plan's MDX without rendering it. */\nexport async function runCheck(file: string): Promise<void> {\n const issues = await checkPlan(resolve(file))\n if (issues.length === 0) {\n process.stdout.write(`${file} is valid\\n`)\n return\n }\n printIssues(file, issues)\n process.stdout.write(`\\n${issues.length} issue(s) found\\n`)\n process.exitCode = 1\n}\n","import { readFile } from 'node:fs/promises'\nimport { compile } from '@mdx-js/mdx'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkGfm from 'remark-gfm'\nimport remarkMdxFrontmatter from 'remark-mdx-frontmatter'\nimport remarkMdx from 'remark-mdx'\nimport remarkParse from 'remark-parse'\nimport { unified } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport { CATALOG } from '@visualplan/core'\n\nexport interface CheckIssue {\n line: number\n column: number\n message: string\n}\n\ninterface JsxAttribute {\n type: string\n name?: string\n value?: unknown\n}\n\ninterface JsxNode {\n type: string\n name?: string | null\n attributes?: JsxAttribute[]\n position?: { start: { line: number; column: number } }\n}\n\nconst COMPONENT_NAMES = CATALOG.filter(entry => /^[A-Z][A-Za-z0-9]*$/.test(entry.name)).map(\n entry => entry.name,\n)\n\nconst ENUMS_BY_COMPONENT = new Map(CATALOG.map(entry => [entry.name, entry.staticEnums]))\n\n/** Validate a plan's MDX: real compile errors plus static enum / unknown-component checks. */\nexport async function checkPlan(mdxPath: string): Promise<CheckIssue[]> {\n const source = await readFile(mdxPath, 'utf8')\n const issues: CheckIssue[] = []\n\n try {\n await compile(source, {\n remarkPlugins: [\n remarkFrontmatter,\n [remarkMdxFrontmatter, { name: 'frontmatter' }],\n remarkGfm,\n ],\n })\n } catch (error) {\n const vfileError = error as {\n line?: number\n column?: number\n place?: { line?: number; column?: number; start?: { line?: number; column?: number } }\n reason?: string\n message?: string\n }\n const message = vfileError.reason ?? vfileError.message ?? 'MDX failed to compile'\n // Some MDX errors (e.g. an unclosed JSX tag) carry no structured position; the\n // location is embedded in the message as \"(line:col-line:col)\". Fall back to the\n // `place` point, then to the message, so the file:line:col prefix is accurate.\n const placeStart = vfileError.place?.start ?? vfileError.place\n const fromMessage = message.match(/\\((\\d+):(\\d+)/)\n return [\n {\n line: vfileError.line ?? placeStart?.line ?? (fromMessage ? Number(fromMessage[1]) : 1),\n column:\n vfileError.column ?? placeStart?.column ?? (fromMessage ? Number(fromMessage[2]) : 1),\n message,\n },\n ]\n }\n\n const tree = unified().use(remarkParse).use(remarkFrontmatter).use(remarkMdx).parse(source)\n\n visit(tree, node => {\n const element = node as unknown as JsxNode\n if (element.type !== 'mdxJsxFlowElement' && element.type !== 'mdxJsxTextElement') return\n const name = element.name\n if (!name) return\n const at = element.position?.start ?? { line: 1, column: 1 }\n\n if (!COMPONENT_NAMES.includes(name)) {\n issues.push({\n line: at.line,\n column: at.column,\n message: `Unknown component <${name}>. Valid components: ${COMPONENT_NAMES.join(', ')}.`,\n })\n return\n }\n\n const enums = ENUMS_BY_COMPONENT.get(name) ?? {}\n for (const [prop, allowed] of Object.entries(enums)) {\n const attribute = element.attributes?.find(\n candidate => candidate.type === 'mdxJsxAttribute' && candidate.name === prop,\n )\n if (attribute && typeof attribute.value === 'string' && !allowed.includes(attribute.value)) {\n issues.push({\n line: at.line,\n column: at.column,\n message: `<${name}> prop ${prop}=\"${attribute.value}\" is invalid. Valid: ${allowed.join(', ')}.`,\n })\n }\n }\n })\n\n return issues\n}\n","import { z } from 'zod'\n\n/**\n * Single source of truth for the VisualPlan component vocabulary.\n *\n * This module is imported by BOTH the browser runtime (for render-time zod\n * validation) and the Node CLI (for static `check` and the `components`\n * catalog printer), so it must stay free of any React, recharts, or mermaid\n * imports. Keep it isomorphic.\n */\n\nexport const STATUS_VALUES = ['planned', 'active', 'done'] as const\nexport const CHANGE_VALUES = ['add', 'modify', 'delete', 'move'] as const\nexport const CHART_TYPE_VALUES = ['bar', 'line', 'pie'] as const\nexport const CALLOUT_TYPE_VALUES = ['note', 'risk', 'decision', 'warn'] as const\n\nexport const phaseSchema = z.object({\n title: z.string().min(1, 'title is required'),\n status: z.enum(STATUS_VALUES).default('planned'),\n})\n\nexport const fileTreeSchema = z.object({\n files: z\n .array(\n z.object({\n path: z.string().min(1, 'each file needs a path'),\n change: z.enum(CHANGE_VALUES),\n }),\n )\n .min(1, 'files must list at least one entry'),\n})\n\nexport const chartSchema = z.object({\n type: z.enum(CHART_TYPE_VALUES),\n title: z.string().optional(),\n data: z\n .array(z.object({ label: z.string(), value: z.number() }))\n .min(1, 'data must have at least one point'),\n})\n\nexport const compareSchema = z.object({\n options: z\n .array(\n z.object({\n name: z.string().min(1, 'each option needs a name'),\n pros: z.array(z.string()).default([]),\n cons: z.array(z.string()).default([]),\n pick: z.boolean().optional(),\n }),\n )\n .min(2, 'compare needs at least two options'),\n})\n\nexport const calloutSchema = z.object({\n type: z.enum(CALLOUT_TYPE_VALUES).default('note'),\n})\n\nexport const questionsSchema = z.object({\n title: z.string().default('Open questions'),\n items: z.array(z.string().min(1)).min(1, 'questions needs at least one item'),\n})\n\nexport const checklistSchema = z.object({\n title: z.string().optional(),\n items: z\n .array(\n z.object({\n text: z.string().min(1, 'each item needs text'),\n done: z.boolean().default(false),\n }),\n )\n .min(1, 'checklist needs at least one item'),\n})\n\n/** Describes a component for the `components` printer and the static checker. */\nexport interface CatalogEntry {\n name: string\n summary: string\n /** Props the CLI can statically validate from MDX source (string-literal enums). */\n staticEnums: Record<string, readonly string[]>\n example: string\n}\n\nexport const CATALOG: readonly CatalogEntry[] = [\n {\n name: 'Phase',\n summary: 'A collapsible plan stage with a status badge. Wraps markdown.',\n staticEnums: { status: STATUS_VALUES },\n example: '<Phase title=\"Build the API\" status=\"active\">\\n 1. Define routes\\n</Phase>',\n },\n {\n name: 'FileTree',\n summary:\n 'A nested directory tree of file changes, built from the paths, with add/modify/delete/move markers.',\n staticEnums: {},\n example:\n '<FileTree files={[{ path: \"src/api/routes.ts\", change: \"add\" }, { path: \"src/api/db.ts\", change: \"modify\" }, { path: \"src/legacy.ts\", change: \"delete\" }]} />',\n },\n {\n name: 'Chart',\n summary: 'A bar/line/pie chart for estimates or metrics.',\n staticEnums: { type: CHART_TYPE_VALUES },\n example:\n '<Chart type=\"bar\" title=\"Effort (days)\" data={[{ label: \"API\", value: 3 }, { label: \"UI\", value: 2 }]} />',\n },\n {\n name: 'Compare',\n summary: 'Side-by-side option cards for weighing approaches.',\n staticEnums: {},\n example:\n '<Compare options={[{ name: \"Postgres\", pros: [\"ACID\"], cons: [\"ops\"], pick: true }, { name: \"SQLite\", pros: [\"simple\"], cons: [\"scale\"] }]} />',\n },\n {\n name: 'Callout',\n summary: 'A highlighted note/risk/decision/warning block. Wraps markdown.',\n staticEnums: { type: CALLOUT_TYPE_VALUES },\n example: '<Callout type=\"risk\">\\n Migration locks the table for ~2s.\\n</Callout>',\n },\n {\n name: 'Questions',\n summary:\n 'Open questions you want the reader to weigh in on before building, as a highlighted panel. Title defaults to \"Open questions\"; override with title.',\n staticEnums: {},\n example:\n '<Questions items={[\"Should refresh tokens rotate on every use?\", \"Is a 15-minute access-token TTL acceptable?\"]} />',\n },\n {\n name: 'Checklist',\n summary: 'Acceptance criteria / definition of done, with done and todo states.',\n staticEnums: {},\n example:\n '<Checklist title=\"Done when\" items={[{ text: \"Returns 429 over the limit\", done: true }, { text: \"Dashboards live\" }]} />',\n },\n {\n name: 'mermaid (code fence)',\n summary:\n 'A flowchart, sequence, state, class, ER, or XY-chart diagram. Write a ```mermaid fenced block. (gantt/pie are not supported by the renderer.)',\n staticEnums: {},\n example: '```mermaid\\nflowchart LR\\n A[Client] --> B[API] --> C[(DB)]\\n```',\n },\n]\n","import { CATALOG } from '@visualplan/core'\n\n/** `vplan components` — print the component vocabulary cheat-sheet. */\nexport function runComponents(): void {\n const lines: string[] = [\n 'VisualPlan components — use these directly in a plan .mdx (no imports):',\n '',\n ]\n for (const entry of CATALOG) {\n lines.push(`${entry.name}`)\n lines.push(` ${entry.summary}`)\n const enums = Object.entries(entry.staticEnums)\n for (const [prop, values] of enums) {\n lines.push(` ${prop}: ${values.join(' | ')}`)\n }\n lines.push(' example:')\n for (const exampleLine of entry.example.split('\\n')) {\n lines.push(` ${exampleLine}`)\n }\n lines.push('')\n }\n lines.push('Start the plan with a `# Title` heading. Do not use YAML frontmatter.')\n process.stdout.write(`${lines.join('\\n')}\\n`)\n}\n","import { basename, dirname, extname, join, resolve } from 'node:path'\nimport open from 'open'\nimport { checkPlan } from '../build/check.js'\nimport { renderToFile, startDevServer } from '../build/compile.js'\nimport { printIssues } from './check.js'\n\nexport interface RenderOptions {\n watch?: boolean\n out?: string\n open?: boolean\n}\n\nfunction defaultOutPath(absMdx: string): string {\n const stem = basename(absMdx, extname(absMdx))\n return join(dirname(absMdx), `${stem}.plan.html`)\n}\n\n/** `vplan render <file>` — validate, then build a static page or start a watch server. */\nexport async function runRender(file: string, options: RenderOptions): Promise<void> {\n const absMdx = resolve(file)\n\n const issues = await checkPlan(absMdx)\n if (issues.length > 0) {\n printIssues(file, issues)\n process.exitCode = 1\n return\n }\n\n if (options.watch) {\n const server = await startDevServer(absMdx)\n process.stdout.write(\n `VisualPlan watching ${file}\\n ${server.url}\\n (edit the file to hot-reload; Ctrl+C to stop)\\n`,\n )\n if (options.open !== false) await open(server.url)\n return\n }\n\n const out = options.out ? resolve(options.out) : defaultOutPath(absMdx)\n await renderToFile(absMdx, out)\n process.stdout.write(`Rendered ${out}\\n`)\n if (options.open !== false) await open(out)\n}\n","import { cp, mkdtemp, rm } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { tmpdir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport mdx from '@mdx-js/rollup'\nimport rehypeExpressiveCode, { type RehypeExpressiveCodeOptions } from 'rehype-expressive-code'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkGfm from 'remark-gfm'\nimport remarkMdxFrontmatter from 'remark-mdx-frontmatter'\nimport { build, createServer, type InlineConfig, type Plugin } from 'vite'\nimport { viteSingleFile } from 'vite-plugin-singlefile'\nimport { remarkMermaid } from './remark-mermaid.js'\n\nconst expressiveCodeOptions: RehypeExpressiveCodeOptions = {\n themes: ['github-dark', 'github-light'],\n useDarkModeMediaQuery: true,\n // The copy-button script does not execute reliably in our client-rendered SPA;\n // frames (titles) are CSS-only, so keep those and drop the interactive button.\n frames: { showCopyToClipboardButton: false },\n // Match the flat ink design: our borders/radius/surfaces/fonts, no shadow, no\n // colored tab accent. Values are CSS vars so the frame chrome tracks light/dark too.\n styleOverrides: {\n borderRadius: '10px',\n borderColor: 'var(--vp-border)',\n codeBackground: 'var(--vp-surface)',\n codeFontFamily: 'var(--vp-mono)',\n codeFontSize: '0.8rem',\n codeLineHeight: '1.6',\n codePaddingBlock: '0.9rem',\n codePaddingInline: '1rem',\n uiFontFamily: 'var(--vp-font)',\n uiFontSize: '0.78rem',\n frames: {\n frameBoxShadowCssValue: 'none',\n // A flat filename header on the same surface as the code, separated by one\n // border. No editor-tab metaphor, no colored indicator line.\n editorBackground: 'var(--vp-surface)',\n editorTabBarBackground: 'var(--vp-surface)',\n editorTabBarBorderBottomColor: 'var(--vp-border)',\n editorActiveTabBackground: 'var(--vp-surface)',\n editorActiveTabForeground: 'var(--vp-muted)',\n editorActiveTabBorderColor: 'transparent',\n editorActiveTabIndicatorTopColor: 'transparent',\n editorActiveTabIndicatorBottomColor: 'transparent',\n editorTabsMarginInlineStart: '0',\n terminalBackground: 'var(--vp-surface)',\n terminalTitlebarBackground: 'var(--vp-surface)',\n terminalTitlebarForeground: 'var(--vp-muted)',\n terminalTitlebarBorderBottomColor: 'var(--vp-border)',\n },\n },\n}\n\nconst require = createRequire(import.meta.url)\n\ninterface RuntimePaths {\n /** Directory Vite roots at: holds index.html plus the runtime source. */\n runtimeDir: string\n /** Core catalog source, aliased to `@visualplan/core` so the runtime import resolves. */\n coreEntry: string\n}\n\n/**\n * Locate the runtime source and the core catalog in both layouts:\n * - Published: both are vendored next to dist/ (`<pkg>/runtime`, `<pkg>/core`).\n * - Dev (workspace): they are sibling packages, resolved via node module resolution.\n * The core path is aliased to `@visualplan/core` in the Vite build, so the runtime's\n * import resolves identically whether the CLI is installed or run from the monorepo.\n */\nfunction findRuntimePaths(): RuntimePaths {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let depth = 0; depth < 6; depth++) {\n const runtimeDir = join(dir, 'runtime')\n const coreEntry = join(dir, 'core', 'index.ts')\n // Require BOTH vendored siblings: the monorepo also has a `runtime/index.html`\n // (under packages/), but its core lives at core/src/index.ts, so only the true\n // vendored layout has core/index.ts next to runtime/.\n if (existsSync(join(runtimeDir, 'index.html')) && existsSync(coreEntry)) {\n return { runtimeDir, coreEntry }\n }\n dir = dirname(dir)\n }\n const runtimeDir = dirname(require.resolve('@visualplan/runtime/package.json'))\n const coreDir = dirname(require.resolve('@visualplan/core/package.json'))\n return { runtimeDir, coreEntry: join(coreDir, 'src', 'index.ts') }\n}\n\nfunction mdxPlugin(): Plugin {\n return {\n enforce: 'pre',\n ...mdx({\n providerImportSource: '@mdx-js/react',\n remarkPlugins: [\n remarkFrontmatter,\n [remarkMdxFrontmatter, { name: 'frontmatter' }],\n remarkGfm,\n // Must run before rehype-expressive-code so mermaid never reaches the highlighter.\n remarkMermaid,\n ],\n rehypePlugins: [[rehypeExpressiveCode, expressiveCodeOptions]],\n }),\n }\n}\n\nfunction baseConfig(paths: RuntimePaths, mdxPath: string): InlineConfig {\n return {\n root: paths.runtimeDir,\n configFile: false,\n logLevel: 'silent',\n resolve: {\n alias: {\n 'virtual:plan': mdxPath,\n '@visualplan/core': paths.coreEntry,\n // The plan .mdx lives anywhere on disk, often outside any node project, but\n // @mdx-js/rollup makes it import react/jsx-runtime and @mdx-js/react. Those\n // are attributed to the plan's own directory, which usually has no\n // node_modules, so resolve them from the CLI's install instead. Without this\n // a plan in a bare directory fails with \"failed to resolve react/jsx-runtime\".\n 'react/jsx-runtime': require.resolve('react/jsx-runtime'),\n 'react/jsx-dev-runtime': require.resolve('react/jsx-dev-runtime'),\n '@mdx-js/react': require.resolve('@mdx-js/react'),\n },\n },\n esbuild: { jsx: 'automatic', jsxImportSource: 'react' },\n plugins: [mdxPlugin()],\n // The runtime, core, and the user's plan span sibling dirs (and a hoisted\n // node_modules) in the monorepo, so the dev server cannot use a single allow\n // root. This is a local tool rendering the user's own file, so fs is unrestricted.\n server: { fs: { strict: false }, open: false },\n }\n}\n\n/** Compile an MDX plan to a single self-contained HTML file at `outPath`. */\nexport async function renderToFile(mdxPath: string, outPath: string): Promise<void> {\n const paths = findRuntimePaths()\n const absMdx = resolve(mdxPath)\n const outDir = await mkdtemp(join(tmpdir(), 'visualplan-build-'))\n try {\n const config = baseConfig(paths, absMdx)\n await build({\n ...config,\n plugins: [...(config.plugins ?? []), viteSingleFile()],\n build: {\n outDir,\n emptyOutDir: true,\n rollupOptions: { input: join(paths.runtimeDir, 'index.html') },\n },\n })\n await cp(join(outDir, 'index.html'), resolve(outPath))\n } finally {\n await rm(outDir, { recursive: true, force: true })\n }\n}\n\nexport interface DevServer {\n url: string\n close: () => Promise<void>\n}\n\n/** Start a hot-reloading dev server for an MDX plan and return its local URL. */\nexport async function startDevServer(mdxPath: string): Promise<DevServer> {\n const paths = findRuntimePaths()\n const absMdx = resolve(mdxPath)\n const server = await createServer(baseConfig(paths, absMdx))\n await server.listen()\n const url =\n server.resolvedUrls?.local[0] ?? `http://localhost:${server.config.server.port ?? 5173}`\n return {\n url,\n close: () => server.close(),\n }\n}\n","import { visit } from 'unist-util-visit'\n\ninterface MdastCode {\n type: 'code'\n lang?: string | null\n value: string\n}\n\ninterface MdastParent {\n children: unknown[]\n}\n\n/**\n * Convert ```mermaid fenced code blocks into `<Mermaid chart=\"...\" />` MDX JSX\n * elements. This runs in the remark (mdast) stage, BEFORE rehype-expressive-code,\n * so the code highlighter never sees mermaid blocks and the diagram renders via the\n * Mermaid component instead.\n */\nexport function remarkMermaid() {\n return (tree: unknown) => {\n visit(\n tree as never,\n 'code',\n (node: MdastCode, index: number | undefined, parent: MdastParent | undefined) => {\n if (node.lang !== 'mermaid' || !parent || index === undefined) return\n parent.children[index] = {\n type: 'mdxJsxFlowElement',\n name: 'Mermaid',\n attributes: [{ type: 'mdxJsxAttribute', name: 'chart', value: node.value }],\n children: [],\n }\n },\n )\n }\n}\n"],"mappings":"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","names":["dirname","join","resolve","resolve","remarkFrontmatter","remarkGfm","remarkMdxFrontmatter","visit","require","runtimeDir","remarkFrontmatter","remarkMdxFrontmatter","remarkGfm","resolve","join","dirname","resolve"]}
|
package/package.json
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vplan",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Render
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Render an AI agent's plans as visual MDX pages instead of walls of text",
|
|
5
5
|
"author": "Brandon Burrus <brandon@burrus.io>",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/brandonburrus/visualplan.git",
|
|
10
|
+
"directory": "packages/cli"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/brandonburrus/visualplan#readme",
|
|
13
|
+
"bugs": "https://github.com/brandonburrus/visualplan/issues",
|
|
7
14
|
"type": "module",
|
|
8
15
|
"engines": {
|
|
9
16
|
"node": ">=20.0.0"
|
|
10
17
|
},
|
|
11
18
|
"bin": {
|
|
12
|
-
"vplan": "
|
|
19
|
+
"vplan": "dist/index.js"
|
|
13
20
|
},
|
|
14
21
|
"files": [
|
|
15
22
|
"dist",
|
|
16
23
|
"runtime",
|
|
17
24
|
"core"
|
|
18
25
|
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"dev": "tsx src/index.ts",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"vendor": "node scripts/vendor.mjs",
|
|
31
|
+
"prepack": "node scripts/vendor.mjs && tsup"
|
|
32
|
+
},
|
|
19
33
|
"dependencies": {
|
|
20
34
|
"@mdx-js/mdx": "^3.1.1",
|
|
21
35
|
"@mdx-js/react": "^3.1.1",
|
|
@@ -38,15 +52,5 @@
|
|
|
38
52
|
"vite": "^8.0.16",
|
|
39
53
|
"vite-plugin-singlefile": "^2.3.3",
|
|
40
54
|
"zod": "^4.4.3"
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"@visualplan/core": "0.1.0",
|
|
44
|
-
"@visualplan/runtime": "0.1.0"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "tsup",
|
|
48
|
-
"dev": "tsx src/index.ts",
|
|
49
|
-
"typecheck": "tsc --noEmit",
|
|
50
|
-
"vendor": "node scripts/vendor.mjs"
|
|
51
55
|
}
|
|
52
|
-
}
|
|
56
|
+
}
|
|
@@ -35,6 +35,14 @@ const COLORS = [
|
|
|
35
35
|
]
|
|
36
36
|
|
|
37
37
|
const AXIS_TICK = { fill: 'var(--vp-muted)', fontSize: 12 }
|
|
38
|
+
|
|
39
|
+
/** Compact large axis numbers (100000 -> 100k) so y-axis ticks never clip. */
|
|
40
|
+
function formatTick(value: number): string {
|
|
41
|
+
const abs = Math.abs(value)
|
|
42
|
+
if (abs >= 1_000_000) return `${+(value / 1_000_000).toFixed(1)}M`
|
|
43
|
+
if (abs >= 1_000) return `${+(value / 1_000).toFixed(1)}k`
|
|
44
|
+
return `${value}`
|
|
45
|
+
}
|
|
38
46
|
const TOOLTIP_STYLE = {
|
|
39
47
|
background: 'var(--vp-surface)',
|
|
40
48
|
border: '1px solid var(--vp-border)',
|
|
@@ -55,9 +63,14 @@ export function Chart(props: ChartProps) {
|
|
|
55
63
|
<BarChart data={data} margin={{ top: 4, right: 8, bottom: 0, left: -16 }}>
|
|
56
64
|
<CartesianGrid strokeDasharray='3 3' stroke='var(--vp-border)' vertical={false} />
|
|
57
65
|
<XAxis dataKey='label' tick={AXIS_TICK} stroke='var(--vp-border)' />
|
|
58
|
-
<YAxis
|
|
66
|
+
<YAxis
|
|
67
|
+
allowDecimals
|
|
68
|
+
tick={AXIS_TICK}
|
|
69
|
+
stroke='var(--vp-border)'
|
|
70
|
+
tickFormatter={formatTick}
|
|
71
|
+
/>
|
|
59
72
|
<Tooltip cursor={{ fill: 'var(--vp-surface-2)' }} contentStyle={TOOLTIP_STYLE} />
|
|
60
|
-
<Bar dataKey='value' radius={[4, 4, 0, 0]} maxBarSize={64}>
|
|
73
|
+
<Bar dataKey='value' radius={[4, 4, 0, 0]} maxBarSize={64} isAnimationActive={false}>
|
|
61
74
|
{data.map((point, index) => (
|
|
62
75
|
<Cell key={point.label} fill={COLORS[index % COLORS.length]} />
|
|
63
76
|
))}
|
|
@@ -67,7 +80,12 @@ export function Chart(props: ChartProps) {
|
|
|
67
80
|
<LineChart data={data} margin={{ top: 4, right: 8, bottom: 0, left: -16 }}>
|
|
68
81
|
<CartesianGrid strokeDasharray='3 3' stroke='var(--vp-border)' vertical={false} />
|
|
69
82
|
<XAxis dataKey='label' tick={AXIS_TICK} stroke='var(--vp-border)' />
|
|
70
|
-
<YAxis
|
|
83
|
+
<YAxis
|
|
84
|
+
allowDecimals
|
|
85
|
+
tick={AXIS_TICK}
|
|
86
|
+
stroke='var(--vp-border)'
|
|
87
|
+
tickFormatter={formatTick}
|
|
88
|
+
/>
|
|
71
89
|
<Tooltip contentStyle={TOOLTIP_STYLE} />
|
|
72
90
|
<Line
|
|
73
91
|
type='monotone'
|
|
@@ -75,12 +93,20 @@ export function Chart(props: ChartProps) {
|
|
|
75
93
|
stroke={COLORS[0]}
|
|
76
94
|
strokeWidth={2.5}
|
|
77
95
|
dot={{ fill: COLORS[0], r: 3 }}
|
|
96
|
+
isAnimationActive={false}
|
|
78
97
|
/>
|
|
79
98
|
</LineChart>
|
|
80
99
|
) : (
|
|
81
100
|
<PieChart>
|
|
82
101
|
<Tooltip contentStyle={TOOLTIP_STYLE} />
|
|
83
|
-
<Pie
|
|
102
|
+
<Pie
|
|
103
|
+
data={data}
|
|
104
|
+
dataKey='value'
|
|
105
|
+
nameKey='label'
|
|
106
|
+
outerRadius={88}
|
|
107
|
+
stroke='none'
|
|
108
|
+
isAnimationActive={false}
|
|
109
|
+
>
|
|
84
110
|
{data.map((point, index) => (
|
|
85
111
|
<Cell key={point.label} fill={COLORS[index % COLORS.length]} />
|
|
86
112
|
))}
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import { IconHelpCircle } from '@tabler/icons-react'
|
|
2
1
|
import { questionsSchema } from '@visualplan/core'
|
|
3
2
|
import { validateProps } from './validate.js'
|
|
4
3
|
|
|
5
4
|
interface QuestionsProps {
|
|
5
|
+
title?: string
|
|
6
6
|
items: string[]
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/** Open questions for the reader to resolve before building, as a highlighted panel. */
|
|
10
10
|
export function Questions(props: QuestionsProps) {
|
|
11
|
-
const { items } = validateProps('Questions', questionsSchema, props)
|
|
11
|
+
const { title, items } = validateProps('Questions', questionsSchema, props)
|
|
12
12
|
return (
|
|
13
13
|
<section className='vp-questions'>
|
|
14
|
-
<div className='vp-
|
|
15
|
-
<IconHelpCircle size={16} stroke={2} className='vp-questions__icon' aria-hidden='true' />
|
|
16
|
-
<span className='vp-questions__title'>Open questions</span>
|
|
17
|
-
</div>
|
|
14
|
+
<div className='vp-questions__title'>{title}</div>
|
|
18
15
|
<ol className='vp-questions__list'>
|
|
19
16
|
{items.map((item, index) => (
|
|
20
17
|
<li key={item} className='vp-questions__item'>
|
package/runtime/theme.css
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
--vp-border-strong: #d6d6d2;
|
|
10
10
|
--vp-accent: #1a1a1c;
|
|
11
11
|
--vp-on-accent: #fbfbfa;
|
|
12
|
+
--vp-link: #2f6fed;
|
|
12
13
|
--vp-done: #15803d;
|
|
13
14
|
--vp-modify: #b45309;
|
|
14
15
|
--vp-risk: #b91c1c;
|
|
@@ -21,12 +22,12 @@
|
|
|
21
22
|
--vp-question: #2f6fed;
|
|
22
23
|
--vp-question-tint: #eef3fe;
|
|
23
24
|
--vp-question-border: #d4e0fb;
|
|
24
|
-
--vp-note: #
|
|
25
|
-
--vp-note-tint: #
|
|
26
|
-
--vp-note-border: #
|
|
27
|
-
--vp-decision: #
|
|
28
|
-
--vp-decision-tint: #
|
|
29
|
-
--vp-decision-border: #
|
|
25
|
+
--vp-note: #2f6fed;
|
|
26
|
+
--vp-note-tint: #eef3fe;
|
|
27
|
+
--vp-note-border: #d4e0fb;
|
|
28
|
+
--vp-decision: #7c3aed;
|
|
29
|
+
--vp-decision-tint: #f6f2fe;
|
|
30
|
+
--vp-decision-border: #e4d8fb;
|
|
30
31
|
--vp-ease: cubic-bezier(0.25, 1, 0.5, 1);
|
|
31
32
|
--vp-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
32
33
|
--vp-mono: "SF Mono", ui-monospace, "JetBrains Mono", Menlo, Consolas, monospace;
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
--vp-border-strong: #3a3a3f;
|
|
45
46
|
--vp-accent: #e9e9e6;
|
|
46
47
|
--vp-on-accent: #161618;
|
|
48
|
+
--vp-link: #7aa2ff;
|
|
47
49
|
--vp-done: #45c97a;
|
|
48
50
|
--vp-modify: #d99a3a;
|
|
49
51
|
--vp-risk: #ef8f8f;
|
|
@@ -56,12 +58,12 @@
|
|
|
56
58
|
--vp-question: #7aa2ff;
|
|
57
59
|
--vp-question-tint: #161d2e;
|
|
58
60
|
--vp-question-border: #2b3a5e;
|
|
59
|
-
--vp-note: #
|
|
60
|
-
--vp-note-tint: #
|
|
61
|
-
--vp-note-border: #
|
|
62
|
-
--vp-decision: #
|
|
63
|
-
--vp-decision-tint: #
|
|
64
|
-
--vp-decision-border: #
|
|
61
|
+
--vp-note: #7aa2ff;
|
|
62
|
+
--vp-note-tint: #161d2e;
|
|
63
|
+
--vp-note-border: #2b3a5e;
|
|
64
|
+
--vp-decision: #b794f6;
|
|
65
|
+
--vp-decision-tint: #1d1830;
|
|
66
|
+
--vp-decision-border: #362a52;
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -118,11 +120,54 @@ body {
|
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
.vp-main a {
|
|
121
|
-
color: var(--vp-
|
|
122
|
-
text-decoration-color: var(--vp-
|
|
123
|
+
color: var(--vp-link);
|
|
124
|
+
text-decoration-color: var(--vp-link);
|
|
125
|
+
text-decoration-thickness: 1px;
|
|
123
126
|
text-underline-offset: 2px;
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
/* GFM tables: row-line style (no outer box, no zebra), header in muted. */
|
|
130
|
+
.vp-main table {
|
|
131
|
+
width: 100%;
|
|
132
|
+
border-collapse: collapse;
|
|
133
|
+
margin: 1.25rem 0;
|
|
134
|
+
font-size: 0.9rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.vp-main th,
|
|
138
|
+
.vp-main td {
|
|
139
|
+
padding: 0.5rem 0.85rem;
|
|
140
|
+
text-align: left;
|
|
141
|
+
vertical-align: top;
|
|
142
|
+
border-bottom: 1px solid var(--vp-border);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.vp-main thead th {
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
color: var(--vp-muted);
|
|
148
|
+
border-bottom-color: var(--vp-border-strong);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.vp-main tbody tr:last-child td {
|
|
152
|
+
border-bottom: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Blockquote: a neutral (non-semantic) quote, not a colored callout. */
|
|
156
|
+
.vp-main blockquote {
|
|
157
|
+
margin: 1.25rem 0;
|
|
158
|
+
padding: 0.1rem 0 0.1rem 1rem;
|
|
159
|
+
border-left: 2px solid var(--vp-border-strong);
|
|
160
|
+
color: var(--vp-muted);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.vp-main blockquote :first-child {
|
|
164
|
+
margin-top: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.vp-main blockquote :last-child {
|
|
168
|
+
margin-bottom: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
126
171
|
:focus-visible {
|
|
127
172
|
outline: 2px solid var(--vp-accent);
|
|
128
173
|
outline-offset: 2px;
|
|
@@ -233,9 +278,10 @@ body {
|
|
|
233
278
|
|
|
234
279
|
.vp-phase__content {
|
|
235
280
|
min-width: 0;
|
|
236
|
-
padding-top: 0.15rem;
|
|
237
281
|
}
|
|
238
282
|
|
|
283
|
+
/* The head matches the node height and centers its content, so the title sits on the
|
|
284
|
+
node's center line (both columns start at the row top). */
|
|
239
285
|
.vp-phase__head {
|
|
240
286
|
display: flex;
|
|
241
287
|
align-items: center;
|
|
@@ -448,7 +494,7 @@ body {
|
|
|
448
494
|
}
|
|
449
495
|
|
|
450
496
|
/* Questions: open questions for the reader. Neutral card like the other list cards,
|
|
451
|
-
with blue kept only as the accent on the
|
|
497
|
+
with blue kept only as the accent on the item numbers. */
|
|
452
498
|
.vp-questions {
|
|
453
499
|
border: 1px solid var(--vp-border);
|
|
454
500
|
background: var(--vp-surface);
|
|
@@ -457,28 +503,14 @@ body {
|
|
|
457
503
|
margin: 1.4rem 0;
|
|
458
504
|
}
|
|
459
505
|
|
|
460
|
-
.
|
|
461
|
-
display: flex;
|
|
462
|
-
align-items: center;
|
|
463
|
-
gap: 0.6rem;
|
|
464
|
-
margin-bottom: 0.6rem;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/* The header icon shares a fixed-width column with the item numbers below, so the
|
|
468
|
-
title and every question text line up on the same left edge. */
|
|
469
|
-
.vp-questions__icon {
|
|
470
|
-
flex: 0 0 auto;
|
|
471
|
-
width: 1.15rem;
|
|
472
|
-
display: inline-flex;
|
|
473
|
-
align-items: center;
|
|
474
|
-
justify-content: center;
|
|
475
|
-
color: var(--vp-question);
|
|
476
|
-
}
|
|
477
|
-
|
|
506
|
+
/* Small uppercase label, identical to the Checklist title. */
|
|
478
507
|
.vp-questions__title {
|
|
508
|
+
font-size: 0.68rem;
|
|
479
509
|
font-weight: 600;
|
|
480
|
-
|
|
481
|
-
|
|
510
|
+
text-transform: uppercase;
|
|
511
|
+
letter-spacing: 0.05em;
|
|
512
|
+
color: var(--vp-muted);
|
|
513
|
+
margin-bottom: 0.55rem;
|
|
482
514
|
}
|
|
483
515
|
|
|
484
516
|
.vp-questions__list {
|
|
@@ -513,7 +545,10 @@ body {
|
|
|
513
545
|
/* Compare */
|
|
514
546
|
.vp-compare {
|
|
515
547
|
display: grid;
|
|
516
|
-
|
|
548
|
+
/* Cards stack once the column is too narrow for two comfortably (narrow windows),
|
|
549
|
+
while keeping the desktop multi-column layout. min(100%, ...) avoids overflow
|
|
550
|
+
on ultra-narrow screens. */
|
|
551
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 15.5rem), 1fr));
|
|
517
552
|
gap: 0.85rem;
|
|
518
553
|
margin: 1.25rem 0;
|
|
519
554
|
}
|
|
@@ -567,15 +602,19 @@ body {
|
|
|
567
602
|
margin: 0.25rem 0;
|
|
568
603
|
}
|
|
569
604
|
|
|
570
|
-
|
|
605
|
+
/* Center the 15px icon on the first text line (same technique as the Checklist),
|
|
606
|
+
so it stays aligned whether the item is one line or wraps to several. */
|
|
607
|
+
.vp-compare__pro,
|
|
608
|
+
.vp-compare__con {
|
|
571
609
|
flex: 0 0 auto;
|
|
572
|
-
margin-top:
|
|
610
|
+
margin-top: calc((1lh - 15px) / 2);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.vp-compare__pro {
|
|
573
614
|
color: var(--vp-done);
|
|
574
615
|
}
|
|
575
616
|
|
|
576
617
|
.vp-compare__con {
|
|
577
|
-
flex: 0 0 auto;
|
|
578
|
-
margin-top: 0.12rem;
|
|
579
618
|
color: var(--vp-risk);
|
|
580
619
|
}
|
|
581
620
|
|