konpeki 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/.agents/skills/authoring-visuals/SKILL.md +82 -0
  2. package/AGENTS.md +33 -0
  3. package/AUTHORING.md +233 -0
  4. package/LICENSE +201 -0
  5. package/README.md +70 -0
  6. package/SETUP.md +86 -0
  7. package/composition/README.md +166 -0
  8. package/composition/compile.ts +227 -0
  9. package/composition/document.ts +709 -0
  10. package/composition/schema.json +2992 -0
  11. package/composition/schema.ts +435 -0
  12. package/composition/theme-tokens.ts +16 -0
  13. package/composition/types.ts +286 -0
  14. package/composition/validate.ts +684 -0
  15. package/composition/vector.ts +143 -0
  16. package/composition/visualizations.ts +335 -0
  17. package/design/README.md +17 -0
  18. package/design/palettes/README.md +14 -0
  19. package/design/palettes/base.ts +14 -0
  20. package/design/palettes/candidates.ts +19 -0
  21. package/design/palettes/index.ts +78 -0
  22. package/design/review/color-theme.md +44 -0
  23. package/design/review/layout.md +16 -0
  24. package/design/review/text.md +18 -0
  25. package/design/review/typography.md +15 -0
  26. package/design/review/visuals.md +31 -0
  27. package/design/semantic-patterns.md +43 -0
  28. package/design/themes/README.md +22 -0
  29. package/design/themes/index.ts +13 -0
  30. package/design/visual-languages/technical-product.md +17 -0
  31. package/design/visual-review.md +88 -0
  32. package/docs/development.md +140 -0
  33. package/docs/workflow.md +61 -0
  34. package/index.html +17 -0
  35. package/lib/assets.d.ts +8 -0
  36. package/lib/charts.ts +18 -0
  37. package/lib/contrast.ts +16 -0
  38. package/lib/layouts.ts +50 -0
  39. package/lib/slide.tsx +42 -0
  40. package/lib/taste.ts +17 -0
  41. package/lib/text.tsx +89 -0
  42. package/lib/typeface.ts +44 -0
  43. package/package.json +85 -0
  44. package/runtime/konpeki.mjs +1685 -0
  45. package/scripts/migrate-react-page.ts +120 -0
  46. package/slides/README.md +153 -0
  47. package/slides/architecture/PROMPT.md +31 -0
  48. package/slides/architecture/index.tsx +102 -0
  49. package/slides/article-brief/PROMPT.md +35 -0
  50. package/slides/article-brief/index.tsx +71 -0
  51. package/slides/bar-chart/PROMPT.md +39 -0
  52. package/slides/bar-chart/index.tsx +97 -0
  53. package/slides/comparison/PROMPT.md +29 -0
  54. package/slides/comparison/index.tsx +95 -0
  55. package/slides/decision-memo/PROMPT.md +34 -0
  56. package/slides/decision-memo/index.tsx +85 -0
  57. package/slides/delivery-plan/PROMPT.md +45 -0
  58. package/slides/delivery-plan/index.tsx +105 -0
  59. package/slides/experiment/PROMPT.md +44 -0
  60. package/slides/experiment/index.tsx +127 -0
  61. package/slides/incident-workflow/PROMPT.md +57 -0
  62. package/slides/incident-workflow/index.tsx +78 -0
  63. package/slides/introducing-konpeki/PROMPT.md +19 -0
  64. package/slides/introducing-konpeki/README.md +54 -0
  65. package/slides/introducing-konpeki/SOURCE.md +20 -0
  66. package/slides/introducing-konpeki/author.ts +163 -0
  67. package/slides/introducing-konpeki/composition.json +3265 -0
  68. package/slides/line-chart/PROMPT.md +40 -0
  69. package/slides/line-chart/index.tsx +72 -0
  70. package/slides/migration/PROMPT.md +38 -0
  71. package/slides/migration/index.tsx +89 -0
  72. package/slides/og-images/PROMPT.md +21 -0
  73. package/slides/og-images/index.tsx +76 -0
  74. package/slides/product-introduction/PROMPT.md +24 -0
  75. package/slides/product-introduction/index.tsx +105 -0
  76. package/slides/research-brief/PROMPT.md +40 -0
  77. package/slides/research-brief/index.tsx +104 -0
  78. package/slides/results-explanation/PROMPT.md +32 -0
  79. package/slides/results-explanation/index.tsx +96 -0
  80. package/slides/retrospective/PROMPT.md +43 -0
  81. package/slides/retrospective/index.tsx +105 -0
  82. package/slides/sankey/PROMPT.md +11 -0
  83. package/slides/sankey/index.tsx +93 -0
  84. package/slides/teaching/PROMPT.md +45 -0
  85. package/slides/teaching/index.tsx +124 -0
  86. package/slides/vertical-bar-charts/PROMPT.md +13 -0
  87. package/slides/vertical-bar-charts/index.tsx +97 -0
  88. package/src/app/App.tsx +694 -0
  89. package/src/assets/konpeki-mark.png +0 -0
  90. package/src/components/Canvas.tsx +1168 -0
  91. package/src/components/DiagramTypeIcon.tsx +78 -0
  92. package/src/components/InspectorPanel.tsx +687 -0
  93. package/src/components/LeftPanel.tsx +107 -0
  94. package/src/components/PageSizePicker.tsx +30 -0
  95. package/src/components/Presentation.tsx +105 -0
  96. package/src/components/RightPanel.tsx +201 -0
  97. package/src/components/VectorOverflowWarning.tsx +46 -0
  98. package/src/components/WorkspaceChrome.tsx +199 -0
  99. package/src/components/ui.tsx +53 -0
  100. package/src/lib/examples/react-page-migration.json +1295 -0
  101. package/src/lib/examples.ts +42 -0
  102. package/src/lib/export-png.ts +101 -0
  103. package/src/lib/file-session.ts +74 -0
  104. package/src/lib/history.ts +53 -0
  105. package/src/lib/model.ts +188 -0
  106. package/src/lib/page-size.ts +24 -0
  107. package/src/lib/presentation.ts +17 -0
  108. package/src/lib/storage.ts +43 -0
  109. package/src/lib/theme.ts +25 -0
  110. package/src/lib/use-file-session.ts +162 -0
  111. package/src/main.tsx +26 -0
  112. package/src/styles/base.css +105 -0
  113. package/src/styles/canvas.css +268 -0
  114. package/src/styles/chrome.css +214 -0
  115. package/src/styles/component-previews.css +386 -0
  116. package/src/styles/feedback.css +71 -0
  117. package/src/styles/left-panel.css +125 -0
  118. package/src/styles/presentation.css +72 -0
  119. package/src/styles/right-panel.css +1172 -0
  120. package/src/styles/shell.css +247 -0
  121. package/vite.config.ts +6 -0
@@ -0,0 +1,32 @@
1
+ Make a three-page presentation explaining a search-quality evaluation to the
2
+ engineering lead at Waymark. Help them decide whether to test a reranker on
3
+ live traffic, balancing retrieval quality against latency.
4
+
5
+ ---
6
+
7
+ Waymark is a fictional internal-document search tool. The evaluation compares
8
+ its baseline retrieval with the same retrieval followed by a reranker. Both
9
+ use the same document snapshot and the same 80 hand-authored queries: 50
10
+ direct lookups and 30 questions requiring several pieces of information.
11
+
12
+ A query passes when at least one reviewer-labeled relevant document appears
13
+ in the top five results. This measures retrieval, not whether a generated
14
+ answer would be correct or complete.
15
+
16
+ - Direct lookups: baseline passes 44 of 50; reranker passes 46 of 50.
17
+ - Multi-part questions: baseline passes 15 of 30; reranker passes 21 of 30.
18
+ - End-to-end median search latency: baseline 180 ms; reranker 310 ms.
19
+ - End-to-end 95th-percentile search latency: baseline 420 ms; reranker 760 ms.
20
+
21
+ These are synthetic, hand-authored results from one illustrative evaluation
22
+ pass, not an executed benchmark. Per-query outcomes, repeated timings, costs
23
+ and reviewer-agreement measurements are unavailable. Aggregate counts do not
24
+ show which individual queries improved or regressed.
25
+
26
+ ---
27
+
28
+ The proposed gate for a limited live trial is at least 80% passing queries
29
+ overall and p95 latency no greater than 800 ms. Assess both conditions, show
30
+ the category-level trade-off and explain what this small offline set cannot
31
+ establish. Passing this gate would justify a limited trial, not a full rollout;
32
+ live query mix, tail latency and individual regressions still need evaluation.
@@ -0,0 +1,96 @@
1
+ import type { ReactNode } from 'react';
2
+ import { fontsReady } from '../../lib/typeface.ts';
3
+
4
+ await fontsReady;
5
+ const ink = '#172B37', muted = '#4D606B', accent = '#006B68', line = '#CBD5DA';
6
+ const T = ({ x = 112, y, size = 36, weight = 400, color = ink, children }: { x?: number; y: number; size?: number; weight?: number; color?: string; children: ReactNode }) =>
7
+ <text x={x} y={y} fontSize={size} fontWeight={weight} fill={color}>{children}</text>;
8
+ function Frame({ page, title, description, children }: { page: number; title: string; description: string; children: ReactNode }) {
9
+ 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-label={`${title}. ${description}`} data-page={page}>
10
+ <rect width="1920" height="1080" fill="#FFFFFF" />
11
+ <T y={151} size={66} weight={600}>{title}</T>
12
+ {children}
13
+ <T x={1760} y={1014} size={28} color={muted}>{page} / 3</T>
14
+ </svg>;
15
+ }
16
+ const Gate = () => <Frame page={1} title="Waymark’s reranker clears the trial gate" description="Synthetic illustrative results, not an executed benchmark. Baseline passes 59 of 80 queries, 73.75%, with p95 420 milliseconds. Reranker passes 67 of 80, 83.75%, with p95 760 milliseconds. Gate: at least 80% passing and p95 at most 800 milliseconds. Only the reranker meets both; a limited trial is justified, not a full rollout.">
17
+ <T y={225} color={muted}>Synthetic, hand-authored results • one illustrative evaluation pass</T>
18
+ <T y={340} size={32} weight={500}>Proposed limited-trial gate</T>
19
+ <T y={405} size={45} weight={500}>≥80% passing queries overall + p95 ≤800 ms</T>
20
+ <line x1={112} y1={468} x2={1808} y2={468} stroke={line} strokeWidth={2} />
21
+ <T x={790} y={522} size={30} color={muted}>Passing queries</T>
22
+ <T x={1250} y={522} size={30} color={muted}>p95 latency</T>
23
+ <T x={1580} y={522} size={30} color={muted}>Both gates</T>
24
+ <T y={610} size={40} weight={500}>Baseline</T>
25
+ <T x={790} y={610} size={40}>59/80 · 73.75%</T>
26
+ <T x={1250} y={610} size={40}>420 ms</T>
27
+ <T x={1580} y={610} size={36}>No</T>
28
+ <line x1={112} y1={655} x2={1808} y2={655} stroke={line} />
29
+ <T y={740} size={40} weight={600} color={accent}>Retrieval + reranker</T>
30
+ <T x={790} y={740} size={40} weight={600} color={accent}>67/80 · 83.75%</T>
31
+ <T x={1250} y={740} size={40} weight={600} color={accent}>760 ms</T>
32
+ <T x={1580} y={740} size={36} weight={600} color={accent}>Yes</T>
33
+ <line x1={112} y1={790} x2={1808} y2={790} stroke={line} strokeWidth={2} />
34
+ <T y={883} size={40} weight={500}>Proceed to a limited live trial, not a full rollout.</T>
35
+ <T y={941} size={32} color={muted}>The reranker has 3.75 percentage points of quality headroom and only 40 ms at p95.</T>
36
+ </Frame>;
37
+
38
+ const Tradeoff = () => <Frame page={2} title="Multi-part retrieval gains most; latency rises" description="Direct lookups: baseline 44 of 50, 88%; reranker 46 of 50, 92%, plus 4 percentage points. Multi-part questions: baseline 15 of 30, 50%; reranker 21 of 30, 70%, plus 20 points. Median latency rises from 180 to 310 milliseconds; p95 from 420 to 760. Same document snapshot and same 80 queries. A pass means at least one reviewer-labeled relevant document in the top five, not a correct or complete generated answer.">
39
+ <T y={225} color={muted}>Same document snapshot and 80 queries: 50 direct lookups, 30 multi-part questions.</T>
40
+ <T y={325} size={32} weight={500}>Queries passing (%)</T>
41
+ <T x={740} y={325} size={28} color={muted}>Baseline</T>
42
+ <T x={960} y={325} size={28} color={accent}>Reranker</T>
43
+ {[0, 50, 100].map(v => <g key={v}><line x1={380 + v * 7} y1={358} x2={380 + v * 7} y2={706} stroke={line} /><T x={365 + v * 7} y={752} size={26} color={muted}>{v}</T></g>)}
44
+ <T y={425} size={32} weight={500}>Direct lookups</T>
45
+ <rect x={380} y={382} width={616} height={40} fill={muted} />
46
+ <rect x={380} y={445} width={644} height={40} fill={accent} />
47
+ <T x={1045} y={413} size={28}>44/50 · 88%</T>
48
+ <T x={1045} y={477} size={28} color={accent}>46/50 · 92%</T>
49
+ <T y={626} size={32} weight={500}>Multi-part</T>
50
+ <rect x={380} y={581} width={350} height={40} fill={muted} />
51
+ <rect x={380} y={644} width={490} height={40} fill={accent} />
52
+ <T x={755} y={613} size={28}>15/30 · 50%</T>
53
+ <T x={895} y={677} size={28} color={accent}>21/30 · 70%</T>
54
+ <T x={1360} y={325} size={32} weight={500}>End-to-end latency</T>
55
+ <T x={1360} y={411} size={30} color={muted}>Baseline / reranker</T>
56
+ <T x={1360} y={479} size={39} weight={500}>180 / 310 ms</T>
57
+ <T x={1360} y={531} size={30} color={muted}>Median · +130 ms</T>
58
+ <T x={1360} y={654} size={39} weight={500}>420 / 760 ms</T>
59
+ <T x={1360} y={706} size={30} color={muted}>p95 · +340 ms</T>
60
+ <T y={830} size={37} weight={500}>+4 points on lookups; +20 on multi-part questions.</T>
61
+ <T y={881} size={30} color={muted}>Multi-part questions require several pieces of information.</T>
62
+ <T y={925} size={30} color={muted}>Pass = at least one reviewer-labeled relevant document in the top five results.</T>
63
+ <T y={969} size={30} color={muted}>Measures retrieval, not generated-answer correctness or completeness. Synthetic results.</T>
64
+ </Frame>;
65
+
66
+ const Trial = () => <Frame page={3} title="Use the live trial to test what offline cannot" description="Recommended next step: limit reranker exposure and retain baseline comparison. Evaluate the live query mix, repeated tail latency, and individual query regressions before expanding. Aggregate counts do not identify improved or regressed queries. Per-query outcomes, repeated timings, costs, and reviewer agreement are unavailable; the small hand-authored offline set cannot establish production performance or rollout readiness.">
67
+ <T y={231} size={38} color={accent} weight={500}>Limit reranker exposure and retain a baseline comparison.</T>
68
+ <T y={331} size={30} color={muted}>Unknown in this offline set</T>
69
+ <T x={860} y={331} size={30} color={muted}>Evidence to collect before expanding</T>
70
+ <line x1={112} y1={370} x2={1808} y2={370} stroke={line} strokeWidth={2} />
71
+ <T y={435} size={37} weight={500}>Production query mix</T>
72
+ <T x={860} y={435} size={34}>Measure passing rates by live query category.</T>
73
+ <T y={486} size={30} color={muted}>80 hand-authored queries may not represent traffic.</T>
74
+ <T x={860} y={486} size={30} color={muted}>Check whether the overall ≥80% result holds.</T>
75
+ <line x1={112} y1={529} x2={1808} y2={529} stroke={line} />
76
+ <T y={594} size={37} weight={500}>Tail-latency stability</T>
77
+ <T x={860} y={594} size={34}>Repeat end-to-end timings under live load.</T>
78
+ <T y={645} size={30} color={muted}>One pass gives no variability estimate.</T>
79
+ <T x={860} y={645} size={30} color={muted}>Check p95 ≤800 ms; current headroom is 40 ms.</T>
80
+ <line x1={112} y1={688} x2={1808} y2={688} stroke={line} />
81
+ <T y={753} size={37} weight={500}>Individual regressions</T>
82
+ <T x={860} y={753} size={34}>Review paired outcomes for each query.</T>
83
+ <T y={804} size={30} color={muted}>Aggregate gains can hide newly failing queries.</T>
84
+ <T x={860} y={804} size={30} color={muted}>Identify which queries improve or regress.</T>
85
+ <line x1={112} y1={849} x2={1808} y2={849} stroke={line} strokeWidth={2} />
86
+ <T y={920} size={30} color={muted}>Also unavailable: costs and reviewer agreement. Collect both before a rollout decision.</T>
87
+ <T y={964} size={30} color={muted}>These synthetic results establish neither production performance nor rollout readiness.</T>
88
+ </Frame>;
89
+
90
+ export const meta = { title: 'Waymark: evaluating a reranker', createdAt: '2026-09-12T14:20:00Z' };
91
+ export const notes = [
92
+ 'Decision: 59/80 baseline and 67/80 reranker. Only reranker satisfies both proposed gates. All supplied results are synthetic.',
93
+ 'Bars share a 0–100% scale. Net category count changes do not identify individual query transitions.',
94
+ 'Live-trial evidence collection is a recommendation, not a supplied operational trial design. No sample size, exposure fraction or duration was specified.',
95
+ ];
96
+ export default [Gate, Tradeoff, Trial];
@@ -0,0 +1,43 @@
1
+ Make a four-page retrospective for the engineers and product lead who built
2
+ Dockline. Explain what they planned, what changed during the project, what
3
+ they actually delivered and what they should do differently next time.
4
+
5
+ ---
6
+
7
+ Dockline is a fictional tool for requesting temporary test environments.
8
+ A four-week pilot involved two engineers and one product lead. The initial
9
+ plan was to let teams request an environment, see its status, extend its
10
+ expiry and have expired environments deleted automatically.
11
+
12
+ Week 1: the team agreed the request fields and built a form plus a status
13
+ page. Provisioning remained a manual operator task. They had not yet assigned
14
+ an owner for deletion failures or defined which environments must be retained.
15
+
16
+ Week 2: review with operators exposed two requirements: some environments
17
+ needed a retention hold, and deletion needed an audit trail. The team removed
18
+ automatic deletion from the pilot scope instead of adding it late. They kept
19
+ expiry reminders and a manual cleanup queue.
20
+
21
+ Week 3: eight invited users submitted 12 pilot requests. Nine included all
22
+ required information; three needed clarification because the form did not
23
+ explain the environment-owner field. Two users expected “submitted” to mean
24
+ “ready”, although provisioning had not finished. Those two users may also
25
+ have submitted requests needing clarification; the counts are separate.
26
+
27
+ Week 4: the form gained an owner-field explanation, and the status page
28
+ distinguished submitted, provisioning and ready. A second exercise used six
29
+ new requests: all had the required fields. It did not retest status understanding.
30
+ These are synthetic project records, not observations of a real pilot.
31
+
32
+ ---
33
+
34
+ Delivered: request form, operator-updated status page, expiry reminders and
35
+ manual cleanup queue. Provisioning and deletion remain manual. Self-service
36
+ expiry extensions, retention holds, deletion audit trail and automatic deletion
37
+ are not implemented. No time-saving or reliability measurements are available.
38
+
39
+ The team's proposed lesson is to involve operators and define lifecycle
40
+ ownership before committing to automation. The next step is to specify retention
41
+ and audit behavior, assign a deletion-failure owner and retest status understanding.
42
+ The small second exercise suggests the form change helped; it does not establish
43
+ a lasting improvement or prove which change caused the result.
@@ -0,0 +1,105 @@
1
+ import type { ReactNode } from 'react';
2
+ import '../../lib/typeface.ts';
3
+
4
+ const ink = '#182D39';
5
+ const muted = '#4E606B';
6
+ const accent = '#006C70';
7
+ const line = '#B9C8CD';
8
+
9
+ function Copy({ x, y, lines, size = 34, weight = 400, color = ink }: {
10
+ x: number; y: number; lines: string[]; size?: number; weight?: number; color?: string;
11
+ }) {
12
+ return <text x={x} y={y} fontSize={size} fontWeight={weight} fill={color}>
13
+ {lines.map((text, i) => <tspan key={i} x={x} dy={i ? size * 1.35 : 0}>{text}</tspan>)}
14
+ </text>;
15
+ }
16
+
17
+ function Frame({ page, title, description, children }: {
18
+ page: number; title: string; description: string; children: ReactNode;
19
+ }) {
20
+ return <svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"
21
+ fontFamily="IBM Plex Sans" role="img" aria-labelledby={`title-${page} desc-${page}`} data-dockline-page={page}>
22
+ <title id={`title-${page}`}>{title}</title>
23
+ <desc id={`desc-${page}`}>{description}</desc>
24
+ <rect width="1920" height="1080" fill="#FFFFFF" />
25
+ <Copy x={112} y={155} lines={[title]} size={64} weight={600} />
26
+ {children}
27
+ <Copy x={1710} y={1018} lines={[`${page} / 4`]} size={26} color={muted} />
28
+ </svg>;
29
+ }
30
+
31
+ const Plan = () => <Frame page={1} title="Dockline planned a self-service lifecycle"
32
+ description="A fictional temporary test-environment tool. Four weeks, two engineers, one product lead. Planned: request, view status, extend expiry, automatic deletion. Week 1 delivered a form and status page with manual operator provisioning. Deletion-failure ownership and retention rules were undefined.">
33
+ <Copy x={112} y={242} lines={['Fictional test-environment project · Four weeks · Two engineers + one product lead']} size={32} color={muted} />
34
+ {[
35
+ ['Request', 'Ask for an', 'environment'],
36
+ ['Track', 'See its', 'status'],
37
+ ['Extend', 'Move its', 'expiry date'],
38
+ ['Delete', 'Remove expired', 'environments automatically'],
39
+ ].map(([title, ...body], i) => <g key={title}>
40
+ <Copy x={112 + i * 430} y={402} lines={[title]} size={44} weight={600} color={accent} />
41
+ <Copy x={112 + i * 430} y={466} lines={body} size={32} />
42
+ </g>)}
43
+ <line x1={112} x2={1808} y1={601} y2={601} stroke={line} strokeWidth={2} />
44
+ <Copy x={112} y={682} lines={['Week 1']} size={38} weight={600} />
45
+ <Copy x={112} y={742} lines={['Request fields agreed.', 'Form and status page built.', 'Provisioning stayed with an operator.']} />
46
+ <Copy x={1030} y={682} lines={['Lifecycle questions left open']} size={38} weight={600} />
47
+ <Copy x={1030} y={742} lines={['Who owns deletion failures?', 'Which environments must be retained?']} />
48
+ </Frame>;
49
+
50
+ const Scope = () => <Frame page={2} title="Operator review changed the pilot scope"
51
+ description="In week 2 operators identified retention holds and a deletion audit trail as requirements. Rather than adding these late, the team removed automatic deletion. Delivered: request form, operator-updated status page, expiry reminders and manual cleanup queue. Provisioning and deletion remain manual.">
52
+ <Copy x={112} y={270} lines={['Week 2 · Requirements surfaced']} size={36} weight={600} color={accent} />
53
+ <Copy x={112} y={344} lines={['Some environments needed', 'a retention hold.', '', 'Deletion needed an audit trail.']} size={38} />
54
+ <Copy x={1050} y={270} lines={['Scope decision']} size={36} weight={600} color={accent} />
55
+ <Copy x={1050} y={344} lines={['Remove automatic deletion', 'from the pilot rather than', 'add these requirements late.']} size={38} />
56
+ <line x1={112} x2={1808} y1={601} y2={601} stroke={line} strokeWidth={2} />
57
+ <Copy x={112} y={681} lines={['Delivered']} size={42} weight={600} />
58
+ <Copy x={112} y={754} lines={['Request form', 'Operator-updated status page']} size={36} />
59
+ <Copy x={1050} y={754} lines={['Expiry reminders', 'Manual cleanup queue']} size={36} />
60
+ <Copy x={112} y={912} lines={['Provisioning and deletion remain manual.']} size={34} weight={500} color={accent} />
61
+ </Frame>;
62
+
63
+ const Evidence = () => <Frame page={3} title="Form completeness improved in a small retest"
64
+ description="Week 3: eight invited users made 12 requests; nine complete and three needing clarification because the environment-owner field was unexplained. Two users confused submitted with ready; these user and request counts may overlap. Week 4 added an owner explanation and distinct submitted, provisioning and ready states. All six new requests had required fields. Status understanding was not retested. This suggests help, not lasting improvement or causation.">
65
+ <Copy x={112} y={213} lines={['Synthetic pilot records']} size={28} color={muted} />
66
+ <Copy x={112} y={270} lines={['Week 3 · Eight invited users']} size={36} weight={600} />
67
+ <Copy x={1050} y={270} lines={['Week 4 · Six new requests']} size={36} weight={600} />
68
+ <Copy x={112} y={383} lines={['9 of 12']} size={76} weight={600} color={accent} />
69
+ <Copy x={1050} y={383} lines={['6 of 6']} size={76} weight={600} color={accent} />
70
+ <Copy x={112} y={440} lines={['requests had all required information.']} size={32} />
71
+ <Copy x={1050} y={440} lines={['new requests had all required fields.']} size={32} />
72
+ <Copy x={112} y={524} lines={['Three needed clarification:', 'the environment-owner field', 'was not explained.']} size={34} />
73
+ <Copy x={1050} y={524} lines={['Added an owner-field explanation.', 'Status now distinguishes submitted,', 'provisioning and ready.']} size={34} />
74
+ <line x1={112} x2={1808} y1={665} y2={665} stroke={line} strokeWidth={2} />
75
+ <Copy x={112} y={726} lines={['Two users read “submitted” as “ready”.', 'They may overlap with requests needing', 'clarification; these are separate counts.']} size={32} />
76
+ <Copy x={1050} y={726} lines={['Status understanding was not retested.', 'The form result suggests help, but does', 'not establish lasting improvement', 'or which change caused the result.']} size={32} />
77
+ </Frame>;
78
+
79
+ const Next = () => <Frame page={4} title="Define lifecycle ownership before automation"
80
+ description="The proposed lesson is to involve operators and define lifecycle ownership before committing to automation. Next: specify retention and audit behavior, assign a deletion-failure owner and retest status understanding. Not implemented: self-service expiry extensions, retention holds, deletion audit trail or automatic deletion. No time-saving or reliability measurements are available.">
81
+ <Copy x={112} y={246} lines={['Proposed lesson: involve operators before committing to automation.']} size={34} color={muted} />
82
+ <Copy x={112} y={352} lines={['Next steps']} size={40} weight={600} color={accent} />
83
+ {[
84
+ ['Specify behavior', 'Define retention holds and deletion audit behavior.'],
85
+ ['Assign ownership', 'Name the owner for deletion failures.'],
86
+ ['Retest understanding', 'Check whether users distinguish submitted from ready.'],
87
+ ].map(([heading, body], i) => <g key={heading}>
88
+ <line x1={112} x2={1808} y1={394 + i * 116} y2={394 + i * 116} stroke={line} strokeWidth={2} />
89
+ <Copy x={112} y={460 + i * 116} lines={[heading]} size={34} weight={600} />
90
+ <Copy x={650} y={460 + i * 116} lines={[body]} size={34} />
91
+ </g>)}
92
+ <line x1={112} x2={1808} y1={742} y2={742} stroke={line} strokeWidth={2} />
93
+ <Copy x={112} y={819} lines={['Not implemented']} size={32} weight={600} />
94
+ <Copy x={650} y={819} lines={['Self-service expiry extensions, retention holds,', 'deletion audit trail and automatic deletion.']} size={32} />
95
+ <Copy x={112} y={942} lines={['No time-saving or reliability measurements are available.']} size={32} color={muted} />
96
+ </Frame>;
97
+
98
+ export const meta = { title: 'Dockline — four-week retrospective' };
99
+ export const notes = [
100
+ 'Synthetic project records supplied in PROMPT.md; not observations of a real pilot. The initial plan was broader than the delivered pilot.',
101
+ 'Retention and audit requirements were discovered, not implemented. Removing automatic deletion was the explicit scope decision.',
102
+ 'Do not add the two users to the three requests. Different denominators and possible overlap. Six new requests are a small exercise, not a causal test.',
103
+ 'These are proposed next steps, not completed work or a delivery commitment. No measured time-saving or reliability claims are supported.',
104
+ ];
105
+ export default [Plan, Scope, Evidence, Next];
@@ -0,0 +1,11 @@
1
+ Make a one-page operations briefing for fictional support team Brook, using a Sankey chart to show how 120 support tickets moved from intake channel through routing to outcome.
2
+
3
+ ---
4
+
5
+ Web intake supplied 70 tickets: 50 entered automated handling and 20 entered specialist handling. Email supplied 50: 30 entered automated handling and 20 entered specialist handling.
6
+
7
+ Of the 80 tickets in automated handling, 70 were resolved and 10 escalated. Of the 40 in specialist handling, 20 were resolved and 20 escalated. Each ticket follows one route at each stage; there are no loops, duplicates or omitted tickets. These are synthetic, hand-authored counts for one fictional week.
8
+
9
+ ---
10
+
11
+ Use flow widths proportional to ticket counts and directly label nodes and link counts. Make the total of 120 and conservation through intermediate nodes clear. Show that specialist handling accounts for 20 of the 30 escalations, without implying it causes escalation or performs worse: ticket complexity and routing criteria are unknown. Channel-to-outcome breakdowns are not supplied and must not be inferred from the merged flows.
@@ -0,0 +1,93 @@
1
+ import { Sankey, type CustomSankeyLayerProps, type DefaultLink } from '@nivo/sankey';
2
+ import { chartDefaults } from '../../lib/charts';
3
+ import { palettes } from '../../lib/taste';
4
+ import '../../lib/typeface';
5
+
6
+ type Node = { id: string; fill: string };
7
+ const data = {
8
+ nodes: [
9
+ { id: 'Web', fill: '#526579' },
10
+ { id: 'Email', fill: '#526579' },
11
+ { id: 'Automated', fill: '#2458C7' },
12
+ { id: 'Specialist', fill: '#2458C7' },
13
+ { id: 'Resolved', fill: '#526579' },
14
+ { id: 'Escalated', fill: '#98451F' },
15
+ ],
16
+ links: [
17
+ { source: 'Web', target: 'Automated', value: 50 },
18
+ { source: 'Web', target: 'Specialist', value: 20 },
19
+ { source: 'Email', target: 'Automated', value: 30 },
20
+ { source: 'Email', target: 'Specialist', value: 20 },
21
+ { source: 'Automated', target: 'Resolved', value: 70 },
22
+ { source: 'Automated', target: 'Escalated', value: 10 },
23
+ { source: 'Specialist', target: 'Resolved', value: 20 },
24
+ { source: 'Specialist', target: 'Escalated', value: 20 },
25
+ ],
26
+ };
27
+
28
+ function DirectLabels({ nodes, links }: CustomSankeyLayerProps<Node, DefaultLink>) {
29
+ return <g fill="#172331" fontFamily="IBM Plex Sans" fontSize={30} fontWeight={500}>
30
+ {links.map(link => {
31
+ // Label close to the source: crossing ribbons remain individually identifiable.
32
+ const t = 0.24;
33
+ const x = link.source.x1 + (link.target.x0 - link.source.x1) * t;
34
+ const curveT = (t - 0.5) * 2;
35
+ // Invert the symmetric cubic's x coordinate to find its corresponding y.
36
+ let u = t;
37
+ for (let i = 0; i < 8; i++) {
38
+ u -= (3 * u - 3 * u * u + 2 * u * u * u - (curveT + 1)) / (3 - 6 * u + 6 * u * u);
39
+ }
40
+ const y = link.pos0 + (link.pos1 - link.pos0) * (3 * u * u - 2 * u * u * u);
41
+ return <g key={`${link.source.id}-${link.target.id}`} data-flow={`${link.source.id}-${link.target.id}`}
42
+ data-count={link.value} data-thickness={link.thickness}>
43
+ <rect x={x - 27} y={y - 23} width={54} height={46} rx={9} fill="white" />
44
+ <text x={x} y={y + 10} textAnchor="middle">{link.value}</text>
45
+ </g>;
46
+ })}
47
+ {nodes.map(node => {
48
+ const middle = node.depth === 1;
49
+ const x = node.depth === 0 ? node.x0 - 20 : middle ? (node.x0 + node.x1) / 2 : node.x1 + 20;
50
+ const y = middle ? node.y0 - 18 : (node.y0 + node.y1) / 2 - 6;
51
+ return <text key={node.id} x={x} y={y} textAnchor={node.depth === 0 ? 'end' : middle ? 'middle' : 'start'}
52
+ data-node={node.id} data-total={node.value} data-height={node.height}
53
+ stroke="white" strokeWidth={7} paintOrder="stroke" strokeLinejoin="round">
54
+ {middle ? `${node.id} · ${node.value}` : node.id}
55
+ {!middle && <tspan x={x} dy={38} fontSize={34} fontWeight={600}>{node.value}</tspan>}
56
+ </text>;
57
+ })}
58
+ </g>;
59
+ }
60
+
61
+ const Brook = () => <svg xmlns="http://www.w3.org/2000/svg" width={1920} height={1080}
62
+ viewBox="0 0 1920 1080" fontFamily="IBM Plex Sans" role="img" aria-labelledby="brook-title brook-description">
63
+ <title id="brook-title">Specialist handling accounts for 20 of 30 escalations</title>
64
+ <desc id="brook-description">Brook, a fictional support team. 120 tickets in one synthetic week.
65
+ Web 70: 50 automated, 20 specialist. Email 50: 30 automated, 20 specialist.
66
+ Automated 80: 70 resolved, 10 escalated. Specialist 40: 20 resolved, 20 escalated.
67
+ Resolved total 90; escalated total 30. Each stage conserves 120 tickets.
68
+ Complexity and routing criteria are unknown; this does not establish causation or relative performance.
69
+ Channel-to-outcome breakdowns are not supplied and cannot be inferred through merged flows.</desc>
70
+ <rect width={1920} height={1080} fill="white" />
71
+ <text x={100} y={122} fill="#172331" fontSize={60} fontWeight={600}>Specialist handling accounts for</text>
72
+ <text x={100} y={193} fill="#172331" fontSize={60} fontWeight={600}>20 of 30 escalations</text>
73
+ <text x={100} y={255} fill="#475564" fontSize={30}>Brook support · 120 tickets · One fictional week · Synthetic, hand-authored counts</text>
74
+ <g fill="#475564" fontSize={27} fontWeight={500}>
75
+ <text x={280} y={321}>Intake · 120</text>
76
+ <text x={940} y={321} textAnchor="middle">Handling · 120</text>
77
+ <text x={1620} y={321} textAnchor="end">Outcome · 120</text>
78
+ </g>
79
+ <g transform="translate(100 345)">
80
+ <Sankey<Node, DefaultLink> {...chartDefaults(palettes.paper)} data={data}
81
+ width={1720} height={485} margin={{ top: 42, right: 200, bottom: 0, left: 180 }}
82
+ colors={node => node.fill} sort="input" align="justify" nodeThickness={24}
83
+ nodeSpacing={95} nodeBorderWidth={0} nodeBorderRadius={0} linkContract={0}
84
+ linkOpacity={0.28} linkBlendMode="normal" enableLinkGradient={false}
85
+ labelTextColor="#172331" layers={['links', 'nodes', DirectLabels]} />
86
+ </g>
87
+ <text x={100} y={883} fill="#172331" fontSize={29} fontWeight={500}>Every ticket follows one route per stage; no loops, duplicates or omissions.</text>
88
+ <text x={100} y={939} fill="#475564" fontSize={28}>Complexity and routing criteria are unknown; these counts do not establish cause or performance.</text>
89
+ <text x={100} y={986} fill="#475564" fontSize={28}>Channel-to-outcome breakdowns are not supplied and cannot be inferred through the merged flows.</text>
90
+ </svg>;
91
+
92
+ export const meta = { title: 'Brook — support ticket flow' };
93
+ export default [Brook];
@@ -0,0 +1,45 @@
1
+ Make a four-page explanation of optimistic concurrency for junior backend
2
+ engineers who know HTTP and basic database updates. Use a worked editing
3
+ conflict to show why checking a revision and writing must be one atomic action.
4
+
5
+ Use sequence diagrams to show the two clients' saves: first the successful
6
+ atomic update and rejected stale save, then the contrasting race when checking
7
+ and writing are separate. Make the request order and stored revisions visible.
8
+
9
+ ---
10
+
11
+ The fictional application Folio stores a document's title and integer revision.
12
+ Document 42 initially has title “Launch notes” and revision 7. Leena and Omar
13
+ both read that state before either saves.
14
+
15
+ Leena changes the title to “Launch checklist”. Omar changes it to “Release notes”.
16
+ With an unconditional update, Leena's save can succeed and then Omar's stale
17
+ save can replace it. The application loses Leena's title without warning.
18
+
19
+ In the revised contract, each save supplies the revision it read. The server
20
+ performs one atomic conditional update: match document ID and expected revision,
21
+ write the new title and increment the revision. It reports success only when
22
+ one row changed. Every title writer uses this contract; a revision is never reused
23
+ for this document, including after any restoration. Assume no deletion here.
24
+
25
+ Leena's request expects revision 7 and succeeds, storing “Launch checklist”
26
+ at revision 8. Omar's request also expects 7 and changes zero rows. The server
27
+ returns HTTP 409 Conflict and leaves revision 8 untouched.
28
+
29
+ After a conflict, the client fetches the current document and shows the user
30
+ both versions. It does not silently retry the stale title with revision 8.
31
+ The user decides whether to keep, revise or replace the current title; a new
32
+ save must use the newly fetched revision and can still conflict again.
33
+
34
+ ---
35
+
36
+ Contrast the atomic update with a separate read-then-write check: both requests
37
+ could read revision 7, both pass the check and then overwrite one another.
38
+ The correctness comes from the database's atomic condition, not the revision
39
+ number merely appearing in the request.
40
+
41
+ This prevents silent lost updates under the stated contract. It does not merge
42
+ edits, choose the better title or make a multi-document change atomic. If a
43
+ successful response is lost, retrying with the old revision can return a
44
+ conflict even though the first save succeeded; revision checks alone do not
45
+ provide request deduplication or an exactly-once acknowledgement.
@@ -0,0 +1,124 @@
1
+ import type { ReactNode } from 'react';
2
+ import '../../lib/typeface.ts';
3
+
4
+ const ink = '#172B3A';
5
+ const muted = '#495D6C';
6
+ const accent = '#006B75';
7
+ const line = '#A8B6BE';
8
+
9
+ function Text({ x, y, children, size = 34, weight = 400, color = ink, anchor = 'start' }: {
10
+ x: number; y: number; children: ReactNode; size?: number; weight?: number; color?: string;
11
+ anchor?: 'start' | 'middle' | 'end';
12
+ }) {
13
+ return <text x={x} y={y} fontSize={size} fontWeight={weight} fill={color} textAnchor={anchor}>{children}</text>;
14
+ }
15
+
16
+ function Frame({ page, title, description, children }: { page: number; title: string; description: string; children: ReactNode }) {
17
+ return <svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"
18
+ fontFamily="IBM Plex Sans" role="img" aria-labelledby={`title-${page} desc-${page}`}>
19
+ <title id={`title-${page}`}>{title}</title><desc id={`desc-${page}`}>{description}</desc>
20
+ <rect width="1920" height="1080" fill="#FFFFFF" />
21
+ <Text x={100} y={140} size={66} weight={600}>{title}</Text>
22
+ {children}
23
+ <Text x={1820} y={1024} size={28} color={muted} anchor="end">{page} / 4</Text>
24
+ </svg>;
25
+ }
26
+
27
+ function Arrow({ from, to, y, label, detail }: { from: number; to: number; y: number; label: string; detail?: string }) {
28
+ const sign = to > from ? 1 : -1;
29
+ return <g>
30
+ <Text x={(from + to) / 2} y={y - (detail ? 55 : 20)} anchor="middle" size={32} weight={500}>{label}</Text>
31
+ {detail && <Text x={(from + to) / 2} y={y - 17} anchor="middle" size={30} color={muted}>{detail}</Text>}
32
+ <path d={`M${from} ${y} H${to} M${to - sign * 16} ${y - 10} L${to} ${y} L${to - sign * 16} ${y + 10}`}
33
+ fill="none" stroke={accent} strokeWidth={3} strokeLinejoin="round" />
34
+ </g>;
35
+ }
36
+
37
+ function Lanes({ middle = 'Server + database', bottom = 795 }: { middle?: string; bottom?: number }) {
38
+ return <g>{[[200, 'Leena'], [960, middle], [1720, 'Omar']].map(([x, label]) => <g key={label}>
39
+ <Text x={Number(x)} y={280} size={36} weight={600} anchor="middle">{label}</Text>
40
+ <line x1={Number(x)} x2={Number(x)} y1={303} y2={bottom} stroke={line} strokeWidth={2} strokeDasharray="6 8" />
41
+ </g>)}</g>;
42
+ }
43
+
44
+ function State({ y, children }: { y: number; children: ReactNode }) {
45
+ return <g><rect x={650} y={y - 33} width={620} height={50} fill="#FFFFFF" stroke={accent} strokeWidth={2} />
46
+ <Text x={960} y={y} size={30} weight={500} anchor="middle" color={accent}>{children}</Text></g>;
47
+ }
48
+
49
+ const LostUpdate = () => <Frame page={1} title="A stale save can erase someone else’s edit"
50
+ description="Folio is fictional. Document 42 initially stores Launch notes, revision 7. Leena and Omar both read it. Leena drafts Launch checklist; Omar drafts Release notes. With unconditional updates, Leena saves first and Omar saves second, silently replacing Leena's title. Revisions alone cannot protect a write that does not check them.">
51
+ <Text x={100} y={219} color={muted}>Folio · fictional document editor · document 42</Text>
52
+ <Text x={100} y={333} size={36} weight={600}>Both read before either saves</Text>
53
+ <Text x={100} y={405} size={48}>“Launch notes”</Text>
54
+ <Text x={100} y={462} color={accent} weight={500}>Stored revision 7</Text>
55
+ <Text x={870} y={333} size={36} weight={600}>Leena’s draft</Text>
56
+ <Text x={870} y={395} size={40}>“Launch checklist”</Text>
57
+ <Text x={1430} y={333} size={36} weight={600}>Omar’s draft</Text>
58
+ <Text x={1430} y={395} size={40}>“Release notes”</Text>
59
+ <Text x={100} y={591} size={36} weight={600}>Without a revision condition</Text>
60
+ <line x1={100} x2={1820} y1={630} y2={630} stroke={line} strokeWidth={2} />
61
+ <Text x={100} y={697} weight={500}>1. Leena saves</Text>
62
+ <Text x={670} y={697}>Stored title becomes “Launch checklist”.</Text>
63
+ <line x1={100} x2={1820} y1={737} y2={737} stroke={line} strokeWidth={2} />
64
+ <Text x={100} y={807} weight={500}>2. Omar saves stale draft</Text>
65
+ <Text x={670} y={807}>Stored title becomes “Release notes”.</Text>
66
+ <line x1={100} x2={1820} y1={847} y2={847} stroke={line} strokeWidth={2} />
67
+ <Text x={100} y={939} size={40} color={accent} weight={500}>Leena’s title is lost. Both saves can appear successful.</Text>
68
+ </Frame>;
69
+
70
+ const Atomic = () => <Frame page={2} title="Make the revision check and write atomic"
71
+ description="Time flows down. Both clients read revision 7. Leena sends Launch checklist with expected revision 7. The database atomically matches document 42 and revision 7, writes the title and increments to 8. One row changes, so the server reports success. Omar sends Release notes expecting 7. Zero rows change, so the server returns HTTP 409 Conflict and preserves Launch checklist at revision 8. Every title writer must follow the contract, revisions are never reused even after restoration, and no deletion is assumed.">
72
+ <Text x={100} y={212} size={32} color={muted}>One database action: match ID + expected revision → write title + increment revision</Text>
73
+ <Lanes />
74
+ <Arrow from={200} to={960} y={378} label="Save “Launch checklist”" detail="document 42 · expected revision 7" />
75
+ <State y={423}>1 row changed · “Launch checklist” · rev 8</State>
76
+ <Arrow from={960} to={200} y={500} label="Success: one row changed" />
77
+ <Arrow from={1720} to={960} y={608} label="Save “Release notes”" detail="document 42 · expected revision 7" />
78
+ <State y={653}>0 rows changed · “Launch checklist” · rev 8</State>
79
+ <Arrow from={960} to={1720} y={774} label="HTTP 409 Conflict" detail="Expected 7 no longer matches stored 8" />
80
+ <Text x={100} y={863} size={36} weight={600}>Success means exactly one row changed.</Text>
81
+ <Text x={100} y={920} size={30}>All writers use this contract; revisions are never reused, even after restoration. Assume no deletion.</Text>
82
+ <Text x={100} y={969} size={30} color={muted}>Prevents silent lost updates; does not merge edits, choose a better title or make multi-document changes atomic.</Text>
83
+ </Frame>;
84
+
85
+ const Race = () => <Frame page={3} title="Separate checks leave a race between writes"
86
+ description="Counterexample; time flows down. Stored revision begins at 7. Leena's separate check reads revision 7 and passes. Omar's separate check also reads 7 and passes before any write. Leena then writes Launch checklist unconditionally and increments to revision 8. Omar writes Release notes unconditionally and increments to revision 9, replacing Leena's edit. The illustrated writes increment the current stored revision but do not condition the write on it. Both checks passed; only an atomic database condition closes the race.">
87
+ <Text x={100} y={212} size={32} color={muted}>Counterexample · stored revision starts at 7 · time flows downward</Text>
88
+ <Lanes bottom={821} />
89
+ <Arrow from={200} to={960} y={374} label="Check expected 7" detail="Read stored 7 → passes" />
90
+ <Arrow from={1720} to={960} y={481} label="Check expected 7" detail="Read stored 7 → passes" />
91
+ <Arrow from={200} to={960} y={601} label="Write “Launch checklist”" detail="Unconditional write + increment" />
92
+ <State y={646}>“Launch checklist” · rev 8</State>
93
+ <Arrow from={1720} to={960} y={761} label="Write “Release notes”" detail="Unconditional write + increment" />
94
+ <State y={806}>“Release notes” · rev 9 · Leena’s edit lost</State>
95
+ <Text x={100} y={904} size={38} weight={500} color={accent}>Both checks passed. Neither constrained the later write.</Text>
96
+ <Text x={100} y={963} size={30} color={muted}>This example increments the stored revision on each write; the missing atomic condition causes the loss.</Text>
97
+ </Frame>;
98
+
99
+ const Recovery = () => <Frame page={4} title="After a conflict, let Omar choose what to save"
100
+ description="On HTTP 409, fetch the current document and show both versions: current Launch checklist at revision 8, and Omar's Release notes. Keep means leave Launch checklist unchanged with no write. Revise means edit a new title using both versions. Replace means deliberately save Release notes. Any new save must use the newly fetched revision, here 8, and can conflict again. Never silently retry Omar's stale title with revision 8. Under the contract this prevents silent lost updates, but does not merge edits, choose the better title, or make multi-document changes atomic. If a successful response is lost, retrying the old revision can conflict despite the first save succeeding; revision checks alone provide neither request deduplication nor exactly-once acknowledgement.">
101
+ <Text x={100} y={224} size={34}>Fetch the current document and show both titles before any new save.</Text>
102
+ <Text x={100} y={334} size={32} weight={600}>Current · revision 8</Text>
103
+ <Text x={100} y={391} size={41} color={accent}>“Launch checklist”</Text>
104
+ <Text x={1030} y={334} size={32} weight={600}>Omar’s draft</Text>
105
+ <Text x={1030} y={391} size={41}>“Release notes”</Text>
106
+ <line x1={100} x2={1820} y1={437} y2={437} stroke={line} strokeWidth={2} />
107
+ <Text x={100} y={502} weight={600}>Keep</Text>
108
+ <Text x={550} y={502} size={36}>Leave “Launch checklist” unchanged. No new save.</Text>
109
+ <line x1={100} x2={1820} y1={540} y2={540} stroke={line} strokeWidth={2} />
110
+ <Text x={100} y={604} weight={600}>Revise</Text>
111
+ <Text x={550} y={604} size={36}>Edit a new title using both versions, then save it.</Text>
112
+ <line x1={100} x2={1820} y1={642} y2={642} stroke={line} strokeWidth={2} />
113
+ <Text x={100} y={706} weight={600}>Replace</Text>
114
+ <Text x={550} y={706} size={36}>Deliberately save “Release notes” over the current title.</Text>
115
+ <line x1={100} x2={1820} y1={744} y2={744} stroke={line} strokeWidth={2} />
116
+ <Text x={100} y={810} size={36} weight={500} color={accent}>A deliberate new save uses the fetched revision, here 8. It can conflict again.</Text>
117
+ <Text x={100} y={865} size={32}>Never silently retry Omar’s stale title with revision 8.</Text>
118
+ <Text x={100} y={937} size={30} color={muted}>If a successful reply is lost, retrying the old revision may conflict even though the save succeeded.</Text>
119
+ <Text x={100} y={979} size={30} color={muted}>Revision checks alone provide neither request deduplication nor exactly-once acknowledgement.</Text>
120
+ </Frame>;
121
+
122
+ export const meta = { title: 'Folio — optimistic concurrency' };
123
+ const pages = [LostUpdate, Atomic, Race, Recovery];
124
+ export default pages;
@@ -0,0 +1,13 @@
1
+ Make a two-page engineering briefing for fictional document workspace Fieldnote. Page 1 should demonstrate a vertical bar chart. Page 2 should demonstrate two vertical bar charts side by side: throughput on the left, search visibility delay on the right.
2
+
3
+ ---
4
+
5
+ Each configuration processed the same 60,000 document updates on the same machine, starting with an empty queue and identical index. Individual updates took 300 seconds with a p95 visibility delay of 2.4 seconds; batches of 20 took 200 seconds with p95 delay of 4.1 seconds; batches of 100 took 150 seconds with p95 delay of 11.8 seconds. Visibility delay runs from submission until the updated document appears in search. All final document versions were correct.
6
+
7
+ These are synthetic, hand-authored results, with one run per configuration and no production measurements or uncertainty estimates.
8
+
9
+ ---
10
+
11
+ Page 1 compares derived throughput: 200, 300 and 400 updates per second. Page 2 compares throughput against the proposed minimum of 250 updates per second and p95 visibility delay against the maximum of 5 seconds. Use zero-based axes, direct values and consistent category order. Each chart needs its own clearly labeled units and scale.
12
+
13
+ Recommend batches of 20 for follow-up testing, not production adoption. Explain why maximum throughput alone is insufficient. Preserve the single-run limitation and the need to repeat tests with sparse arrivals and bursts.