konpeki 0.3.0 → 0.4.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/AGENTS.md +21 -28
- package/AUTHORING.md +68 -229
- package/CONTRIBUTING.md +10 -23
- package/README.md +96 -87
- package/SETUP.md +50 -114
- package/docs/development.md +43 -248
- package/docs/workflow.md +6 -108
- package/html/README.md +140 -0
- package/html/browser.ts +103 -0
- package/html/document.ts +38 -0
- package/html/floor.ts +34 -0
- package/html/index.html +5 -0
- package/html/inspect.ts +313 -0
- package/html/preview.css +62 -0
- package/html/preview.tsx +463 -0
- package/html/review-hints.ts +63 -0
- package/html/server.ts +99 -0
- package/html/source.ts +104 -0
- package/html/starter.ts +6 -0
- package/html/theme-authoring.md +176 -0
- package/html/theme.md +61 -0
- package/index.html +9 -9
- package/package.json +21 -35
- package/plugin.json +1 -1
- package/public/og.png +0 -0
- package/runtime/browser-B-29TH1a.mjs +595 -0
- package/runtime/floor-Cmk7G3pU.mjs +41 -0
- package/runtime/konpeki.mjs +90 -1410
- package/runtime/server-BAqC_5WD.mjs +2 -0
- package/runtime/server-DfRpfcY9.mjs +205 -0
- package/runtime/source-B_Ui9tMp.mjs +141 -0
- package/runtime/source-CZQq9GUO.mjs +2 -0
- package/skills/konpeki/SKILL.md +88 -172
- package/skills/konpeki/assets/blank.html +17 -0
- package/skills/konpeki/floor.md +76 -0
- package/skills/konpeki/references/cover.md +27 -0
- package/skills/konpeki/references/long-document.md +40 -0
- package/skills/konpeki/references/one-pager.md +27 -0
- package/skills/konpeki/references/patterns.md +118 -0
- package/skills/konpeki/references/resume.md +28 -0
- package/skills/konpeki/references/slides.md +28 -0
- package/skills/konpeki/scripts/ensure-runtime.mjs +10 -31
- package/skills/konpeki/scripts/prepare-document.mjs +25 -14
- package/src/components/PageBoard.tsx +156 -0
- package/src/lib/alignment.ts +21 -0
- package/src/lib/page-board.ts +25 -0
- package/src/lib/review-position.ts +19 -0
- package/src/styles/base.css +4 -6
- package/src/styles/feedback.css +97 -61
- package/src/styles/shell.css +95 -323
- package/theme-base.css +90 -0
- package/theme.css +56 -0
- package/vite.config.ts +2 -5
- package/composition/README.md +0 -156
- package/composition/compile.ts +0 -227
- package/composition/document.ts +0 -600
- package/composition/schema.json +0 -3001
- package/composition/schema.ts +0 -437
- package/composition/theme-tokens.ts +0 -16
- package/composition/types.ts +0 -269
- package/composition/validate.ts +0 -226
- package/composition/vector.ts +0 -143
- package/composition/visualizations.ts +0 -319
- package/design/README.md +0 -17
- package/design/palettes/README.md +0 -14
- package/design/palettes/base.ts +0 -14
- package/design/palettes/candidates.ts +0 -19
- package/design/palettes/index.ts +0 -78
- package/design/review/color-theme.md +0 -44
- package/design/review/layout.md +0 -16
- package/design/review/text.md +0 -18
- package/design/review/typography.md +0 -15
- package/design/review/visuals.md +0 -31
- package/design/semantic-patterns.md +0 -43
- package/design/themes/README.md +0 -40
- package/design/themes/index.ts +0 -24
- package/design/visual-languages/technical-product.md +0 -17
- package/design/visual-review.md +0 -88
- package/lib/assets.d.ts +0 -8
- package/lib/charts.ts +0 -18
- package/lib/contrast.ts +0 -16
- package/lib/layouts.ts +0 -50
- package/lib/slide.tsx +0 -42
- package/lib/taste.ts +0 -17
- package/lib/text.tsx +0 -89
- package/lib/typeface.ts +0 -44
- package/scripts/migrate-react-page.ts +0 -120
- package/skills/konpeki/assets/blank.json +0 -23
- package/slides/README.md +0 -153
- package/slides/architecture/PROMPT.md +0 -31
- package/slides/architecture/index.tsx +0 -102
- package/slides/article-brief/PROMPT.md +0 -35
- package/slides/article-brief/index.tsx +0 -71
- package/slides/bar-chart/PROMPT.md +0 -39
- package/slides/bar-chart/index.tsx +0 -97
- package/slides/comparison/PROMPT.md +0 -29
- package/slides/comparison/index.tsx +0 -95
- package/slides/decision-memo/PROMPT.md +0 -34
- package/slides/decision-memo/index.tsx +0 -85
- package/slides/delivery-plan/PROMPT.md +0 -45
- package/slides/delivery-plan/index.tsx +0 -105
- package/slides/experiment/PROMPT.md +0 -44
- package/slides/experiment/index.tsx +0 -127
- package/slides/incident-workflow/PROMPT.md +0 -57
- package/slides/incident-workflow/index.tsx +0 -78
- package/slides/introducing-konpeki/PROMPT.md +0 -40
- package/slides/introducing-konpeki/README.md +0 -76
- package/slides/introducing-konpeki/SOURCE.md +0 -26
- package/slides/introducing-konpeki/author.ts +0 -165
- package/slides/introducing-konpeki/composition.json +0 -3270
- package/slides/line-chart/PROMPT.md +0 -40
- package/slides/line-chart/index.tsx +0 -72
- package/slides/migration/PROMPT.md +0 -38
- package/slides/migration/index.tsx +0 -89
- package/slides/og-images/PROMPT.md +0 -21
- package/slides/og-images/index.tsx +0 -76
- package/slides/product-introduction/PROMPT.md +0 -24
- package/slides/product-introduction/index.tsx +0 -105
- package/slides/research-brief/PROMPT.md +0 -40
- package/slides/research-brief/index.tsx +0 -104
- package/slides/results-explanation/PROMPT.md +0 -32
- package/slides/results-explanation/index.tsx +0 -96
- package/slides/retrospective/PROMPT.md +0 -43
- package/slides/retrospective/index.tsx +0 -105
- package/slides/sankey/PROMPT.md +0 -11
- package/slides/sankey/index.tsx +0 -93
- package/slides/teaching/PROMPT.md +0 -45
- package/slides/teaching/index.tsx +0 -124
- package/slides/vertical-bar-charts/PROMPT.md +0 -13
- package/slides/vertical-bar-charts/index.tsx +0 -97
- package/src/app/App.tsx +0 -820
- package/src/components/BuildOrb.tsx +0 -40
- package/src/components/Canvas.tsx +0 -1185
- package/src/components/DiagramTypeIcon.tsx +0 -78
- package/src/components/InspectorPanel.tsx +0 -773
- package/src/components/LeftPanel.tsx +0 -120
- package/src/components/PageSizePicker.tsx +0 -30
- package/src/components/Presentation.tsx +0 -105
- package/src/components/RevisionNotes.tsx +0 -56
- package/src/components/RightPanel.tsx +0 -201
- package/src/components/VectorOverflowWarning.tsx +0 -46
- package/src/components/WorkspaceChrome.tsx +0 -288
- package/src/components/ui.tsx +0 -53
- package/src/lib/examples/react-page-migration.json +0 -1295
- package/src/lib/examples.ts +0 -42
- package/src/lib/export-png.ts +0 -104
- package/src/lib/file-session.ts +0 -87
- package/src/lib/history.ts +0 -53
- package/src/lib/model.ts +0 -188
- package/src/lib/page-size.ts +0 -24
- package/src/lib/presentation.ts +0 -17
- package/src/lib/review.ts +0 -26
- package/src/lib/storage.ts +0 -71
- package/src/lib/theme.ts +0 -25
- package/src/lib/use-file-session.ts +0 -227
- package/src/main.tsx +0 -29
- package/src/styles/canvas.css +0 -299
- package/src/styles/chrome.css +0 -384
- package/src/styles/component-previews.css +0 -386
- package/src/styles/left-panel.css +0 -166
- package/src/styles/presentation.css +0 -72
- package/src/styles/right-panel.css +0 -1215
package/html/server.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { createServer as createHTTPServer } from "node:http";
|
|
3
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
4
|
+
import { dirname, extname, isAbsolute, relative, resolve } from "node:path";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { createServer } from "vite";
|
|
8
|
+
import { fileSource } from "./source.ts";
|
|
9
|
+
import { documentHTML, documentPolicy } from "./document.ts";
|
|
10
|
+
|
|
11
|
+
const types: Record<string, string> = { ".css": "text/css", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp", ".gif": "image/gif", ".svg": "image/svg+xml", ".woff": "font/woff", ".woff2": "font/woff2", ".ttf": "font/ttf", ".otf": "font/otf" };
|
|
12
|
+
export const sessionRoute = "/__konpeki/html";
|
|
13
|
+
|
|
14
|
+
/** Same authenticated document/asset surface for preview and browser exports. */
|
|
15
|
+
export function htmlSession(path: string, token = randomBytes(24).toString("base64url")) {
|
|
16
|
+
const store = fileSource(resolve(path));
|
|
17
|
+
const prefix = `${sessionRoute}/${token}`;
|
|
18
|
+
function send(res: ServerResponse, status: number, value: unknown) {
|
|
19
|
+
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" });
|
|
20
|
+
res.end(JSON.stringify(value));
|
|
21
|
+
}
|
|
22
|
+
async function handler(req: IncomingMessage, res: ServerResponse, next: () => void) {
|
|
23
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
24
|
+
if (!url.pathname.startsWith(sessionRoute)) return next();
|
|
25
|
+
try {
|
|
26
|
+
if (url.pathname === sessionRoute) {
|
|
27
|
+
if (req.headers["x-konpeki-session"] !== token) return send(res, 403, { error: "Invalid preview session." });
|
|
28
|
+
if (req.method === "GET") return send(res, 200, await store.read());
|
|
29
|
+
if (req.method !== "PATCH") return send(res, 405, { error: "Method not allowed." });
|
|
30
|
+
let size = 0; const chunks: Buffer[] = [];
|
|
31
|
+
for await (const chunk of req) {
|
|
32
|
+
size += chunk.length;
|
|
33
|
+
if (size > 16384) throw Object.assign(new Error("Correction is too large."), { status: 413 });
|
|
34
|
+
chunks.push(chunk);
|
|
35
|
+
}
|
|
36
|
+
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
37
|
+
if (!body || typeof body.revision !== "string" || !body.edit || !["move", "delete", "undo"].includes(body.edit.kind))
|
|
38
|
+
return send(res, 400, { error: "Invalid correction." });
|
|
39
|
+
return send(res, 200, await store.edit(body.revision, body.edit));
|
|
40
|
+
}
|
|
41
|
+
if (!url.pathname.startsWith(`${prefix}/`)) return send(res, 403, { error: "Invalid preview session." });
|
|
42
|
+
if (req.method !== "GET") return send(res, 405, { error: "Method not allowed." });
|
|
43
|
+
if (url.pathname === `${prefix}/document/`) {
|
|
44
|
+
const snapshot = await store.read();
|
|
45
|
+
if (url.searchParams.has("revision") && url.searchParams.get("revision") !== snapshot.revision)
|
|
46
|
+
return send(res, 409, { error: "The source changed. Retry with its latest revision." });
|
|
47
|
+
const page = url.searchParams.get("page") ?? snapshot.pages[0];
|
|
48
|
+
if (!snapshot.pages.includes(page)) return send(res, 404, { error: "Unknown page." });
|
|
49
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": documentPolicy, "Cache-Control": "no-store", "Referrer-Policy": "no-referrer", "X-Content-Type-Options": "nosniff" });
|
|
50
|
+
return res.end(documentHTML(snapshot.source, page));
|
|
51
|
+
}
|
|
52
|
+
const assets = `${prefix}/document/`;
|
|
53
|
+
if (!url.pathname.startsWith(assets)) return send(res, 404, { error: "Unknown resource." });
|
|
54
|
+
const name = decodeURIComponent(url.pathname.slice(assets.length));
|
|
55
|
+
const type = types[extname(name).toLowerCase()];
|
|
56
|
+
if (!type || name.split(/[\\/]/).some(part => part.startsWith("."))) return send(res, 403, { error: "Only local image, font and CSS assets are served." });
|
|
57
|
+
let base = await realpath(dirname(resolve(path))), file: string;
|
|
58
|
+
try { file = await realpath(resolve(base, name)); }
|
|
59
|
+
catch (error) {
|
|
60
|
+
// A linked default theme is available in previews and fixtures. Copies
|
|
61
|
+
// made by prepare-document carry these assets beside the HTML instead.
|
|
62
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT" || !/^theme(?:-base)?\.css$/.test(name)) throw error;
|
|
63
|
+
base = fileURLToPath(new URL("../", import.meta.url));
|
|
64
|
+
file = await realpath(resolve(base, name));
|
|
65
|
+
}
|
|
66
|
+
const within = relative(base, file);
|
|
67
|
+
if (isAbsolute(within) || within.startsWith("..")) return send(res, 403, { error: "Asset is outside the document directory." });
|
|
68
|
+
const bytes = await readFile(file);
|
|
69
|
+
res.writeHead(200, { "Content-Type": type, "Cache-Control": "no-store", "Content-Security-Policy": documentPolicy, "X-Content-Type-Options": "nosniff" });
|
|
70
|
+
res.end(bytes);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
const status = error instanceof SyntaxError || error instanceof URIError ? 400 : (error as { code?: string }).code === "ENOENT" ? 404 : Number((error as { status?: number }).status ?? 500);
|
|
73
|
+
send(res, status, { error: error instanceof Error ? error.message : "Preview failed." });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { token, prefix, store, handler };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function documentServer(path: string) {
|
|
80
|
+
const session = htmlSession(path);
|
|
81
|
+
await session.store.read();
|
|
82
|
+
const server = createHTTPServer((req, res) => void session.handler(req, res, () => { res.writeHead(404); res.end(); }));
|
|
83
|
+
await new Promise<void>((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", () => resolve()); });
|
|
84
|
+
const address = server.address() as { port: number };
|
|
85
|
+
return { ...session, origin: `http://127.0.0.1:${address.port}`, close: () => new Promise<void>((resolve, reject) => server.close(e => e ? reject(e) : resolve())) };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function previewHTML(path: string, host: string, port: number, root: string, strictPort = false) {
|
|
89
|
+
const session = htmlSession(path);
|
|
90
|
+
await session.store.read();
|
|
91
|
+
const server = await createServer({ root, configFile: false, logLevel: "silent", server: {
|
|
92
|
+
host, port, strictPort,
|
|
93
|
+
fs: { allow: [root, dirname(fileURLToPath(import.meta.resolve("@fontsource/ibm-plex-sans/package.json")))], deny: [".env", ".env.*", "*.{crt,pem,key,p12,pfx}", ".npmrc", "**/.git/**"] },
|
|
94
|
+
}, plugins: [{ name: "konpeki-html", configureServer(server) { server.middlewares.use((req, res, next) => void session.handler(req, res, next)); } }] });
|
|
95
|
+
try { await server.listen(); } catch (e) { await server.close(); throw e; }
|
|
96
|
+
const address = server.httpServer!.address() as { port: number };
|
|
97
|
+
const displayHost = host === "0.0.0.0" || host === "::" ? "localhost" : host;
|
|
98
|
+
return { server, url: `http://${displayHost.includes(":") ? `[${displayHost}]` : displayHost}:${address.port}/html/?session=${session.token}` };
|
|
99
|
+
}
|
package/html/source.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { readFile, writeFile, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { parse, type DefaultTreeAdapterMap } from "parse5";
|
|
5
|
+
import postcss from "postcss";
|
|
6
|
+
|
|
7
|
+
type Element = DefaultTreeAdapterMap["element"];
|
|
8
|
+
export type Edit = { page: string; id: string } & ({ kind: "move"; translate: string } | { kind: "delete" });
|
|
9
|
+
export const revision = (source: string) => createHash("sha256").update(source).digest("hex");
|
|
10
|
+
const attr = (node: Element, name: string) => node.attrs.find(a => a.name === name)?.value;
|
|
11
|
+
function fail(message: string, status = 422): never { throw Object.assign(new Error(message), { status }); }
|
|
12
|
+
|
|
13
|
+
export function inspectSource(source: string) {
|
|
14
|
+
const tree = parse(source, { sourceCodeLocationInfo: true, onParseError(error) {
|
|
15
|
+
if (error.code === "duplicate-attribute") fail("Duplicate HTML attributes are ambiguous; remove them before editing.");
|
|
16
|
+
} });
|
|
17
|
+
const elements: Element[] = [];
|
|
18
|
+
function visit(node: DefaultTreeAdapterMap["node"]) {
|
|
19
|
+
if ("tagName" in node) elements.push(node);
|
|
20
|
+
if ("childNodes" in node) node.childNodes.forEach(visit);
|
|
21
|
+
}
|
|
22
|
+
visit(tree);
|
|
23
|
+
const pages = elements.filter(node => attr(node, "data-page") !== undefined);
|
|
24
|
+
if (!pages.length) fail("Add an id and data-page to each page element.");
|
|
25
|
+
const ids = new Set<string>();
|
|
26
|
+
for (const node of elements) {
|
|
27
|
+
if (["script", "iframe", "object", "embed", "base", "template", "form", "input", "button", "textarea", "select", "animate", "animatetransform", "set"].includes(node.tagName.toLowerCase()) ||
|
|
28
|
+
node.tagName === "meta" && attr(node, "http-equiv") !== undefined ||
|
|
29
|
+
node.attrs.some(a => /^on/i.test(a.name) || ["href", "src", "action"].includes(a.name) && /^\s*javascript:/i.test(a.value)))
|
|
30
|
+
fail("Documents must be static HTML/CSS and inline SVG, without scripts, event handlers, embedded applications or controls.");
|
|
31
|
+
const id = attr(node, "id");
|
|
32
|
+
if (id !== undefined && (!id || ids.has(id))) fail("Element IDs must be nonempty and unique within the HTML file.");
|
|
33
|
+
if (id) ids.add(id);
|
|
34
|
+
}
|
|
35
|
+
for (const page of pages) {
|
|
36
|
+
if (!attr(page, "id") || page.parentNode?.nodeName !== "body")
|
|
37
|
+
fail("Pages need an id and must be direct children of body.");
|
|
38
|
+
}
|
|
39
|
+
return { pages, elements };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function patchSource(source: string, edit: Edit) {
|
|
43
|
+
const { pages, elements } = inspectSource(source);
|
|
44
|
+
const page = pages.find(node => attr(node, "id") === edit.page);
|
|
45
|
+
const node = elements.find(node => attr(node, "id") === edit.id);
|
|
46
|
+
if (!page || !node) fail("The selected page or element no longer exists.");
|
|
47
|
+
let ancestor: DefaultTreeAdapterMap["node"] | undefined = node;
|
|
48
|
+
while (ancestor && ancestor !== page) ancestor = "parentNode" in ancestor ? ancestor.parentNode ?? undefined : undefined;
|
|
49
|
+
if (!ancestor || node === page) fail("Select an element inside the page.");
|
|
50
|
+
const location = node.sourceCodeLocation;
|
|
51
|
+
if (!location?.startTag) fail("This element does not have an unambiguous source location.");
|
|
52
|
+
if (edit.kind === "delete") return source.slice(0, location.startOffset) + source.slice(location.endOffset);
|
|
53
|
+
if (edit.kind !== "move" || typeof edit.translate !== "string" || !/^-?\d+(?:\.\d+)?px -?\d+(?:\.\d+)?px$/.test(edit.translate) || edit.translate.length > 100)
|
|
54
|
+
fail("Invalid correction.");
|
|
55
|
+
// Parse the authored CSS, not CSSOM's normalized serialization. PostCSS keeps
|
|
56
|
+
// unrelated declaration values, comments and whitespace, including hex colors.
|
|
57
|
+
// Inline styles have no source maps; never follow authored sourceMappingURLs.
|
|
58
|
+
let css;
|
|
59
|
+
try { css = postcss.parse(attr(node, "style") ?? "", { map: false }); }
|
|
60
|
+
catch { fail("The inline style could not be parsed. Correct it in source before moving."); }
|
|
61
|
+
const declarations = css.nodes.filter(n => n.type === "decl" && n.prop.toLowerCase() === "translate");
|
|
62
|
+
for (const declaration of declarations) if (declaration.type === "decl") declaration.value = edit.translate;
|
|
63
|
+
if (!declarations.length) css.append({ prop: "translate", value: edit.translate });
|
|
64
|
+
css.raws.semicolon = true;
|
|
65
|
+
const escaped = css.toString().replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<");
|
|
66
|
+
const style = location.attrs?.style;
|
|
67
|
+
const start = style?.startOffset ?? location.startTag.endOffset - (source[location.startTag.endOffset - 2] === "/" ? 2 : 1);
|
|
68
|
+
const end = style?.endOffset ?? start;
|
|
69
|
+
return source.slice(0, start) + `${style ? "" : " "}style="${escaped}"` + source.slice(end);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function fileSource(path: string) {
|
|
73
|
+
path = resolve(path);
|
|
74
|
+
let queue: Promise<unknown> = Promise.resolve();
|
|
75
|
+
const history: { before: string; after: string }[] = [];
|
|
76
|
+
async function read() {
|
|
77
|
+
const source = await readFile(path, "utf8");
|
|
78
|
+
const { pages } = inspectSource(source);
|
|
79
|
+
return { source, revision: revision(source), name: basename(path), path, key: revision(path), pages: pages.map(p => attr(p, "id")!) };
|
|
80
|
+
}
|
|
81
|
+
function edit(expected: string, change: Edit | { kind: "undo" }) {
|
|
82
|
+
const result = queue.then(async () => {
|
|
83
|
+
const current = await read();
|
|
84
|
+
if (current.revision !== expected) fail("Source changed outside this preview. Reloaded the latest file; retry your correction.", 409);
|
|
85
|
+
const previous = history.at(-1);
|
|
86
|
+
if (change.kind === "undo" && (!previous || previous.after !== current.revision))
|
|
87
|
+
fail("Nothing safe to undo: the source may have changed externally.", 409);
|
|
88
|
+
const next = change.kind === "undo" ? previous!.before : patchSource(current.source, change);
|
|
89
|
+
inspectSource(next);
|
|
90
|
+
const temporary = join(dirname(path), `.${basename(path)}.${randomBytes(8).toString("hex")}.tmp`);
|
|
91
|
+
try {
|
|
92
|
+
await writeFile(temporary, next, { flag: "wx" });
|
|
93
|
+
if (revision(await readFile(path, "utf8")) !== current.revision) fail("Source changed while saving. Retry your correction.", 409);
|
|
94
|
+
await rename(temporary, path);
|
|
95
|
+
} finally { await rm(temporary, { force: true }); }
|
|
96
|
+
if (change.kind === "undo") history.pop();
|
|
97
|
+
else { history.push({ before: current.source, after: revision(next) }); if (history.length > 30) history.shift(); }
|
|
98
|
+
return read();
|
|
99
|
+
});
|
|
100
|
+
queue = result.catch(() => {});
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
return { read, edit };
|
|
104
|
+
}
|
package/html/starter.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Author a theme: contract v1
|
|
2
|
+
|
|
3
|
+
A theme is a local CSS entry point plus its fonts and other licensed assets.
|
|
4
|
+
Documents use the names below rather than depending on a particular palette or
|
|
5
|
+
font. Replace `theme.css` to change the visual treatment without rewriting HTML.
|
|
6
|
+
Different font metrics can still change wrapping and fit: inspect every page
|
|
7
|
+
after a swap.
|
|
8
|
+
|
|
9
|
+
Every public theme custom property uses the `--kp-` prefix. A compatible theme
|
|
10
|
+
entry point sets `--kp-theme: 1` on `:root`; the shared base intentionally does
|
|
11
|
+
not set this marker, so loading `theme-base.css` alone does not opt into theme
|
|
12
|
+
checks.
|
|
13
|
+
|
|
14
|
+
`theme.css` supplies reusable appearance. [AUTHORING.md](../AUTHORING.md) and
|
|
15
|
+
design skills supply judgment: what to emphasize, how to arrange evidence, and
|
|
16
|
+
when a treatment is appropriate. Document CSS owns composition and data geometry.
|
|
17
|
+
|
|
18
|
+
Document authors only need [Use a theme](theme.md). Read this full contract when
|
|
19
|
+
creating or changing a stylesheet. A theme may carry short optional `NOTES.md`
|
|
20
|
+
beside its CSS for look-specific judgment; keep general authoring guidance in
|
|
21
|
+
AUTHORING.md. Notes do not add tokens or change the checks.
|
|
22
|
+
|
|
23
|
+
## Type roles
|
|
24
|
+
|
|
25
|
+
Every role has `--kp-font-ROLE`, `--kp-leading-ROLE`, `--kp-weight-ROLE`, and
|
|
26
|
+
`--kp-tracking-ROLE`. Sizes and leading resolve through custom properties to
|
|
27
|
+
literal positive pixel lengths such as `24px`; v1 does not accept `rem` or `calc()`
|
|
28
|
+
for these slots. Weights must have declared webfont faces; tracking uses `em` or `px`.
|
|
29
|
+
These names form complete treatments, not independent invitations to mix seven
|
|
30
|
+
sizes with seven line heights.
|
|
31
|
+
|
|
32
|
+
| Role / `data-type` | Semantic default | Family | Default color role |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| `fine` | Explicit hook only | `--kp-font-family` | `--kp-muted` |
|
|
35
|
+
| `caption` | `figcaption` | `--kp-font-family` | `--kp-muted` |
|
|
36
|
+
| `body` | Page text | `--kp-font-family` | `--kp-fg` |
|
|
37
|
+
| `lead` | `h3` | `--kp-font-family` | Inherited |
|
|
38
|
+
| `heading` | `h2` | `--kp-font-family-heading` | Inherited |
|
|
39
|
+
| `title` | `h1` | `--kp-font-family-heading` | `--kp-accent` |
|
|
40
|
+
| `display` | Explicit hook only | `--kp-font-family-display` | `--kp-accent` |
|
|
41
|
+
|
|
42
|
+
An explicit role replaces an element's semantic default: `h2 data-type="caption"`
|
|
43
|
+
uses the caption treatment, and remains an `h2`. An unmarked paragraph inherits
|
|
44
|
+
its context. `strong` uses `--kp-weight-strong`. Inline `code` keeps its context's
|
|
45
|
+
size and leading but uses `--kp-font-family-mono`, `--kp-weight-mono`, and the
|
|
46
|
+
accent. All four family slots are required; they may refer to the same face.
|
|
47
|
+
|
|
48
|
+
`inspect` reports the resolved `theme.type` for each page, including size, leading,
|
|
49
|
+
weight, tracking, and family. Use those measurements to plan line lengths without
|
|
50
|
+
opening CSS: Cobalt's link preset has body 32/44 and title 72/80 CSS px.
|
|
51
|
+
|
|
52
|
+
## Color roles
|
|
53
|
+
|
|
54
|
+
Konpeki's only bundled theme is cobalt blue on a light canvas, independent of OS
|
|
55
|
+
preference. Define every token below when adapting it for a brief.
|
|
56
|
+
|
|
57
|
+
| Tokens | Meaning |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `--kp-bg`, `--kp-fg`, `--kp-muted` | Canvas, primary ink, supporting ink |
|
|
60
|
+
| `--kp-surface` | Secondary surface |
|
|
61
|
+
| `--kp-line-subtle`, `--kp-line`, `--kp-line-strong` | Three boundary strengths |
|
|
62
|
+
| `--kp-contrast`, `--kp-on-contrast`, `--kp-on-contrast-muted` | Contrasting surface and its primary/supporting ink |
|
|
63
|
+
| `--kp-accent`, `--kp-wash`, `--kp-inverse` | Primary accent, tinted surface, ink for an accent field |
|
|
64
|
+
| `--kp-emphasis`, `--kp-emphasis-wash` | Secondary emphasis and its surface |
|
|
65
|
+
| `--kp-complete`, `--kp-attention`, `--kp-blocked` | Status, always paired with a non-color cue |
|
|
66
|
+
| `--kp-category-1` … `--kp-category-6` | Unordered identities; keep identity consistent across the document |
|
|
67
|
+
| `--kp-sequence-1` … `--kp-sequence-5` | Ordered amounts, from least to most emphasized |
|
|
68
|
+
|
|
69
|
+
Palette membership is not contrast approval. Check actual text/background pairs;
|
|
70
|
+
chart fills and subtle boundaries need not be suitable text colors. In particular,
|
|
71
|
+
`inverse` is ink for `accent`, not for every category or sequential fill. Pale
|
|
72
|
+
sequential steps and washes need direct labels or contrasting outlines when used
|
|
73
|
+
as chart marks. A contrasting surface owns its ink: set both its background and
|
|
74
|
+
its text colors in document CSS rather than inheriting the canvas's ink.
|
|
75
|
+
|
|
76
|
+
## Rhythm, shape, and output geometry
|
|
77
|
+
|
|
78
|
+
- `--kp-unit` is the theme's spacing unit. The base derives `--kp-space-1`, `-2`, `-3`,
|
|
79
|
+
`-4`, `-5`, `-6`, `-8`, `-10`, `-12`, and `-16` as multiples on each page.
|
|
80
|
+
Choose gaps by relationship.
|
|
81
|
+
- `--kp-rule-width`, `--kp-stroke`, and `--kp-stroke-heavy` describe rules, diagram
|
|
82
|
+
strokes, and heavy marks. `--kp-radius-small` and `--kp-radius` describe corners.
|
|
83
|
+
`hr` uses the rule width and `--kp-line`; other boundaries, strokes, and corners
|
|
84
|
+
are applied by document CSS. These tokens do not mandate panels or rounded pages.
|
|
85
|
+
- `--kp-page-width`, `--kp-page-height`, and `--kp-page-margin` describe the output canvas.
|
|
86
|
+
The shared [base stylesheet](../theme-base.css) provides these presets:
|
|
87
|
+
|
|
88
|
+
| `data-size` | Width × height | Default margin |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `presentation` or omitted | 1920 × 1080 px | 72 px |
|
|
91
|
+
| `portrait` | 1080 × 1350 px | 60 px |
|
|
92
|
+
| `square` | 1080 × 1080 px | 60 px |
|
|
93
|
+
| `link` | 1200 × 630 px | 48 px |
|
|
94
|
+
| `article` | 1600 × 600 px | 48 px |
|
|
95
|
+
| `a4` | 210 × 297 mm | 56 px |
|
|
96
|
+
| `explainer` | 1200 × 1600 px | 60 px |
|
|
97
|
+
| `gallery` | 1600 × 1000 px | 64 px |
|
|
98
|
+
|
|
99
|
+
The base also supplies the existing seven-step type sizes and leading per
|
|
100
|
+
preset, box sizing, margin resets, and role application. It does not choose page
|
|
101
|
+
layout. Theme authors may tune type steps and margins on `[data-page]` or
|
|
102
|
+
`[data-size="…"]` after the import; changing them on `:root` will not override
|
|
103
|
+
the page's own values. Keep preset dimensions stable across themes.
|
|
104
|
+
|
|
105
|
+
## Create a theme
|
|
106
|
+
|
|
107
|
+
When a brief brings its own look, adapt a copy of [`theme.css`](../theme.css).
|
|
108
|
+
Keep `theme-base.css` and any local font files and their licenses beside it.
|
|
109
|
+
Change token values and font declarations in the copy.
|
|
110
|
+
The HTML continues to link only `theme.css`; its first rule imports the local
|
|
111
|
+
base. Keep that import before font faces and other rules.
|
|
112
|
+
|
|
113
|
+
The default requests IBM Plex Sans normal 400/600/700, italic 400, and Plex Mono
|
|
114
|
+
normal 400 from Google Fonts. Preview and export need access to
|
|
115
|
+
`fonts.googleapis.com` and `fonts.gstatic.com`; remote updates may change font
|
|
116
|
+
metrics. Use licensed local files or embedded font data for offline or
|
|
117
|
+
reproducible rendering.
|
|
118
|
+
|
|
119
|
+
Supply additional subsets, weights, and styles when needed. Loading a font does
|
|
120
|
+
not prove it covers every glyph. Do not depend on a machine's installed fonts
|
|
121
|
+
or synthesized faces for required content. Font-load failures and installed-font
|
|
122
|
+
fallback remain export errors regardless of where the declared fonts are hosted.
|
|
123
|
+
|
|
124
|
+
## Verify a theme
|
|
125
|
+
|
|
126
|
+
Define every listed token, directly or through the shared base. Run `konpeki check`
|
|
127
|
+
on a specimen for each supported page size. Use all font families and weights in
|
|
128
|
+
the specimen to exercise their loading. The existing inspector validates the
|
|
129
|
+
active definition on each page, including unused tokens.
|
|
130
|
+
|
|
131
|
+
For example, start a specimen beside the theme and its assets with:
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<!doctype html>
|
|
135
|
+
<html lang="en">
|
|
136
|
+
<head><link rel="stylesheet" href="theme.css"></head>
|
|
137
|
+
<body>
|
|
138
|
+
<main id="specimen" data-page data-size="link">
|
|
139
|
+
<h1>Theme specimen</h1><p>Body, <strong>emphasis</strong>, and <code>code</code>.</p>
|
|
140
|
+
</main>
|
|
141
|
+
</body>
|
|
142
|
+
</html>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Theme diagnostics require `--kp-theme: 1`. Other versions warn and skip these
|
|
146
|
+
checks; a stylesheet without the marker does not opt in.
|
|
147
|
+
|
|
148
|
+
- **Errors:** missing required tokens, invalid colors or type treatments,
|
|
149
|
+
non-increasing type sizes, leading smaller than its size, and text-role contrast
|
|
150
|
+
below 4.5:1. Text pairs are `fg`/`muted` on `bg`/`surface`/`wash`, the two
|
|
151
|
+
`on-contrast` inks on `contrast`, `accent` on `bg`, and `inverse` on `accent`.
|
|
152
|
+
Each role, strong and mono need a declared normal face at their requested weight.
|
|
153
|
+
Declared weight ranges are supported. Used text must have its requested style
|
|
154
|
+
and weight when its primary family has font-face declarations.
|
|
155
|
+
- **Warnings:** category marks below 3:1 against `bg`, exact duplicate category
|
|
156
|
+
colors, unknown `data-type` roles, root type/page values shadowed on a page,
|
|
157
|
+
and used colors, size/leading pairs, nonzero CSS corner radii, border widths or
|
|
158
|
+
painted SVG stroke widths outside the active theme. Zero corners and SVG circle
|
|
159
|
+
radii are allowed: tokens do not require every box to be rounded or forbid
|
|
160
|
+
circular chart marks. SVG text checks size only, because coordinates own its
|
|
161
|
+
line placement. SVG `line` fill is ignored because it does not paint.
|
|
162
|
+
|
|
163
|
+
The CLI also audits actual glyph fonts through Chromium. Installed-font use is
|
|
164
|
+
an error, including undeclared families and missing glyphs that fall back to a
|
|
165
|
+
system font. This audit runs on unthemed content too. Browser preview cannot run
|
|
166
|
+
CDP and only checks declarations and loading; run CLI inspection before delivery.
|
|
167
|
+
Face coverage checks declarations, not font-binary metadata: do not mislabel a
|
|
168
|
+
static font as a weight range. Include every family, weight, style and script
|
|
169
|
+
used in a theme specimen to exercise the actual files.
|
|
170
|
+
|
|
171
|
+
Contrast checks cover opaque sRGB only. Duplicate detection is not a test for
|
|
172
|
+
perceptual distinguishability or color-vision accessibility. Inspection cannot
|
|
173
|
+
prove a value was written with `var()` or establish good composition. Line-height
|
|
174
|
+
overlap boxes are estimates, not glyph-ink measurements. Render and review every page, including wrapping, chart labels,
|
|
175
|
+
and meaning. Compatible alternatives need no `data-theme="custom"` opt-out;
|
|
176
|
+
reserve that for deliberately unthemed content.
|
package/html/theme.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Use a theme
|
|
2
|
+
|
|
3
|
+
Link `theme.css` and keep `theme-base.css` beside it. The default loads Google
|
|
4
|
+
Fonts and needs network access. A document may instead use local or embedded
|
|
5
|
+
fonts; keep any local files and their licenses beside it.
|
|
6
|
+
Use the tokens below without reading their CSS values. `inspect` reports resolved
|
|
7
|
+
type sizes, leading, weights and families under each page's `theme.type`.
|
|
8
|
+
Theme construction and diagnostics are in [Author a theme](theme-authoring.md).
|
|
9
|
+
|
|
10
|
+
## Type
|
|
11
|
+
|
|
12
|
+
Use semantic HTML. `data-type` changes appearance without changing reading order.
|
|
13
|
+
An explicit role replaces the semantic default; unmarked paragraphs inherit.
|
|
14
|
+
|
|
15
|
+
| `data-type` | Default | Purpose |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `fine` | Explicit only | Small supporting detail |
|
|
18
|
+
| `caption` | `figcaption` | Captions and sources |
|
|
19
|
+
| `body` | Page text | Main reading text |
|
|
20
|
+
| `lead` | `h3` | Lead or minor heading |
|
|
21
|
+
| `heading` | `h2` | Section heading |
|
|
22
|
+
| `title` | `h1` | Main headline |
|
|
23
|
+
| `display` | Explicit only | Large headline |
|
|
24
|
+
|
|
25
|
+
Custom SVG can use `--kp-font-ROLE`, `--kp-leading-ROLE`, `--kp-weight-ROLE` and
|
|
26
|
+
`--kp-tracking-ROLE`. Keep each size/leading pair together. Font slots are
|
|
27
|
+
`--kp-font-family`, `-heading`, `-display`, and `-mono`. `strong`, `code` and `em`
|
|
28
|
+
work directly; unsupported weights/styles and system-font fallback are errors.
|
|
29
|
+
|
|
30
|
+
## Color and shape
|
|
31
|
+
|
|
32
|
+
All names below start with `--kp-`:
|
|
33
|
+
|
|
34
|
+
| Tokens | Use |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `bg`, `fg`, `muted`, `surface` | Canvas, main/supporting ink, secondary surface |
|
|
37
|
+
| `accent`, `wash`, `inverse` | Accent, tint, ink on the accent |
|
|
38
|
+
| `contrast`, `on-contrast`, `on-contrast-muted` | Contrasting surface and its inks |
|
|
39
|
+
| `line-subtle`, `line`, `line-strong` | Boundary strengths |
|
|
40
|
+
| `emphasis`, `emphasis-wash` | Secondary emphasis and tint |
|
|
41
|
+
| `complete`, `attention`, `blocked` | Status, paired with a non-color cue |
|
|
42
|
+
| `category-1` … `-6`; `sequence-1` … `-5` | Identities; ordered amounts |
|
|
43
|
+
| `space-1`, `-2`, `-3`, `-4`, `-5`, `-6`, `-8`, `-10`, `-12`, `-16` | Gaps/padding by relationship |
|
|
44
|
+
| `rule-width`, `stroke`, `stroke-heavy`; `radius-small`, `radius` | Boundaries, strokes; corners |
|
|
45
|
+
|
|
46
|
+
Palette membership is not contrast approval. Set a contrasting surface's ink
|
|
47
|
+
explicitly. Pale chart marks need labels or outlines; `inverse` is not universal
|
|
48
|
+
ink for every fill. Document CSS owns layout and data geometry.
|
|
49
|
+
|
|
50
|
+
## Page presets
|
|
51
|
+
|
|
52
|
+
Set `data-size` on each `[data-page]`: `presentation` (default, 1920×1080),
|
|
53
|
+
`portrait` (1080×1350), `square` (1080×1080), `link` (1200×630), `article`
|
|
54
|
+
(1600×600), `a4` (210×297 mm), `explainer` (1200×1600), or `gallery` (1600×1000).
|
|
55
|
+
Dimensions otherwise use CSS px. `--kp-page-width`, `-height`, and `-margin`
|
|
56
|
+
describe the canvas. Override page/type sizes on `[data-page]`, not `:root`.
|
|
57
|
+
|
|
58
|
+
Keep theme changes separate from composition. Read a theme's optional `NOTES.md`
|
|
59
|
+
for its taste; general judgment stays in AUTHORING.md. After a swap, inspect and
|
|
60
|
+
render every page. Reserve `data-theme="custom"` for deliberately unthemed
|
|
61
|
+
content; it does not disable layout or font checks.
|
package/index.html
CHANGED
|
@@ -7,27 +7,27 @@
|
|
|
7
7
|
<link rel="icon" type="image/png" sizes="128x128" href="/src/assets/konpeki-mark.png" />
|
|
8
8
|
<meta
|
|
9
9
|
name="description"
|
|
10
|
-
content="
|
|
10
|
+
content="Review agent-authored HTML visuals. Select an element, leave a comment, and copy feedback for your agent. No account needed."
|
|
11
11
|
/>
|
|
12
12
|
<meta property="og:type" content="website" />
|
|
13
13
|
<meta property="og:site_name" content="Konpeki" />
|
|
14
|
-
<meta property="og:title" content="Konpeki —
|
|
15
|
-
<meta property="og:description" content="
|
|
16
|
-
<meta property="og:url" content="https://vcfgdev.github.io/konpeki
|
|
14
|
+
<meta property="og:title" content="Konpeki — Visual review" />
|
|
15
|
+
<meta property="og:description" content="Review agent-authored HTML visuals. Select an element, leave a comment, and copy feedback for your agent. No account needed." />
|
|
16
|
+
<meta property="og:url" content="https://vcfgdev.github.io/konpeki/" />
|
|
17
17
|
<meta property="og:image" content="https://vcfgdev.github.io/konpeki/og.png" />
|
|
18
18
|
<meta property="og:image:type" content="image/png" />
|
|
19
19
|
<meta property="og:image:width" content="1280" />
|
|
20
20
|
<meta property="og:image:height" content="640" />
|
|
21
|
-
<meta property="og:image:alt" content="
|
|
21
|
+
<meta property="og:image:alt" content="Create clear visuals with your coding agent. Blue brush mark on warm paper, with the konpeki wordmark below." />
|
|
22
22
|
<meta name="twitter:card" content="summary_large_image" />
|
|
23
|
-
<meta name="twitter:title" content="Konpeki —
|
|
24
|
-
<meta name="twitter:description" content="
|
|
23
|
+
<meta name="twitter:title" content="Konpeki — Visual review" />
|
|
24
|
+
<meta name="twitter:description" content="Review agent-authored HTML visuals. Select an element, leave a comment, and copy feedback for your agent. No account needed." />
|
|
25
25
|
<meta name="twitter:image" content="https://vcfgdev.github.io/konpeki/og.png" />
|
|
26
|
-
<meta name="twitter:image:alt" content="
|
|
26
|
+
<meta name="twitter:image:alt" content="Create clear visuals with your coding agent. Blue brush mark on warm paper, with the konpeki wordmark below." />
|
|
27
27
|
<title>Konpeki</title>
|
|
28
28
|
</head>
|
|
29
29
|
<body>
|
|
30
30
|
<div id="root"></div>
|
|
31
|
-
<script type="module" src="/
|
|
31
|
+
<script type="module" src="/html/preview.tsx"></script>
|
|
32
32
|
</body>
|
|
33
33
|
</html>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "konpeki",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Agent-first HTML visual authoring and review",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -23,33 +23,27 @@
|
|
|
23
23
|
"CONTRIBUTING.md",
|
|
24
24
|
"SECURITY.md",
|
|
25
25
|
"SETUP.md",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"theme.css",
|
|
29
|
+
"theme-base.css",
|
|
26
30
|
"docs/workflow.md",
|
|
27
31
|
"docs/development.md",
|
|
28
32
|
"plugin.json",
|
|
29
33
|
"skills/konpeki/SKILL.md",
|
|
34
|
+
"skills/konpeki/floor.md",
|
|
35
|
+
"skills/konpeki/references/*.md",
|
|
30
36
|
"skills/konpeki/scripts/*.mjs",
|
|
31
|
-
"skills/konpeki/assets/blank.
|
|
32
|
-
"
|
|
37
|
+
"skills/konpeki/assets/blank.html",
|
|
38
|
+
"html/*.{ts,tsx,css,html,md}",
|
|
39
|
+
"runtime/**/*.{mjs,js}",
|
|
33
40
|
"index.html",
|
|
34
41
|
"vite.config.ts",
|
|
35
42
|
"public/og.png",
|
|
36
|
-
"src/
|
|
37
|
-
"src/
|
|
38
|
-
"src/
|
|
39
|
-
"src/lib/*.ts",
|
|
40
|
-
"src/lib/examples/react-page-migration.json",
|
|
41
|
-
"src/styles/*.css",
|
|
43
|
+
"src/components/PageBoard.tsx",
|
|
44
|
+
"src/lib/{page-board,alignment,review-position}.ts",
|
|
45
|
+
"src/styles/{base,shell,feedback}.css",
|
|
42
46
|
"src/assets/konpeki-mark.png",
|
|
43
|
-
"composition/{README.md,compile.ts,document.ts,schema.json,schema.ts,theme-tokens.ts,types.ts,validate.ts,vector.ts,visualizations.ts}",
|
|
44
|
-
"design/*.md",
|
|
45
|
-
"design/{palettes,themes}/*.{md,ts}",
|
|
46
|
-
"design/review/*.md",
|
|
47
|
-
"design/visual-languages/technical-product.md",
|
|
48
|
-
"lib/{assets.d.ts,charts.ts,contrast.ts,layouts.ts,slide.tsx,taste.ts,text.tsx,typeface.ts}",
|
|
49
|
-
"scripts/migrate-react-page.ts",
|
|
50
|
-
"slides/README.md",
|
|
51
|
-
"slides/{architecture,article-brief,bar-chart,comparison,decision-memo,delivery-plan,experiment,incident-workflow,line-chart,migration,og-images,product-introduction,research-brief,results-explanation,retrospective,sankey,teaching,vertical-bar-charts}/{PROMPT.md,index.tsx}",
|
|
52
|
-
"slides/introducing-konpeki/{README.md,PROMPT.md,SOURCE.md,composition.json,author.ts}",
|
|
53
47
|
"!**/*.test.ts"
|
|
54
48
|
],
|
|
55
49
|
"engines": {
|
|
@@ -61,31 +55,23 @@
|
|
|
61
55
|
"prepack": "node scripts/build-cli.mjs",
|
|
62
56
|
"preview": "vite preview --host 0.0.0.0 --port 4318",
|
|
63
57
|
"check": "tsc --noEmit",
|
|
64
|
-
"test": "node --test
|
|
58
|
+
"test": "node --test html/*.test.ts src/lib/*.test.ts scripts/*.test.mjs",
|
|
65
59
|
"check:package": "node scripts/check-package.mjs",
|
|
66
|
-
"konpeki": "node bin/konpeki.mjs"
|
|
67
|
-
"composition:generate": "node composition/generate.ts",
|
|
68
|
-
"example:migrate-page": "node scripts/migrate-react-page.ts"
|
|
60
|
+
"konpeki": "node bin/konpeki.mjs"
|
|
69
61
|
},
|
|
70
62
|
"dependencies": {
|
|
71
|
-
"@fontsource/hanken-grotesk": "5.3.0",
|
|
72
63
|
"@fontsource/ibm-plex-sans": "5.3.0",
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"@nivo/line": "0.99.0",
|
|
78
|
-
"@nivo/sankey": "0.99.0",
|
|
79
|
-
"@xmldom/xmldom": "0.9.12",
|
|
80
|
-
"ajv": "^8.20.0",
|
|
81
|
-
"proper-lockfile": "4.1.2",
|
|
64
|
+
"parse5": "8.0.0",
|
|
65
|
+
"pdf-lib": "1.17.1",
|
|
66
|
+
"playwright": "1.63.0",
|
|
67
|
+
"postcss": "8.5.28",
|
|
82
68
|
"react": "19.2.8",
|
|
83
69
|
"react-dom": "19.2.8",
|
|
84
70
|
"vite": "^8.3.0"
|
|
85
71
|
},
|
|
86
72
|
"devDependencies": {
|
|
73
|
+
"@fontsource/ibm-plex-mono": "5.3.0",
|
|
87
74
|
"@types/node": "^24.0.0",
|
|
88
|
-
"@types/proper-lockfile": "4.1.4",
|
|
89
75
|
"@types/react": "^19.2.18",
|
|
90
76
|
"@types/react-dom": "^19.2.5",
|
|
91
77
|
"typescript": "^5.9.3"
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "konpeki",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "Create editable visuals with your coding agent.",
|
|
6
6
|
"repository": "https://github.com/vcfgdev/konpeki",
|
|
7
7
|
"license": "Apache-2.0",
|
package/public/og.png
CHANGED
|
Binary file
|