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.
- package/.agents/skills/authoring-visuals/SKILL.md +82 -0
- package/AGENTS.md +33 -0
- package/AUTHORING.md +233 -0
- package/LICENSE +201 -0
- package/README.md +70 -0
- package/SETUP.md +86 -0
- package/composition/README.md +166 -0
- package/composition/compile.ts +227 -0
- package/composition/document.ts +709 -0
- package/composition/schema.json +2992 -0
- package/composition/schema.ts +435 -0
- package/composition/theme-tokens.ts +16 -0
- package/composition/types.ts +286 -0
- package/composition/validate.ts +684 -0
- package/composition/vector.ts +143 -0
- package/composition/visualizations.ts +335 -0
- package/design/README.md +17 -0
- package/design/palettes/README.md +14 -0
- package/design/palettes/base.ts +14 -0
- package/design/palettes/candidates.ts +19 -0
- package/design/palettes/index.ts +78 -0
- package/design/review/color-theme.md +44 -0
- package/design/review/layout.md +16 -0
- package/design/review/text.md +18 -0
- package/design/review/typography.md +15 -0
- package/design/review/visuals.md +31 -0
- package/design/semantic-patterns.md +43 -0
- package/design/themes/README.md +22 -0
- package/design/themes/index.ts +13 -0
- package/design/visual-languages/technical-product.md +17 -0
- package/design/visual-review.md +88 -0
- package/docs/development.md +140 -0
- package/docs/workflow.md +61 -0
- package/index.html +17 -0
- package/lib/assets.d.ts +8 -0
- package/lib/charts.ts +18 -0
- package/lib/contrast.ts +16 -0
- package/lib/layouts.ts +50 -0
- package/lib/slide.tsx +42 -0
- package/lib/taste.ts +17 -0
- package/lib/text.tsx +89 -0
- package/lib/typeface.ts +44 -0
- package/package.json +85 -0
- package/runtime/konpeki.mjs +1685 -0
- package/scripts/migrate-react-page.ts +120 -0
- package/slides/README.md +153 -0
- package/slides/architecture/PROMPT.md +31 -0
- package/slides/architecture/index.tsx +102 -0
- package/slides/article-brief/PROMPT.md +35 -0
- package/slides/article-brief/index.tsx +71 -0
- package/slides/bar-chart/PROMPT.md +39 -0
- package/slides/bar-chart/index.tsx +97 -0
- package/slides/comparison/PROMPT.md +29 -0
- package/slides/comparison/index.tsx +95 -0
- package/slides/decision-memo/PROMPT.md +34 -0
- package/slides/decision-memo/index.tsx +85 -0
- package/slides/delivery-plan/PROMPT.md +45 -0
- package/slides/delivery-plan/index.tsx +105 -0
- package/slides/experiment/PROMPT.md +44 -0
- package/slides/experiment/index.tsx +127 -0
- package/slides/incident-workflow/PROMPT.md +57 -0
- package/slides/incident-workflow/index.tsx +78 -0
- package/slides/introducing-konpeki/PROMPT.md +19 -0
- package/slides/introducing-konpeki/README.md +54 -0
- package/slides/introducing-konpeki/SOURCE.md +20 -0
- package/slides/introducing-konpeki/author.ts +163 -0
- package/slides/introducing-konpeki/composition.json +3265 -0
- package/slides/line-chart/PROMPT.md +40 -0
- package/slides/line-chart/index.tsx +72 -0
- package/slides/migration/PROMPT.md +38 -0
- package/slides/migration/index.tsx +89 -0
- package/slides/og-images/PROMPT.md +21 -0
- package/slides/og-images/index.tsx +76 -0
- package/slides/product-introduction/PROMPT.md +24 -0
- package/slides/product-introduction/index.tsx +105 -0
- package/slides/research-brief/PROMPT.md +40 -0
- package/slides/research-brief/index.tsx +104 -0
- package/slides/results-explanation/PROMPT.md +32 -0
- package/slides/results-explanation/index.tsx +96 -0
- package/slides/retrospective/PROMPT.md +43 -0
- package/slides/retrospective/index.tsx +105 -0
- package/slides/sankey/PROMPT.md +11 -0
- package/slides/sankey/index.tsx +93 -0
- package/slides/teaching/PROMPT.md +45 -0
- package/slides/teaching/index.tsx +124 -0
- package/slides/vertical-bar-charts/PROMPT.md +13 -0
- package/slides/vertical-bar-charts/index.tsx +97 -0
- package/src/app/App.tsx +694 -0
- package/src/assets/konpeki-mark.png +0 -0
- package/src/components/Canvas.tsx +1168 -0
- package/src/components/DiagramTypeIcon.tsx +78 -0
- package/src/components/InspectorPanel.tsx +687 -0
- package/src/components/LeftPanel.tsx +107 -0
- package/src/components/PageSizePicker.tsx +30 -0
- package/src/components/Presentation.tsx +105 -0
- package/src/components/RightPanel.tsx +201 -0
- package/src/components/VectorOverflowWarning.tsx +46 -0
- package/src/components/WorkspaceChrome.tsx +199 -0
- package/src/components/ui.tsx +53 -0
- package/src/lib/examples/react-page-migration.json +1295 -0
- package/src/lib/examples.ts +42 -0
- package/src/lib/export-png.ts +101 -0
- package/src/lib/file-session.ts +74 -0
- package/src/lib/history.ts +53 -0
- package/src/lib/model.ts +188 -0
- package/src/lib/page-size.ts +24 -0
- package/src/lib/presentation.ts +17 -0
- package/src/lib/storage.ts +43 -0
- package/src/lib/theme.ts +25 -0
- package/src/lib/use-file-session.ts +162 -0
- package/src/main.tsx +26 -0
- package/src/styles/base.css +105 -0
- package/src/styles/canvas.css +268 -0
- package/src/styles/chrome.css +214 -0
- package/src/styles/component-previews.css +386 -0
- package/src/styles/feedback.css +71 -0
- package/src/styles/left-panel.css +125 -0
- package/src/styles/presentation.css +72 -0
- package/src/styles/right-panel.css +1172 -0
- package/src/styles/shell.css +247 -0
- package/vite.config.ts +6 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import '@fontsource/ibm-plex-sans/latin-400.css';
|
|
3
|
+
import '@fontsource/ibm-plex-sans/latin-500.css';
|
|
4
|
+
import '@fontsource/ibm-plex-sans/latin-600.css';
|
|
5
|
+
|
|
6
|
+
const ink = '#172B3A';
|
|
7
|
+
const muted = '#52616C';
|
|
8
|
+
const accent = '#096F78';
|
|
9
|
+
const rule = '#B9C4CA';
|
|
10
|
+
const results = [
|
|
11
|
+
{ name: 'Individual', time: 300, median: 1.2, p95: 2.4 },
|
|
12
|
+
{ name: 'Batch of 20', time: 200, median: 1.8, p95: 4.1 },
|
|
13
|
+
{ name: 'Batch of 100', time: 150, median: 4.9, p95: 11.8 },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function Copy({ x = 112, y, size = 36, weight = 400, color = ink, children, anchor = 'start' }: {
|
|
17
|
+
x?: number; y: number; size?: number; weight?: number; color?: string;
|
|
18
|
+
children: ReactNode; anchor?: 'start' | 'middle' | 'end';
|
|
19
|
+
}) {
|
|
20
|
+
return <text x={x} y={y} fontSize={size} fontWeight={weight} fill={color} textAnchor={anchor}>{children}</text>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Frame({ page, title, description, children }: {
|
|
24
|
+
page: number; title: string; description: string; children: ReactNode;
|
|
25
|
+
}) {
|
|
26
|
+
return <svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"
|
|
27
|
+
fontFamily="IBM Plex Sans, sans-serif" role="img" aria-label={`${title}. ${description}`} data-fieldnote-page={page}>
|
|
28
|
+
<rect width="1920" height="1080" fill="#FFFFFF" />
|
|
29
|
+
<Copy y={158} size={68} weight={600}>{title}</Copy>
|
|
30
|
+
{children}
|
|
31
|
+
<Copy x={1808} y={1020} size={28} color={muted} anchor="end">{page} / 3</Copy>
|
|
32
|
+
</svg>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const Evidence = () => <Frame page={1} title="Batching buys speed at a freshness cost"
|
|
36
|
+
description="Fieldnote is a fictional document workspace. Synthetic, hand-authored results, one run per configuration. The same 60,000 updates ran on the same machine, starting with an empty queue and identical index. Individual: 300 seconds, 200 updates per second, median delay 1.2 seconds, p95 2.4 seconds. Batch of 20: 200 seconds, 300 updates per second, median 1.8 seconds, p95 4.1 seconds. Batch of 100: 150 seconds, 400 updates per second, median 4.9 seconds, p95 11.8 seconds. Visibility delay is submission to search. All final versions were correct, with no missing updates; no repeated runs, uncertainty estimates or production measurements.">
|
|
37
|
+
<Copy y={237} size={36} color={muted}>Fieldnote is a fictional workspace; its indexer handles updates individually.</Copy>
|
|
38
|
+
<Copy y={291} size={36} color={muted}>Same 60,000 updates · same machine · empty queue · identical starting index</Copy>
|
|
39
|
+
|
|
40
|
+
<Copy y={403} size={30} weight={500}>Configuration</Copy>
|
|
41
|
+
<Copy x={770} y={383} size={30} weight={500} anchor="end">Processing</Copy>
|
|
42
|
+
<Copy x={770} y={425} size={30} color={muted} anchor="end">seconds</Copy>
|
|
43
|
+
<Copy x={1090} y={383} size={30} weight={500} anchor="end">Throughput</Copy>
|
|
44
|
+
<Copy x={1090} y={425} size={30} color={muted} anchor="end">updates / sec</Copy>
|
|
45
|
+
<Copy x={1450} y={383} size={30} weight={500} anchor="end">Median delay</Copy>
|
|
46
|
+
<Copy x={1450} y={425} size={30} color={muted} anchor="end">seconds</Copy>
|
|
47
|
+
<Copy x={1808} y={383} size={30} weight={500} anchor="end">95th-percentile delay</Copy>
|
|
48
|
+
<Copy x={1808} y={425} size={30} color={muted} anchor="end">seconds</Copy>
|
|
49
|
+
{[460, 565, 670, 775].map(y => <line key={y} x1={112} x2={1808} y1={y} y2={y} stroke={rule} strokeWidth={2} />)}
|
|
50
|
+
{results.map((r, i) => <g key={r.name}>
|
|
51
|
+
<Copy y={528 + i * 105} size={40} weight={500}>{r.name}</Copy>
|
|
52
|
+
<Copy x={770} y={528 + i * 105} size={42} anchor="end">{r.time}</Copy>
|
|
53
|
+
<Copy x={1090} y={528 + i * 105} size={42} anchor="end">{60000 / r.time}</Copy>
|
|
54
|
+
<Copy x={1450} y={528 + i * 105} size={42} anchor="end">{r.median.toFixed(1)}</Copy>
|
|
55
|
+
<Copy x={1808} y={528 + i * 105} size={42} anchor="end">{r.p95.toFixed(1)}</Copy>
|
|
56
|
+
</g>)}
|
|
57
|
+
<Copy y={832} size={30} color={muted}>Visibility delay: submission → searchable document. Throughput: 60,000 ÷ processing seconds.</Copy>
|
|
58
|
+
<Copy y={884} size={30}>All final document versions matched expectations; no missing updates in the final check.</Copy>
|
|
59
|
+
<Copy y={949} size={30} color={muted}>Synthetic, hand-authored results · one run each · no uncertainty estimates or production measurements</Copy>
|
|
60
|
+
</Frame>;
|
|
61
|
+
|
|
62
|
+
function Bars({ kind, x, width }: { kind: 'rate' | 'delay'; x: number; width: number }) {
|
|
63
|
+
const rate = kind === 'rate';
|
|
64
|
+
const max = rate ? 400 : 12;
|
|
65
|
+
const target = rate ? 250 : 5;
|
|
66
|
+
const thresholdX = x + target / max * width;
|
|
67
|
+
return <g>
|
|
68
|
+
<Copy x={x} y={348} size={38} weight={500}>{rate ? 'Throughput · updates / sec' : '95th-percentile delay · seconds'}</Copy>
|
|
69
|
+
<Copy x={x} y={397} size={30} color={muted}>{rate ? 'At least 250 required' : 'No more than 5 required'}</Copy>
|
|
70
|
+
<line x1={thresholdX} x2={thresholdX} y1={440} y2={762} stroke={ink} strokeDasharray="9 9" strokeWidth={2} />
|
|
71
|
+
{results.map((r, i) => {
|
|
72
|
+
const value = rate ? 60000 / r.time : r.p95;
|
|
73
|
+
const pass = rate ? value >= target : value <= target;
|
|
74
|
+
const y = 468 + i * 110;
|
|
75
|
+
return <g key={r.name}>
|
|
76
|
+
<rect x={x} y={y} width={value / max * width} height={44} fill={i === 1 ? accent : muted} />
|
|
77
|
+
<Copy x={x + width + 24} y={y + 34} size={30} weight={500}>{value}{pass ? ' ✓' : ' ×'}</Copy>
|
|
78
|
+
</g>;
|
|
79
|
+
})}
|
|
80
|
+
<line x1={x} x2={x + width} y1={768} y2={768} stroke={rule} strokeWidth={2} />
|
|
81
|
+
{(rate ? [0, 100, 200, 300, 400] : [0, 3, 6, 9, 12]).map(tick => <g key={tick}>
|
|
82
|
+
<line x1={x + tick / max * width} x2={x + tick / max * width} y1={768} y2={780} stroke={rule} strokeWidth={2} />
|
|
83
|
+
<Copy x={x + tick / max * width} y={820} size={28} anchor="middle" color={muted}>{tick}</Copy>
|
|
84
|
+
</g>)}
|
|
85
|
+
</g>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const Decision = () => <Frame page={2} title="Batches of 20 deserve the follow-up test"
|
|
89
|
+
description="Only batches of 20 meet both proposed acceptance criteria in these single synthetic runs: at least 250 updates per second and p95 delay no greater than 5 seconds. Individual: 200 updates per second fails throughput, 2.4 seconds passes delay. Batch of 20: 300 and 4.1 both pass. Batch of 100: 400 passes throughput, 11.8 fails delay. The fastest configuration exceeds the delay ceiling by 6.8 seconds. These results support a follow-up test, not a production rollout.">
|
|
90
|
+
<Copy y={237} size={36} color={muted}>Only this configuration meets both proposed criteria in the single-run results.</Copy>
|
|
91
|
+
{results.map((r, i) => <Copy key={r.name} y={502 + i * 110} size={34} weight={i === 1 ? 600 : 400} color={i === 1 ? accent : ink}>{r.name}</Copy>)}
|
|
92
|
+
<Bars kind="rate" x={445} width={485} />
|
|
93
|
+
<Bars kind="delay" x={1170} width={485} />
|
|
94
|
+
<Copy x={112} y={893} size={34}>Batch of 100 is fastest, but its p95 delay exceeds the ceiling by 6.8 seconds.</Copy>
|
|
95
|
+
<Copy x={112} y={949} size={30} color={muted}>Synthetic, one run each. ✓ meets criterion · × misses criterion. This supports testing, not rollout.</Copy>
|
|
96
|
+
</Frame>;
|
|
97
|
+
|
|
98
|
+
const Next = () => <Frame page={3} title="Repeat the runs, then vary the arrival pattern"
|
|
99
|
+
description="Prioritize batches of 20 and retain individual and batch-of-100 controls. Repeat runs under the original fixed-workload conditions and report run-to-run variability rather than selecting the best run. Include sparse arrivals to measure waits for a partial batch, recording the flush policy; include bursts to measure queue growth and recovery. Measure throughput, median and p95 submission-to-search delay, and final-version correctness. A fixed workload does not establish live-traffic behavior. Reassess batches of 20 against at least 250 updates per second under offered load and p95 no greater than 5 seconds; revise batching or flushing and retest if either fails.">
|
|
100
|
+
<Copy y={237} size={36} color={muted}>Prioritize batches of 20; retain individual updates and batches of 100 as controls.</Copy>
|
|
101
|
+
|
|
102
|
+
<Copy y={363} size={42} weight={600}>Repeat the fixed workload</Copy>
|
|
103
|
+
<Copy y={425} size={34}>Keep the machine, starting index and</Copy>
|
|
104
|
+
<Copy y={471} size={34}>60,000 updates comparable. Report</Copy>
|
|
105
|
+
<Copy y={517} size={34}>run-to-run variability, not the best run.</Copy>
|
|
106
|
+
|
|
107
|
+
<Copy x={1000} y={363} size={42} weight={600}>Exercise sparse arrivals and bursts</Copy>
|
|
108
|
+
<Copy x={1000} y={425} size={34}>Sparse: measure waiting for a partial batch;</Copy>
|
|
109
|
+
<Copy x={1000} y={471} size={34}>record the policy for flushing that batch.</Copy>
|
|
110
|
+
<Copy x={1000} y={539} size={34}>Bursts: track queue growth and recovery,</Copy>
|
|
111
|
+
<Copy x={1000} y={585} size={34}>including submission-to-search delay.</Copy>
|
|
112
|
+
|
|
113
|
+
<line x1={112} x2={1808} y1={650} y2={650} stroke={rule} strokeWidth={2} />
|
|
114
|
+
<Copy y={717} size={40} weight={600} color={accent}>Use both gates before advancing</Copy>
|
|
115
|
+
<Copy y={777} size={34}>Recheck ≥250 updates/sec under offered load and p95 delay ≤5 sec;</Copy>
|
|
116
|
+
<Copy y={823} size={34}>track median delay and final-version correctness. If either gate fails, revise</Copy>
|
|
117
|
+
<Copy y={869} size={34}>batching or flushing and retest. Sparse traffic tests freshness, not peak capacity.</Copy>
|
|
118
|
+
<Copy y={949} size={30} color={muted}>The fixed workload does not establish live-traffic behavior. No production measurements exist yet.</Copy>
|
|
119
|
+
</Frame>;
|
|
120
|
+
|
|
121
|
+
export const meta = { title: 'Fieldnote: batching search-index updates' };
|
|
122
|
+
export const notes = [
|
|
123
|
+
'Source: the unchanged PROMPT.md. Fieldnote and all results are fictional. Values are not production evidence.',
|
|
124
|
+
'Recommendation is limited to a follow-up test. Rates are derived from 60,000 updates divided by processing time.',
|
|
125
|
+
'Repeated controls, recording the flush policy and queue recovery are proposed implementation details, not measured findings. No flush policy or repetition count was supplied.',
|
|
126
|
+
];
|
|
127
|
+
export default [Evidence, Decision, Next];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Make a three-page presentation walking a new on-call engineer through a
|
|
2
|
+
webhook delivery incident at Lantern. Show how the team moved from symptoms
|
|
3
|
+
to a supported diagnosis, mitigated the problem and checked recovery.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Lantern is a fictional notification service. Its API accepts events into a
|
|
8
|
+
durable queue, and workers deliver them to customer webhook endpoints.
|
|
9
|
+
Queue age measures how long the oldest pending event has been waiting.
|
|
10
|
+
The delivery objective is for 99% of accepted events to receive a successful
|
|
11
|
+
endpoint response within five minutes.
|
|
12
|
+
|
|
13
|
+
This fictional incident happened on 18 August 2026. All times are UTC.
|
|
14
|
+
|
|
15
|
+
09:00 — A worker release increased concurrent deliveries per worker from
|
|
16
|
+
20 to 80. Worker count and incoming event volume stayed unchanged.
|
|
17
|
+
|
|
18
|
+
09:05 — An alert fired: oldest pending event age reached six minutes.
|
|
19
|
+
The queue contained 18,000 events.
|
|
20
|
+
|
|
21
|
+
09:08 — The on-call engineer confirmed that API acceptance remained healthy.
|
|
22
|
+
Worker CPU was below 50%, but outbound requests were timing out more often.
|
|
23
|
+
The team considered a traffic surge, customer endpoint failures and a
|
|
24
|
+
worker-side connection bottleneck.
|
|
25
|
+
|
|
26
|
+
09:12 — Incoming volume matched its pre-release level. Timeouts appeared
|
|
27
|
+
across many unrelated customer endpoints. Worker traces showed requests
|
|
28
|
+
waiting for an available outbound connection. Each worker's connection pool
|
|
29
|
+
was still limited to 20 connections.
|
|
30
|
+
|
|
31
|
+
09:15 — The team rolled back the concurrency setting to 20. It did not purge
|
|
32
|
+
the queue or replay already completed jobs.
|
|
33
|
+
|
|
34
|
+
09:20 — Connection waits and timeout rates returned to their pre-release
|
|
35
|
+
levels. The queue still contained 24,000 events, down from a peak of 30,000.
|
|
36
|
+
The oldest pending event was 14 minutes old.
|
|
37
|
+
|
|
38
|
+
09:40 — Queue depth returned to its usual range of 0–500 events. The oldest
|
|
39
|
+
pending event was 40 seconds old.
|
|
40
|
+
|
|
41
|
+
10:10 — For the preceding 30 minutes, 99.3% of newly accepted events received
|
|
42
|
+
a successful endpoint response within five minutes.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
The evidence supports a mismatch between worker concurrency and the connection
|
|
47
|
+
pool as the incident's cause. Recovery after rollback strengthens that
|
|
48
|
+
explanation, but the team has not yet reproduced the failure in a controlled test.
|
|
49
|
+
|
|
50
|
+
Separate mitigation from recovery: lower timeout rates at 09:20 did not mean
|
|
51
|
+
the backlog had cleared. The 10:10 result describes newly accepted events;
|
|
52
|
+
it does not erase the delivery-objective breach during the incident.
|
|
53
|
+
|
|
54
|
+
The follow-up work is to reproduce the mismatch under load, validate concurrency
|
|
55
|
+
and pool settings together before release, and add connection-wait time to the
|
|
56
|
+
rollout dashboard. No data loss was observed, but the team has not completed
|
|
57
|
+
an event-by-event delivery audit.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import '@fontsource/ibm-plex-sans/latin-400.css';
|
|
3
|
+
import '@fontsource/ibm-plex-sans/latin-600.css';
|
|
4
|
+
|
|
5
|
+
const ink = '#152B39', muted = '#465D6B', accent = '#006B75';
|
|
6
|
+
function Copy({ x = 112, y, lines, size = 34, color = ink, weight = 400 }: {
|
|
7
|
+
x?: number; y: number; lines: string[]; size?: number; color?: string; weight?: number;
|
|
8
|
+
}) {
|
|
9
|
+
return <text x={x} y={y} fill={color} fontSize={size} fontWeight={weight}>
|
|
10
|
+
{lines.map((s, i) => <tspan key={i} x={x} dy={i ? size * 1.32 : 0}>{s}</tspan>)}
|
|
11
|
+
</text>;
|
|
12
|
+
}
|
|
13
|
+
function Frame({ title, description, page, children }: { title: string; description: string; page: number; children: ReactNode }) {
|
|
14
|
+
return <svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" fontFamily="IBM Plex Sans" role="img" aria-labelledby={`title-${page} desc-${page}`}>
|
|
15
|
+
<title id={`title-${page}`}>{title}</title><desc id={`desc-${page}`}>{description}</desc>
|
|
16
|
+
<rect width="1920" height="1080" fill="#FFFFFF" />
|
|
17
|
+
<Copy y={135} lines={[title]} size={64} weight={600} />
|
|
18
|
+
{children}
|
|
19
|
+
<Copy x={1760} y={1025} lines={[`${page} / 3`]} size={26} color={muted} />
|
|
20
|
+
</svg>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const Diagnosis = () => <Frame page={1} title="The release outpaced the connection pool" description="Fictional Lantern incident, 18 August 2026, UTC. API accepts events into a durable queue; workers deliver to customer webhook endpoints. Concurrency rose from 20 to 80 per worker at 09:00 while the pool stayed at 20. At 09:05 oldest pending age was six minutes and queue depth 18,000. At 09:08 API acceptance was healthy, CPU below 50%, outbound timeouts increased. At 09:12 steady traffic, widespread endpoint timeouts and traces waiting for connections support a worker-side bottleneck rather than traffic surge or independent customer failures. Not yet reproduced under controlled load.">
|
|
24
|
+
<Copy y={202} lines={['Lantern · fictional incident · 18 August 2026 · all times UTC']} size={28} color={muted} />
|
|
25
|
+
<Copy y={290} lines={['API accepts events']} weight={600} />
|
|
26
|
+
<Copy x={630} y={290} lines={['Durable queue']} weight={600} />
|
|
27
|
+
<Copy x={1150} y={290} lines={['Workers deliver to webhooks']} weight={600} />
|
|
28
|
+
<path d="M435 278 H578 M564 266 L578 278 L564 290 M910 278 H1098 M1084 266 L1098 278 L1084 290" stroke={accent} strokeWidth="3" fill="none" />
|
|
29
|
+
<Copy y={350} lines={['Objective: 99% of accepted events get a successful endpoint response within five minutes.']} size={31} color={muted} />
|
|
30
|
+
<Copy y={447} lines={['09:00 Release']} size={36} weight={600} color={accent} />
|
|
31
|
+
<Copy y={502} lines={['Concurrency per worker: 20 → 80', 'Worker count and incoming volume unchanged.']} size={32} />
|
|
32
|
+
<Copy y={630} lines={['09:05–09:08 Symptoms']} size={36} weight={600} color={accent} />
|
|
33
|
+
<Copy y={685} lines={['18,000 queued events; oldest waiting 6 min.', 'API acceptance healthy; worker CPU <50%.', 'Outbound requests timed out more often.']} size={32} />
|
|
34
|
+
<Copy x={970} y={447} lines={['09:12 Test the three explanations']} size={36} weight={600} color={accent} />
|
|
35
|
+
<Copy x={970} y={510} lines={['Traffic surge? Volume matched pre-release.', 'Endpoint failures? Many unrelated endpoints.', 'Worker bottleneck? Traces waited for a', 'connection; each pool still had only 20.']} size={32} />
|
|
36
|
+
<Copy x={970} y={737} lines={['Supported diagnosis: concurrency / pool mismatch.', 'The evidence points to shared worker capacity.']} size={31} weight={600} />
|
|
37
|
+
<Copy x={970} y={853} lines={['The failure has not yet been reproduced', 'in a controlled test.']} size={29} color={muted} />
|
|
38
|
+
<Copy y={925} lines={['Queue age = time the oldest', 'pending event has waited.']} size={29} color={muted} />
|
|
39
|
+
</Frame>;
|
|
40
|
+
|
|
41
|
+
const Mitigation = () => <Frame page={2} title="Rollback relieved waits before the backlog cleared" description="At 09:15 the team rolled concurrency back to 20, without purging the queue or replaying completed jobs. At 09:20 connection waits and timeout rates were back to pre-release levels, but 24,000 events remained queued, down from a peak of 30,000, and oldest pending age was 14 minutes. At 09:40 queue depth was in its usual 0–500 range and oldest age 40 seconds. Recovery after rollback strengthens the diagnosis but does not replace controlled reproduction.">
|
|
42
|
+
<Copy y={208} lines={['Operational sequence · all times UTC · fictional incident']} size={28} color={muted} />
|
|
43
|
+
<Copy y={340} lines={['09:15']} size={72} color={accent} weight={600} />
|
|
44
|
+
<Copy y={410} lines={['Mitigate the mismatch']} size={37} weight={600} />
|
|
45
|
+
<Copy y={478} lines={['Roll concurrency back to 20.', 'Keep the durable queue intact.', 'Do not replay completed jobs.']} size={33} />
|
|
46
|
+
<Copy y={685} lines={['Why this intervention?']} size={32} weight={600} />
|
|
47
|
+
<Copy y={738} lines={['Match concurrent deliveries', 'to the existing 20-connection', 'pool rather than add pressure.']} size={32} />
|
|
48
|
+
<Copy x={760} y={340} lines={['09:20']} size={72} color={accent} weight={600} />
|
|
49
|
+
<Copy x={760} y={410} lines={['Transport stabilizes']} size={37} weight={600} />
|
|
50
|
+
<Copy x={760} y={478} lines={['Connection waits and timeout', 'rates return to pre-release levels.']} size={33} />
|
|
51
|
+
<Copy x={760} y={635} lines={['24,000 events remain']} size={40} weight={600} />
|
|
52
|
+
<Copy x={760} y={695} lines={['Down from a peak of 30,000.', 'Oldest pending age: 14 min.', 'The backlog is still draining.']} size={32} />
|
|
53
|
+
<Copy x={1340} y={340} lines={['09:40']} size={72} color={accent} weight={600} />
|
|
54
|
+
<Copy x={1340} y={410} lines={['Queue normalizes']} size={37} weight={600} />
|
|
55
|
+
<Copy x={1340} y={478} lines={['Depth returns to its usual', 'range of 0–500 events.']} size={33} />
|
|
56
|
+
<Copy x={1340} y={635} lines={['40 seconds']} size={40} weight={600} />
|
|
57
|
+
<Copy x={1340} y={695} lines={['Oldest pending event age.', 'Confirm backlog recovery', 'separately from timeouts.']} size={32} />
|
|
58
|
+
<Copy y={935} lines={['Recovery after rollback strengthens the diagnosis; controlled reproduction is still needed.']} size={31} color={muted} />
|
|
59
|
+
</Frame>;
|
|
60
|
+
|
|
61
|
+
const Recovery = () => <Frame page={3} title="Recovery is a measured window, not a clean slate" description="At 10:10 UTC, 99.3% of newly accepted events over the preceding 30 minutes received a successful endpoint response within five minutes, exceeding the 99% objective for that cohort. It does not erase the incident delivery-objective breach. No data loss observed, but event-by-event audit incomplete. Next: reproduce mismatch under load, validate concurrency and pool settings together before release, and add connection-wait time to the rollout dashboard.">
|
|
62
|
+
<Copy y={208} lines={['10:10 UTC check · fictional incident']} size={28} color={muted} />
|
|
63
|
+
<Copy y={385} lines={['99.3%']} size={132} color={accent} weight={600} />
|
|
64
|
+
<Copy y={466} lines={['of newly accepted events']} size={42} weight={600} />
|
|
65
|
+
<Copy y={533} lines={['received a successful endpoint response', 'within five minutes in the preceding', '30 minutes (09:40–10:10 UTC).']} size={34} />
|
|
66
|
+
<Copy y={730} lines={['Objective: 99% within five minutes.']} size={33} weight={600} />
|
|
67
|
+
<Copy y={794} lines={['This cohort met the objective. The incident’s', 'delivery-objective breach still happened.']} size={33} />
|
|
68
|
+
<Copy x={1050} y={330} lines={['Close the evidence gaps']} size={43} weight={600} />
|
|
69
|
+
<Copy x={1050} y={420} lines={['Reproduce the mismatch under load.']} size={34} weight={600} />
|
|
70
|
+
<Copy x={1050} y={473} lines={['Test the supported cause in a controlled run.']} size={31} color={muted} />
|
|
71
|
+
<Copy x={1050} y={575} lines={['Validate both settings before release.']} size={34} weight={600} />
|
|
72
|
+
<Copy x={1050} y={628} lines={['Check concurrency and pool capacity together.']} size={31} color={muted} />
|
|
73
|
+
<Copy x={1050} y={730} lines={['Expose connection-wait time.']} size={34} weight={600} />
|
|
74
|
+
<Copy x={1050} y={783} lines={['Add the signal to the rollout dashboard.']} size={31} color={muted} />
|
|
75
|
+
<Copy y={957} lines={['No data loss was observed; an event-by-event delivery audit is not yet complete.']} size={31} color={muted} />
|
|
76
|
+
</Frame>;
|
|
77
|
+
|
|
78
|
+
export default [Diagnosis, Mitigation, Recovery];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Public brief excerpt
|
|
2
|
+
|
|
3
|
+
This file retains the original creation request and its authoring context.
|
|
4
|
+
Implementation debugging and product-development follow-ups are omitted; this
|
|
5
|
+
is not the complete conversation record.
|
|
6
|
+
|
|
7
|
+
> build a pre to introduce konpeki
|
|
8
|
+
|
|
9
|
+
## Authoring record
|
|
10
|
+
|
|
11
|
+
- Requested mode: unspecified. Resolved mode: default.
|
|
12
|
+
- Explicit visual preferences: none supplied.
|
|
13
|
+
- Interpretation: “pre” means presentation. Build a six-slide English introduction
|
|
14
|
+
for people and coding-agent users who have not used Konpeki.
|
|
15
|
+
- Direction: white, IBM Plex Sans, dark ink and Konpeki blue; restrained diagrams,
|
|
16
|
+
clear hierarchy, no invented metrics or unsupported export claims.
|
|
17
|
+
- Sources: the repository's documented capabilities;
|
|
18
|
+
see SOURCE.md for concrete references and qualifications.
|
|
19
|
+
- Deliverable: composition JSON, browser presentation, reviewed captures.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Introducing Konpeki
|
|
2
|
+
|
|
3
|
+
A six-slide English introduction for first-time users. Defaults to Plex on paper
|
|
4
|
+
with theme-linked colors and fonts; deck appearance can switch palettes and
|
|
5
|
+
Paper/Night without repainting vector elements. Uses the default authoring mode.
|
|
6
|
+
|
|
7
|
+
1. **Meet Konpeki** — one shared canvas for people and coding agents.
|
|
8
|
+
2. **A round-trip workflow** — draft, download, agent revision, open and present.
|
|
9
|
+
3. **Five semantic components** — Text block, Diagram, Chart, Image and Table.
|
|
10
|
+
4. **Keep the detail editable** — stable vector elements inside their owners.
|
|
11
|
+
5. **What works today** — capabilities alongside practical boundaries.
|
|
12
|
+
6. **Try a real brief** — setup and an example request.
|
|
13
|
+
|
|
14
|
+
## Open and revise
|
|
15
|
+
|
|
16
|
+
Run `pnpm dev` from the repository root and open
|
|
17
|
+
`?example=introducing-konpeki` in the application. Select **Present** for viewing.
|
|
18
|
+
The example preview does not autosave: use **Download** to keep changes, then
|
|
19
|
+
**Open** the saved JSON from the ordinary canvas for browser-local autosave.
|
|
20
|
+
|
|
21
|
+
[composition.json](composition.json) is the authoritative editable deck. Each
|
|
22
|
+
slide contains semantic components. Ordinary text uses native content and typography
|
|
23
|
+
fields; diagrams and custom specimens keep editable vector elements. Double-click
|
|
24
|
+
ordinary text to edit it directly; Content intent remains separate agent guidance.
|
|
25
|
+
This is not a second React deck runtime or a collection of page images.
|
|
26
|
+
|
|
27
|
+
[author.ts](author.ts) records the initial drawing instructions. Running
|
|
28
|
+
`node slides/introducing-konpeki/author.ts` validates and regenerates the JSON;
|
|
29
|
+
it **replaces manual canvas revisions**, so do not rerun it over revised work.
|
|
30
|
+
See [the public brief excerpt](PROMPT.md) and [source facts / provenance](SOURCE.md).
|
|
31
|
+
|
|
32
|
+
## Review
|
|
33
|
+
|
|
34
|
+
- `pnpm check`, `pnpm test` (91 tests), `pnpm build`, `git diff --check`: passed.
|
|
35
|
+
- Inspected all six actual canvas renders at 1920×1080 and 1024×768, DPR2.
|
|
36
|
+
- Browser text bounds checked after font loading: no text outside its component
|
|
37
|
+
viewport at either size. Visible navigation exercised across all six pages.
|
|
38
|
+
- Enlarged essential notes and clarified placeholder wording after first review.
|
|
39
|
+
- Follow-up alignment review: removed renderer insets that displaced vector
|
|
40
|
+
titles and body text. Added `review.mjs` to verify each authored viewport maps
|
|
41
|
+
to its component rectangle within 0.5 screen pixels at both review sizes.
|
|
42
|
+
Run `node slides/introducing-konpeki/review.mjs` with the dev server running
|
|
43
|
+
and `agent-browser` installed; inspect its captures after the assertions pass.
|
|
44
|
+
- Native-text verification: all six slides passed text/viewport checks in Plex
|
|
45
|
+
Paper, Precision Night and Editorial Paper, at both sizes (36 states).
|
|
46
|
+
Pass output directory, theme label and mode after the base URL to `review.mjs`.
|
|
47
|
+
`node scripts/check-themes.mjs` exercises all eight palettes in both modes,
|
|
48
|
+
fixed overrides, overflow warnings, linking, undo/redo, Prompt guidance and
|
|
49
|
+
autosave/reload/presentation. Literal imported artwork is intentionally fixed.
|
|
50
|
+
- Final reviewed presentation captures: [screenshots](screenshots/).
|
|
51
|
+
- Chart values are illustrative; diagrams are explanatory artwork, not screenshots.
|
|
52
|
+
- No PDF/PPTX export, native mobile or cross-application fidelity was tested.
|
|
53
|
+
|
|
54
|
+

|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Source facts and provenance
|
|
2
|
+
|
|
3
|
+
This is a factual introduction to Konpeki, not a fictional customer case study.
|
|
4
|
+
|
|
5
|
+
| Claim | Repository evidence |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| One canvas for human drafts, agent output, revision and presentation | ../../README.md; ../../AGENTS.md |
|
|
8
|
+
| Composition JSON is authoritative; imported JSX never executes | ../../composition/README.md; ../../AUTHORING.md |
|
|
9
|
+
| Five top-level kinds: Text block, Diagram, Chart, Image, Table | ../../composition/types.ts |
|
|
10
|
+
| Open / Download / Present workflow; browser-local storage | ../../README.md; ../../src/lib/storage.ts |
|
|
11
|
+
| Stable vector IDs, text/attribute edits, ordering, deletion and undo/redo | ../../README.md; ../../composition/vector.ts |
|
|
12
|
+
| Standard component illustrations are drafts, not data or image uploads | ../../README.md |
|
|
13
|
+
| Node 24+, pnpm; repository distribution, no Konpeki account/backend required | ../../README.md; ../../package.json |
|
|
14
|
+
|
|
15
|
+
The architecture drawings, vector selection example and component specimens are
|
|
16
|
+
original explanatory artwork for this deck, not product screenshots. The tiny
|
|
17
|
+
chart uses explicitly labeled illustrative values, not measured product data.
|
|
18
|
+
No third-party image assets or customer claims are used. IBM Plex Sans comes from
|
|
19
|
+
the repository's Fontsource dependency. No PDF/PPTX or cross-application fidelity
|
|
20
|
+
is claimed.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Initial authoring source. composition.json is the editable deck of record.
|
|
2
|
+
// Re-running this script replaces it; preserve canvas revisions before rebuilding.
|
|
3
|
+
import { writeFile } from "node:fs/promises";
|
|
4
|
+
import { createComponent } from "../../composition/document.ts";
|
|
5
|
+
import { validateComposition } from "../../composition/validate.ts";
|
|
6
|
+
import {
|
|
7
|
+
compositionSchema, canvasSize, canvasPadding,
|
|
8
|
+
type CompositionComponent, type CompositionDocument, type CompositionSlide,
|
|
9
|
+
type VectorElement,
|
|
10
|
+
} from "../../composition/types.ts";
|
|
11
|
+
|
|
12
|
+
const ink = "theme:ink", blue = "theme:accent", muted = "theme:muted", line = "theme:divider";
|
|
13
|
+
function text(id: string, value: string, x: number, y: number, size = 34, fill = ink, weight = 400): VectorElement {
|
|
14
|
+
return { id, kind: "text", text: value, attributes: { x, y, "font-family": size >= 56 ? "theme:heading-font" : "theme:body-font", "font-size": size, "font-weight": weight, fill } };
|
|
15
|
+
}
|
|
16
|
+
function rect(id: string, x: number, y: number, width: number, height: number, stroke = line, fill = "theme:background"): VectorElement {
|
|
17
|
+
return { id, kind: "rect", attributes: { x, y, width, height, rx: 4, fill, stroke, "stroke-width": 2 } };
|
|
18
|
+
}
|
|
19
|
+
function path(id: string, d: string, stroke = blue, width = 3): VectorElement {
|
|
20
|
+
return { id, kind: "path", attributes: { d, fill: "none", stroke, "stroke-width": width, "stroke-linecap": "round", "stroke-linejoin": "round" } };
|
|
21
|
+
}
|
|
22
|
+
function arrow(id: string, x: number, y: number, end: number) {
|
|
23
|
+
return path(id, `M${x} ${y}H${end}M${end - 14} ${y - 12}L${end} ${y}L${end - 14} ${y + 12}`);
|
|
24
|
+
}
|
|
25
|
+
function component(id: string, kind: CompositionComponent["kind"], x: number, y: number, width: number, height: number, description: string, elements: VectorElement[]): CompositionComponent {
|
|
26
|
+
const base = createComponent(kind, 1);
|
|
27
|
+
return { ...base, id, slotIds: [`${id}-content`], preferredRect: { x, y, width, height }, intent: description,
|
|
28
|
+
customVisual: { format: "vector", viewBox: { x: 0, y: 0, width, height }, fit: "contain", description, elements } };
|
|
29
|
+
}
|
|
30
|
+
function copy(id: string, x: number, y: number, width: number, lines: string[], size = 36, color = ink, weight = 400) {
|
|
31
|
+
const result = component(id, "text-block", x, y, width, Math.ceil(lines.length * size * 1.4 + 20), lines.join(" "),
|
|
32
|
+
lines.map((value, index) => text(`${id}-line-${index + 1}`, value, 0, size + 8 + index * size * 1.4, size, color, weight)));
|
|
33
|
+
if (result.kind === "text-block" && (id.endsWith("-title") || id === "brand")) result.appearance.role = "title";
|
|
34
|
+
if (result.kind === "text-block") {
|
|
35
|
+
delete result.customVisual;
|
|
36
|
+
result.content = lines.join("\n");
|
|
37
|
+
result.textStyle = { size, weight: weight as 400 | 500 | 600, lineHeight: 1.4,
|
|
38
|
+
color: color === blue ? "accent" : color === muted ? "muted" : "ink", font: size >= 56 ? "heading" : "body" };
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function slide(id: string, name: string, question: string, components: CompositionComponent[]): CompositionSlide {
|
|
43
|
+
return { id, name, canvas: canvasSize, innerPadding: canvasPadding, pageNumber: { style: "01/02", color: "muted" },
|
|
44
|
+
audience: "People and coding-agent users discovering Konpeki", question, intendedViewingSize: "presentation",
|
|
45
|
+
components, contentSlots: components.map((c) => ({ id: c.slotIds[0], label: c.id, role: "body", required: true, instruction: c.intent! })),
|
|
46
|
+
groups: [], readingOrder: components.map((c) => ({ kind: "component", id: c.id })), paintOrder: components.map((c) => c.id), relationships: [] };
|
|
47
|
+
}
|
|
48
|
+
function heading(id: string, title: string, subtitle: string) {
|
|
49
|
+
return [copy(`${id}-title`, 112, 88, 1696, [title], 62, ink, 600), copy(`${id}-subtitle`, 112, 184, 1696, [subtitle], 32, muted)];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const slides: CompositionSlide[] = [];
|
|
53
|
+
slides.push(slide("introduction", "Meet Konpeki", "What is Konpeki?", [
|
|
54
|
+
copy("brand", 112, 140, 820, ["Konpeki"], 112, blue, 600),
|
|
55
|
+
copy("promise", 112, 328, 960, ["A shared slide canvas", "for people and coding agents."], 56, ink, 500),
|
|
56
|
+
copy("summary", 112, 548, 870, ["Sketch the idea. Generate the detail.", "Keep revising the same document."], 36, muted),
|
|
57
|
+
component("shared-canvas", "diagram", 1090, 185, 700, 590, "A single editable composition contains a headline, diagram and supporting text; people and agents revise it.", [
|
|
58
|
+
rect("canvas-frame", 8, 25, 670, 395),
|
|
59
|
+
text("canvas-title", "An idea, made clear", 45, 93, 36, ink, 500),
|
|
60
|
+
path("headline-rule", "M45 120H640", line, 2),
|
|
61
|
+
rect("source-node", 50, 183, 160, 90, blue), rect("result-node", 310, 183, 320, 90, blue),
|
|
62
|
+
text("source-label", "Idea", 98, 239, 28), text("result-label", "Explanation", 382, 239, 28),
|
|
63
|
+
arrow("connection", 228, 228, 292),
|
|
64
|
+
path("body-lines", "M50 323H395M50 351H525", line, 8),
|
|
65
|
+
text("document-label", "composition.json", 178, 484, 32, blue, 500),
|
|
66
|
+
text("participants", "Human edits + agent revisions", 116, 548, 28, muted),
|
|
67
|
+
]),
|
|
68
|
+
copy("intro-note", 112, 870, 1696, ["For technical explanations, product stories and decision decks."], 34),
|
|
69
|
+
]));
|
|
70
|
+
|
|
71
|
+
slides.push(slide("workflow", "A round-trip workflow", "How do a person and an agent work together?", [
|
|
72
|
+
...heading("workflow", "A handoff that comes back to the canvas", "The agent works on the document you can keep editing."),
|
|
73
|
+
component("roundtrip", "diagram", 112, 342, 1696, 470, "Draft on the canvas, download composition JSON, ask a coding agent to revise it, then open the returned JSON and present. Further edits repeat this loop.", [
|
|
74
|
+
...[0, 1, 2, 3].flatMap((index) => {
|
|
75
|
+
const x = index * 438;
|
|
76
|
+
return [text(`number-${index}`, `0${index + 1}`, x, 48, 26, blue, 500),
|
|
77
|
+
text(`step-${index}`, ["Draft", "Download", "Agent revises", "Open + present"][index], x, 122, 42, ink, 500),
|
|
78
|
+
text(`detail-a-${index}`, ["Place components.", "Save the editable", "Return a completed", "Review the result."][index], x, 190, 29, muted),
|
|
79
|
+
text(`detail-b-${index}`, ["Describe the intent.", "composition JSON.", "composition JSON.", "Make the next edit."][index], x, 234, 29, muted),
|
|
80
|
+
...(index < 3 ? [arrow(`handoff-${index}`, x + 338, 109, x + 398)] : [])];
|
|
81
|
+
}),
|
|
82
|
+
path("revision-loop", "M1510 285V365H115V285M103 299L115 285L127 299", blue),
|
|
83
|
+
text("loop-label", "Revisions stay in the same editable format", 490, 431, 32, blue),
|
|
84
|
+
]),
|
|
85
|
+
copy("workflow-note", 112, 870, 1696, ["Use your coding agent alongside Konpeki; bring its returned JSON back to the canvas."], 34, ink),
|
|
86
|
+
]));
|
|
87
|
+
|
|
88
|
+
slides.push(slide("components", "Five semantic components", "What can a slide contain?", [
|
|
89
|
+
...heading("components", "Five components. One composition.", "Each component keeps its meaning, outer geometry and editable interior."),
|
|
90
|
+
component("text-specimen", "text-block", 112, 360, 296, 360, "Text block: headlines, body text, captions and footnotes.", [
|
|
91
|
+
text("label", "Text block", 0, 40, 38, ink, 500), text("sample", "Aa", 0, 166, 96, blue),
|
|
92
|
+
text("detail-1", "Headlines, body", 0, 277, 28, muted), text("detail-2", "and captions", 0, 317, 28, muted),
|
|
93
|
+
]),
|
|
94
|
+
component("diagram-specimen", "diagram", 462, 360, 296, 360, "Diagram: entities and their relationships.", [
|
|
95
|
+
text("label", "Diagram", 0, 40, 38, ink, 500), rect("node-1", 4, 112, 94, 78, blue), rect("node-2", 195, 112, 94, 78, blue), arrow("edge", 112, 151, 181),
|
|
96
|
+
text("detail-1", "Entities and", 0, 277, 28, muted), text("detail-2", "relationships", 0, 317, 28, muted),
|
|
97
|
+
]),
|
|
98
|
+
component("chart-specimen", "chart", 812, 360, 296, 400, "Chart specimen with illustrative values A 2 and B 3; not product measurements.", [
|
|
99
|
+
text("label", "Chart", 0, 40, 38, ink, 500), path("axis", "M5 84V209H285", line, 2),
|
|
100
|
+
rect("bar-a", 54, 129, 66, 80, blue, blue), rect("bar-b", 188, 89, 66, 120, blue, blue),
|
|
101
|
+
text("value-a", "2", 77, 117, 24), text("value-b", "3", 209, 77, 24), text("axis-a", "A", 77, 240, 22, muted), text("axis-b", "B", 209, 240, 22, muted),
|
|
102
|
+
text("detail-1", "Quantitative", 0, 297, 28, muted), text("detail-2", "comparisons", 0, 337, 28, muted), text("caveat", "Illustrative values", 0, 388, 26, ink),
|
|
103
|
+
]),
|
|
104
|
+
component("image-specimen", "image", 1162, 360, 296, 360, "Image: a visual asset or illustration, represented here by original vector artwork.", [
|
|
105
|
+
text("label", "Image", 0, 40, 38, ink, 500), rect("frame", 4, 91, 278, 135),
|
|
106
|
+
{ id: "sun", kind: "circle", attributes: { cx: 227, cy: 126, r: 17, fill: blue } },
|
|
107
|
+
path("landscape", "M24 205L88 135L151 194L193 159L264 205", blue, 4),
|
|
108
|
+
text("detail-1", "Visual assets", 0, 277, 28, muted), text("detail-2", "and illustrations", 0, 317, 28, muted),
|
|
109
|
+
]),
|
|
110
|
+
component("table-specimen", "table", 1512, 360, 296, 360, "Table: structured comparisons with clear row and column associations.", [
|
|
111
|
+
text("label", "Table", 0, 40, 38, ink, 500),
|
|
112
|
+
text("col-a", "Stage", 0, 120, 26, ink, 500), text("col-b", "Owner", 157, 120, 26, ink, 500),
|
|
113
|
+
path("rules", "M0 136H285M0 190H285M0 244H285", line, 2),
|
|
114
|
+
text("row-a", "Draft", 0, 174, 25), text("row-a-owner", "Human", 157, 174, 25), text("row-b", "Revise", 0, 228, 25), text("row-b-owner", "Agent", 157, 228, 25),
|
|
115
|
+
text("detail", "Structured detail", 0, 317, 28, muted),
|
|
116
|
+
]),
|
|
117
|
+
copy("component-note", 112, 876, 1696, ["Draft previews express intent. Agents return completed artwork as editable vectors."], 34, ink),
|
|
118
|
+
]));
|
|
119
|
+
|
|
120
|
+
slides.push(slide("editing", "Keep the detail editable", "What remains editable after an agent finishes?", [
|
|
121
|
+
...heading("editing", "The detail stays editable", "Revise a label, a path or a group without replacing the whole slide."),
|
|
122
|
+
component("editable-diagram", "diagram", 112, 340, 980, 475, "Explanatory illustration: a selected Review node inside its owning Diagram component. The shape keeps stable ID review-node.", [
|
|
123
|
+
rect("owner", 5, 5, 965, 390), text("owner-label", "Diagram · review-flow", 35, 53, 27, muted),
|
|
124
|
+
rect("draft", 55, 158, 220, 120), rect("review-node", 385, 158, 220, 120, blue), rect("present", 715, 158, 220, 120),
|
|
125
|
+
text("draft-label", "Draft", 113, 232, 38), text("review-label", "Review", 433, 232, 38, blue, 500), text("present-label", "Present", 753, 232, 38),
|
|
126
|
+
arrow("draft-review", 293, 218, 367), arrow("review-present", 623, 218, 697),
|
|
127
|
+
...[[380,153],[600,153],[380,273],[600,273]].map(([x,y], i) => rect(`handle-${i}`,x,y,10,10,blue)),
|
|
128
|
+
text("stable-id", "Selected element: review-node", 36, 450, 32, blue),
|
|
129
|
+
]),
|
|
130
|
+
copy("editing-actions-title", 1210, 343, 580, ["Change the part that matters"], 35, ink, 500),
|
|
131
|
+
copy("editing-actions", 1210, 440, 580, ["Edit text and attributes.", "Reorder sibling elements.", "Delete a shape or a group.", "Undo and redo your changes."], 31, muted),
|
|
132
|
+
copy("editing-note", 112, 885, 1696, ["Composition JSON owns the structure. Imported JSX never executes in the canvas."], 34, ink),
|
|
133
|
+
]));
|
|
134
|
+
|
|
135
|
+
slides.push(slide("today", "What works today", "What should a first-time user expect?", [
|
|
136
|
+
...heading("today", "An editable workflow, with clear boundaries", "Use the canvas for drafting, revision and presentation today."),
|
|
137
|
+
copy("available-heading", 112, 350, 790, ["Available now"], 42, blue, 500),
|
|
138
|
+
copy("available-1", 112, 451, 790, ["Arrange slides and semantic components.", "Open and download composition JSON."], 33),
|
|
139
|
+
copy("available-2", 112, 605, 790, ["Revise vector text, shapes and paths.", "Present with the same canvas renderer."], 33),
|
|
140
|
+
copy("available-3", 112, 790, 790, ["Browser-local drafts.", "No Konpeki account or backend required."], 32, ink),
|
|
141
|
+
copy("limits-heading", 1020, 350, 788, ["Know the boundaries"], 42, ink, 500),
|
|
142
|
+
copy("limits-1", 1020, 451, 788, ["Standard previews are placeholders.", "They do not load data or image files."], 33),
|
|
143
|
+
copy("limits-2", 1020, 605, 788, ["Your coding agent supplies the output.", "Review its facts, layout and caveats."], 33),
|
|
144
|
+
copy("limits-3", 1020, 790, 788, ["Desktop-first editing; storage is local.", "No PDF/PPTX fidelity claim."], 32, ink),
|
|
145
|
+
]));
|
|
146
|
+
|
|
147
|
+
slides.push(slide("start", "Try a real brief", "How do I start?", [
|
|
148
|
+
...heading("start", "Bring a brief. Build an editable deck.", "Start with one real explanation you need to give."),
|
|
149
|
+
copy("setup-heading", 112, 350, 710, ["Open the project"], 40, ink, 500),
|
|
150
|
+
copy("repository", 112, 435, 850, ["github.com/vcfgdev/konpeki"], 38, blue, 500),
|
|
151
|
+
copy("commands", 112, 543, 840, ["pnpm install --frozen-lockfile", "pnpm dev"], 34),
|
|
152
|
+
copy("requirements", 112, 696, 780, ["Node.js 24+ · pnpm", "A coding agent · a browser"], 34, ink),
|
|
153
|
+
copy("brief-heading", 1020, 350, 788, ["Give your agent a concrete brief"], 40, ink, 500),
|
|
154
|
+
copy("brief", 1020, 450, 788, ["“Explain this system to a new engineer.", "Show the request flow and failure path.", "Preserve the source facts and caveats.", "Return an editable composition JSON.”"], 31),
|
|
155
|
+
copy("next-step", 1020, 708, 788, ["Open the result. Make one revision.", "Then present it."], 34, blue, 500),
|
|
156
|
+
copy("closing", 112, 906, 1696, ["This presentation is itself an editable Konpeki composition."], 30, muted),
|
|
157
|
+
]));
|
|
158
|
+
|
|
159
|
+
const document: CompositionDocument = { schema: compositionSchema, title: "Introducing Konpeki", authoringMode: "default", theme: { id: "plex", mode: "paper" }, slides };
|
|
160
|
+
const result = validateComposition(document);
|
|
161
|
+
if (!result.ok) throw new Error(JSON.stringify(result.issues, null, 2));
|
|
162
|
+
await writeFile(new URL("./composition.json", import.meta.url), JSON.stringify(result.document, null, 2) + "\n");
|
|
163
|
+
console.log(`Authored ${slides.length} editable slides.`);
|