konpeki 0.1.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 (121) hide show
  1. package/.agents/skills/authoring-visuals/SKILL.md +82 -0
  2. package/AGENTS.md +33 -0
  3. package/AUTHORING.md +233 -0
  4. package/LICENSE +201 -0
  5. package/README.md +70 -0
  6. package/SETUP.md +86 -0
  7. package/composition/README.md +166 -0
  8. package/composition/compile.ts +227 -0
  9. package/composition/document.ts +709 -0
  10. package/composition/schema.json +2992 -0
  11. package/composition/schema.ts +435 -0
  12. package/composition/theme-tokens.ts +16 -0
  13. package/composition/types.ts +286 -0
  14. package/composition/validate.ts +684 -0
  15. package/composition/vector.ts +143 -0
  16. package/composition/visualizations.ts +335 -0
  17. package/design/README.md +17 -0
  18. package/design/palettes/README.md +14 -0
  19. package/design/palettes/base.ts +14 -0
  20. package/design/palettes/candidates.ts +19 -0
  21. package/design/palettes/index.ts +78 -0
  22. package/design/review/color-theme.md +44 -0
  23. package/design/review/layout.md +16 -0
  24. package/design/review/text.md +18 -0
  25. package/design/review/typography.md +15 -0
  26. package/design/review/visuals.md +31 -0
  27. package/design/semantic-patterns.md +43 -0
  28. package/design/themes/README.md +22 -0
  29. package/design/themes/index.ts +13 -0
  30. package/design/visual-languages/technical-product.md +17 -0
  31. package/design/visual-review.md +88 -0
  32. package/docs/development.md +140 -0
  33. package/docs/workflow.md +61 -0
  34. package/index.html +17 -0
  35. package/lib/assets.d.ts +8 -0
  36. package/lib/charts.ts +18 -0
  37. package/lib/contrast.ts +16 -0
  38. package/lib/layouts.ts +50 -0
  39. package/lib/slide.tsx +42 -0
  40. package/lib/taste.ts +17 -0
  41. package/lib/text.tsx +89 -0
  42. package/lib/typeface.ts +44 -0
  43. package/package.json +85 -0
  44. package/runtime/konpeki.mjs +1685 -0
  45. package/scripts/migrate-react-page.ts +120 -0
  46. package/slides/README.md +153 -0
  47. package/slides/architecture/PROMPT.md +31 -0
  48. package/slides/architecture/index.tsx +102 -0
  49. package/slides/article-brief/PROMPT.md +35 -0
  50. package/slides/article-brief/index.tsx +71 -0
  51. package/slides/bar-chart/PROMPT.md +39 -0
  52. package/slides/bar-chart/index.tsx +97 -0
  53. package/slides/comparison/PROMPT.md +29 -0
  54. package/slides/comparison/index.tsx +95 -0
  55. package/slides/decision-memo/PROMPT.md +34 -0
  56. package/slides/decision-memo/index.tsx +85 -0
  57. package/slides/delivery-plan/PROMPT.md +45 -0
  58. package/slides/delivery-plan/index.tsx +105 -0
  59. package/slides/experiment/PROMPT.md +44 -0
  60. package/slides/experiment/index.tsx +127 -0
  61. package/slides/incident-workflow/PROMPT.md +57 -0
  62. package/slides/incident-workflow/index.tsx +78 -0
  63. package/slides/introducing-konpeki/PROMPT.md +19 -0
  64. package/slides/introducing-konpeki/README.md +54 -0
  65. package/slides/introducing-konpeki/SOURCE.md +20 -0
  66. package/slides/introducing-konpeki/author.ts +163 -0
  67. package/slides/introducing-konpeki/composition.json +3265 -0
  68. package/slides/line-chart/PROMPT.md +40 -0
  69. package/slides/line-chart/index.tsx +72 -0
  70. package/slides/migration/PROMPT.md +38 -0
  71. package/slides/migration/index.tsx +89 -0
  72. package/slides/og-images/PROMPT.md +21 -0
  73. package/slides/og-images/index.tsx +76 -0
  74. package/slides/product-introduction/PROMPT.md +24 -0
  75. package/slides/product-introduction/index.tsx +105 -0
  76. package/slides/research-brief/PROMPT.md +40 -0
  77. package/slides/research-brief/index.tsx +104 -0
  78. package/slides/results-explanation/PROMPT.md +32 -0
  79. package/slides/results-explanation/index.tsx +96 -0
  80. package/slides/retrospective/PROMPT.md +43 -0
  81. package/slides/retrospective/index.tsx +105 -0
  82. package/slides/sankey/PROMPT.md +11 -0
  83. package/slides/sankey/index.tsx +93 -0
  84. package/slides/teaching/PROMPT.md +45 -0
  85. package/slides/teaching/index.tsx +124 -0
  86. package/slides/vertical-bar-charts/PROMPT.md +13 -0
  87. package/slides/vertical-bar-charts/index.tsx +97 -0
  88. package/src/app/App.tsx +694 -0
  89. package/src/assets/konpeki-mark.png +0 -0
  90. package/src/components/Canvas.tsx +1168 -0
  91. package/src/components/DiagramTypeIcon.tsx +78 -0
  92. package/src/components/InspectorPanel.tsx +687 -0
  93. package/src/components/LeftPanel.tsx +107 -0
  94. package/src/components/PageSizePicker.tsx +30 -0
  95. package/src/components/Presentation.tsx +105 -0
  96. package/src/components/RightPanel.tsx +201 -0
  97. package/src/components/VectorOverflowWarning.tsx +46 -0
  98. package/src/components/WorkspaceChrome.tsx +199 -0
  99. package/src/components/ui.tsx +53 -0
  100. package/src/lib/examples/react-page-migration.json +1295 -0
  101. package/src/lib/examples.ts +42 -0
  102. package/src/lib/export-png.ts +101 -0
  103. package/src/lib/file-session.ts +74 -0
  104. package/src/lib/history.ts +53 -0
  105. package/src/lib/model.ts +188 -0
  106. package/src/lib/page-size.ts +24 -0
  107. package/src/lib/presentation.ts +17 -0
  108. package/src/lib/storage.ts +43 -0
  109. package/src/lib/theme.ts +25 -0
  110. package/src/lib/use-file-session.ts +162 -0
  111. package/src/main.tsx +26 -0
  112. package/src/styles/base.css +105 -0
  113. package/src/styles/canvas.css +268 -0
  114. package/src/styles/chrome.css +214 -0
  115. package/src/styles/component-previews.css +386 -0
  116. package/src/styles/feedback.css +71 -0
  117. package/src/styles/left-panel.css +125 -0
  118. package/src/styles/presentation.css +72 -0
  119. package/src/styles/right-panel.css +1172 -0
  120. package/src/styles/shell.css +247 -0
  121. package/vite.config.ts +6 -0
@@ -0,0 +1,435 @@
1
+ import {
2
+ authoringModes,
3
+ componentKinds,
4
+ compositionSchema,
5
+ themeIds,
6
+ themeModes,
7
+ vectorElementKinds,
8
+ } from "./types.ts";
9
+ import { chartTemplates, diagramTypes } from "./visualizations.ts";
10
+ import { tokensForAttribute } from "./theme-tokens.ts";
11
+
12
+ const literalVectorAttribute = { oneOf: [{ type: "string", maxLength: 10000, pattern: "^(?!theme:)" }, { type: "number" }] };
13
+
14
+ // Shared constrained vocabulary for schema validation and canvas controls.
15
+ const alignment = ["start", "center", "end"];
16
+ const titleStyle = ["plain", "prominent"];
17
+ const border = ["none", "outline", "filled"];
18
+ const colorScheme = [
19
+ "monochrome",
20
+ "accent-with-muted-context",
21
+ "categorical",
22
+ "sequential",
23
+ "status",
24
+ ];
25
+ const emphasis = ["none", "primary", "latest-series", "exception"];
26
+ const density = ["sparse", "standard"];
27
+ export const tableStyles = [
28
+ "header-rule",
29
+ "top-header-bottom",
30
+ "row-rules",
31
+ "full-grid",
32
+ ] as const;
33
+ export type TableStyle = (typeof tableStyles)[number];
34
+
35
+ export function tableAppearanceForStyle(style: TableStyle) {
36
+ if (style === "top-header-bottom")
37
+ return {
38
+ header: "both" as const,
39
+ grid: "none" as const,
40
+ border: "none" as const,
41
+ };
42
+ if (style === "row-rules")
43
+ return {
44
+ header: "row" as const,
45
+ grid: "rows" as const,
46
+ border: "none" as const,
47
+ };
48
+ if (style === "full-grid")
49
+ return {
50
+ header: "row" as const,
51
+ grid: "all" as const,
52
+ border: "none" as const,
53
+ };
54
+ return {
55
+ header: "row" as const,
56
+ grid: "none" as const,
57
+ border: "none" as const,
58
+ };
59
+ }
60
+
61
+ export function tableStyleForAppearance(appearance?: {
62
+ header?: string;
63
+ grid?: string;
64
+ border?: string;
65
+ }): TableStyle {
66
+ if (appearance?.grid === "all" || appearance?.header === "column")
67
+ return "full-grid";
68
+ if (appearance?.grid === "rows") return "row-rules";
69
+ if (appearance?.header === "both" || appearance?.border === "outline")
70
+ return "top-header-bottom";
71
+ return "header-rule";
72
+ }
73
+
74
+ export const appearanceOptions: Record<
75
+ (typeof componentKinds)[number],
76
+ Record<string, readonly string[]>
77
+ > = {
78
+ "text-block": {
79
+ role: ["title", "subtitle", "body", "caption", "footnote"],
80
+ alignment,
81
+ border,
82
+ layout: [
83
+ "single",
84
+ "two-column",
85
+ "three-column",
86
+ "two-plus-two",
87
+ "four-column",
88
+ "one-plus-three",
89
+ "three-plus-one",
90
+ ],
91
+ orientation: ["horizontal", "vertical"],
92
+ purpose: ["narrative", "comparison", "emphasis"],
93
+ treatment: ["plain", "subtle", "strong"],
94
+ logicalOrder: [
95
+ "none",
96
+ "parallel",
97
+ "progressive",
98
+ "cyclical",
99
+ "general-to-specific",
100
+ "hierarchical",
101
+ ],
102
+ titleStyle,
103
+ rule: ["none", "top", "bottom"],
104
+ },
105
+ chart: {
106
+ template: chartTemplates,
107
+ selection: ["auto", "explicit"],
108
+ border,
109
+ colorScheme,
110
+ orientation: ["horizontal", "vertical"],
111
+ emphasis,
112
+ density,
113
+ legend: ["none", "top", "right", "bottom"],
114
+ },
115
+ diagram: {
116
+ type: diagramTypes,
117
+ selection: ["auto", "explicit"],
118
+ border,
119
+ colorScheme,
120
+ emphasis,
121
+ density,
122
+ },
123
+ image: { border, fit: ["cover", "contain"] },
124
+ table: {
125
+ header: ["row", "none", "column", "both"],
126
+ grid: ["none", "rows", "all"],
127
+ border,
128
+ colorScheme,
129
+ density,
130
+ },
131
+ };
132
+ const text = { type: "string", minLength: 1, pattern: "\\S" };
133
+ const string = { type: "string" };
134
+ const enumeration = (values: readonly string[]) => ({
135
+ type: "string",
136
+ enum: values,
137
+ });
138
+ const object = (
139
+ properties: Record<string, unknown>,
140
+ required = Object.keys(properties),
141
+ ) => ({ type: "object", properties, required, additionalProperties: false });
142
+ const array = (items: unknown, minItems = 0) => ({
143
+ type: "array",
144
+ items,
145
+ minItems,
146
+ });
147
+ const ids = { ...array(text, 1), uniqueItems: true };
148
+ const endpoint = object({ nodeId: text, slotId: text }, ["nodeId"]);
149
+ const rect = object({
150
+ x: { type: "number", minimum: 0, maximum: 4096 },
151
+ y: { type: "number", minimum: 0, maximum: 4096 },
152
+ width: { type: "number", exclusiveMinimum: 0, maximum: 4096 },
153
+ height: { type: "number", exclusiveMinimum: 0, maximum: 4096 },
154
+ });
155
+ const visualBase = {
156
+ viewBox: object({
157
+ x: { type: "number" },
158
+ y: { type: "number" },
159
+ width: { type: "number", exclusiveMinimum: 0, maximum: 100000 },
160
+ height: { type: "number", exclusiveMinimum: 0, maximum: 100000 },
161
+ }),
162
+ description: { ...text, maxLength: 1000 },
163
+ fit: enumeration(["contain", "cover", "stretch"]),
164
+ };
165
+ const customVisual = {
166
+ oneOf: [
167
+ object(
168
+ {
169
+ format: { const: "vector" },
170
+ elements: {
171
+ ...array(
172
+ object(
173
+ {
174
+ id: text,
175
+ kind: enumeration(vectorElementKinds),
176
+ parentId: text,
177
+ attributes: {
178
+ type: "object",
179
+ maxProperties: 40,
180
+ properties: Object.fromEntries(["fill", "stroke", "color", "font-family"].map((name) => [name, {
181
+ anyOf: [literalVectorAttribute, { enum: tokensForAttribute(name).map((token) => `theme:${token}`) }],
182
+ }])),
183
+ additionalProperties: literalVectorAttribute,
184
+ },
185
+ text: { type: "string", maxLength: 10000 },
186
+ },
187
+ ["id", "kind", "attributes"],
188
+ ),
189
+ 0,
190
+ ),
191
+ maxItems: 2000,
192
+ },
193
+ ...visualBase,
194
+ },
195
+ ["format", "elements", "viewBox", "description"],
196
+ ),
197
+ object(
198
+ {
199
+ format: { const: "svg" },
200
+ source: {
201
+ type: "string",
202
+ minLength: 1,
203
+ maxLength: 500000,
204
+ pattern: "^\\s*<svg(?:\\s|>)",
205
+ },
206
+ ...visualBase,
207
+ },
208
+ ["format", "source", "viewBox", "description"],
209
+ ),
210
+ ],
211
+ };
212
+ const topology = object({
213
+ kind: { const: "explicit" },
214
+ nodes: {
215
+ ...array(object({
216
+ id: text,
217
+ slotId: text,
218
+ visible: { type: "boolean" },
219
+ preferredRect: rect,
220
+ primitive: {
221
+ oneOf: [
222
+ object({
223
+ kind: { const: "shape" },
224
+ shape: enumeration(["rectangle", "rounded-rectangle", "circle", "line"]),
225
+ color: enumeration(["ink", "muted", "accent"]),
226
+ fill: enumeration(["none", "solid", "wash"]),
227
+ }, ["kind", "shape"]),
228
+ object({
229
+ kind: { const: "icon" },
230
+ name: text,
231
+ style: enumeration(["outline", "filled"]),
232
+ color: enumeration(["ink", "muted", "accent"]),
233
+ }, ["kind"]),
234
+ ],
235
+ },
236
+ }, ["id", "slotId"]), 1),
237
+ maxItems: 24,
238
+ },
239
+ edges: {
240
+ ...array(
241
+ object({ from: text, to: text, label: { ...text, maxLength: 120 } }, [
242
+ "from",
243
+ "to",
244
+ ]),
245
+ 0,
246
+ ),
247
+ maxItems: 48,
248
+ },
249
+ });
250
+ const components = componentKinds.map((kind) => {
251
+ const options = appearanceOptions[kind];
252
+ const requiredAppearance = [
253
+ ...(kind === "chart" ? ["template"] : []),
254
+ ...(kind === "text-block" ? ["role"] : []),
255
+ ...(kind === "diagram" ? ["type"] : []),
256
+ ];
257
+ const component = object(
258
+ {
259
+ id: text,
260
+ kind: { const: kind },
261
+ preferredRect: rect,
262
+ slotIds: ids,
263
+ intent: string,
264
+ customVisual,
265
+ ...(kind === "text-block" ? {
266
+ content: { type: "string", maxLength: 50000 },
267
+ textStyle: object({
268
+ size: { type: "number", minimum: 8, maximum: 240 },
269
+ weight: { enum: [400, 500, 600] },
270
+ lineHeight: { type: "number", minimum: 1, maximum: 3 },
271
+ color: enumeration(["ink", "muted", "accent"]),
272
+ font: enumeration(["heading", "body"]),
273
+ }, []),
274
+ } : {}),
275
+ appearance: object(
276
+ Object.fromEntries(
277
+ Object.entries(options).map(([key, values]) => [
278
+ key,
279
+ enumeration(values),
280
+ ]),
281
+ ),
282
+ requiredAppearance,
283
+ ),
284
+ ...(kind === "diagram" || kind === "chart" ? { topology } : {}),
285
+ },
286
+ [
287
+ "id",
288
+ "kind",
289
+ "preferredRect",
290
+ "slotIds",
291
+ ...(requiredAppearance.length ? ["appearance"] : []),
292
+ ],
293
+ );
294
+ return kind === "chart"
295
+ ? {
296
+ ...component,
297
+ allOf: [{
298
+ if: { required: ["topology"] },
299
+ then: {
300
+ properties: {
301
+ appearance: {
302
+ properties: { template: { const: "sankey" } },
303
+ },
304
+ },
305
+ },
306
+ }],
307
+ }
308
+ : component;
309
+ });
310
+ export const schema = {
311
+ $schema: "https://json-schema.org/draft/2020-12/schema",
312
+ $id: "https://vcfgdev.github.io/konpeki/composition/v18/schema.json",
313
+ title: compositionSchema,
314
+ ...object(
315
+ {
316
+ schema: { const: compositionSchema },
317
+ title: text,
318
+ authoringMode: enumeration(authoringModes),
319
+ theme: object({
320
+ id: enumeration(themeIds),
321
+ mode: enumeration(themeModes),
322
+ }),
323
+ slides: array(
324
+ object(
325
+ {
326
+ id: text,
327
+ name: text,
328
+ canvas: object({
329
+ width: { type: "integer", minimum: 256, maximum: 4096 },
330
+ height: { type: "integer", minimum: 256, maximum: 4096 },
331
+ }),
332
+ innerPadding: object({
333
+ top: { type: "number", minimum: 0 },
334
+ right: { type: "number", minimum: 0 },
335
+ bottom: { type: "number", minimum: 0 },
336
+ left: { type: "number", minimum: 0 },
337
+ }),
338
+ pageNumber: object({
339
+ style: enumeration(["none", "01", "01/02"]),
340
+ color: enumeration(["ink", "muted", "accent"]),
341
+ }),
342
+ audience: text,
343
+ question: text,
344
+ intendedViewingSize: enumeration(["presentation", "social", "article", "custom"]),
345
+ contentSlots: array(
346
+ {
347
+ oneOf: [
348
+ object({
349
+ id: text,
350
+ label: text,
351
+ required: { type: "boolean" },
352
+ instruction: text,
353
+ role: enumeration([
354
+ "takeaway",
355
+ "body",
356
+ "evidence",
357
+ "comparison-item",
358
+ "process-step",
359
+ "entity",
360
+ "image",
361
+ "table",
362
+ ]),
363
+ }),
364
+ object({
365
+ id: text,
366
+ label: text,
367
+ required: { type: "boolean" },
368
+ instruction: text,
369
+ role: enumeration(["qualification", "source"]),
370
+ targets: ids,
371
+ }),
372
+ ],
373
+ },
374
+ 0,
375
+ ),
376
+ components: array({ oneOf: components }, 0),
377
+ groups: array(
378
+ object({ id: text, label: string, childIds: ids }, [
379
+ "id",
380
+ "childIds",
381
+ ]),
382
+ ),
383
+ readingOrder: {
384
+ ...array(
385
+ object({ kind: enumeration(["component", "group"]), id: text }),
386
+ 0,
387
+ ),
388
+ uniqueItems: true,
389
+ },
390
+ paintOrder: { ...array(text, 0), uniqueItems: true },
391
+ relationships: array(
392
+ object(
393
+ {
394
+ id: text,
395
+ kind: enumeration([
396
+ "flows-to",
397
+ "qualifies",
398
+ "compares-with",
399
+ "depends-on",
400
+ "connects-to",
401
+ ]),
402
+ direction: enumeration([
403
+ "forward",
404
+ "bidirectional",
405
+ "undirected",
406
+ ]),
407
+ from: endpoint,
408
+ to: endpoint,
409
+ label: string,
410
+ },
411
+ ["id", "kind", "direction", "from", "to"],
412
+ ),
413
+ ),
414
+ },
415
+ [
416
+ "id",
417
+ "name",
418
+ "canvas",
419
+ "audience",
420
+ "question",
421
+ "intendedViewingSize",
422
+ "contentSlots",
423
+ "components",
424
+ "groups",
425
+ "readingOrder",
426
+ "paintOrder",
427
+ "relationships",
428
+ ],
429
+ ),
430
+ 1,
431
+ ),
432
+ },
433
+ ["schema", "title", "slides"],
434
+ ),
435
+ };
@@ -0,0 +1,16 @@
1
+ /** Explicit opt-in bindings. Literal SVG attributes always remain fixed. */
2
+ export const colorTokens = ["ink", "muted", "background", "surface", "divider", "accent", "on-accent", "wash"] as const;
3
+ export const fontTokens = ["heading-font", "body-font"] as const;
4
+ export type ThemeToken = typeof colorTokens[number] | typeof fontTokens[number];
5
+ export function tokensForAttribute(name: string): readonly ThemeToken[] {
6
+ return name === "font-family" ? fontTokens
7
+ : ["fill", "stroke", "color"].includes(name) ? colorTokens : [];
8
+ }
9
+ export function validThemeBinding(name: string, value: string | number): boolean {
10
+ return typeof value !== "string" || !value.startsWith("theme:") ||
11
+ tokensForAttribute(name).some((token) => value === `theme:${token}`);
12
+ }
13
+ export function resolveVectorAttribute(name: string, value: string | number): string | number {
14
+ return typeof value === "string" && value.startsWith("theme:") && validThemeBinding(name, value)
15
+ ? `var(--vector-${value.slice(6)})` : value;
16
+ }