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,1685 @@
1
+ #!/usr/bin/env node
2
+ import { createHash, randomBytes } from "node:crypto";
3
+ import { access, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { createServer } from "vite";
7
+ import { tmpdir } from "node:os";
8
+ import { Ajv2020 } from "ajv/dist/2020.js";
9
+ import "@xmldom/xmldom";
10
+ //#region composition/types.ts
11
+ var compositionSchema = "konpeki-composition/v18";
12
+ var legacyCompositionSchemas = [
13
+ "konpeki-composition/v1",
14
+ "konpeki-composition/v2",
15
+ "konpeki-composition/v3",
16
+ "konpeki-composition/v4",
17
+ "konpeki-composition/v5",
18
+ "konpeki-composition/v6",
19
+ "konpeki-composition/v7",
20
+ "konpeki-composition/v8",
21
+ "konpeki-composition/v9",
22
+ "konpeki-composition/v10",
23
+ "konpeki-composition/v11",
24
+ "konpeki-composition/v12",
25
+ "konpeki-composition/v13",
26
+ "konpeki-composition/v14",
27
+ "konpeki-composition/v15",
28
+ "konpeki-composition/v16",
29
+ "konpeki-composition/v17"
30
+ ];
31
+ var canvasSize = {
32
+ width: 1920,
33
+ height: 1080
34
+ };
35
+ var canvasPadding = {
36
+ top: 72,
37
+ right: 112,
38
+ bottom: 0,
39
+ left: 112
40
+ };
41
+ var authoringModes = ["default", "dynamic"];
42
+ var themeIds = [
43
+ "plex",
44
+ "precision",
45
+ "editorial",
46
+ "blue-cyan",
47
+ "orange-coral",
48
+ "yellow",
49
+ "green",
50
+ "graphite"
51
+ ];
52
+ var themeModes = ["paper", "night"];
53
+ var vectorElementKinds = [
54
+ "g",
55
+ "rect",
56
+ "circle",
57
+ "ellipse",
58
+ "line",
59
+ "polyline",
60
+ "polygon",
61
+ "path",
62
+ "text",
63
+ "tspan"
64
+ ];
65
+ var componentKinds = [
66
+ "text-block",
67
+ "chart",
68
+ "diagram",
69
+ "image",
70
+ "table"
71
+ ];
72
+ //#endregion
73
+ //#region composition/visualizations.ts
74
+ var diagramDefinitions = {
75
+ architecture: {
76
+ label: "Architecture",
77
+ category: "Systems & technical structure",
78
+ layout: "hub-and-spoke",
79
+ slotRole: "entity",
80
+ expression: "Show system boundaries, responsibilities, interfaces, and directional communication without implying physical deployment."
81
+ },
82
+ "it-current-state": {
83
+ label: "IT current state",
84
+ category: "Systems & technical structure",
85
+ layout: "hub-and-spoke",
86
+ slotRole: "entity",
87
+ expression: "Show the systems that exist today, their ownership, integration paths, friction, duplication, and known constraints."
88
+ },
89
+ "high-level": {
90
+ label: "High-level overview",
91
+ category: "Systems & technical structure",
92
+ layout: "layered",
93
+ slotRole: "entity",
94
+ expression: "Reduce the system to a small number of named capabilities and major relationships; suppress implementation detail."
95
+ },
96
+ deployment: {
97
+ label: "Deployment",
98
+ category: "Systems & technical structure",
99
+ layout: "layered",
100
+ slotRole: "entity",
101
+ expression: "Place software artifacts inside the environments, hosts, zones, or runtimes where they execute and show cross-zone communication."
102
+ },
103
+ flowchart: {
104
+ label: "Flowchart",
105
+ category: "Flow, interaction & behavior",
106
+ layout: "branching",
107
+ slotRole: "process-step",
108
+ expression: "Distinguish actions, decisions, merge points, and start or end states; label decision branches explicitly."
109
+ },
110
+ sequence: {
111
+ label: "Sequence",
112
+ category: "Flow, interaction & behavior",
113
+ layout: "request-flow",
114
+ slotRole: "entity",
115
+ expression: "Order actors horizontally and messages vertically through time; distinguish calls, returns, asynchronous messages, and branches."
116
+ },
117
+ "state-machine": {
118
+ label: "State machine",
119
+ category: "Flow, interaction & behavior",
120
+ layout: "branching",
121
+ slotRole: "entity",
122
+ expression: "Treat nodes as legal states and edges as guarded transitions; preserve initial, terminal, retry, and invalid transition semantics."
123
+ },
124
+ swimlane: {
125
+ label: "Swimlane",
126
+ category: "Flow, interaction & behavior",
127
+ layout: "layered",
128
+ slotRole: "process-step",
129
+ expression: "Partition steps by responsible actor or team and make every cross-lane handoff visible."
130
+ },
131
+ process: {
132
+ label: "Process",
133
+ category: "Flow, interaction & behavior",
134
+ layout: "linear",
135
+ slotRole: "process-step",
136
+ expression: "Show ordered work from trigger to outcome, including branches, loops, owners, and exceptional paths when supplied."
137
+ },
138
+ "data-flow": {
139
+ label: "Data flow",
140
+ category: "Flow, interaction & behavior",
141
+ layout: "hub-and-spoke",
142
+ slotRole: "entity",
143
+ expression: "Show data producers, transformations, stores, and consumers; label payloads and direction independently from control flow."
144
+ },
145
+ "dp-integration": {
146
+ label: "Data-product integration",
147
+ category: "Flow, interaction & behavior",
148
+ layout: "request-flow",
149
+ slotRole: "entity",
150
+ expression: "Show producer and consumer responsibilities, contracts, integration boundaries, and exchange direction for each data product."
151
+ },
152
+ "er-data-model": {
153
+ label: "ER / data model",
154
+ category: "Data & software modeling",
155
+ layout: "hub-and-spoke",
156
+ slotRole: "entity",
157
+ expression: "Represent entities with key attributes and label relationship cardinality and optionality at both endpoints."
158
+ },
159
+ medallion: {
160
+ label: "Medallion",
161
+ category: "Data & software modeling",
162
+ layout: "layered",
163
+ slotRole: "entity",
164
+ expression: "Arrange data products by progressive quality tier and show transformations from raw through refined to consumption-ready data."
165
+ },
166
+ "dp-security-matrix": {
167
+ label: "Data-product security matrix",
168
+ category: "Data & software modeling",
169
+ layout: "layered",
170
+ slotRole: "entity",
171
+ expression: "Use rows and columns as explicit subject and resource axes; encode permissions or controls at their intersections."
172
+ },
173
+ "dependency-graph": {
174
+ label: "Dependency graph",
175
+ category: "Data & software modeling",
176
+ layout: "branching",
177
+ slotRole: "entity",
178
+ expression: "Preserve many-parent fan-in, shared dependencies, and cycles; orient every dependency edge consistently."
179
+ },
180
+ "uml-class": {
181
+ label: "UML class",
182
+ category: "Data & software modeling",
183
+ layout: "hub-and-spoke",
184
+ slotRole: "entity",
185
+ expression: "Keep class names, attributes, and operations in separate compartments and preserve inheritance, aggregation, and composition notation."
186
+ },
187
+ "database-schema": {
188
+ label: "Database schema",
189
+ category: "Data & software modeling",
190
+ layout: "hub-and-spoke",
191
+ slotRole: "entity",
192
+ expression: "Show tables with typed columns, keys, constraints, and column-level foreign-key endpoints."
193
+ },
194
+ nested: {
195
+ label: "Nested containment",
196
+ category: "Hierarchy, containment & organization",
197
+ layout: "layered",
198
+ slotRole: "entity",
199
+ expression: "Use enclosure to express scope and ownership; containment must not be replaced by proximity alone."
200
+ },
201
+ tree: {
202
+ label: "Tree",
203
+ category: "Hierarchy, containment & organization",
204
+ layout: "branching",
205
+ slotRole: "entity",
206
+ expression: "Show a strict one-parent hierarchy with consistent depth and sibling ordering."
207
+ },
208
+ "org-chart": {
209
+ label: "Org chart",
210
+ category: "Hierarchy, containment & organization",
211
+ layout: "branching",
212
+ slotRole: "entity",
213
+ expression: "Show reporting structure, role ownership, and escalation paths; do not imply technical dependency."
214
+ },
215
+ "layer-stack": {
216
+ label: "Layer stack",
217
+ category: "Hierarchy, containment & organization",
218
+ layout: "layered",
219
+ slotRole: "entity",
220
+ expression: "Order abstraction or responsibility layers consistently and show only legitimate cross-layer dependencies."
221
+ },
222
+ "pyramid-funnel": {
223
+ label: "Pyramid / funnel",
224
+ category: "Hierarchy, containment & organization",
225
+ layout: "layered",
226
+ slotRole: "entity",
227
+ expression: "Use width and vertical order to encode rank, concentration, or drop-off; state which meaning applies."
228
+ },
229
+ timeline: {
230
+ label: "Timeline",
231
+ category: "Time & work",
232
+ layout: "linear",
233
+ slotRole: "process-step",
234
+ expression: "Place dated events on an honest temporal axis and preserve meaningful interval differences."
235
+ },
236
+ gantt: {
237
+ label: "Gantt",
238
+ category: "Time & work",
239
+ layout: "layered",
240
+ slotRole: "process-step",
241
+ expression: "Encode task start, duration, overlap, milestones, dependencies, and ownership against a shared time scale."
242
+ },
243
+ kanban: {
244
+ label: "Kanban",
245
+ category: "Time & work",
246
+ layout: "layered",
247
+ slotRole: "entity",
248
+ expression: "Use columns as workflow states, preserve card ownership and priority, and show WIP limits when supplied."
249
+ },
250
+ "user-journey": {
251
+ label: "User journey",
252
+ category: "Time & work",
253
+ layout: "linear",
254
+ slotRole: "process-step",
255
+ expression: "Order experience stages and align actions, touchpoints, pain points, and sentiment to each stage."
256
+ },
257
+ "story-map": {
258
+ label: "Story map",
259
+ category: "Time & work",
260
+ layout: "layered",
261
+ slotRole: "entity",
262
+ expression: "Use columns for narrative activity order and rows for release slices; preserve the release-cut line."
263
+ },
264
+ quadrant: {
265
+ label: "Quadrant",
266
+ category: "Quantitative & analytical",
267
+ layout: "layered",
268
+ slotRole: "entity",
269
+ expression: "Position items against two named axes with meaningful scales and make quadrant interpretation explicit."
270
+ },
271
+ "loop-flywheel": {
272
+ label: "Loop / flywheel",
273
+ category: "Conceptual & strategic",
274
+ layout: "cyclical",
275
+ slotRole: "process-step",
276
+ expression: "Show a reinforcing cycle with explicit direction and explain how each stage increases momentum or feeds the next."
277
+ },
278
+ venn: {
279
+ label: "Venn",
280
+ category: "Conceptual & strategic",
281
+ layout: "hub-and-spoke",
282
+ slotRole: "entity",
283
+ expression: "Use overlapping regions to encode set membership and label every meaningful intersection."
284
+ },
285
+ fishbone: {
286
+ label: "Fishbone",
287
+ category: "Conceptual & strategic",
288
+ layout: "branching",
289
+ slotRole: "entity",
290
+ expression: "Group candidate causes by category along branches that converge on one clearly stated effect."
291
+ },
292
+ "wardley-map": {
293
+ label: "Wardley map",
294
+ category: "Conceptual & strategic",
295
+ layout: "layered",
296
+ slotRole: "entity",
297
+ expression: "Position components by user visibility and evolution, then show value-chain dependencies and movement without treating the map as runtime architecture."
298
+ }
299
+ };
300
+ var diagramTypes = Object.keys(diagramDefinitions);
301
+ var chartTemplates = Object.keys({
302
+ "grouped-bar": {
303
+ label: "Grouped bar",
304
+ expression: "Compare quantitative values across categories with a shared zero baseline; use grouped series only when each category has comparable values.",
305
+ source: "diagram-design"
306
+ },
307
+ line: {
308
+ label: "Line",
309
+ expression: "Encode values on a shared scale and connect observations in meaningful order, usually time; preserve intervals and do not imply missing measurements.",
310
+ source: "diagram-design"
311
+ },
312
+ pie: {
313
+ label: "Pie",
314
+ expression: "Show a small set of mutually exclusive parts of one whole; preserve the total and label values directly when comparison by angle is difficult.",
315
+ source: "konpeki"
316
+ },
317
+ "annotated-detail": {
318
+ label: "Annotated detail",
319
+ expression: "Focus on one visual detail and connect concise annotations to exact evidence without obscuring the underlying image or chart.",
320
+ source: "konpeki"
321
+ },
322
+ radar: {
323
+ label: "Radar",
324
+ expression: "Compare multivariate profiles on consistently normalized radial axes; preserve axis order, scale, and direction across every series.",
325
+ source: "diagram-design"
326
+ },
327
+ treemap: {
328
+ label: "Treemap",
329
+ expression: "Encode part-to-whole magnitude by rectangle area within explicit hierarchy; preserve parent totals and avoid implying rank from position.",
330
+ source: "diagram-design"
331
+ },
332
+ scatter: {
333
+ label: "Scatter",
334
+ expression: "Position observations by two measured quantitative variables; preserve axis scales, units, outliers, and uncertainty about correlation.",
335
+ source: "diagram-design"
336
+ },
337
+ sankey: {
338
+ label: "Sankey",
339
+ expression: "Encode conserved quantity through splits and merges using proportional ribbon width; preserve node totals, flow direction, and units.",
340
+ source: "diagram-design"
341
+ }
342
+ });
343
+ Array.from(new Set(Object.values(diagramDefinitions).map((definition) => definition.category)));
344
+ function migrateLegacyDiagramType(type, layout) {
345
+ if (typeof type !== "string" || typeof layout !== "string") return void 0;
346
+ return {
347
+ "process/linear": "process",
348
+ "process/branching": "flowchart",
349
+ "process/cyclical": "loop-flywheel",
350
+ "system/layered": "layer-stack",
351
+ "system/request-flow": "sequence",
352
+ "system/hub-and-spoke": "architecture"
353
+ }[`${type}/${layout}`];
354
+ }
355
+ //#endregion
356
+ //#region composition/theme-tokens.ts
357
+ /** Explicit opt-in bindings. Literal SVG attributes always remain fixed. */
358
+ var colorTokens = [
359
+ "ink",
360
+ "muted",
361
+ "background",
362
+ "surface",
363
+ "divider",
364
+ "accent",
365
+ "on-accent",
366
+ "wash"
367
+ ];
368
+ var fontTokens = ["heading-font", "body-font"];
369
+ function tokensForAttribute(name) {
370
+ return name === "font-family" ? fontTokens : [
371
+ "fill",
372
+ "stroke",
373
+ "color"
374
+ ].includes(name) ? colorTokens : [];
375
+ }
376
+ function validThemeBinding(name, value) {
377
+ return typeof value !== "string" || !value.startsWith("theme:") || tokensForAttribute(name).some((token) => value === `theme:${token}`);
378
+ }
379
+ //#endregion
380
+ //#region composition/schema.ts
381
+ var literalVectorAttribute = { oneOf: [{
382
+ type: "string",
383
+ maxLength: 1e4,
384
+ pattern: "^(?!theme:)"
385
+ }, { type: "number" }] };
386
+ var alignment = [
387
+ "start",
388
+ "center",
389
+ "end"
390
+ ];
391
+ var titleStyle = ["plain", "prominent"];
392
+ var border = [
393
+ "none",
394
+ "outline",
395
+ "filled"
396
+ ];
397
+ var colorScheme = [
398
+ "monochrome",
399
+ "accent-with-muted-context",
400
+ "categorical",
401
+ "sequential",
402
+ "status"
403
+ ];
404
+ var emphasis = [
405
+ "none",
406
+ "primary",
407
+ "latest-series",
408
+ "exception"
409
+ ];
410
+ var density = ["sparse", "standard"];
411
+ var appearanceOptions = {
412
+ "text-block": {
413
+ role: [
414
+ "title",
415
+ "subtitle",
416
+ "body",
417
+ "caption",
418
+ "footnote"
419
+ ],
420
+ alignment,
421
+ border,
422
+ layout: [
423
+ "single",
424
+ "two-column",
425
+ "three-column",
426
+ "two-plus-two",
427
+ "four-column",
428
+ "one-plus-three",
429
+ "three-plus-one"
430
+ ],
431
+ orientation: ["horizontal", "vertical"],
432
+ purpose: [
433
+ "narrative",
434
+ "comparison",
435
+ "emphasis"
436
+ ],
437
+ treatment: [
438
+ "plain",
439
+ "subtle",
440
+ "strong"
441
+ ],
442
+ logicalOrder: [
443
+ "none",
444
+ "parallel",
445
+ "progressive",
446
+ "cyclical",
447
+ "general-to-specific",
448
+ "hierarchical"
449
+ ],
450
+ titleStyle,
451
+ rule: [
452
+ "none",
453
+ "top",
454
+ "bottom"
455
+ ]
456
+ },
457
+ chart: {
458
+ template: chartTemplates,
459
+ selection: ["auto", "explicit"],
460
+ border,
461
+ colorScheme,
462
+ orientation: ["horizontal", "vertical"],
463
+ emphasis,
464
+ density,
465
+ legend: [
466
+ "none",
467
+ "top",
468
+ "right",
469
+ "bottom"
470
+ ]
471
+ },
472
+ diagram: {
473
+ type: diagramTypes,
474
+ selection: ["auto", "explicit"],
475
+ border,
476
+ colorScheme,
477
+ emphasis,
478
+ density
479
+ },
480
+ image: {
481
+ border,
482
+ fit: ["cover", "contain"]
483
+ },
484
+ table: {
485
+ header: [
486
+ "row",
487
+ "none",
488
+ "column",
489
+ "both"
490
+ ],
491
+ grid: [
492
+ "none",
493
+ "rows",
494
+ "all"
495
+ ],
496
+ border,
497
+ colorScheme,
498
+ density
499
+ }
500
+ };
501
+ var text = {
502
+ type: "string",
503
+ minLength: 1,
504
+ pattern: "\\S"
505
+ };
506
+ var string = { type: "string" };
507
+ var enumeration = (values) => ({
508
+ type: "string",
509
+ enum: values
510
+ });
511
+ var object = (properties, required = Object.keys(properties)) => ({
512
+ type: "object",
513
+ properties,
514
+ required,
515
+ additionalProperties: false
516
+ });
517
+ var array = (items, minItems = 0) => ({
518
+ type: "array",
519
+ items,
520
+ minItems
521
+ });
522
+ var ids = {
523
+ ...array(text, 1),
524
+ uniqueItems: true
525
+ };
526
+ var endpoint = object({
527
+ nodeId: text,
528
+ slotId: text
529
+ }, ["nodeId"]);
530
+ var rect = object({
531
+ x: {
532
+ type: "number",
533
+ minimum: 0,
534
+ maximum: 4096
535
+ },
536
+ y: {
537
+ type: "number",
538
+ minimum: 0,
539
+ maximum: 4096
540
+ },
541
+ width: {
542
+ type: "number",
543
+ exclusiveMinimum: 0,
544
+ maximum: 4096
545
+ },
546
+ height: {
547
+ type: "number",
548
+ exclusiveMinimum: 0,
549
+ maximum: 4096
550
+ }
551
+ });
552
+ var visualBase = {
553
+ viewBox: object({
554
+ x: { type: "number" },
555
+ y: { type: "number" },
556
+ width: {
557
+ type: "number",
558
+ exclusiveMinimum: 0,
559
+ maximum: 1e5
560
+ },
561
+ height: {
562
+ type: "number",
563
+ exclusiveMinimum: 0,
564
+ maximum: 1e5
565
+ }
566
+ }),
567
+ description: {
568
+ ...text,
569
+ maxLength: 1e3
570
+ },
571
+ fit: enumeration([
572
+ "contain",
573
+ "cover",
574
+ "stretch"
575
+ ])
576
+ };
577
+ var customVisual = { oneOf: [object({
578
+ format: { const: "vector" },
579
+ elements: {
580
+ ...array(object({
581
+ id: text,
582
+ kind: enumeration(vectorElementKinds),
583
+ parentId: text,
584
+ attributes: {
585
+ type: "object",
586
+ maxProperties: 40,
587
+ properties: Object.fromEntries([
588
+ "fill",
589
+ "stroke",
590
+ "color",
591
+ "font-family"
592
+ ].map((name) => [name, { anyOf: [literalVectorAttribute, { enum: tokensForAttribute(name).map((token) => `theme:${token}`) }] }])),
593
+ additionalProperties: literalVectorAttribute
594
+ },
595
+ text: {
596
+ type: "string",
597
+ maxLength: 1e4
598
+ }
599
+ }, [
600
+ "id",
601
+ "kind",
602
+ "attributes"
603
+ ]), 0),
604
+ maxItems: 2e3
605
+ },
606
+ ...visualBase
607
+ }, [
608
+ "format",
609
+ "elements",
610
+ "viewBox",
611
+ "description"
612
+ ]), object({
613
+ format: { const: "svg" },
614
+ source: {
615
+ type: "string",
616
+ minLength: 1,
617
+ maxLength: 5e5,
618
+ pattern: "^\\s*<svg(?:\\s|>)"
619
+ },
620
+ ...visualBase
621
+ }, [
622
+ "format",
623
+ "source",
624
+ "viewBox",
625
+ "description"
626
+ ])] };
627
+ var topology = object({
628
+ kind: { const: "explicit" },
629
+ nodes: {
630
+ ...array(object({
631
+ id: text,
632
+ slotId: text,
633
+ visible: { type: "boolean" },
634
+ preferredRect: rect,
635
+ primitive: { oneOf: [object({
636
+ kind: { const: "shape" },
637
+ shape: enumeration([
638
+ "rectangle",
639
+ "rounded-rectangle",
640
+ "circle",
641
+ "line"
642
+ ]),
643
+ color: enumeration([
644
+ "ink",
645
+ "muted",
646
+ "accent"
647
+ ]),
648
+ fill: enumeration([
649
+ "none",
650
+ "solid",
651
+ "wash"
652
+ ])
653
+ }, ["kind", "shape"]), object({
654
+ kind: { const: "icon" },
655
+ name: text,
656
+ style: enumeration(["outline", "filled"]),
657
+ color: enumeration([
658
+ "ink",
659
+ "muted",
660
+ "accent"
661
+ ])
662
+ }, ["kind"])] }
663
+ }, ["id", "slotId"]), 1),
664
+ maxItems: 24
665
+ },
666
+ edges: {
667
+ ...array(object({
668
+ from: text,
669
+ to: text,
670
+ label: {
671
+ ...text,
672
+ maxLength: 120
673
+ }
674
+ }, ["from", "to"]), 0),
675
+ maxItems: 48
676
+ }
677
+ });
678
+ var components = componentKinds.map((kind) => {
679
+ const options = appearanceOptions[kind];
680
+ const requiredAppearance = [
681
+ ...kind === "chart" ? ["template"] : [],
682
+ ...kind === "text-block" ? ["role"] : [],
683
+ ...kind === "diagram" ? ["type"] : []
684
+ ];
685
+ const component = object({
686
+ id: text,
687
+ kind: { const: kind },
688
+ preferredRect: rect,
689
+ slotIds: ids,
690
+ intent: string,
691
+ customVisual,
692
+ ...kind === "text-block" ? {
693
+ content: {
694
+ type: "string",
695
+ maxLength: 5e4
696
+ },
697
+ textStyle: object({
698
+ size: {
699
+ type: "number",
700
+ minimum: 8,
701
+ maximum: 240
702
+ },
703
+ weight: { enum: [
704
+ 400,
705
+ 500,
706
+ 600
707
+ ] },
708
+ lineHeight: {
709
+ type: "number",
710
+ minimum: 1,
711
+ maximum: 3
712
+ },
713
+ color: enumeration([
714
+ "ink",
715
+ "muted",
716
+ "accent"
717
+ ]),
718
+ font: enumeration(["heading", "body"])
719
+ }, [])
720
+ } : {},
721
+ appearance: object(Object.fromEntries(Object.entries(options).map(([key, values]) => [key, enumeration(values)])), requiredAppearance),
722
+ ...kind === "diagram" || kind === "chart" ? { topology } : {}
723
+ }, [
724
+ "id",
725
+ "kind",
726
+ "preferredRect",
727
+ "slotIds",
728
+ ...requiredAppearance.length ? ["appearance"] : []
729
+ ]);
730
+ return kind === "chart" ? {
731
+ ...component,
732
+ allOf: [{
733
+ if: { required: ["topology"] },
734
+ then: { properties: { appearance: { properties: { template: { const: "sankey" } } } } }
735
+ }]
736
+ } : component;
737
+ });
738
+ var schema = {
739
+ $schema: "https://json-schema.org/draft/2020-12/schema",
740
+ $id: "https://vcfgdev.github.io/konpeki/composition/v18/schema.json",
741
+ title: compositionSchema,
742
+ ...object({
743
+ schema: { const: compositionSchema },
744
+ title: text,
745
+ authoringMode: enumeration(authoringModes),
746
+ theme: object({
747
+ id: enumeration(themeIds),
748
+ mode: enumeration(themeModes)
749
+ }),
750
+ slides: array(object({
751
+ id: text,
752
+ name: text,
753
+ canvas: object({
754
+ width: {
755
+ type: "integer",
756
+ minimum: 256,
757
+ maximum: 4096
758
+ },
759
+ height: {
760
+ type: "integer",
761
+ minimum: 256,
762
+ maximum: 4096
763
+ }
764
+ }),
765
+ innerPadding: object({
766
+ top: {
767
+ type: "number",
768
+ minimum: 0
769
+ },
770
+ right: {
771
+ type: "number",
772
+ minimum: 0
773
+ },
774
+ bottom: {
775
+ type: "number",
776
+ minimum: 0
777
+ },
778
+ left: {
779
+ type: "number",
780
+ minimum: 0
781
+ }
782
+ }),
783
+ pageNumber: object({
784
+ style: enumeration([
785
+ "none",
786
+ "01",
787
+ "01/02"
788
+ ]),
789
+ color: enumeration([
790
+ "ink",
791
+ "muted",
792
+ "accent"
793
+ ])
794
+ }),
795
+ audience: text,
796
+ question: text,
797
+ intendedViewingSize: enumeration([
798
+ "presentation",
799
+ "social",
800
+ "article",
801
+ "custom"
802
+ ]),
803
+ contentSlots: array({ oneOf: [object({
804
+ id: text,
805
+ label: text,
806
+ required: { type: "boolean" },
807
+ instruction: text,
808
+ role: enumeration([
809
+ "takeaway",
810
+ "body",
811
+ "evidence",
812
+ "comparison-item",
813
+ "process-step",
814
+ "entity",
815
+ "image",
816
+ "table"
817
+ ])
818
+ }), object({
819
+ id: text,
820
+ label: text,
821
+ required: { type: "boolean" },
822
+ instruction: text,
823
+ role: enumeration(["qualification", "source"]),
824
+ targets: ids
825
+ })] }, 0),
826
+ components: array({ oneOf: components }, 0),
827
+ groups: array(object({
828
+ id: text,
829
+ label: string,
830
+ childIds: ids
831
+ }, ["id", "childIds"])),
832
+ readingOrder: {
833
+ ...array(object({
834
+ kind: enumeration(["component", "group"]),
835
+ id: text
836
+ }), 0),
837
+ uniqueItems: true
838
+ },
839
+ paintOrder: {
840
+ ...array(text, 0),
841
+ uniqueItems: true
842
+ },
843
+ relationships: array(object({
844
+ id: text,
845
+ kind: enumeration([
846
+ "flows-to",
847
+ "qualifies",
848
+ "compares-with",
849
+ "depends-on",
850
+ "connects-to"
851
+ ]),
852
+ direction: enumeration([
853
+ "forward",
854
+ "bidirectional",
855
+ "undirected"
856
+ ]),
857
+ from: endpoint,
858
+ to: endpoint,
859
+ label: string
860
+ }, [
861
+ "id",
862
+ "kind",
863
+ "direction",
864
+ "from",
865
+ "to"
866
+ ]))
867
+ }, [
868
+ "id",
869
+ "name",
870
+ "canvas",
871
+ "audience",
872
+ "question",
873
+ "intendedViewingSize",
874
+ "contentSlots",
875
+ "components",
876
+ "groups",
877
+ "readingOrder",
878
+ "paintOrder",
879
+ "relationships"
880
+ ]), 1)
881
+ }, [
882
+ "schema",
883
+ "title",
884
+ "slides"
885
+ ])
886
+ };
887
+ //#endregion
888
+ //#region composition/vector.ts
889
+ var vectorAttributeNames = /* @__PURE__ */ new Set([
890
+ "x",
891
+ "y",
892
+ "dx",
893
+ "dy",
894
+ "x1",
895
+ "y1",
896
+ "x2",
897
+ "y2",
898
+ "width",
899
+ "height",
900
+ "rx",
901
+ "ry",
902
+ "cx",
903
+ "cy",
904
+ "r",
905
+ "d",
906
+ "points",
907
+ "transform",
908
+ "fill",
909
+ "fill-opacity",
910
+ "stroke",
911
+ "stroke-width",
912
+ "stroke-opacity",
913
+ "stroke-linecap",
914
+ "stroke-linejoin",
915
+ "stroke-dasharray",
916
+ "opacity",
917
+ "font-family",
918
+ "font-size",
919
+ "font-weight",
920
+ "font-style",
921
+ "text-anchor",
922
+ "dominant-baseline",
923
+ "vector-effect"
924
+ ]);
925
+ //#endregion
926
+ //#region composition/validate.ts
927
+ var structural = new Ajv2020({
928
+ allErrors: true,
929
+ strict: false
930
+ }).compile(schema);
931
+ function migratePreV5Document(input) {
932
+ const slide = input.slide;
933
+ const components = Array.isArray(slide?.components) ? slide.components.map((value) => {
934
+ if (!value || typeof value !== "object") return value;
935
+ const component = value;
936
+ if (component.kind !== "callout" && component.kind !== "comparison" && component.kind !== "text-block") return component;
937
+ const previous = component.appearance && typeof component.appearance === "object" ? component.appearance : {};
938
+ const common = Object.fromEntries([
939
+ "alignment",
940
+ "border",
941
+ "titleStyle",
942
+ "rule"
943
+ ].flatMap((key) => key in previous ? [[key, previous[key]]] : []));
944
+ const appearance = component.kind === "callout" ? {
945
+ ...common,
946
+ layout: "single",
947
+ orientation: "horizontal",
948
+ purpose: "emphasis",
949
+ treatment: "strong"
950
+ } : component.kind === "comparison" ? {
951
+ ...common,
952
+ layout: "two-column",
953
+ orientation: "horizontal",
954
+ purpose: "comparison",
955
+ treatment: "plain"
956
+ } : {
957
+ ...previous,
958
+ purpose: previous.purpose ?? "narrative",
959
+ treatment: previous.treatment ?? "plain"
960
+ };
961
+ return {
962
+ ...component,
963
+ kind: "text-block",
964
+ appearance
965
+ };
966
+ }) : slide?.components;
967
+ return {
968
+ ...Object.fromEntries(Object.entries(input).filter(([key]) => key !== "target" && key !== "slide")),
969
+ schema: "konpeki-composition/v5",
970
+ ...slide ? { slides: [{
971
+ ...slide,
972
+ name: "Slide 01",
973
+ components
974
+ }] } : {}
975
+ };
976
+ }
977
+ function migrateV5Document(input) {
978
+ const slides = Array.isArray(input.slides) ? input.slides.map((value) => {
979
+ if (!value || typeof value !== "object") return value;
980
+ const slide = value;
981
+ const previousComponents = Array.isArray(slide.components) ? slide.components : [];
982
+ const pageNumber = previousComponents.find((component) => component.kind === "page-number");
983
+ const pageAppearance = pageNumber?.appearance && typeof pageNumber.appearance === "object" ? pageNumber.appearance : {};
984
+ const removedIds = new Set(previousComponents.filter((component) => component.kind === "page-number").map((component) => String(component.id)));
985
+ const removedSlots = new Set(previousComponents.filter((component) => component.kind === "page-number").flatMap((component) => Array.isArray(component.slotIds) ? component.slotIds.map(String) : []));
986
+ const components = previousComponents.flatMap((component) => {
987
+ const appearance = component.appearance && typeof component.appearance === "object" ? component.appearance : {};
988
+ if (component.kind === "page-number") return [];
989
+ if (component.kind === "evidence") return [{
990
+ ...component,
991
+ kind: "visual"
992
+ }];
993
+ if (component.kind === "process") return [{
994
+ ...component,
995
+ kind: "diagram",
996
+ appearance: {
997
+ ...appearance,
998
+ type: "process"
999
+ }
1000
+ }];
1001
+ if (component.kind === "system-map") return [{
1002
+ ...component,
1003
+ kind: "diagram",
1004
+ appearance: {
1005
+ ...appearance,
1006
+ type: "system"
1007
+ }
1008
+ }];
1009
+ return [component];
1010
+ });
1011
+ const componentIds = new Set(components.map((component) => String(component.id)));
1012
+ const groups = Array.isArray(slide.groups) ? slide.groups.map((group) => ({
1013
+ ...group,
1014
+ childIds: Array.isArray(group.childIds) ? group.childIds.filter((id) => !removedIds.has(String(id))) : group.childIds
1015
+ })).filter((group) => !Array.isArray(group.childIds) || group.childIds.length > 0) : slide.groups;
1016
+ const groupIds = new Set(Array.isArray(groups) ? groups.map((group) => String(group.id)) : []);
1017
+ const contentSlots = Array.isArray(slide.contentSlots) ? slide.contentSlots.filter((slot) => !removedSlots.has(String(slot.id))).map((slot) => {
1018
+ if (!Array.isArray(slot.targets)) return slot;
1019
+ const targets = slot.targets.filter((target) => !removedIds.has(String(target)) && !removedSlots.has(String(target)));
1020
+ return {
1021
+ ...slot,
1022
+ targets: targets.length ? targets : [String(components[0]?.id ?? "")]
1023
+ };
1024
+ }) : slide.contentSlots;
1025
+ return {
1026
+ ...slide,
1027
+ pageNumber: {
1028
+ style: pageNumber ? pageAppearance.style ?? "01" : "none",
1029
+ color: pageAppearance.color ?? "muted"
1030
+ },
1031
+ components,
1032
+ contentSlots,
1033
+ groups,
1034
+ readingOrder: Array.isArray(slide.readingOrder) ? slide.readingOrder.filter((entry) => entry.kind === "group" ? groupIds.has(String(entry.id)) : componentIds.has(String(entry.id))) : slide.readingOrder,
1035
+ paintOrder: Array.isArray(slide.paintOrder) ? slide.paintOrder.filter((id) => !removedIds.has(String(id))) : slide.paintOrder,
1036
+ relationships: Array.isArray(slide.relationships) ? slide.relationships.filter((relationship) => {
1037
+ const from = relationship.from;
1038
+ const to = relationship.to;
1039
+ return !removedIds.has(String(from?.nodeId)) && !removedIds.has(String(to?.nodeId));
1040
+ }) : slide.relationships
1041
+ };
1042
+ }) : input.slides;
1043
+ return {
1044
+ ...input,
1045
+ schema: compositionSchema,
1046
+ slides
1047
+ };
1048
+ }
1049
+ function migrateV7Document(input) {
1050
+ const slides = Array.isArray(input.slides) ? input.slides.map((value) => {
1051
+ if (!value || typeof value !== "object") return value;
1052
+ const slide = value;
1053
+ const components = Array.isArray(slide.components) ? slide.components.map((value) => {
1054
+ if (!value || typeof value !== "object") return value;
1055
+ const component = value;
1056
+ const appearance = component.appearance && typeof component.appearance === "object" ? component.appearance : {};
1057
+ if (component.kind !== "visual" || appearance.template !== "image") return component;
1058
+ return {
1059
+ ...component,
1060
+ kind: "image",
1061
+ appearance: {
1062
+ ...appearance.border ? { border: appearance.border } : {},
1063
+ fit: "contain"
1064
+ }
1065
+ };
1066
+ }) : slide.components;
1067
+ return {
1068
+ ...slide,
1069
+ components
1070
+ };
1071
+ }) : input.slides;
1072
+ return {
1073
+ ...input,
1074
+ schema: compositionSchema,
1075
+ slides
1076
+ };
1077
+ }
1078
+ function migrateV10Document(input) {
1079
+ const slides = Array.isArray(input.slides) ? input.slides.map((value) => {
1080
+ if (!value || typeof value !== "object") return value;
1081
+ const slide = value;
1082
+ const components = Array.isArray(slide.components) ? slide.components.map((value) => {
1083
+ if (!value || typeof value !== "object") return value;
1084
+ const component = value;
1085
+ const appearance = component.appearance && typeof component.appearance === "object" ? component.appearance : {};
1086
+ if (component.kind === "headline") return {
1087
+ ...component,
1088
+ kind: "text-block",
1089
+ appearance: {
1090
+ ...appearance,
1091
+ role: "title",
1092
+ layout: "single",
1093
+ orientation: "horizontal",
1094
+ purpose: "narrative",
1095
+ treatment: "plain",
1096
+ logicalOrder: "none"
1097
+ }
1098
+ };
1099
+ if (component.kind === "footnote") return {
1100
+ ...component,
1101
+ kind: "text-block",
1102
+ appearance: {
1103
+ ...appearance,
1104
+ role: "footnote",
1105
+ layout: "single",
1106
+ orientation: "horizontal",
1107
+ purpose: "narrative",
1108
+ treatment: "plain",
1109
+ logicalOrder: "none",
1110
+ titleStyle: "plain"
1111
+ }
1112
+ };
1113
+ if (component.kind === "text-block") return {
1114
+ ...component,
1115
+ appearance: {
1116
+ role: "body",
1117
+ ...appearance
1118
+ }
1119
+ };
1120
+ if (component.kind === "visual") return {
1121
+ ...component,
1122
+ kind: "chart"
1123
+ };
1124
+ if (component.kind === "diagram") {
1125
+ const { template, ...rest } = appearance;
1126
+ return {
1127
+ ...component,
1128
+ appearance: {
1129
+ ...rest,
1130
+ layout: template ?? appearance.layout ?? "linear"
1131
+ }
1132
+ };
1133
+ }
1134
+ if (component.kind === "icon" || component.kind === "shape") {
1135
+ const slotIds = Array.isArray(component.slotIds) ? component.slotIds.map(String) : [];
1136
+ const slotId = slotIds[0] ?? `${String(component.id)}-content`;
1137
+ const primitive = component.kind === "icon" ? {
1138
+ kind: "icon",
1139
+ ...typeof component.intent === "string" && component.intent.trim() ? { name: component.intent.trim() } : {},
1140
+ ...appearance.style ? { style: appearance.style } : {},
1141
+ ...appearance.color ? { color: appearance.color } : {}
1142
+ } : {
1143
+ kind: "shape",
1144
+ shape: appearance.shape ?? "rectangle",
1145
+ ...appearance.color ? { color: appearance.color } : {},
1146
+ ...appearance.fill ? { fill: appearance.fill } : {}
1147
+ };
1148
+ return {
1149
+ ...component,
1150
+ kind: "diagram",
1151
+ slotIds: slotIds.length ? slotIds : [slotId],
1152
+ appearance: {
1153
+ type: "system",
1154
+ layout: "hub-and-spoke",
1155
+ border: appearance.border ?? "none",
1156
+ colorScheme: "accent-with-muted-context",
1157
+ emphasis: "primary",
1158
+ density: "sparse"
1159
+ },
1160
+ topology: {
1161
+ kind: "explicit",
1162
+ nodes: (slotIds.length ? slotIds : [slotId]).map((currentSlotId, index) => ({
1163
+ id: index ? `${String(component.id)}-slot-${index + 1}` : String(component.id),
1164
+ slotId: currentSlotId,
1165
+ ...index ? { visible: false } : { primitive },
1166
+ ...component.preferredRect && typeof component.preferredRect === "object" ? { preferredRect: component.preferredRect } : {}
1167
+ })),
1168
+ edges: []
1169
+ }
1170
+ };
1171
+ }
1172
+ return component;
1173
+ }) : slide.components;
1174
+ const contentSlots = Array.isArray(slide.contentSlots) ? slide.contentSlots.map((value) => {
1175
+ if (!value || typeof value !== "object") return value;
1176
+ const slot = value;
1177
+ return slot.role === "icon" || slot.role === "shape" ? {
1178
+ ...slot,
1179
+ role: "entity"
1180
+ } : slot;
1181
+ }) : slide.contentSlots;
1182
+ return {
1183
+ ...slide,
1184
+ components,
1185
+ contentSlots
1186
+ };
1187
+ }) : input.slides;
1188
+ return {
1189
+ ...input,
1190
+ schema: "konpeki-composition/v11",
1191
+ slides
1192
+ };
1193
+ }
1194
+ function migrateV11Document(input) {
1195
+ const slides = Array.isArray(input.slides) ? input.slides.map((value) => {
1196
+ if (!value || typeof value !== "object") return value;
1197
+ const slide = value;
1198
+ const components = Array.isArray(slide.components) ? slide.components.map((value) => {
1199
+ if (!value || typeof value !== "object") return value;
1200
+ const component = value;
1201
+ if (component.kind !== "diagram") return component;
1202
+ const appearance = component.appearance && typeof component.appearance === "object" ? component.appearance : {};
1203
+ const migratedType = migrateLegacyDiagramType(appearance.type, appearance.layout);
1204
+ const { layout, ...rest } = appearance;
1205
+ return {
1206
+ ...component,
1207
+ appearance: {
1208
+ ...rest,
1209
+ type: migratedType ?? null
1210
+ }
1211
+ };
1212
+ }) : slide.components;
1213
+ return {
1214
+ ...slide,
1215
+ components
1216
+ };
1217
+ }) : input.slides;
1218
+ return {
1219
+ ...input,
1220
+ schema: compositionSchema,
1221
+ slides
1222
+ };
1223
+ }
1224
+ function migrateInterimV12Sankey(input) {
1225
+ if (!input || typeof input !== "object") return input;
1226
+ const document = input;
1227
+ if (document.schema !== "konpeki-composition/v18" || !Array.isArray(document.slides)) return input;
1228
+ const slides = document.slides.map((value) => {
1229
+ if (!value || typeof value !== "object") return value;
1230
+ const slide = value;
1231
+ if (!Array.isArray(slide.components)) return value;
1232
+ const components = slide.components.map((componentValue) => {
1233
+ if (!componentValue || typeof componentValue !== "object") return componentValue;
1234
+ const component = componentValue;
1235
+ const appearance = component.appearance && typeof component.appearance === "object" ? component.appearance : {};
1236
+ if (component.kind !== "diagram" || appearance.type !== "sankey") return componentValue;
1237
+ const { type: _type, ...sharedAppearance } = appearance;
1238
+ return {
1239
+ ...component,
1240
+ kind: "chart",
1241
+ appearance: {
1242
+ ...sharedAppearance,
1243
+ template: "sankey"
1244
+ }
1245
+ };
1246
+ });
1247
+ return {
1248
+ ...slide,
1249
+ components
1250
+ };
1251
+ });
1252
+ return {
1253
+ ...document,
1254
+ slides
1255
+ };
1256
+ }
1257
+ function preserveLegacyVisualizationChoices(input) {
1258
+ if (!input || typeof input !== "object") return input;
1259
+ const document = input;
1260
+ if (!Array.isArray(document.slides)) return input;
1261
+ return {
1262
+ ...document,
1263
+ slides: document.slides.map((value) => {
1264
+ if (!value || typeof value !== "object") return value;
1265
+ const slide = value;
1266
+ if (!Array.isArray(slide.components)) return value;
1267
+ return {
1268
+ ...slide,
1269
+ components: slide.components.map((value) => {
1270
+ if (!value || typeof value !== "object") return value;
1271
+ const component = value;
1272
+ if (component.kind !== "diagram" && component.kind !== "chart" || !component.appearance || typeof component.appearance !== "object") return value;
1273
+ return {
1274
+ ...component,
1275
+ appearance: {
1276
+ ...component.appearance,
1277
+ selection: "explicit"
1278
+ }
1279
+ };
1280
+ })
1281
+ };
1282
+ })
1283
+ };
1284
+ }
1285
+ function validateComposition(input) {
1286
+ const legacy = Boolean(input && typeof input === "object" && "schema" in input && legacyCompositionSchemas.includes(input.schema));
1287
+ const legacyInput = input;
1288
+ const normalized = migrateInterimV12Sankey(legacy ? [
1289
+ "konpeki-composition/v12",
1290
+ "konpeki-composition/v13",
1291
+ "konpeki-composition/v14",
1292
+ "konpeki-composition/v15",
1293
+ "konpeki-composition/v16",
1294
+ "konpeki-composition/v17"
1295
+ ].includes(String(legacyInput.schema)) ? {
1296
+ ...legacyInput,
1297
+ schema: compositionSchema
1298
+ } : migrateV11Document(legacyInput.schema === "konpeki-composition/v11" ? legacyInput : migrateV10Document([
1299
+ "konpeki-composition/v6",
1300
+ "konpeki-composition/v7",
1301
+ "konpeki-composition/v8",
1302
+ "konpeki-composition/v9",
1303
+ "konpeki-composition/v10"
1304
+ ].includes(String(legacyInput.schema)) ? migrateV7Document(legacyInput) : migrateV7Document(migrateV5Document(legacyInput.schema === "konpeki-composition/v5" ? legacyInput : migratePreV5Document(legacyInput))))) : input);
1305
+ const candidate = legacy ? preserveLegacyVisualizationChoices(normalized) : normalized;
1306
+ if (!structural(candidate)) return {
1307
+ ok: false,
1308
+ issues: (structural.errors ?? []).map((error) => ({
1309
+ path: error.instancePath,
1310
+ message: error.message ?? "Invalid value"
1311
+ }))
1312
+ };
1313
+ const issues = [];
1314
+ const fail = (path, message) => issues.push({
1315
+ path,
1316
+ message
1317
+ });
1318
+ const unique = (ids, path) => {
1319
+ if (new Set(ids).size !== ids.length) fail(path, "IDs must be unique");
1320
+ };
1321
+ unique(candidate.slides.map((slide) => slide.id), "/slides");
1322
+ candidate.slides.forEach((slide, slideIndex) => {
1323
+ const base = `/slides/${slideIndex}`;
1324
+ const padding = slide.innerPadding;
1325
+ if (padding && (padding.left + padding.right >= slide.canvas.width || padding.top + padding.bottom >= slide.canvas.height)) fail(`${base}/innerPadding`, "Padding leaves no page content area");
1326
+ const components = new Map(slide.components.map((item) => [item.id, item]));
1327
+ const slots = new Set(slide.contentSlots.map((item) => item.id));
1328
+ const groups = new Map(slide.groups.map((item) => [item.id, item]));
1329
+ unique([...components.keys(), ...groups.keys()], `${base}/groups`);
1330
+ unique(slide.components.map((item) => item.id), `${base}/components`);
1331
+ unique(slide.contentSlots.map((item) => item.id), `${base}/contentSlots`);
1332
+ unique(slide.groups.map((item) => item.id), `${base}/groups`);
1333
+ unique(slide.relationships.map((item) => item.id), `${base}/relationships`);
1334
+ const assigned = [];
1335
+ for (const component of slide.components) {
1336
+ const path = `${base}/components/${component.id}`;
1337
+ const r = component.preferredRect;
1338
+ if (r.x + r.width > slide.canvas.width || r.y + r.height > slide.canvas.height) fail(path, "Rectangle exceeds canvas");
1339
+ assigned.push(...component.slotIds);
1340
+ for (const id of component.slotIds) if (!slots.has(id)) fail(path, `Unknown slot: ${id}`);
1341
+ if (component.customVisual) {
1342
+ if (component.customVisual.format === "svg") {
1343
+ const source = component.customVisual.source;
1344
+ if (/<script\b|<foreignObject\b|<\?xml-stylesheet\b|<!DOCTYPE\b|\son[a-z]+\s*=|(?:href|src)\s*=\s*["'](?!data:|#)|url\(\s*["']?(?!data:|#)/i.test(source)) fail(path, "Custom SVG must be self-contained and cannot include scripts, foreign objects, event handlers, document declarations, or external resources");
1345
+ const sourceViewBox = source.match(/<svg\b[^>]*\bviewBox\s*=\s*(["'])([^"']+)\1/i)?.[2]?.trim().split(/[\s,]+/).map(Number);
1346
+ const declaredViewBox = component.customVisual.viewBox;
1347
+ if (sourceViewBox?.length !== 4 || sourceViewBox.some((value) => !Number.isFinite(value)) || sourceViewBox[0] !== declaredViewBox.x || sourceViewBox[1] !== declaredViewBox.y || sourceViewBox[2] !== declaredViewBox.width || sourceViewBox[3] !== declaredViewBox.height) fail(path, "Custom SVG root viewBox must match the declared local viewport");
1348
+ } else {
1349
+ const known = /* @__PURE__ */ new Set();
1350
+ const kinds = new Map(component.customVisual.elements.map((element) => [element.id, element.kind]));
1351
+ for (const element of component.customVisual.elements) {
1352
+ if (known.has(element.id)) fail(path, `Duplicate vector element ID: ${element.id}`);
1353
+ if (element.parentId && !known.has(element.parentId)) fail(path, `Vector parent must precede its child: ${element.parentId}`);
1354
+ const parentKind = element.parentId ? kinds.get(element.parentId) : void 0;
1355
+ if (element.parentId && parentKind !== "g" && !(element.kind === "tspan" && (parentKind === "text" || parentKind === "tspan"))) fail(path, "Vector children require a group or a text/tspan parent for tspans");
1356
+ if (element.kind === "tspan" && parentKind !== "text" && parentKind !== "tspan") fail(path, "Vector tspans require a text or tspan parent");
1357
+ if (element.text !== void 0 && element.kind !== "text" && element.kind !== "tspan") fail(path, "Only vector text and tspans may contain text");
1358
+ known.add(element.id);
1359
+ for (const [name, value] of Object.entries(element.attributes)) {
1360
+ if (!validThemeBinding(name, value)) fail(path, `Invalid theme binding for vector attribute: ${name}`);
1361
+ if (!vectorAttributeNames.has(name)) fail(path, `Unsupported vector attribute: ${name}`);
1362
+ if (typeof value === "string" && /url\s*\(|javascript:|data:/i.test(value)) fail(path, `Unsafe vector attribute value: ${name}`);
1363
+ }
1364
+ }
1365
+ }
1366
+ }
1367
+ if (component.kind === "chart" && component.topology && component.appearance.template !== "sankey") fail(path, "Only Sankey charts may define topology");
1368
+ if ((component.kind === "diagram" || component.kind === "chart") && component.topology) {
1369
+ const { nodes, edges } = component.topology;
1370
+ unique(nodes.map((node) => node.id), path);
1371
+ unique(nodes.map((node) => node.slotId), path);
1372
+ unique(edges.map((edge) => JSON.stringify([
1373
+ edge.from,
1374
+ edge.to,
1375
+ edge.label ?? ""
1376
+ ])), path);
1377
+ const known = new Set(nodes.map((node) => node.id));
1378
+ if (nodes.length !== component.slotIds.length) fail(path, "Topology must represent every component slot");
1379
+ for (const node of nodes) {
1380
+ if (!component.slotIds.includes(node.slotId)) fail(path, "Topology slot must belong to its component");
1381
+ const nodeRect = node.preferredRect;
1382
+ if (nodeRect && (nodeRect.x < r.x || nodeRect.y < r.y || nodeRect.x + nodeRect.width > r.x + r.width || nodeRect.y + nodeRect.height > r.y + r.height)) fail(path, "Topology node rectangle must stay within its component");
1383
+ }
1384
+ for (const edge of edges) {
1385
+ if (!known.has(edge.from) || !known.has(edge.to)) fail(path, "Unknown topology endpoint");
1386
+ if (edge.from === edge.to) fail(path, "Topology self-edges are not supported");
1387
+ }
1388
+ }
1389
+ }
1390
+ for (const id of slots) if (!assigned.includes(id)) fail(`${base}/contentSlots`, `Unassigned content slot: ${id}`);
1391
+ const grouped = [];
1392
+ for (const group of slide.groups) for (const id of group.childIds) {
1393
+ grouped.push(id);
1394
+ if (!components.has(id)) fail(`${base}/groups`, "Groups may contain only components");
1395
+ }
1396
+ unique(grouped, `${base}/groups`);
1397
+ const reading = slide.readingOrder.flatMap((entry) => {
1398
+ if (entry.kind === "component") {
1399
+ if (!components.has(entry.id)) fail(`${base}/readingOrder`, "Unknown component");
1400
+ return [entry.id];
1401
+ }
1402
+ const group = groups.get(entry.id);
1403
+ if (!group) fail(`${base}/readingOrder`, "Unknown group");
1404
+ return group?.childIds ?? [];
1405
+ });
1406
+ for (const [path, order] of [["readingOrder", reading], ["paintOrder", slide.paintOrder]]) if (order.length !== components.size || new Set(order).size !== components.size || order.some((id) => !components.has(id))) fail(`${base}/${path}`, "Must contain every component exactly once");
1407
+ for (const slot of slide.contentSlots) if ("targets" in slot) {
1408
+ for (const id of slot.targets) if (!components.has(id) && !slots.has(id)) fail(`${base}/contentSlots`, "Unknown qualification/source target");
1409
+ }
1410
+ for (const relationship of slide.relationships) for (const endpoint of [relationship.from, relationship.to]) {
1411
+ const component = components.get(endpoint.nodeId);
1412
+ if (!component) fail(`${base}/relationships`, "Unknown relationship node");
1413
+ if (endpoint.slotId && !component?.slotIds.includes(endpoint.slotId)) fail(`${base}/relationships`, "Relationship slot must belong to its endpoint");
1414
+ }
1415
+ });
1416
+ return issues.length ? {
1417
+ ok: false,
1418
+ issues
1419
+ } : {
1420
+ ok: true,
1421
+ document: legacy ? {
1422
+ ...candidate,
1423
+ slides: candidate.slides.map((slide) => ({
1424
+ ...slide,
1425
+ components: slide.components.map((component) => component.kind === "text-block" && !component.customVisual && component.content === void 0 ? {
1426
+ ...component,
1427
+ content: component.intent ?? ""
1428
+ } : component)
1429
+ }))
1430
+ } : candidate
1431
+ };
1432
+ }
1433
+ //#endregion
1434
+ //#region composition/compile.ts
1435
+ function canonicalJSON(input) {
1436
+ const sort = (value) => Array.isArray(value) ? value.map(sort) : value && typeof value === "object" ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, sort(item)])) : value;
1437
+ return JSON.stringify(sort(input), null, 2);
1438
+ }
1439
+ canvasPadding.left, canvasPadding.left, canvasPadding.left, canvasSize.width - canvasPadding.left - canvasPadding.right, canvasPadding.left, canvasPadding.left;
1440
+ function parseCompositionJSON(raw) {
1441
+ try {
1442
+ const result = validateComposition(JSON.parse(raw));
1443
+ if (result.ok) return {
1444
+ ok: true,
1445
+ document: result.document
1446
+ };
1447
+ const issue = result.issues[0];
1448
+ return {
1449
+ ok: false,
1450
+ message: `${issue?.path || "Document"}: ${issue?.message || "Invalid composition"}`
1451
+ };
1452
+ } catch {
1453
+ return {
1454
+ ok: false,
1455
+ message: "The selected file is not valid JSON."
1456
+ };
1457
+ }
1458
+ }
1459
+ //#endregion
1460
+ //#region bin/session-store.ts
1461
+ function revisionFor(value) {
1462
+ return createHash("sha256").update(value).digest("hex");
1463
+ }
1464
+ function requestFileFor(compositionPath) {
1465
+ const key = revisionFor(resolve(compositionPath));
1466
+ return join(tmpdir(), "konpeki", `${key}.request.json`);
1467
+ }
1468
+ async function readCompositionFile(compositionPath) {
1469
+ const raw = await readFile(compositionPath, "utf8");
1470
+ const parsed = parseCompositionJSON(raw);
1471
+ if (!parsed.ok) throw new Error(parsed.message);
1472
+ return {
1473
+ document: parsed.document,
1474
+ revision: revisionFor(raw),
1475
+ name: basename(compositionPath)
1476
+ };
1477
+ }
1478
+ async function saveCompositionFile(compositionPath, expectedRevision, document) {
1479
+ const validation = validateComposition(document);
1480
+ if (!validation.ok) {
1481
+ const issue = validation.issues[0];
1482
+ throw Object.assign(/* @__PURE__ */ new Error(`${issue?.path || "document"} ${issue?.message || "is invalid"}`), { code: "INVALID_COMPOSITION" });
1483
+ }
1484
+ const currentRevision = revisionFor(await readFile(compositionPath, "utf8"));
1485
+ if (currentRevision !== expectedRevision) throw Object.assign(/* @__PURE__ */ new Error("The composition changed outside this browser."), {
1486
+ code: "REVISION_CONFLICT",
1487
+ revision: currentRevision
1488
+ });
1489
+ const value = `${canonicalJSON(document)}\n`;
1490
+ const temporary = join(dirname(compositionPath), `.${basename(compositionPath)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
1491
+ await writeFile(temporary, value, {
1492
+ encoding: "utf8",
1493
+ flag: "wx"
1494
+ });
1495
+ try {
1496
+ await rename(temporary, compositionPath);
1497
+ } catch (error) {
1498
+ await rm(temporary, { force: true });
1499
+ throw error;
1500
+ }
1501
+ return { revision: revisionFor(value) };
1502
+ }
1503
+ async function writeBuildRequest(compositionPath, request) {
1504
+ const current = await readCompositionFile(compositionPath);
1505
+ if (current.revision !== request.revision) throw Object.assign(/* @__PURE__ */ new Error("Save the latest composition before requesting a build."), {
1506
+ code: "REVISION_CONFLICT",
1507
+ revision: current.revision
1508
+ });
1509
+ const output = requestFileFor(compositionPath);
1510
+ await mkdir(dirname(output), { recursive: true });
1511
+ const value = {
1512
+ schema: "konpeki-build-request/v1",
1513
+ compositionPath: resolve(compositionPath),
1514
+ revision: request.revision,
1515
+ instruction: request.instruction.trim(),
1516
+ slideId: request.slideId,
1517
+ ...request.componentId ? { componentId: request.componentId } : {},
1518
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1519
+ };
1520
+ await writeFile(output, `${JSON.stringify(value, null, 2)}\n`, "utf8");
1521
+ return {
1522
+ output,
1523
+ request: value
1524
+ };
1525
+ }
1526
+ //#endregion
1527
+ //#region bin/session-plugin.ts
1528
+ var maximumBodyBytes = 16777216;
1529
+ var route = "/__konpeki/session";
1530
+ function sendJSON(response, status, value) {
1531
+ response.writeHead(status, {
1532
+ "content-type": "application/json; charset=utf-8",
1533
+ "cache-control": "no-store"
1534
+ });
1535
+ response.end(JSON.stringify(value));
1536
+ }
1537
+ async function readJSON(request) {
1538
+ let size = 0;
1539
+ const chunks = [];
1540
+ for await (const chunk of request) {
1541
+ size += chunk.length;
1542
+ if (size > maximumBodyBytes) throw Object.assign(/* @__PURE__ */ new Error("Request body is too large."), { code: "BODY_TOO_LARGE" });
1543
+ chunks.push(chunk);
1544
+ }
1545
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
1546
+ }
1547
+ function errorResponse(error) {
1548
+ const code = error instanceof Error && "code" in error ? error.code : void 0;
1549
+ return {
1550
+ status: code === "REVISION_CONFLICT" ? 409 : code === "INVALID_COMPOSITION" ? 422 : code === "BODY_TOO_LARGE" ? 413 : error instanceof SyntaxError ? 400 : 500,
1551
+ body: {
1552
+ error: error instanceof Error ? error.message : "File-session request failed.",
1553
+ ...error && typeof error === "object" && "revision" in error ? { revision: error.revision } : {}
1554
+ }
1555
+ };
1556
+ }
1557
+ function fileSessionPlugin({ compositionPath, token, onBuildRequest }) {
1558
+ return {
1559
+ name: "konpeki-file-session",
1560
+ enforce: "pre",
1561
+ configureServer(server) {
1562
+ server.middlewares.use(async (request, response, next) => {
1563
+ const url = new URL(request.url || "/", "http://localhost");
1564
+ if (!url.pathname.startsWith(route)) return next();
1565
+ if (request.headers["x-konpeki-session"] !== token) {
1566
+ sendJSON(response, 403, { error: "Invalid session token." });
1567
+ return;
1568
+ }
1569
+ try {
1570
+ if (url.pathname === route && request.method === "GET") {
1571
+ sendJSON(response, 200, await readCompositionFile(compositionPath));
1572
+ return;
1573
+ }
1574
+ if (url.pathname === route && request.method === "PUT") {
1575
+ const body = await readJSON(request);
1576
+ sendJSON(response, 200, await saveCompositionFile(compositionPath, body.revision, body.document));
1577
+ return;
1578
+ }
1579
+ if (url.pathname === `${route}/build` && request.method === "POST") {
1580
+ const body = await readJSON(request);
1581
+ if (typeof body.instruction !== "string" || !body.instruction.trim()) {
1582
+ sendJSON(response, 400, { error: "Describe what the agent should build." });
1583
+ return;
1584
+ }
1585
+ const result = await writeBuildRequest(compositionPath, body);
1586
+ onBuildRequest?.(result);
1587
+ sendJSON(response, 201, { ok: true });
1588
+ return;
1589
+ }
1590
+ sendJSON(response, 404, { error: "Unknown file-session endpoint." });
1591
+ } catch (error) {
1592
+ const responseError = errorResponse(error);
1593
+ sendJSON(response, responseError.status, responseError.body);
1594
+ }
1595
+ });
1596
+ }
1597
+ };
1598
+ }
1599
+ //#endregion
1600
+ //#region bin/konpeki.mjs
1601
+ var root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
1602
+ function usage() {
1603
+ console.error(`Usage:
1604
+ konpeki preview <composition.json> [--host <host>] [--port <port>]
1605
+ konpeki validate <composition.json>
1606
+ konpeki wait <composition.json>`);
1607
+ }
1608
+ function option(name, fallback) {
1609
+ const index = process.argv.indexOf(name);
1610
+ return index === -1 ? fallback : process.argv[index + 1];
1611
+ }
1612
+ async function preview(input) {
1613
+ const compositionPath = resolve(input);
1614
+ await readCompositionFile(compositionPath);
1615
+ const token = randomBytes(24).toString("base64url");
1616
+ const host = option("--host", "127.0.0.1");
1617
+ const port = Number(option("--port", "4318"));
1618
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Port must be an integer from 1 to 65535.");
1619
+ await rm(requestFileFor(compositionPath), { force: true });
1620
+ const server = await createServer({
1621
+ root,
1622
+ configFile: resolve(root, "vite.config.ts"),
1623
+ server: {
1624
+ host,
1625
+ port,
1626
+ strictPort: true
1627
+ },
1628
+ plugins: [fileSessionPlugin({
1629
+ compositionPath,
1630
+ token,
1631
+ onBuildRequest: ({ request }) => {
1632
+ console.log(`\nBuild requested for ${request.compositionPath}`);
1633
+ console.log(`Revision: ${request.revision}`);
1634
+ console.log(`Instruction: ${request.instruction}`);
1635
+ console.log(`Run: konpeki wait ${JSON.stringify(request.compositionPath)}\n`);
1636
+ }
1637
+ })]
1638
+ });
1639
+ await server.listen();
1640
+ const address = server.httpServer?.address();
1641
+ const actualPort = address && typeof address === "object" ? address.port : port;
1642
+ const displayHost = host === "0.0.0.0" || host === "::" ? "localhost" : host;
1643
+ console.log(`Konpeki is editing ${compositionPath}`);
1644
+ console.log(`http://${displayHost}:${actualPort}/?session=${encodeURIComponent(token)}`);
1645
+ console.log(`Waiting for edits and Build it requests. Press Ctrl+C to stop.`);
1646
+ }
1647
+ async function validate(input) {
1648
+ const result = await readCompositionFile(resolve(input));
1649
+ console.log(`${result.name}: valid (${result.revision})`);
1650
+ }
1651
+ async function waitForRequest(input) {
1652
+ const requestPath = requestFileFor(resolve(input));
1653
+ console.log(`Waiting for a Build it request for ${resolve(input)}…`);
1654
+ for (;;) {
1655
+ try {
1656
+ await access(requestPath);
1657
+ const value = await readFile(requestPath, "utf8");
1658
+ await rm(requestPath, { force: true });
1659
+ process.stdout.write(value);
1660
+ return;
1661
+ } catch (error) {
1662
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
1663
+ }
1664
+ await new Promise((resolveWait) => setTimeout(resolveWait, 250));
1665
+ }
1666
+ }
1667
+ var command = process.argv[2];
1668
+ var input = process.argv[3];
1669
+ if (!command || !input) {
1670
+ usage();
1671
+ process.exitCode = 1;
1672
+ } else try {
1673
+ if (command === "preview") await preview(input);
1674
+ else if (command === "validate") await validate(input);
1675
+ else if (command === "wait") await waitForRequest(input);
1676
+ else {
1677
+ usage();
1678
+ process.exitCode = 1;
1679
+ }
1680
+ } catch (error) {
1681
+ console.error(error instanceof Error ? error.message : error);
1682
+ process.exitCode = 1;
1683
+ }
1684
+ //#endregion
1685
+ export {};