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.
Files changed (162) hide show
  1. package/AGENTS.md +21 -28
  2. package/AUTHORING.md +68 -229
  3. package/CONTRIBUTING.md +10 -23
  4. package/README.md +96 -87
  5. package/SETUP.md +50 -114
  6. package/docs/development.md +43 -248
  7. package/docs/workflow.md +6 -108
  8. package/html/README.md +140 -0
  9. package/html/browser.ts +103 -0
  10. package/html/document.ts +38 -0
  11. package/html/floor.ts +34 -0
  12. package/html/index.html +5 -0
  13. package/html/inspect.ts +313 -0
  14. package/html/preview.css +62 -0
  15. package/html/preview.tsx +463 -0
  16. package/html/review-hints.ts +63 -0
  17. package/html/server.ts +99 -0
  18. package/html/source.ts +104 -0
  19. package/html/starter.ts +6 -0
  20. package/html/theme-authoring.md +176 -0
  21. package/html/theme.md +61 -0
  22. package/index.html +9 -9
  23. package/package.json +21 -35
  24. package/plugin.json +1 -1
  25. package/public/og.png +0 -0
  26. package/runtime/browser-B-29TH1a.mjs +595 -0
  27. package/runtime/floor-Cmk7G3pU.mjs +41 -0
  28. package/runtime/konpeki.mjs +90 -1410
  29. package/runtime/server-BAqC_5WD.mjs +2 -0
  30. package/runtime/server-DfRpfcY9.mjs +205 -0
  31. package/runtime/source-B_Ui9tMp.mjs +141 -0
  32. package/runtime/source-CZQq9GUO.mjs +2 -0
  33. package/skills/konpeki/SKILL.md +88 -172
  34. package/skills/konpeki/assets/blank.html +17 -0
  35. package/skills/konpeki/floor.md +76 -0
  36. package/skills/konpeki/references/cover.md +27 -0
  37. package/skills/konpeki/references/long-document.md +40 -0
  38. package/skills/konpeki/references/one-pager.md +27 -0
  39. package/skills/konpeki/references/patterns.md +118 -0
  40. package/skills/konpeki/references/resume.md +28 -0
  41. package/skills/konpeki/references/slides.md +28 -0
  42. package/skills/konpeki/scripts/ensure-runtime.mjs +10 -31
  43. package/skills/konpeki/scripts/prepare-document.mjs +25 -14
  44. package/src/components/PageBoard.tsx +156 -0
  45. package/src/lib/alignment.ts +21 -0
  46. package/src/lib/page-board.ts +25 -0
  47. package/src/lib/review-position.ts +19 -0
  48. package/src/styles/base.css +4 -6
  49. package/src/styles/feedback.css +97 -61
  50. package/src/styles/shell.css +95 -323
  51. package/theme-base.css +90 -0
  52. package/theme.css +56 -0
  53. package/vite.config.ts +2 -5
  54. package/composition/README.md +0 -156
  55. package/composition/compile.ts +0 -227
  56. package/composition/document.ts +0 -600
  57. package/composition/schema.json +0 -3001
  58. package/composition/schema.ts +0 -437
  59. package/composition/theme-tokens.ts +0 -16
  60. package/composition/types.ts +0 -269
  61. package/composition/validate.ts +0 -226
  62. package/composition/vector.ts +0 -143
  63. package/composition/visualizations.ts +0 -319
  64. package/design/README.md +0 -17
  65. package/design/palettes/README.md +0 -14
  66. package/design/palettes/base.ts +0 -14
  67. package/design/palettes/candidates.ts +0 -19
  68. package/design/palettes/index.ts +0 -78
  69. package/design/review/color-theme.md +0 -44
  70. package/design/review/layout.md +0 -16
  71. package/design/review/text.md +0 -18
  72. package/design/review/typography.md +0 -15
  73. package/design/review/visuals.md +0 -31
  74. package/design/semantic-patterns.md +0 -43
  75. package/design/themes/README.md +0 -40
  76. package/design/themes/index.ts +0 -24
  77. package/design/visual-languages/technical-product.md +0 -17
  78. package/design/visual-review.md +0 -88
  79. package/lib/assets.d.ts +0 -8
  80. package/lib/charts.ts +0 -18
  81. package/lib/contrast.ts +0 -16
  82. package/lib/layouts.ts +0 -50
  83. package/lib/slide.tsx +0 -42
  84. package/lib/taste.ts +0 -17
  85. package/lib/text.tsx +0 -89
  86. package/lib/typeface.ts +0 -44
  87. package/scripts/migrate-react-page.ts +0 -120
  88. package/skills/konpeki/assets/blank.json +0 -23
  89. package/slides/README.md +0 -153
  90. package/slides/architecture/PROMPT.md +0 -31
  91. package/slides/architecture/index.tsx +0 -102
  92. package/slides/article-brief/PROMPT.md +0 -35
  93. package/slides/article-brief/index.tsx +0 -71
  94. package/slides/bar-chart/PROMPT.md +0 -39
  95. package/slides/bar-chart/index.tsx +0 -97
  96. package/slides/comparison/PROMPT.md +0 -29
  97. package/slides/comparison/index.tsx +0 -95
  98. package/slides/decision-memo/PROMPT.md +0 -34
  99. package/slides/decision-memo/index.tsx +0 -85
  100. package/slides/delivery-plan/PROMPT.md +0 -45
  101. package/slides/delivery-plan/index.tsx +0 -105
  102. package/slides/experiment/PROMPT.md +0 -44
  103. package/slides/experiment/index.tsx +0 -127
  104. package/slides/incident-workflow/PROMPT.md +0 -57
  105. package/slides/incident-workflow/index.tsx +0 -78
  106. package/slides/introducing-konpeki/PROMPT.md +0 -40
  107. package/slides/introducing-konpeki/README.md +0 -76
  108. package/slides/introducing-konpeki/SOURCE.md +0 -26
  109. package/slides/introducing-konpeki/author.ts +0 -165
  110. package/slides/introducing-konpeki/composition.json +0 -3270
  111. package/slides/line-chart/PROMPT.md +0 -40
  112. package/slides/line-chart/index.tsx +0 -72
  113. package/slides/migration/PROMPT.md +0 -38
  114. package/slides/migration/index.tsx +0 -89
  115. package/slides/og-images/PROMPT.md +0 -21
  116. package/slides/og-images/index.tsx +0 -76
  117. package/slides/product-introduction/PROMPT.md +0 -24
  118. package/slides/product-introduction/index.tsx +0 -105
  119. package/slides/research-brief/PROMPT.md +0 -40
  120. package/slides/research-brief/index.tsx +0 -104
  121. package/slides/results-explanation/PROMPT.md +0 -32
  122. package/slides/results-explanation/index.tsx +0 -96
  123. package/slides/retrospective/PROMPT.md +0 -43
  124. package/slides/retrospective/index.tsx +0 -105
  125. package/slides/sankey/PROMPT.md +0 -11
  126. package/slides/sankey/index.tsx +0 -93
  127. package/slides/teaching/PROMPT.md +0 -45
  128. package/slides/teaching/index.tsx +0 -124
  129. package/slides/vertical-bar-charts/PROMPT.md +0 -13
  130. package/slides/vertical-bar-charts/index.tsx +0 -97
  131. package/src/app/App.tsx +0 -820
  132. package/src/components/BuildOrb.tsx +0 -40
  133. package/src/components/Canvas.tsx +0 -1185
  134. package/src/components/DiagramTypeIcon.tsx +0 -78
  135. package/src/components/InspectorPanel.tsx +0 -773
  136. package/src/components/LeftPanel.tsx +0 -120
  137. package/src/components/PageSizePicker.tsx +0 -30
  138. package/src/components/Presentation.tsx +0 -105
  139. package/src/components/RevisionNotes.tsx +0 -56
  140. package/src/components/RightPanel.tsx +0 -201
  141. package/src/components/VectorOverflowWarning.tsx +0 -46
  142. package/src/components/WorkspaceChrome.tsx +0 -288
  143. package/src/components/ui.tsx +0 -53
  144. package/src/lib/examples/react-page-migration.json +0 -1295
  145. package/src/lib/examples.ts +0 -42
  146. package/src/lib/export-png.ts +0 -104
  147. package/src/lib/file-session.ts +0 -87
  148. package/src/lib/history.ts +0 -53
  149. package/src/lib/model.ts +0 -188
  150. package/src/lib/page-size.ts +0 -24
  151. package/src/lib/presentation.ts +0 -17
  152. package/src/lib/review.ts +0 -26
  153. package/src/lib/storage.ts +0 -71
  154. package/src/lib/theme.ts +0 -25
  155. package/src/lib/use-file-session.ts +0 -227
  156. package/src/main.tsx +0 -29
  157. package/src/styles/canvas.css +0 -299
  158. package/src/styles/chrome.css +0 -384
  159. package/src/styles/component-previews.css +0 -386
  160. package/src/styles/left-panel.css +0 -166
  161. package/src/styles/presentation.css +0 -72
  162. package/src/styles/right-panel.css +0 -1215
@@ -0,0 +1,2 @@
1
+ import { r as previewHTML } from "./server-DfRpfcY9.mjs";
2
+ export { previewHTML };
@@ -0,0 +1,205 @@
1
+ import { t as fileSource } from "./source-B_Ui9tMp.mjs";
2
+ import { dirname, extname, isAbsolute, relative, resolve } from "node:path";
3
+ import { readFile, realpath } from "node:fs/promises";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createServer } from "node:http";
6
+ import { randomBytes } from "node:crypto";
7
+ import { createServer as createServer$1 } from "vite";
8
+ import { parse, parseFragment, serialize } from "parse5";
9
+ //#region html/document.ts
10
+ var documentPolicy = "default-src 'none'; script-src 'none'; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data:; base-uri 'none'; form-action 'none'";
11
+ /** The only external resources permitted by CLI inspection/export. */
12
+ function isGoogleFontResource(url, resourceType) {
13
+ const resource = new URL(url);
14
+ if (resource.protocol !== "https:" || resource.port || resource.username || resource.password) return false;
15
+ return resourceType === "stylesheet" && resource.hostname === "fonts.googleapis.com" && ["/css", "/css2"].includes(resource.pathname) || resourceType === "font" && resource.hostname === "fonts.gstatic.com" && resource.pathname.startsWith("/s/");
16
+ }
17
+ /** A view of the source, never a serialization written back to it. Keep every
18
+ * page in the DOM so nth-child selectors and shared SVG definitions stay stable. */
19
+ function documentHTML(source, pageId) {
20
+ const tree = parse(source);
21
+ const html = tree.childNodes.find((n) => n.nodeName === "html");
22
+ const head = html.childNodes.find((n) => n.nodeName === "head");
23
+ const body = html.childNodes.find((n) => n.nodeName === "body");
24
+ for (const child of body.childNodes) {
25
+ if (!("attrs" in child)) continue;
26
+ child.attrs = child.attrs.filter((a) => a.name !== "data-konpeki-current");
27
+ if (child.attrs.some((a) => a.name === "data-page") && child.attrs.some((a) => a.name === "id" && a.value === pageId)) child.attrs.push({
28
+ name: "data-konpeki-current",
29
+ value: ""
30
+ });
31
+ }
32
+ head.childNodes.unshift(...parseFragment(`<meta http-equiv="Content-Security-Policy" content="${documentPolicy}">`).childNodes);
33
+ head.childNodes.push(...parseFragment(`<style>
34
+ html, body { margin: 0 !important; padding: 0 !important; }
35
+ body { display: flex !important; flex-direction: column !important; align-items: flex-start !important; }
36
+ body > [data-page] { margin: 0 !important; flex: none !important; }
37
+ body > [data-page]:not([data-konpeki-current]) { display: none !important; }
38
+ *, *::before, *::after { animation: none !important; transition: none !important; }
39
+ @media print { body > [data-page] { break-before: auto !important; break-after: auto !important; } }
40
+ </style>`).childNodes);
41
+ return serialize(tree);
42
+ }
43
+ //#endregion
44
+ //#region html/server.ts
45
+ var types = {
46
+ ".css": "text/css",
47
+ ".png": "image/png",
48
+ ".jpg": "image/jpeg",
49
+ ".jpeg": "image/jpeg",
50
+ ".webp": "image/webp",
51
+ ".gif": "image/gif",
52
+ ".svg": "image/svg+xml",
53
+ ".woff": "font/woff",
54
+ ".woff2": "font/woff2",
55
+ ".ttf": "font/ttf",
56
+ ".otf": "font/otf"
57
+ };
58
+ var sessionRoute = "/__konpeki/html";
59
+ /** Same authenticated document/asset surface for preview and browser exports. */
60
+ function htmlSession(path, token = randomBytes(24).toString("base64url")) {
61
+ const store = fileSource(resolve(path));
62
+ const prefix = `${sessionRoute}/${token}`;
63
+ function send(res, status, value) {
64
+ res.writeHead(status, {
65
+ "Content-Type": "application/json",
66
+ "Cache-Control": "no-store",
67
+ "Referrer-Policy": "no-referrer"
68
+ });
69
+ res.end(JSON.stringify(value));
70
+ }
71
+ async function handler(req, res, next) {
72
+ const url = new URL(req.url ?? "/", "http://localhost");
73
+ if (!url.pathname.startsWith("/__konpeki/html")) return next();
74
+ try {
75
+ if (url.pathname === "/__konpeki/html") {
76
+ if (req.headers["x-konpeki-session"] !== token) return send(res, 403, { error: "Invalid preview session." });
77
+ if (req.method === "GET") return send(res, 200, await store.read());
78
+ if (req.method !== "PATCH") return send(res, 405, { error: "Method not allowed." });
79
+ let size = 0;
80
+ const chunks = [];
81
+ for await (const chunk of req) {
82
+ size += chunk.length;
83
+ if (size > 16384) throw Object.assign(/* @__PURE__ */ new Error("Correction is too large."), { status: 413 });
84
+ chunks.push(chunk);
85
+ }
86
+ const body = JSON.parse(Buffer.concat(chunks).toString());
87
+ if (!body || typeof body.revision !== "string" || !body.edit || ![
88
+ "move",
89
+ "delete",
90
+ "undo"
91
+ ].includes(body.edit.kind)) return send(res, 400, { error: "Invalid correction." });
92
+ return send(res, 200, await store.edit(body.revision, body.edit));
93
+ }
94
+ if (!url.pathname.startsWith(`${prefix}/`)) return send(res, 403, { error: "Invalid preview session." });
95
+ if (req.method !== "GET") return send(res, 405, { error: "Method not allowed." });
96
+ if (url.pathname === `${prefix}/document/`) {
97
+ const snapshot = await store.read();
98
+ if (url.searchParams.has("revision") && url.searchParams.get("revision") !== snapshot.revision) return send(res, 409, { error: "The source changed. Retry with its latest revision." });
99
+ const page = url.searchParams.get("page") ?? snapshot.pages[0];
100
+ if (!snapshot.pages.includes(page)) return send(res, 404, { error: "Unknown page." });
101
+ res.writeHead(200, {
102
+ "Content-Type": "text/html; charset=utf-8",
103
+ "Content-Security-Policy": documentPolicy,
104
+ "Cache-Control": "no-store",
105
+ "Referrer-Policy": "no-referrer",
106
+ "X-Content-Type-Options": "nosniff"
107
+ });
108
+ return res.end(documentHTML(snapshot.source, page));
109
+ }
110
+ const assets = `${prefix}/document/`;
111
+ if (!url.pathname.startsWith(assets)) return send(res, 404, { error: "Unknown resource." });
112
+ const name = decodeURIComponent(url.pathname.slice(assets.length));
113
+ const type = types[extname(name).toLowerCase()];
114
+ if (!type || name.split(/[\\/]/).some((part) => part.startsWith("."))) return send(res, 403, { error: "Only local image, font and CSS assets are served." });
115
+ let base = await realpath(dirname(resolve(path))), file;
116
+ try {
117
+ file = await realpath(resolve(base, name));
118
+ } catch (error) {
119
+ if (error.code !== "ENOENT" || !/^theme(?:-base)?\.css$/.test(name)) throw error;
120
+ base = fileURLToPath(new URL("../", import.meta.url));
121
+ file = await realpath(resolve(base, name));
122
+ }
123
+ const within = relative(base, file);
124
+ if (isAbsolute(within) || within.startsWith("..")) return send(res, 403, { error: "Asset is outside the document directory." });
125
+ const bytes = await readFile(file);
126
+ res.writeHead(200, {
127
+ "Content-Type": type,
128
+ "Cache-Control": "no-store",
129
+ "Content-Security-Policy": documentPolicy,
130
+ "X-Content-Type-Options": "nosniff"
131
+ });
132
+ res.end(bytes);
133
+ } catch (error) {
134
+ send(res, error instanceof SyntaxError || error instanceof URIError ? 400 : error.code === "ENOENT" ? 404 : Number(error.status ?? 500), { error: error instanceof Error ? error.message : "Preview failed." });
135
+ }
136
+ }
137
+ return {
138
+ token,
139
+ prefix,
140
+ store,
141
+ handler
142
+ };
143
+ }
144
+ async function documentServer(path) {
145
+ const session = htmlSession(path);
146
+ await session.store.read();
147
+ const server = createServer((req, res) => void session.handler(req, res, () => {
148
+ res.writeHead(404);
149
+ res.end();
150
+ }));
151
+ await new Promise((resolve, reject) => {
152
+ server.once("error", reject);
153
+ server.listen(0, "127.0.0.1", () => resolve());
154
+ });
155
+ const address = server.address();
156
+ return {
157
+ ...session,
158
+ origin: `http://127.0.0.1:${address.port}`,
159
+ close: () => new Promise((resolve, reject) => server.close((e) => e ? reject(e) : resolve()))
160
+ };
161
+ }
162
+ async function previewHTML(path, host, port, root, strictPort = false) {
163
+ const session = htmlSession(path);
164
+ await session.store.read();
165
+ const server = await createServer$1({
166
+ root,
167
+ configFile: false,
168
+ logLevel: "silent",
169
+ server: {
170
+ host,
171
+ port,
172
+ strictPort,
173
+ fs: {
174
+ allow: [root, dirname(fileURLToPath(import.meta.resolve("@fontsource/ibm-plex-sans/package.json")))],
175
+ deny: [
176
+ ".env",
177
+ ".env.*",
178
+ "*.{crt,pem,key,p12,pfx}",
179
+ ".npmrc",
180
+ "**/.git/**"
181
+ ]
182
+ }
183
+ },
184
+ plugins: [{
185
+ name: "konpeki-html",
186
+ configureServer(server) {
187
+ server.middlewares.use((req, res, next) => void session.handler(req, res, next));
188
+ }
189
+ }]
190
+ });
191
+ try {
192
+ await server.listen();
193
+ } catch (e) {
194
+ await server.close();
195
+ throw e;
196
+ }
197
+ const address = server.httpServer.address();
198
+ const displayHost = host === "0.0.0.0" || host === "::" ? "localhost" : host;
199
+ return {
200
+ server,
201
+ url: `http://${displayHost.includes(":") ? `[${displayHost}]` : displayHost}:${address.port}/html/?session=${session.token}`
202
+ };
203
+ }
204
+ //#endregion
205
+ export { isGoogleFontResource as a, sessionRoute as i, htmlSession as n, previewHTML as r, documentServer as t };
@@ -0,0 +1,141 @@
1
+ import { basename, dirname, join, resolve } from "node:path";
2
+ import { readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { createHash, randomBytes } from "node:crypto";
4
+ import { parse } from "parse5";
5
+ import postcss from "postcss";
6
+ //#region html/source.ts
7
+ var revision = (source) => createHash("sha256").update(source).digest("hex");
8
+ var attr = (node, name) => node.attrs.find((a) => a.name === name)?.value;
9
+ function fail(message, status = 422) {
10
+ throw Object.assign(new Error(message), { status });
11
+ }
12
+ function inspectSource(source) {
13
+ const tree = parse(source, {
14
+ sourceCodeLocationInfo: true,
15
+ onParseError(error) {
16
+ if (error.code === "duplicate-attribute") fail("Duplicate HTML attributes are ambiguous; remove them before editing.");
17
+ }
18
+ });
19
+ const elements = [];
20
+ function visit(node) {
21
+ if ("tagName" in node) elements.push(node);
22
+ if ("childNodes" in node) node.childNodes.forEach(visit);
23
+ }
24
+ visit(tree);
25
+ const pages = elements.filter((node) => attr(node, "data-page") !== void 0);
26
+ if (!pages.length) fail("Add an id and data-page to each page element.");
27
+ const ids = /* @__PURE__ */ new Set();
28
+ for (const node of elements) {
29
+ if ([
30
+ "script",
31
+ "iframe",
32
+ "object",
33
+ "embed",
34
+ "base",
35
+ "template",
36
+ "form",
37
+ "input",
38
+ "button",
39
+ "textarea",
40
+ "select",
41
+ "animate",
42
+ "animatetransform",
43
+ "set"
44
+ ].includes(node.tagName.toLowerCase()) || node.tagName === "meta" && attr(node, "http-equiv") !== void 0 || node.attrs.some((a) => /^on/i.test(a.name) || [
45
+ "href",
46
+ "src",
47
+ "action"
48
+ ].includes(a.name) && /^\s*javascript:/i.test(a.value))) fail("Documents must be static HTML/CSS and inline SVG, without scripts, event handlers, embedded applications or controls.");
49
+ const id = attr(node, "id");
50
+ if (id !== void 0 && (!id || ids.has(id))) fail("Element IDs must be nonempty and unique within the HTML file.");
51
+ if (id) ids.add(id);
52
+ }
53
+ for (const page of pages) if (!attr(page, "id") || page.parentNode?.nodeName !== "body") fail("Pages need an id and must be direct children of body.");
54
+ return {
55
+ pages,
56
+ elements
57
+ };
58
+ }
59
+ function patchSource(source, edit) {
60
+ const { pages, elements } = inspectSource(source);
61
+ const page = pages.find((node) => attr(node, "id") === edit.page);
62
+ const node = elements.find((node) => attr(node, "id") === edit.id);
63
+ if (!page || !node) fail("The selected page or element no longer exists.");
64
+ let ancestor = node;
65
+ while (ancestor && ancestor !== page) ancestor = "parentNode" in ancestor ? ancestor.parentNode ?? void 0 : void 0;
66
+ if (!ancestor || node === page) fail("Select an element inside the page.");
67
+ const location = node.sourceCodeLocation;
68
+ if (!location?.startTag) fail("This element does not have an unambiguous source location.");
69
+ if (edit.kind === "delete") return source.slice(0, location.startOffset) + source.slice(location.endOffset);
70
+ if (edit.kind !== "move" || typeof edit.translate !== "string" || !/^-?\d+(?:\.\d+)?px -?\d+(?:\.\d+)?px$/.test(edit.translate) || edit.translate.length > 100) fail("Invalid correction.");
71
+ let css;
72
+ try {
73
+ css = postcss.parse(attr(node, "style") ?? "", { map: false });
74
+ } catch {
75
+ fail("The inline style could not be parsed. Correct it in source before moving.");
76
+ }
77
+ const declarations = css.nodes.filter((n) => n.type === "decl" && n.prop.toLowerCase() === "translate");
78
+ for (const declaration of declarations) if (declaration.type === "decl") declaration.value = edit.translate;
79
+ if (!declarations.length) css.append({
80
+ prop: "translate",
81
+ value: edit.translate
82
+ });
83
+ css.raws.semicolon = true;
84
+ const escaped = css.toString().replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;");
85
+ const style = location.attrs?.style;
86
+ const start = style?.startOffset ?? location.startTag.endOffset - (source[location.startTag.endOffset - 2] === "/" ? 2 : 1);
87
+ const end = style?.endOffset ?? start;
88
+ return source.slice(0, start) + `${style ? "" : " "}style="${escaped}"` + source.slice(end);
89
+ }
90
+ function fileSource(path) {
91
+ path = resolve(path);
92
+ let queue = Promise.resolve();
93
+ const history = [];
94
+ async function read() {
95
+ const source = await readFile(path, "utf8");
96
+ const { pages } = inspectSource(source);
97
+ return {
98
+ source,
99
+ revision: revision(source),
100
+ name: basename(path),
101
+ path,
102
+ key: revision(path),
103
+ pages: pages.map((p) => attr(p, "id"))
104
+ };
105
+ }
106
+ function edit(expected, change) {
107
+ const result = queue.then(async () => {
108
+ const current = await read();
109
+ if (current.revision !== expected) fail("Source changed outside this preview. Reloaded the latest file; retry your correction.", 409);
110
+ const previous = history.at(-1);
111
+ if (change.kind === "undo" && (!previous || previous.after !== current.revision)) fail("Nothing safe to undo: the source may have changed externally.", 409);
112
+ const next = change.kind === "undo" ? previous.before : patchSource(current.source, change);
113
+ inspectSource(next);
114
+ const temporary = join(dirname(path), `.${basename(path)}.${randomBytes(8).toString("hex")}.tmp`);
115
+ try {
116
+ await writeFile(temporary, next, { flag: "wx" });
117
+ if (revision(await readFile(path, "utf8")) !== current.revision) fail("Source changed while saving. Retry your correction.", 409);
118
+ await rename(temporary, path);
119
+ } finally {
120
+ await rm(temporary, { force: true });
121
+ }
122
+ if (change.kind === "undo") history.pop();
123
+ else {
124
+ history.push({
125
+ before: current.source,
126
+ after: revision(next)
127
+ });
128
+ if (history.length > 30) history.shift();
129
+ }
130
+ return read();
131
+ });
132
+ queue = result.catch(() => {});
133
+ return result;
134
+ }
135
+ return {
136
+ read,
137
+ edit
138
+ };
139
+ }
140
+ //#endregion
141
+ export { revision as i, inspectSource as n, patchSource as r, fileSource as t };
@@ -0,0 +1,2 @@
1
+ import { t as fileSource } from "./source-B_Ui9tMp.mjs";
2
+ export { fileSource };
@@ -1,186 +1,102 @@
1
1
  ---
2
2
  name: konpeki
3
- description: Opens the Konpeki editor with init, or generates and revises editable visuals with generate. Use when asked to use Konpeki or create covers, social graphics, product announcements, charts, diagrams, article headers or presentations.
4
- compatibility: Requires Node.js 24+, npm, a coding agent with file and command access, and a browser. Runtime installation needs network access and the host's approval.
3
+ description: Creates, checks, renders, and revises static HTML visuals. Use for landscape slides, résumés, multi-page documents, one-pagers/cards, and covers/social graphics, including charts and diagrams within them.
4
+ compatibility: Requires Node.js 24+, file and command access, and a matching Konpeki runtime installed in the workspace or a prepared checkout. Inspection and export require pinned Chromium.
5
5
  ---
6
6
 
7
7
  # Konpeki
8
8
 
9
- Use one of two modes from the invocation or conversation:
10
-
11
- - **init [composition.json]**: prepare the runtime and open a blank or existing
12
- file-backed editor. Stop when it is ready; do not generate artwork or claim to
13
- be listening for reviews.
14
- - **generate [brief / materials]**: create or revise the visual, open its preview,
15
- inspect and repair it, then handle feedback in the same conversation. Prepare
16
- the runtime automatically; a separate init command is never required.
17
-
18
- A creation brief without a mode, including “Use Konpeki to…”, means **generate**.
19
- Bare “Konpeki” with no brief opens the editor as **init**. Follow-up feedback stays
20
- with the current document; users need not repeat the skill or their materials.
21
- Use existing chat, attachments, referenced files and canvas intent as inputs.
22
- Ask only for missing information needed for faithful work, not a repeated brief.
23
-
24
- Invocation belongs to the host: Codex CLI/IDE uses `$konpeki init` or
25
- `$konpeki generate …`; a standalone Claude Code skill uses `/konpeki init` or
26
- `/konpeki generate …`. Other clients may use skill selection or natural language;
27
- plugin installations may namespace the skill. These are agent workflows, not
28
- `konpeki init` / `konpeki generate` terminal commands.
29
-
30
- ## 0. Find the runtime once
31
-
32
- From the user's chosen workspace, run the script bundled beside this skill:
9
+ Turn a brief and evidence into a finished visual with HTML as editable source and
10
+ PNG/PDF as delivery artifacts. Infer a suitable form and complete the work rather
11
+ than requiring an outline or design checkpoint, unless the user requests one.
12
+
13
+ ## Before authoring
14
+
15
+ 1. Resolve the runtime. From an installed skill, run
16
+ `node scripts/ensure-runtime.mjs` with no arguments; it reports a compatible
17
+ checkout or locally installed package. Its `root` locates the guides below;
18
+ run its `cli` path with Node. See `SETUP.md` if no runtime is found.
19
+ 2. From that root, read `AUTHORING.md` for accuracy and editorial judgment and
20
+ `html/README.md` for all HTML, file, theme, diagnostic, preview, and CLI
21
+ rules. Do not duplicate or override those rules here.
22
+ 3. Ground the work in the requested audience, takeaway, supplied evidence,
23
+ destination, and output format. Ask only when missing facts or conflicting
24
+ constraints prevent faithful work.
25
+ 4. Infer the format and read its guide below. Read only the relevant sections
26
+ of [content patterns](references/patterns.md) when the work mixes text with
27
+ visuals or includes a chart, table, comparison, process, system map,
28
+ hierarchy, or annotated detail.
29
+
30
+ ## Choose the output guide
31
+
32
+ These guides cover content, layout, and review decisions, not fixed templates
33
+ or a placement grid. The brief overrides format defaults, never accuracy,
34
+ readability, or the floor's bans. Do not ask the user to choose a taxonomy.
35
+
36
+ | Requested output | Read |
37
+ | --- | --- |
38
+ | Landscape presentation or deck | [Slides](references/slides.md) |
39
+ | Résumé or CV | [Résumé](references/resume.md) |
40
+ | Report, essay, proposal, or other sustained multi-page reading | [Long document](references/long-document.md) |
41
+ | Bounded brief, reference sheet, one-pager, or card at A4 or another size | [One-pager/card](references/one-pager.md) |
42
+ | Cover, thumbnail, link preview, or social graphic | [Cover/social graphic](references/cover.md) |
43
+
44
+ For a mixed-format request, read each applicable guide. A one-page résumé still
45
+ uses the résumé guide; a diagram within a report uses the long-document guide
46
+ and the relevant pattern section. Start freely when none fits exactly.
47
+
48
+ ## Create or revise
49
+
50
+ 1. Choose the user's document path. For a new document, run
51
+ `node scripts/prepare-document.mjs <cli> <document.html>` to place a validated
52
+ starter and theme stylesheets beside it without overwriting existing files.
53
+ The default theme uses Google Fonts; use local or embedded fonts for offline
54
+ or reproducible rendering.
55
+ 2. Reread any existing source before changing it. Preserve stable IDs, facts,
56
+ provenance, deliberate human edits, and unrelated or newer changes.
57
+ 3. Plan each page before writing HTML, in your working notes: its format and
58
+ viewing size, the headline as a claim, the elements it carries in reading
59
+ order, and what the format's budget forces out.
60
+ 4. Read [floor.md](floor.md) immediately before writing or revising HTML. Its
61
+ bans have no exceptions; its defaults yield only to an explicit brief.
62
+ 5. Read `html/theme.md` for the theme API. Link the default Cobalt `theme.css`.
63
+ When the brief brings its own look, adapt a copy using the same contract.
64
+ Read CSS implementation only when changing a theme, not when using one.
65
+ Author the HTML and page-specific composition; use complete type roles and
66
+ theme tokens.
67
+ 6. Run `node <cli> validate <document.html>` and fix source-contract errors.
68
+ 7. Run `node <cli> inspect <document.html>`; use `--page N` or `--details` when useful.
69
+ Use each page's resolved `theme.type` to plan type and line lengths.
70
+ Resolve errors. Fix each warning, or keep it with a reason you will report;
71
+ floor warnings use their rule ID as the diagnostic code.
72
+ 8. Render every affected page at 2x PNG. `render` prints the floor's review
73
+ checklist: check the page against it at intended size and enlarged, along
74
+ with facts, readability, clipping and consistency.
75
+ 9. Repeat editing, validation, inspection, and visual review until sound. Inspect
76
+ a requested PDF separately because it is a distinct artifact.
77
+
78
+ Example focused loop:
33
79
 
34
80
  ```sh
35
- node "<absolute-path-to-this-skill>/scripts/ensure-runtime.mjs"
81
+ node <cli> validate document.html
82
+ node <cli> inspect document.html
83
+ node <cli> render document.html --page 1 --format png --scale 2 --output page-1.png
36
84
  ```
37
85
 
38
- It reuses a compatible workspace installation, the surrounding Konpeki checkout,
39
- or a previously installed cache. If none exists, follow the host's permission
40
- flow, then rerun with `--install`. This installs the pinned npm release in a
41
- user cache, not in the user's project, and does not change their agent guidance.
42
- If Node.js 24+ or npm is missing, report that prerequisite and follow the host's
43
- toolchain setup rules; never claim the runtime is ready when setup failed.
44
-
45
- The script returns JSON with `root` (runtime resources) and `cli` (the executable
46
- path). In the instructions below, `<root>` and `<cli>` mean those returned absolute
47
- paths. Quote paths in shell commands. They may be outside the skill directory;
48
- do not assume a copied skill contains the runtime. Read resources from `<root>`
49
- and keep authored documents in the user's workspace, outside the runtime/cache.
86
+ ## Human feedback
50
87
 
51
- ## init — open the editor without generating
88
+ Use `node <cli> preview <document.html>` only when interactive review is useful. After
89
+ the reviewer uses **Copy & clear**, use the full local path in the prompt to
90
+ reread current source, then address feedback by stable target ID. If a target no
91
+ longer exists or feedback conflicts with newer work, report that instead of
92
+ guessing. Re-run the verification loop after source-backed moves, deletions, or
93
+ agent-authored revisions.
52
94
 
53
- Use the explicit document path, otherwise the document already active in this
54
- conversation. With neither, use `slides/untitled/composition.json`. If several
55
- documents are plausible, ask which to open instead of guessing.
95
+ ## Deliver
56
96
 
57
- Run the bundled helper with the resolved CLI and chosen destination:
58
-
59
- ```sh
60
- node "<absolute-path-to-this-skill>/scripts/prepare-document.mjs" "<cli>" "<composition.json>"
61
- ```
62
-
63
- It creates a validated blank page only when the file is missing. Existing files
64
- are validated without rewriting them, and unreadable files are left untouched.
65
- It returns `{ compositionPath, created }`; this means the document is prepared,
66
- not that its browser is open. Do not replace a failed document with an example.
67
-
68
- Follow **Open the file-backed preview** below. Verify the intended document loads,
69
- then return its path and usable editor link. For a new document, verify an empty
70
- page with editing controls. Stop here until the person asks to generate or revise.
71
- Do not start an agent listener just because an editor is open.
72
-
73
- ## generate — interpret the brief
74
-
75
- Read `<root>/AGENTS.md` and `<root>/AUTHORING.md`. Use the user's existing prompt as the
76
- brief; do not ask them to repeat it in the canvas. Establish the source facts,
77
- audience, takeaway and destination. Ask only when missing facts or conflicting
78
- requirements prevent a faithful result.
79
-
80
- Continue the document opened by init or the current conversation unless the
81
- person requests a new one. Before changing an existing document, inspect
82
- `node "<cli>" request "<composition.json>"`. Claim a `submitted` request with
83
- `wait` and follow **Review handoff** below. A `working` request may belong to
84
- another agent; coordinate ownership before resuming, rather than claiming it again.
85
- With no active request, work from the brief and current composition normally.
86
-
87
- Use `default` authoring mode unless requested otherwise. Explicit visual
88
- preferences override taste defaults, not factual fidelity or readability. Use
89
- the comparison workflow in `<root>/AUTHORING.md`
90
- only when requested or accepted. A single page is a complete creation; choose
91
- dimensions for its destination rather than assuming a slide deck.
92
-
93
- ### Author or revise the composition
94
-
95
- Read `<root>/composition/README.md`. For a new visual,
96
- create `slides/<name>/composition.json` unless the user supplies another path.
97
- Use an unused destination; do not overwrite a different document at that path.
98
- Save the creative brief in `PROMPT.md` and substantial facts, citations and asset
99
- provenance in `SOURCE.md`. Separate assumptions from supplied facts. Preserve
100
- creative requests accurately, but omit private coordination and environment or
101
- agent metadata; label excerpts and redactions rather than calling them verbatim.
102
-
103
- For revisions, reread the current file first. Preserve unrelated content,
104
- component/vector IDs and human-edited geometry. Honor explicit chart or diagram
105
- choices. Recompose for a new aspect ratio instead of stretching or cropping.
106
-
107
- Prefer native text and semantic components. When standard drafts cannot express
108
- the visual, use editable vectors inside their owning component. Reuse
109
- `<root>/design/README.md` and reference examples as needed.
110
- Trusted React may generate SVG for conversion, but imported JSX must not execute
111
- in the canvas or become a second document source.
112
-
113
- ### Validate, render and repair
114
-
115
- ```sh
116
- node "<cli>" validate "<composition.json>"
117
- ```
118
-
119
- Follow **Open the file-backed preview** below, then inspect
120
- every affected page and requested theme at presentation and smaller review sizes
121
- after fonts load. Check factual fidelity, text bounds, contrast, reading order
122
- and relationships. Repair consequential issues and inspect fresh renders.
123
-
124
- Follow `<root>/docs/development.md` verification guidance for
125
- code changes. Validation alone is not visual review. If a required check cannot
126
- run, state the limitation; do not claim it passed. Honor requested checkpoints;
127
- otherwise continue to finished output.
128
-
129
- ### Deliver and continue revisions
130
-
131
- Return the composition path, usable preview, reviewed images or requested
132
- exports, source attribution and verification limitations. Verify exports
133
- separately; browser images do not prove editable PDF/PPTX or font fidelity.
134
- Include any generator source while keeping composition JSON authoritative.
135
-
136
- Accept follow-up chat feedback without requiring another generate invocation.
137
- Reread the current composition each time, preserve human edits, and repeat the
138
- render/repair checks. For canvas feedback, the person submits notes with **Build
139
- it**. A submitted request can be handled by invoking generate again or by an
140
- agent already waiting for it. Do not treat unsent notes as a submitted request.
141
-
142
- ## Open the file-backed preview
143
-
144
- Reuse a known live preview for the same absolute document path; verify it still
145
- loads that document. Otherwise start:
146
-
147
- ```sh
148
- node "<cli>" preview "<composition.json>"
149
- ```
97
+ Deliver requested PNG/PDF artifacts first. Keep editable HTML and local resources
98
+ in the agent workspace; share them when useful or requested. Report every
99
+ warning you kept and why, and any review limitations. Do not claim checks you did
100
+ not run.
150
101
 
151
- Keep the process alive using the host's supported service mechanism. Open the
152
- exact printed session URL in the host's in-app browser when available, otherwise
153
- the person's regular browser. For a remote workspace, use its authenticated
154
- preview or port forwarding and preserve the session query; a remote loopback URL
155
- is not a usable handoff. Session URLs grant editing access: do not publish them.
156
- Verify the intended document actually loads before calling setup complete. If
157
- browser access is unavailable, return the usable link and state it was not checked.
158
- Do not substitute the standalone playground: it cannot save to the agent's file
159
- or submit canvas review requests.
160
-
161
- ## Review handoff
162
-
163
- When explicitly waiting for ongoing canvas reviews, run
164
- `node "<cli>" wait "<composition.json>"` alongside the preview using the host's
165
- supported long-running tool. Report whether a listener is actually running.
166
- **Build it** cannot wake an idle agent. Without a listener, use its **Copy prompt**
167
- handoff or ask the person to resume generate in agent chat; do not claim a permanent
168
- connection. On receiving a request,
169
- reread the named file and compare its revision with the request. If it changed,
170
- reconcile against the latest document instead of applying a stale rewrite.
171
- Apply every attached note to its named page, component or vector-element ID;
172
- without notes, respect the selected scope. Preserve unrelated edits and stable
173
- IDs. A request is already marked working when `wait` returns; it is not deleted.
174
- Validate, render, inspect and repair the result, then run
175
- `node "<cli>" finish "<composition.json>" <request-id> --message "Updated and checked"`.
176
- File changes alone do not resolve notes. If blocked, finish with
177
- `--status needs-clarification --message "..."` or `--status failed --message "..."`;
178
- unresolved notes remain for retry. Never mark a partially handled batch done.
179
- Use `node "<cli>" request "<composition.json>"` to recover an interrupted request and
180
- verify it is still active before further writes. Cancellation does not stop your
181
- process: stop work if the request is no longer active. Do not edit the feedback
182
- sidecar directly. Return to `wait` only when the person requested a continuing loop.
183
-
184
- See `<root>/docs/workflow.md` for the full handoff contract.
185
- Never bypass revision checks or mutate hidden browser storage to replace the
186
- document. Do not publish without permission.
102
+ Do not publish, push, deploy, or create a release without explicit permission.
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Untitled visual</title>
6
+ <link rel="stylesheet" href="theme.css">
7
+ <style>
8
+ h1 { max-width: 850px; }
9
+ p { max-width: 760px; margin-top: var(--kp-space-3); color: var(--kp-muted); }
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <main id="page-1" data-page data-size="link">
14
+ <h1 id="title">Untitled visual</h1>
15
+ </main>
16
+ </body>
17
+ </html>