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,76 @@
1
+ # Authoring floor
2
+
3
+ Read this immediately before writing or revising HTML. These are the rules
4
+ agents break when working from habit. Each has an ID. `inspect` reports the
5
+ `(inspect)` rules as diagnostics under the same code, and `render` prints the
6
+ `(review)` rules as the checklist for looking at the page.
7
+
8
+ Fix every floor warning or justify keeping it in your final report. An opt-out
9
+ attribute such as `data-kp-allow="label-above-headline"` needs that
10
+ justification too.
11
+
12
+ ## Bans
13
+
14
+ No brief, theme or habit earns these back.
15
+
16
+ - `label-above-headline` (inspect): No label, kicker, eyebrow or metadata line
17
+ above a headline or section heading. Put the product, date, series or source
18
+ in the headline, the supporting line, a caption or the footer. Running heads
19
+ repeated across multi-page print are exempt. A label the brief requires, such
20
+ as Before/After, sits outside the headline's column.
21
+ - `stranded-word` (inspect): No headline line holds one stranded word after a
22
+ fuller line, including the line before a `<br>`. Reword it or change its
23
+ measure; break with `<br>` only where the meaning divides. Never shrink type
24
+ to fit.
25
+ - `restated-takeaway` (review): State the takeaway once: a subtitle, caption,
26
+ callout or note adds evidence, a qualification or a next step, never the
27
+ headline in other words.
28
+ - `slogan-copy` (review): No slogan contrasts ("Not a feature. A platform.",
29
+ "a signal, not proof") and no chains of em dashes. Keep a "not X" clause only
30
+ where it corrects a likely misreading, such as concentrations, not emissions.
31
+ - `decorative-container` (review): No panel, card, badge or pill unless it marks
32
+ a distinct object, state or contrasting field, and never a panel inside a
33
+ panel. Use space, alignment and rules first.
34
+ - `honest-encoding` (review): Amount bars start at zero, arrows point the true
35
+ direction, and nothing implies causal, temporal or quantitative meaning the
36
+ source lacks. Colour never carries meaning alone; pair it with a label, shape
37
+ or position.
38
+
39
+ ## Defaults
40
+
41
+ The brief or a theme's `NOTES.md` may override these, explicitly.
42
+
43
+ - `headline-first` (review): Reading starts with the headline, and the headline
44
+ states the claim. "Cold starts fell 62%" is a claim; "Q3 results" is a topic.
45
+ - `one-focus` (review): Accent marks the headline and at most one other element:
46
+ the main claim, path or data point. Everything else uses ink, muted ink or
47
+ categorical colour.
48
+ - `format-budget` (review): Fit content to the viewing size: a link card holds a
49
+ headline, one supporting line and a date, and a slide holds one idea. Full
50
+ detail belongs on the one-pager or report.
51
+ - `grouping` (review): Space shows grouping: an item sits closer to what it
52
+ qualifies than to the next subject, and true peers share size, weight and
53
+ treatment. New edges align with existing ones.
54
+ - `page-footer` (review): Keep page-level source notes and page numbers together
55
+ in a reserved footer, clear of body content. Reserve that space in the first
56
+ layout, not after filling the page. Do not pin the page number independently
57
+ while leaving its source note in body flow. Citations that explain a specific
58
+ figure or claim still belong beside that content.
59
+ - `diagram-economy` (review): A diagram uses at most three box styles, each with
60
+ a meaning. Arrowheads scale with the stroke (`markerUnits="strokeWidth"`) and
61
+ stay clear of labels.
62
+
63
+ ## Verify
64
+
65
+ `inspect` enforces these as errors or warnings; read the diagnostic message.
66
+
67
+ - `small-text` (inspect): Text is at least 24 CSS px on screen presets and 11 on
68
+ A4. These are floors for small print, not sizes for body text.
69
+ - `low-contrast` (inspect): Text contrast is at least 4.5:1, or 3:1 for large
70
+ text, including nested roles and code on contrasting fields.
71
+ - `text-overflow`, `clipped-text`, `text-overlap`, `page-overflow` (inspect):
72
+ Nothing leaves the page, hides behind a clip or collides. Recompose; do not
73
+ hide, truncate or shrink required copy.
74
+ - `font-fallback` (inspect): Every glyph comes from a loaded webfont, whether
75
+ Google-hosted, local, or embedded. Supply the required subsets, weights, and
76
+ styles; do not rely on installed-font fallback or synthesized faces.
@@ -0,0 +1,27 @@
1
+ # Cover or social graphic
2
+
3
+ Use for a cover, thumbnail, link preview, announcement image, or social graphic.
4
+ Apply the [shared guidance](../SKILL.md#before-authoring) and the
5
+ [authoring floor](../floor.md); this is not a miniature document.
6
+
7
+ - Use the requested destination dimensions and crop. If none are supplied for
8
+ a general link-preview cover, start with the theme's `link` preset. Square,
9
+ portrait, and other ratios are equally valid for their intended channels.
10
+ - Make the main subject or message recognizable at thumbnail size. Let imagery
11
+ lead when it communicates the subject; do not require a large slogan on every
12
+ cover. Keep supporting copy short without stripping essential qualifications.
13
+ - A deliberate headline break may improve composition. Check the actual words
14
+ and font rather than fixing a line count or inserting breaks into every label.
15
+ - Keep essential text, faces, logos, and marks clear of the expected crop and
16
+ platform overlays. Do not invent universal safe-area numbers; use the brief's
17
+ placement requirements or verify the destination when placement is critical.
18
+ - Use supplied or approved imagery and preserve its meaning. Do not stretch
19
+ images, crop away evidence, or imply an unverified product interface is real.
20
+ - For mixed compositions, use [text with visuals](patterns.md#text-with-visuals)
21
+ to choose the image role, crop, text region, and any edge or contrast treatment.
22
+ Do not add badges, eyebrows, metadata, or a call to action simply because other
23
+ covers contain them.
24
+
25
+ Inspect both the full-resolution export and a reduced preview at the intended
26
+ viewing size. Verify the headline remains legible and essential details survive
27
+ the expected crop. Export PNG at the requested size; PDF is optional if requested.
@@ -0,0 +1,40 @@
1
+ # Multi-page long document
2
+
3
+ Use for reports, essays, field guides, proposals, and other sustained reading.
4
+ Apply the [shared guidance](../SKILL.md#before-authoring) and the
5
+ [authoring floor](../floor.md), not slide-density conventions.
6
+
7
+ - Start with the theme's portrait `a4` preset for an unspecified printable
8
+ document; honor Letter or other destination sizes. Keep margins, body
9
+ typography, and heading levels consistent across pages.
10
+ - Test the page structure before filling the document: a short page and a dense
11
+ page, both with multiline source notes. Apply the floor's `page-footer` rule.
12
+ A flex-column page with non-shrinking body and footer, and `margin-top: auto`
13
+ on the footer, puts spare space above it rather than between its parts.
14
+ Check the same bottom margin and body clearance on both pages; neither may
15
+ rely on hiding overflow or shrinking text. Then reuse that structure.
16
+ - Organize the argument into sections before dividing it into pages. Use normal
17
+ HTML text flow within each page, not separately positioned lines or sentences.
18
+ Choose line measure and leading for continuous reading at the delivered size.
19
+ - Author every page explicitly. Konpeki will not flow an overflowing paragraph
20
+ into a new page; CSS print-break hints do not replace `data-page` boundaries.
21
+ Measure real text with the loaded fonts before deciding where to split it.
22
+ - Keep headings with their opening paragraph and captions with their figures.
23
+ Avoid isolated first or last lines and stranded short list items. Split a long
24
+ paragraph only at a readable boundary, preserving its wording and punctuation.
25
+ - Keep complete table rows together. When a table spans pages, repeat its
26
+ column headings and make units and continuation clear. Do not silently drop
27
+ rows, references, or qualifications to meet a page count.
28
+ - Use page numbers for navigation. Add running titles or a contents page only
29
+ when the length and structure warrant them. Keep footnotes and citations with
30
+ the claims they qualify; essential evidence is not decorative footer clutter.
31
+ - On revision, recheck the following pages: a changed paragraph can alter the
32
+ best page breaks even when the edited page no longer overflows.
33
+
34
+ Inspect every PDF page, including the final page, for continuity, uneven density,
35
+ awkward breaks, missing text, and footer grouping and clearance. Compare page
36
+ endings across the document; a detached footer can pass overflow checks. Review
37
+ text extraction order when searchable or accessible reading is part of delivery.
38
+
39
+ The footer check comes from the VulcanBench SWE-v4 report: its source note floated
40
+ above an independently pinned page number despite passing overflow inspection.
@@ -0,0 +1,27 @@
1
+ # One-pager or card
2
+
3
+ Use for a bounded brief, summary, reference sheet, invitation, or information
4
+ card. Apply the [shared guidance](../SKILL.md#before-authoring) and the
5
+ [authoring floor](../floor.md). A résumé or presentation slide uses its own guide
6
+ even when it contains only one page.
7
+
8
+ - Deliver one explicit page. Use the `a4` preset for an unspecified printable
9
+ one-pager; for a card, choose dimensions for its destination. Landscape, portrait,
10
+ square, and custom sizes are all valid; do not force cards into a slide ratio.
11
+ - Establish the page's reader task: understand a proposal, compare options,
12
+ recall information, or act on an invitation. Let that task determine emphasis
13
+ and section order instead of imposing a fixed number of content blocks.
14
+ - Retain the evidence needed to interpret the main message. A headline, a
15
+ prominent number, and a call to action are not sufficient if scope, units,
16
+ conditions, or supporting facts have disappeared.
17
+ - Use columns for genuinely parallel or scannable material. Use flowing prose
18
+ for an argument. Do not turn every paragraph into an equally prominent card.
19
+ - Add contact details or a next action only when the brief calls for them.
20
+ Keep them findable without giving boilerplate the same weight as the content.
21
+ - Treat the one-page limit as an editorial constraint, not permission to use
22
+ tiny type. Condense repetition and recompose; if required content still cannot
23
+ fit legibly, surface that conflict rather than adding a second page silently.
24
+
25
+ Check the whole page at its intended print or display size, including small cards.
26
+ Whitespace should reveal hierarchy, not leave essential copy crowded into one
27
+ corner while a decorative region consumes the page.
@@ -0,0 +1,118 @@
1
+ # Content and visual patterns
2
+
3
+ Read only the sections that occur in the work, after the
4
+ [shared guidance](../SKILL.md#before-authoring) and [authoring floor](../floor.md).
5
+ Choose by the audience's question, not a favorite layout. Plain text or an image
6
+ may be enough. Infer the form without asking the user to choose a diagram taxonomy;
7
+ preserve an explicitly requested form unless a change is agreed.
8
+
9
+ ## Text with visuals
10
+
11
+ Use when text shares a composition with photography, screenshots, illustrations,
12
+ charts, diagrams, or other graphic elements, in any output format.
13
+
14
+ - Decide the visual's role: evidence, explanation, main subject, or atmosphere.
15
+ A chart remains evidence even when it looks decorative. A screenshot may
16
+ contain text the reader needs to read; budget for that at delivery size.
17
+ Keep supporting text that adds meaning, not a caption just to fill a region.
18
+ - Plan the crop, focal subject, and text region together before filling the
19
+ page. Keep text clear of faces, subjects, meaningful details, and data marks.
20
+ Do not finish the visual and squeeze required copy into the remaining space.
21
+ - Use side-by-side regions when text and visual explain each other and both
22
+ remain readable. Stack them when the format is narrow or the visual needs
23
+ full width. Overlay text when the image has a suitable text region or its
24
+ atmosphere should fill the composition. These are choices, not fixed templates.
25
+ - For two columns, give text the claim and interpretation, and the visual the
26
+ evidence or explanation; avoid repetition. Allocate width for readable visual
27
+ details and a useful text measure, not an automatic 50/50 split. Align meaningful
28
+ anchors such as the headline and plot area, accounting for whitespace inside
29
+ an image. Keep captions, units, legends, and sources with their visual. Recompose
30
+ into stacked regions rather than squeezing both columns in a narrow format.
31
+ - First find a readable text region through placement: beside the visual or
32
+ in quiet image space. Add an opaque field over an image only when needed for
33
+ required text, not to demonstrate a treatment. Separate regions need not be
34
+ cards, and white is only one field color. Set its ink explicitly, including
35
+ nested type roles; a contrast field should not obscure meaningful content.
36
+ - Use local darkening or lightening to support text when the image can retain
37
+ its meaning. Use an edge gradient or mask when an atmospheric image needs to
38
+ blend into the surrounding field; a clean edge is equally valid. Apply the
39
+ effect to the image or a separate overlay, never to the text or a shared
40
+ parent. Do not use text shadows as a substitute for a readable region.
41
+ - Keep informational visuals intact. Do not fade chart marks or axes, hide
42
+ screenshot details, or turn a meaningful diagram into background texture.
43
+ Place explanations near what they qualify, with clearance from marks and
44
+ labels. Preserve aspect ratio and meaningful relative scale; identify crops
45
+ and alterations when they could change how evidence is interpreted.
46
+ - Review the final crop at delivery size and reduced size, not just enlarged.
47
+ Include captions, sources, and qualifications in that review. Check local
48
+ contrast under every line, subject visibility, effect boundaries, and whether
49
+ the visual still communicates its purpose. Solid-color contrast checks cannot
50
+ establish readability over images, gradients, or masks.
51
+
52
+ ## Charts and quantities
53
+
54
+ - Choose the encoding for the question: comparison, change over time,
55
+ distribution, or parts of a whole. Preserve units, denominators, time windows,
56
+ uncertainty, missing values, and sources.
57
+ - Apply the floor's `honest-encoding` rule. Make scales explicit; distinguish
58
+ a truncated axis from a complete magnitude. Keep scales consistent across
59
+ charts meant to be compared.
60
+ - Derive mark lengths, areas, and positions from the supplied values, not visual
61
+ guesses. For parts or branches, reconcile totals and explain any remainder.
62
+ - Keep labels near their marks and give annotations clearance from marks and
63
+ axes. Do not turn uncertain or illustrative data into apparently measured
64
+ precision.
65
+
66
+ ## Tables
67
+
68
+ - Use a table for exact lookup or repeated attributes, not merely to position
69
+ unrelated paragraphs. Name columns and units; align comparable numbers and
70
+ use consistent precision. Distinguish zero, unavailable, and not applicable.
71
+ - Align short row labels with the first line of multiline descriptions. Let
72
+ rows grow with content; do not hide a long cell to maintain equal heights.
73
+ - Use rules or fills when they clarify rows, groups, or totals. A table-ending
74
+ rule belongs to the table, not the page footer. Keep notes clear of that rule.
75
+ - On continued tables, preserve complete rows and repeat the information needed
76
+ to interpret the next page.
77
+
78
+ ## Comparisons and decisions
79
+
80
+ - Compare options against the same criteria, units, and scope. Preserve material
81
+ drawbacks and unknowns; do not invent scores to create a neat ranking.
82
+ - Align corresponding rows or measures so the reader need not reconstruct the
83
+ mapping. Different evidence lengths do not require identical box heights.
84
+ - Make a recommendation distinguishable from the evidence. Keep its conditions
85
+ near it rather than presenting a conditional choice as universally best.
86
+
87
+ ## Processes and sequences
88
+
89
+ - Show inputs, actors or steps, outcomes, and meaningful blockers. Label branches
90
+ and return paths; pending is not approved and retrying is not completed.
91
+ - Use arrows only for supported direction, dependency, or order. Preserve message
92
+ order and named participants when time is the story. Label asynchronous work,
93
+ failure paths, and guarded transitions when those distinctions matter.
94
+ - Give every connector an identifiable source and destination. Route it clear
95
+ of labels and unrelated nodes; near-touches must not imply junctions. Useful
96
+ elbows are preferable to diagonal fans or detached endpoints. Check arrowheads
97
+ and shafts at delivery size, not just coordinate bounds.
98
+
99
+ ## Systems and hierarchies
100
+
101
+ - Name entities, connections, and meaningful boundaries. Use enclosure only for
102
+ actual containment, ownership, or scope; proximity alone must not invent one.
103
+ - Keep parent-child meaning consistent. Use a system map rather than a tree
104
+ when shared ownership or cross-links are central to the explanation.
105
+ - Label connection meaning and direction. A static dependency map is not an
106
+ execution trace; use a sequence when event order is the reader's question.
107
+ - Preserve specialized notation when requested. Draw it directly in HTML/SVG;
108
+ do not invent a component schema or convert every relationship to one box type.
109
+
110
+ ## Annotated details
111
+
112
+ - Retain enough context to locate the detail. Put labels near the relevant
113
+ evidence and make crops or mockups explicit.
114
+ - Verify what the artifact actually demonstrates; do not add unsupported
115
+ specifications or measurements. An annotation should clarify, not obscure.
116
+
117
+ Inspect the rendered pattern: are relationships, quantities, and labels clear
118
+ without narration? Browser overflow checks cannot establish their truth.
@@ -0,0 +1,28 @@
1
+ # Résumé
2
+
3
+ Use for a résumé or CV, even when it is a single page. Apply the
4
+ [shared guidance](../SKILL.md#before-authoring) and the
5
+ [authoring floor](../floor.md); do not treat it as a marketing slide or a generic card.
6
+
7
+ - Start with the theme's portrait `a4` preset; use Letter or another size when
8
+ the destination calls for it. Let relevant supplied experience determine
9
+ length rather than assuming every résumé must fit one page.
10
+ - Make the person's name, contact details, and relevant experience easy to scan.
11
+ Choose section order for the candidate and role; do not manufacture a summary,
12
+ project, award, or skills section merely to fill a template.
13
+ - Keep organization, role, dates, and location unambiguous. Use consistent date
14
+ formatting and clear chronology; align dates without detaching them from the
15
+ role they describe.
16
+ - Write concise evidence-led accomplishments from supplied facts. Never invent
17
+ metrics, seniority, qualifications, or causal claims. Preserve materially
18
+ different roles rather than merging them to make the layout cleaner.
19
+ - Start with a simple reading order and flowing text. Use additional columns
20
+ only when they remain readable and extract in the intended order. Avoid
21
+ skill-rating bars and icon-only contact information.
22
+ - Keep a role heading with its first accomplishment. Prefer breaks between
23
+ roles or sections; if an entry must continue, make that continuation clear.
24
+ Remove low-value repetition before reducing type to meet a page limit.
25
+
26
+ Inspect the PDF at normal reading size. Check contact text, links, dates,
27
+ selectable text, and extraction order. Do not claim ATS compatibility from
28
+ appearance or a clean overflow report alone.
@@ -0,0 +1,28 @@
1
+ # Landscape slides
2
+
3
+ Use for presentation pages or a deck, including a single slide. Apply the
4
+ [shared guidance](../SKILL.md#before-authoring) and the
5
+ [authoring floor](../floor.md); the brief overrides format defaults below.
6
+
7
+ - Start with the theme's 16:9 `presentation` preset when the destination
8
+ supplies no size. Honor a requested ratio and keep deck dimensions consistent
9
+ unless a mixed-size deliverable is intentional.
10
+ - Build a reading sequence: each page advances the explanation rather than
11
+ repeating the previous page's conclusion. Do not impose a fixed opening,
12
+ agenda, or closing-page formula on every brief.
13
+ - Give a page one main point and enough evidence to support it. A takeaway
14
+ headline is useful; a giant headline that displaces the explanation is not.
15
+ - Compose for viewing at a distance. Start with the preset's resolved type
16
+ roles, then check the intended view; the small-text floor is not a body-copy
17
+ target. Keep sources, units, and caveats legible too.
18
+ - Prefer short, coherent text groups over document-length paragraphs. Use a
19
+ diagram, chart, image, or comparison when it carries the explanation; do not
20
+ require a visual on every page or flatten every idea into matching cards.
21
+ - When content exceeds a page, redistribute it into a meaningful sequence
22
+ rather than shrinking everything. Respect an explicit page-count constraint.
23
+ - Keep recurring type, margins, and semantic colors consistent while varying
24
+ composition when the content changes. A title page need not dictate every page.
25
+
26
+ Review the deck at fitted size and in reading order. Check that comparisons use
27
+ the same criteria and scales, and that each page remains understandable without
28
+ presenter notes. Konpeki delivers static pages, not a presentation player.
@@ -1,23 +1,22 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
- import { homedir } from "node:os";
5
- import { dirname, join, resolve } from "node:path";
4
+ import { dirname, join } from "node:path";
6
5
  import { fileURLToPath } from "node:url";
7
6
 
8
7
  // A copied skill must resolve a known runtime, not follow repo-relative links.
9
- const version = "0.3.0";
10
- const probeDocument = fileURLToPath(new URL("../assets/blank.json", import.meta.url));
8
+ const version = "0.4.0";
9
+ const probeDocument = fileURLToPath(new URL("../assets/blank.html", import.meta.url));
11
10
  function runtime(root) {
12
11
  try {
13
12
  const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
14
13
  if (pkg.name !== "konpeki" || pkg.version !== version) return;
15
14
  const cli = join(root, pkg.bin.konpeki);
16
15
  const sourceCLI = join(root, "bin/konpeki.mjs");
17
- if (!["AGENTS.md", "AUTHORING.md", "composition/README.md", "design/README.md", "docs/workflow.md"]
16
+ if (!["AUTHORING.md", "html/README.md", "theme.css", "theme-base.css"]
18
17
  .every(path => existsSync(join(root, path)))) return;
19
18
  // The development checkout runs TypeScript directly on the pinned Node.
20
- for (const candidate of new Set([cli, sourceCLI])) {
19
+ for (const candidate of [sourceCLI, cli]) {
21
20
  if (!existsSync(candidate)) continue;
22
21
  const probe = spawnSync(process.execPath, [candidate, "validate", probeDocument], {
23
22
  stdio: "ignore",
@@ -33,35 +32,15 @@ function runtime(root) {
33
32
  try {
34
33
  if (Number(process.versions.node.split(".")[0]) < 24)
35
34
  throw new Error("Konpeki needs Node.js 24+. Use your host's approved toolchain setup, then retry.");
36
- if (process.argv.slice(2).some(arg => arg !== "--install"))
37
- throw new Error("Usage: node ensure-runtime.mjs [--install]");
35
+ if (process.argv.length > 2) throw new Error("Usage: node ensure-runtime.mjs");
38
36
  let workspace;
39
37
  try {
40
38
  workspace = dirname(createRequire(join(process.cwd(), "package.json")).resolve("konpeki/package.json"));
41
39
  } catch {}
42
40
  const bundled = fileURLToPath(new URL("../../../", import.meta.url));
43
- const cacheBase = process.platform === "win32"
44
- ? process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local")
45
- : process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
46
- const cache = resolve(cacheBase, "konpeki", version);
47
- const cachedRoot = join(cache, "node_modules", "konpeki");
48
- let found = (workspace && runtime(workspace)) || runtime(bundled) || runtime(cachedRoot);
49
- if (!found) {
50
- if (!process.argv.includes("--install"))
51
- throw new Error(`Konpeki ${version} is not installed. After the host approves installation, rerun with --install. This uses a user cache and leaves project dependencies unchanged.`);
52
- mkdirSync(cache, { recursive: true });
53
- // Explicit local prefix prevents npm from walking up into an ancestor project.
54
- // Resolve "." from the absolute cwd; no user path enters Windows shell text.
55
- const result = spawnSync("npm", ["install", "--prefix=.", "--global=false", "--save-exact", "--no-audit", "--no-fund", `konpeki@${version}`], {
56
- cwd: cache,
57
- stdio: ["inherit", 2, 2],
58
- shell: process.platform === "win32",
59
- });
60
- if (result.error || result.status !== 0)
61
- throw new Error("Konpeki installation failed. Check npm/network access and retry; the runtime is not ready.");
62
- found = runtime(cachedRoot);
63
- if (!found) throw new Error("The installed Konpeki runtime is incomplete or incompatible.");
64
- }
41
+ const found = (workspace && runtime(workspace)) || runtime(bundled);
42
+ if (!found)
43
+ throw new Error("Konpeki 0.4.0 is required. Check availability with npm view konpeki@0.4.0 version, then install that exact version in this workspace. If unavailable, use a locally packed tarball or prepared checkout. The 0.3.x runtime is incompatible. See SETUP.md.");
65
44
  console.log(JSON.stringify(found));
66
45
  } catch (error) {
67
46
  console.error(error.message);
@@ -1,15 +1,15 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { dirname, resolve } from "node:path";
2
+ import { constants, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  try {
7
7
  const [cli, input, ...extra] = process.argv.slice(2);
8
- if (!cli || !input || extra.length)
9
- throw new Error("Usage: node prepare-document.mjs <cli> <composition.json>");
10
- const compositionPath = resolve(input);
8
+ if (!cli || !input || extra.length) throw new Error("Usage: node prepare-document.mjs <cli> <document.html>");
9
+ if (!/\.html?$/i.test(input)) throw new Error("Use an .html document path.");
10
+ const documentPath = resolve(input);
11
11
  function validate(path) {
12
- // Use the published JS CLI, not TypeScript imports from node_modules.
12
+ // Use the supplied CLI, not internal TypeScript imports from node_modules.
13
13
  const result = spawnSync(process.execPath, [cli, "validate", path], {
14
14
  stdio: ["ignore", 2, 2],
15
15
  });
@@ -17,23 +17,34 @@ try {
17
17
  throw new Error("Document validation failed; existing files were not changed.");
18
18
  }
19
19
  let created = false;
20
- if (!existsSync(compositionPath)) {
21
- if (existsSync(`${compositionPath}.review.json`))
22
- throw new Error("Review data exists without its composition. Restore the document or choose a new path.");
23
- const template = fileURLToPath(new URL("../assets/blank.json", import.meta.url));
20
+ if (!existsSync(documentPath)) {
21
+ const template = fileURLToPath(new URL("../assets/blank.html", import.meta.url));
24
22
  validate(template);
25
23
  const blank = readFileSync(template);
26
- mkdirSync(dirname(compositionPath), { recursive: true });
24
+ const root = resolve(dirname(cli), "..");
25
+ const assets = new Map([["theme.css", join(root, "theme.css")], ["theme-base.css", join(root, "theme-base.css")]]);
26
+ // Preflight every asset before writing any: never replace an author's
27
+ // adapted stylesheet in a shared folder.
28
+ for (const [asset, source] of assets) {
29
+ const destination = join(dirname(documentPath), asset);
30
+ if (existsSync(destination) && !readFileSync(destination).equals(readFileSync(source)))
31
+ throw new Error(`Existing asset differs: ${destination}. Use a new directory; existing files were not changed.`);
32
+ }
33
+ mkdirSync(dirname(documentPath), { recursive: true });
34
+ for (const [asset, source] of assets) {
35
+ try { copyFileSync(source, join(dirname(documentPath), asset), constants.COPYFILE_EXCL); }
36
+ catch (error) { if (error.code !== "EEXIST") throw error; }
37
+ }
27
38
  try {
28
- writeFileSync(compositionPath, blank, { flag: "wx" });
39
+ writeFileSync(documentPath, blank, { flag: "wx" });
29
40
  created = true;
30
41
  } catch (error) {
31
42
  // Another initializer may have created it; reopen, never replace it.
32
43
  if (error.code !== "EEXIST") throw error;
33
44
  }
34
45
  }
35
- validate(compositionPath);
36
- console.log(JSON.stringify({ compositionPath, created }));
46
+ validate(documentPath);
47
+ console.log(JSON.stringify({ documentPath, created }));
37
48
  } catch (error) {
38
49
  console.error(error.message);
39
50
  process.exitCode = 1;
@@ -0,0 +1,156 @@
1
+ import { useEffect, useId, useRef, useState, type ReactNode, type RefObject } from "react";
2
+ import { layoutPages } from "../lib/page-board.ts";
3
+
4
+ export type PageBoardControls = {
5
+ goTo: (id: string, fit?: boolean) => void;
6
+ pan: (x: number, y: number) => void;
7
+ zoom: (factor: number) => void;
8
+ stopZoom: () => void;
9
+ };
10
+
11
+ export function PageBoard<Page extends { id: string; name: string; canvas: { width: number; height: number } }>({ draft, children, controls, onActivePage }: {
12
+ draft: { pages: Page[] }; children: (page: Page, index: number) => ReactNode;
13
+ controls: RefObject<PageBoardControls | null>; onActivePage: (id: string) => void;
14
+ }) {
15
+ const root = useRef<HTMLDivElement>(null);
16
+ const content = useRef<HTMLDivElement>(null);
17
+ const [space, setSpace] = useState({ width: 0, height: 0 });
18
+ const marker = useId();
19
+ useEffect(() => {
20
+ const viewport = root.current!.closest<HTMLElement>(".workspace")!;
21
+ const observer = new ResizeObserver(() => {
22
+ const help = viewport.querySelector<HTMLElement>(".board-help");
23
+ const footer = help ? help.offsetHeight + parseFloat(getComputedStyle(help).marginTop) : 0;
24
+ const top = root.current!.getBoundingClientRect().top - viewport.getBoundingClientRect().top + viewport.scrollTop;
25
+ const padding = getComputedStyle(viewport);
26
+ const width = viewport.clientWidth - parseFloat(padding.paddingLeft) - parseFloat(padding.paddingRight);
27
+ const height = Math.max(0, viewport.clientHeight - top - parseFloat(padding.paddingBottom) - footer);
28
+ setSpace(current => current.width === width && current.height === height ? current : { width, height });
29
+ });
30
+ observer.observe(root.current!);
31
+ observer.observe(viewport);
32
+ return () => observer.disconnect();
33
+ }, []);
34
+ useEffect(() => {
35
+ const viewport = root.current!.closest<HTMLElement>(".workspace")!;
36
+ const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
37
+ let scale = 1, goal = 1, gestureScale: number | undefined;
38
+ let frame = 0, lastTime = 0, x = 0, y = 0, smooth = true;
39
+ let anchorX = 0, anchorY = 0;
40
+ function activePage() {
41
+ const center = viewport.getBoundingClientRect().left + viewport.clientWidth / 2;
42
+ const pages = Array.from(root.current!.querySelectorAll<HTMLElement>(".board-page"));
43
+ const page = pages.reduce<HTMLElement | undefined>((nearest, page) => {
44
+ const rect = page.getBoundingClientRect(), previous = nearest?.getBoundingClientRect();
45
+ return !previous || Math.abs(rect.left + rect.width / 2 - center) < Math.abs(previous.left + previous.width / 2 - center) ? page : nearest;
46
+ }, undefined);
47
+ if (page?.dataset.page) onActivePage(page.dataset.page);
48
+ }
49
+ function stopZoom() {
50
+ cancelAnimationFrame(frame); frame = 0; goal = scale;
51
+ if (content.current) delete content.current.dataset.zooming;
52
+ }
53
+ function animate(time: number) {
54
+ frame = 0;
55
+ const board = content.current;
56
+ if (!board) return;
57
+ const distance = Math.log(goal / scale);
58
+ const fraction = smooth && !reducedMotion.matches ? 1 - Math.exp(-Math.min(time - lastTime, 64) / 40) : 1;
59
+ const next = Math.abs(distance) < .002 ? goal : scale * Math.exp(distance * fraction);
60
+ lastTime = time;
61
+ // Keep view-only zoom out of React. Retain the original anchor across
62
+ // frames so integer scroll rounding cannot accumulate into pointer drift.
63
+ board.style.zoom = String(next);
64
+ const after = board.getBoundingClientRect();
65
+ viewport.scrollLeft += after.left + anchorX * next - x;
66
+ viewport.scrollTop += after.top + anchorY * next - y;
67
+ scale = next;
68
+ activePage();
69
+ if (scale !== goal) frame = requestAnimationFrame(animate);
70
+ else delete board.dataset.zooming;
71
+ }
72
+ function zoomAt(next: number, clientX: number, clientY: number, ease: boolean) {
73
+ if (!content.current) return;
74
+ const bounds = content.current.getBoundingClientRect();
75
+ anchorX = (clientX - bounds.left) / scale;
76
+ anchorY = (clientY - bounds.top) / scale;
77
+ goal = Math.min(4, Math.max(.1, next));
78
+ x = clientX; y = clientY; smooth = ease;
79
+ content.current.dataset.zooming = "true";
80
+ if (!frame) { lastTime = performance.now(); frame = requestAnimationFrame(animate); }
81
+ }
82
+ controls.current = {
83
+ stopZoom,
84
+ goTo(id, fit = false) {
85
+ const page = root.current!.querySelector<HTMLElement>(`.board-page[data-page="${CSS.escape(id)}"]`);
86
+ if (!page || !content.current) return;
87
+ stopZoom();
88
+ if (fit) { scale = goal = 1; content.current.style.zoom = "1"; }
89
+ const rect = page.getBoundingClientRect(), bounds = viewport.getBoundingClientRect();
90
+ viewport.scrollLeft += rect.left + rect.width / 2 - bounds.left - viewport.clientWidth / 2;
91
+ viewport.scrollTop += rect.top + rect.height / 2 - bounds.top - viewport.clientHeight / 2;
92
+ onActivePage(id);
93
+ },
94
+ pan(dx, dy) { stopZoom(); viewport.scrollLeft += dx; viewport.scrollTop += dy; activePage(); },
95
+ zoom(factor) {
96
+ const bounds = viewport.getBoundingClientRect();
97
+ zoomAt(goal * factor, bounds.left + viewport.clientWidth / 2, bounds.top + viewport.clientHeight / 2, true);
98
+ },
99
+ };
100
+ function wheel(event: WheelEvent) {
101
+ // Floating review panels own their scrolling, not the canvas beneath them.
102
+ if ((event.target as Element).closest(".revision-notes,dialog")) return;
103
+ if (!event.ctrlKey && !event.metaKey) {
104
+ // A fitted row has no vertical travel. Let a plain mouse wheel navigate
105
+ // it, while preserving native two-axis panning when zoomed in.
106
+ if (!event.deltaX && viewport.scrollHeight <= viewport.clientHeight + 1 && viewport.scrollWidth > viewport.clientWidth) {
107
+ event.preventDefault();
108
+ const unit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? viewport.clientWidth : 1;
109
+ viewport.scrollLeft += event.deltaY * unit;
110
+ }
111
+ return;
112
+ }
113
+ event.preventDefault();
114
+ if (gestureScale !== undefined) return; // Safari can emit both event families.
115
+ const unit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? viewport.clientHeight : 1;
116
+ zoomAt(goal * Math.exp(-event.deltaY * unit * .004), event.clientX, event.clientY, true);
117
+ }
118
+ function gesture(event: Event) {
119
+ const pinch = event as Event & { scale: number; clientX: number; clientY: number };
120
+ event.preventDefault();
121
+ if (event.type === "gesturestart") gestureScale = goal;
122
+ else if (event.type === "gestureend") gestureScale = undefined;
123
+ else if (gestureScale !== undefined) zoomAt(gestureScale * pinch.scale, pinch.clientX, pinch.clientY, false);
124
+ }
125
+ const observer = new ResizeObserver(activePage);
126
+ observer.observe(root.current!);
127
+ observer.observe(viewport);
128
+ viewport.addEventListener("scroll", activePage);
129
+ viewport.addEventListener("wheel", wheel, { passive: false });
130
+ for (const type of ["gesturestart", "gesturechange", "gestureend"]) viewport.addEventListener(type, gesture, { passive: false });
131
+ return () => {
132
+ cancelAnimationFrame(frame);
133
+ controls.current = null;
134
+ observer.disconnect();
135
+ viewport.removeEventListener("scroll", activePage);
136
+ viewport.removeEventListener("wheel", wheel);
137
+ for (const type of ["gesturestart", "gesturechange", "gestureend"]) viewport.removeEventListener(type, gesture);
138
+ };
139
+ }, [controls, onActivePage]);
140
+ const layout = layoutPages(draft.pages.map(page => page.canvas), space.width, space.height);
141
+ return <div ref={root} className="page-board" style={{ minHeight: space.height }} aria-label="All pages in reading order">
142
+ {space.width > 0 && <div ref={content} className="page-board-content" style={{ width: layout.width, height: layout.height, zoom: 1 }}>
143
+ <svg className="page-order" width={layout.width} height={layout.height} aria-hidden="true">
144
+ <defs><marker id={marker} markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path d="M1 1 7 4 1 7" /></marker></defs>
145
+ {layout.arrows.map((path, index) => <path key={draft.pages[index].id} data-from={draft.pages[index].id} data-to={draft.pages[index + 1].id} d={path} markerEnd={`url(#${marker})`} />)}
146
+ </svg>
147
+ {draft.pages.map((page, index) => {
148
+ const box = layout.pages[index];
149
+ return <article key={page.id} className="board-page" data-page={page.id} aria-label={`Page ${index + 1}: ${page.name}`}
150
+ style={{ left: box.x, top: box.y - 32, width: box.width }}>
151
+ {children(page, index)}
152
+ </article>;
153
+ })}
154
+ </div>}
155
+ </div>;
156
+ }