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,40 @@
1
+ Make a two-page briefing for the engineering lead at Tide about eight weeks
2
+ of service latency. Use a line chart to explain the trend, then explain what
3
+ the team can conclude and what it should investigate next.
4
+
5
+ Show both series on the same millisecond axis, with direct labels at their
6
+ last observations and a horizontal target line at 700 ms. Show the missing
7
+ observation as a gap rather than connecting across it or substituting zero.
8
+
9
+ ---
10
+
11
+ Tide is a fictional document service. The following weekly p95 request latencies
12
+ are synthetic, hand-authored data. Each value describes that endpoint's requests
13
+ for the whole week, not an average of daily percentiles. Lower is better.
14
+
15
+ Week starting | Search p95 (ms) | Document-open p95 (ms)
16
+ 1 June 2026 | 920 | 620
17
+ 8 June 2026 | 860 | 610
18
+ 15 June 2026 | 810 | 640
19
+ 22 June 2026 | missing | 630
20
+ 29 June 2026 | 740 | 650
21
+ 6 July 2026 | 690 | 670
22
+ 13 July 2026 | 660 | 660
23
+ 20 July 2026 | 640 | 680
24
+
25
+ Search telemetry was unavailable for the week starting 22 June; its latency
26
+ is unknown. The team's weekly p95 target is at most 700 ms for each endpoint.
27
+ An index change was enabled at the start of 29 June. Mark that event without
28
+ presenting it as the cause of the trend: search was already improving before it.
29
+
30
+ ---
31
+
32
+ The last three observed search weeks meet the target, and all eight observed
33
+ document-open weeks meet it. Search's first-to-last observed p95 falls from
34
+ 920 to 640 ms. This does not establish continuous target compliance, individual
35
+ request latency or the effect of the index change.
36
+
37
+ Request counts, traffic mix, error rates and uncertainty estimates are unavailable.
38
+ There was no control group. Recommend restoring reliable telemetry and checking
39
+ traffic mix and errors before attributing the improvement or declaring the work
40
+ finished. Keep the distinction between observed progress and causal evidence clear.
@@ -0,0 +1,72 @@
1
+ import type { ReactNode } from 'react';
2
+ import '../../lib/typeface.ts';
3
+
4
+ const ink = '#182B36', muted = '#52636E', accent = '#006D77';
5
+ const dates = ['1 Jun', '8 Jun', '15 Jun', '22 Jun', '29 Jun', '6 Jul', '13 Jul', '20 Jul'];
6
+ const search = [920, 860, 810, null, 740, 690, 660, 640];
7
+ const documentOpen = [620, 610, 640, 630, 650, 670, 660, 680];
8
+ const x = (i: number) => 180 + i * 185;
9
+ const y = (value: number) => 800 - (value - 600) * 1.3;
10
+ function Label({ x, y, children, size = 32, color = ink, weight = 400 }: {
11
+ x: number; y: number; children: ReactNode; size?: number; color?: string; weight?: number;
12
+ }) { return <text x={x} y={y} fontSize={size} fill={color} fontWeight={weight}>{children}</text>; }
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"
15
+ fontFamily="IBM Plex Sans" role="img" aria-label={`${title}. ${description}`} data-page={page}>
16
+ <rect width="1920" height="1080" fill="#FFFFFF" />
17
+ <Label x={110} y={143} size={64} weight={600}>{title}</Label>
18
+ {children}
19
+ <Label x={1765} y={1020} size={26} color={muted}>{page} / 2</Label>
20
+ </svg>;
21
+ }
22
+ const Trend = () => <Frame page={1} title="Search reaches Tide’s weekly latency target"
23
+ description="Synthetic weekly p95 milliseconds, lower is better. Weeks starting 1 June through 20 July 2026. Search: 920, 860, 810, unknown, 740, 690, 660, 640. Document-open: 620, 610, 640, 630, 650, 670, 660, 680. Target at most 700. Search telemetry missing 22 June. Index change enabled 29 June; search was already improving.">
24
+ <Label x={110} y={210} size={30} color={muted}>Tide · fictional document service · synthetic weekly data · June–July 2026</Label>
25
+ <Label x={180} y={299} size={28} color={muted}>Weekly p95 (ms) · lower is better</Label>
26
+ {[600, 700, 800, 900, 1000].map(v => <g key={v}>
27
+ <line x1={180} x2={1475} y1={y(v)} y2={y(v)} stroke={v === 700 ? muted : '#DCE2E5'} strokeWidth={v === 700 ? 3 : 2} strokeDasharray={v === 700 ? '12 9' : undefined} />
28
+ <text x={152} y={y(v) + 9} textAnchor="end" fontSize={28} fill={muted}>{v}</text>
29
+ </g>)}
30
+ <Label x={1510} y={y(700) - 8} size={28} color={muted}>Target ≤700 ms</Label>
31
+ <line x1={x(4)} x2={x(4)} y1={355} y2={810} stroke={muted} strokeWidth={2} strokeDasharray="4 7" />
32
+ <Label x={x(4) + 20} y={357} size={28} color={muted}>29 Jun · index change enabled</Label>
33
+ <Label x={x(4) + 20} y={396} size={28} color={muted}>Search was already improving</Label>
34
+ <path data-series="search" d={`M${x(0)},${y(920)} L${x(1)},${y(860)} L${x(2)},${y(810)} M${x(4)},${y(740)} L${x(5)},${y(690)} L${x(6)},${y(660)} L${x(7)},${y(640)}`} fill="none" stroke={accent} strokeWidth={6} />
35
+ <polyline data-series="document-open" points={documentOpen.map((v, i) => `${x(i)},${y(v)}`).join(' ')} fill="none" stroke={ink} strokeWidth={4} strokeDasharray="13 8" />
36
+ {documentOpen.map((v, i) => <rect key={i} data-value={v} data-week={i} x={x(i)-7} y={y(v)-7} width={14} height={14} fill="white" stroke={ink} strokeWidth={3} />)}
37
+ {search.map((v, i) => v === null ? null : <circle key={i} data-value={v} data-week={i} cx={x(i)} cy={y(v)} r={8} fill={accent} />)}
38
+ <Label x={1510} y={y(680) + 13} size={30} weight={500}>Document-open 680</Label>
39
+ <Label x={1510} y={y(640) + 19} size={30} color={accent} weight={600}>Search 640</Label>
40
+ <Label x={648} y={510} size={28} color={accent}>Telemetry missing</Label>
41
+ <Label x={648} y={548} size={28} color={accent}>22 Jun · latency unknown</Label>
42
+ {dates.map((date, i) => <text key={date} x={x(i)} y={858} textAnchor="middle" fontSize={28} fill={muted}>{date}</text>)}
43
+ <Label x={180} y={908} size={26} color={muted}>Week starting · each p95 covers the whole week, not an average of daily percentiles</Label>
44
+ <Label x={110} y={995} size={34} weight={500}>Search meets target in its last 3 observed weeks; document-open meets it in all 8.</Label>
45
+ </Frame>;
46
+
47
+ const Decision = () => <Frame page={2} title="Verify the improvement before closing the work"
48
+ description="Search first-to-last observed weekly p95 fell 280 milliseconds, from 920 to 640. This does not prove continuous compliance, individual request latency or causality. Request counts, traffic mix, errors and uncertainty estimates are unavailable, with no control group. Restore telemetry and inspect traffic mix and errors before attribution.">
49
+ <Label x={110} y={224} size={34} color={muted}>Observed progress is clear. The effect of the index change is still unknown.</Label>
50
+ <Label x={110} y={352} size={38} weight={600}>What the observations support</Label>
51
+ <Label x={110} y={438} size={72} color={accent} weight={600}>920 → 640 ms</Label>
52
+ <Label x={110} y={494} size={32}>Search’s first-to-last observed weekly p95</Label>
53
+ <Label x={110} y={541} size={32}>fell 280 ms across the eight-week window.</Label>
54
+ <Label x={110} y={637} size={32}>Weekly p95 does not establish continuous</Label>
55
+ <Label x={110} y={683} size={32}>target compliance or individual request latency.</Label>
56
+ <Label x={110} y={779} size={32}>Search was improving before 29 June.</Label>
57
+ <Label x={110} y={825} size={32}>There was no control group, so the index</Label>
58
+ <Label x={110} y={871} size={32}>change’s effect cannot be isolated.</Label>
59
+ <Label x={1010} y={352} size={38} weight={600}>What the engineering lead should do next</Label>
60
+ <Label x={1010} y={437} size={34} weight={600} color={accent}>Restore reliable telemetry</Label>
61
+ <Label x={1010} y={487} size={32}>The 22 June search week is unknown.</Label>
62
+ <Label x={1010} y={533} size={32}>Check collection coverage before calling it stable.</Label>
63
+ <Label x={1010} y={631} size={34} weight={600} color={accent}>Check traffic mix and errors</Label>
64
+ <Label x={1010} y={681} size={32}>Request counts, mix, error rates and uncertainty</Label>
65
+ <Label x={1010} y={727} size={32}>estimates are unavailable. Gather them to test</Label>
66
+ <Label x={1010} y={773} size={32}>whether the observed weeks are comparable.</Label>
67
+ <Label x={1010} y={871} size={32} weight={500}>Hold off on attribution and declaring the work finished.</Label>
68
+ </Frame>;
69
+
70
+ export const meta = { title: 'Tide — Eight weeks of service latency' };
71
+ export const notes = ['Synthetic hand-authored data; exact source and prompt accompany this entry.', 'Observational evidence only. No causal estimate is available.'];
72
+ export default [Trend, Decision];
@@ -0,0 +1,38 @@
1
+ Make a three-page presentation explaining a database-field migration to the
2
+ engineers maintaining Juniper. Show how old and new application versions can
3
+ coexist, when reads can switch, and where rollback stops being simple.
4
+
5
+ ---
6
+
7
+ Juniper is a fictional team directory. It is renaming the database column
8
+ full_name to display_name without changing the stored value or the public API.
9
+ The table has an immutable numeric primary key. There is one primary database;
10
+ all application reads and writes in this scenario use it.
11
+
12
+ The old application reads and writes only full_name. First add a nullable
13
+ display_name column. Deploy a bridge version that still reads full_name but
14
+ writes both columns in the same transaction. Old versions may coexist during
15
+ this deployment, so display_name is not yet authoritative.
16
+
17
+ After all old writers and their in-flight transactions have drained, backfill
18
+ in primary-key batches. Each batch uses a database UPDATE to copy the current
19
+ full_name into display_name where they differ, including null differences.
20
+ Updates lock the affected rows; no values are read into application memory
21
+ and written back later. Bridge writes continue during the backfill.
22
+
23
+ Switch reads to display_name only after the backfill finishes and a full-table
24
+ comparison finds zero differences. Keep dual writes during the read switch
25
+ and a seven-day observation period.
26
+
27
+ ---
28
+
29
+ Before cleanup, a read-switch rollback uses the bridge version. Returning to
30
+ the original old writer would require repeating writer drain and reconciliation
31
+ before attempting another read switch.
32
+
33
+ After the observation period, remove full_name access from the application,
34
+ then drop the column only after no running version depends on it. Once dropped,
35
+ rolling back to those older versions requires restoring and repopulating the
36
+ column first. The seven days are a proposed policy, not proof of safety. No
37
+ database engine or measured lock duration is supplied; test DDL and batch size
38
+ on representative data before scheduling the migration.
@@ -0,0 +1,89 @@
1
+ import type { ReactNode } from 'react';
2
+ import '../../lib/typeface.ts';
3
+
4
+ const ink = '#172D32', muted = '#4A6065', accent = '#00796B', line = '#CDD8D8';
5
+ function Copy({ x, y, lines, size = 36, color = ink, weight = 400 }: {
6
+ x: number; y: number; lines: string[]; size?: number; color?: string; weight?: number;
7
+ }) {
8
+ return <text x={x} y={y} fontSize={size} fill={color} fontWeight={weight} aria-label={lines.join(' ')}>
9
+ {lines.map((text, i) => <tspan key={i} x={x} dy={i ? size * 1.3 : 0}>{text}</tspan>)}
10
+ </text>;
11
+ }
12
+ function Frame({ number, title, description, children }: {
13
+ number: number; title: string; description: string; children: ReactNode;
14
+ }) {
15
+ return <svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"
16
+ fontFamily="IBM Plex Sans" role="img" aria-labelledby={`title-${number} desc-${number}`} data-page={number}>
17
+ <title id={`title-${number}`}>{title}</title><desc id={`desc-${number}`}>{description}</desc>
18
+ <rect width="1920" height="1080" fill="#FFFFFF" />
19
+ <Copy x={100} y={145} lines={[title]} size={64} weight={600} />
20
+ {children}
21
+ <Copy x={1710} y={1026} lines={[`${number} / 3`]} size={28} color={muted} />
22
+ </svg>;
23
+ }
24
+ function Coexistence() {
25
+ return <Frame number={1} title="Keep full_name authoritative during rollout"
26
+ description="Juniper renames full_name to display_name without changing values or the public API. Add a nullable column, then deploy the bridge. Old applications read and write full_name only. The bridge reads full_name and writes both columns in one transaction. While old writers coexist, display_name is not authoritative. All reads and writes use one primary database.">
27
+ <Copy x={100} y={230} lines={['Rename full_name → display_name. Stored values and the public API stay unchanged.']} size={34} color={muted} />
28
+ <Copy x={100} y={280} lines={['Juniper · Fictional migration example']} size={28} color={muted} />
29
+ <Copy x={100} y={340} lines={['Add nullable display_name, then deploy the bridge.']} size={40} weight={500} color={accent} />
30
+ <Copy x={100} y={450} lines={['Running version']} size={30} color={muted} />
31
+ <Copy x={690} y={450} lines={['Reads']} size={30} color={muted} />
32
+ <Copy x={1170} y={450} lines={['Writes']} size={30} color={muted} />
33
+ <line x1={100} x2={1820} y1={480} y2={480} stroke={line} strokeWidth={2} />
34
+ <Copy x={100} y={552} lines={['Old application']} size={40} weight={500} />
35
+ <Copy x={690} y={552} lines={['full_name']} size={40} />
36
+ <Copy x={1170} y={552} lines={['full_name only']} size={40} />
37
+ <line x1={100} x2={1820} y1={595} y2={595} stroke={line} />
38
+ <Copy x={100} y={666} lines={['Bridge version']} size={40} weight={500} />
39
+ <Copy x={690} y={666} lines={['full_name']} size={40} />
40
+ <Copy x={1170} y={666} lines={['full_name + display_name']} size={36} />
41
+ <Copy x={1170} y={720} lines={['In the same transaction']} size={30} color={accent} />
42
+ <Copy x={100} y={825} lines={['Old writers can leave display_name stale.']} size={44} weight={500} color={accent} />
43
+ <Copy x={100} y={887} lines={['Do not switch reads yet. All application traffic uses one primary database.']} size={34} color={muted} />
44
+ </Frame>;
45
+ }
46
+ function Reconcile() {
47
+ return <Frame number={2} title="Gate the read switch on zero differences"
48
+ description="First drain all old writers and their in-flight transactions. Then backfill in immutable numeric primary-key batches with database UPDATEs copying current full_name to display_name wherever they differ, including null differences. Row locks protect affected rows; no application-memory read and write-back. Bridge writes continue. Only after completion and a full-table comparison finding zero differences may reads switch; keep dual writes.">
49
+ <Copy x={100} y={230} lines={['Bridge writes continue while the database reconciles current values.']} size={36} color={muted} />
50
+ <line x1={955} x2={955} y1={315} y2={905} stroke={line} strokeWidth={2} />
51
+ <Copy x={100} y={355} lines={['1 Drain old writers']} size={44} weight={500} color={accent} />
52
+ <Copy x={155} y={417} lines={['Wait for every old writer and its', 'in-flight transactions to finish.']} />
53
+ <Copy x={100} y={555} lines={['2 Backfill in primary-key batches']} size={40} weight={500} color={accent} />
54
+ <Copy x={155} y={617} lines={['Use the immutable numeric key.', 'Finish every batch before validation.']} />
55
+ <Copy x={100} y={755} lines={['3 Compare the full table']} size={44} weight={500} color={accent} />
56
+ <Copy x={155} y={817} lines={['Require zero differences.', 'Then switch reads; keep dual writes.']} />
57
+ <Copy x={1040} y={355} lines={['Inside each database UPDATE']} size={40} weight={500} />
58
+ <Copy x={1040} y={440} lines={['Current full_name → display_name', 'Only where values differ,', 'including null differences.']} size={36} />
59
+ <Copy x={1040} y={635} lines={['Updates lock the affected rows.']} size={36} weight={500} />
60
+ <Copy x={1040} y={705} lines={['No values are read into application', 'memory and written back later.']} size={34} color={muted} />
61
+ <Copy x={1040} y={845} lines={['The bridge keeps both columns in sync.']} size={32} color={accent} />
62
+ </Frame>;
63
+ }
64
+ function Rollback() {
65
+ return <Frame number={3} title="Dropping full_name changes the rollback path"
66
+ description="Read display_name and retain dual writes for a proposed seven-day observation period. Before cleanup, roll a read switch back to the bridge. Returning to the original old writer requires repeating writer drain and reconciliation before another read switch. After observation remove full_name access from the application, then drop only after no running version depends on it. After drop, older versions require restoring and repopulating full_name. Seven days is policy, not safety proof. No database engine or measured lock duration is supplied; test DDL and batch size on representative data before scheduling.">
67
+ <Copy x={100} y={230} lines={['Preserve a simple read rollback through observation; remove dependencies before DDL.']} size={34} color={muted} />
68
+ <Copy x={100} y={350} lines={['Observe for 7 days']} size={40} weight={500} color={accent} />
69
+ <Copy x={720} y={350} lines={['Remove old access']} size={40} weight={500} color={accent} />
70
+ <Copy x={1350} y={350} lines={['Drop full_name']} size={40} weight={500} color={accent} />
71
+ <path d="M475 337 H660 L648 327 M660 337 L648 347 M1085 337 H1290 L1278 327 M1290 337 L1278 347" fill="none" stroke={accent} strokeWidth={3} />
72
+ <Copy x={100} y={420} lines={['Read display_name.', 'Keep dual writes.']} size={34} />
73
+ <Copy x={720} y={420} lines={['After observation, remove', 'full_name access from the app.']} size={34} />
74
+ <Copy x={1350} y={420} lines={['Only when no running', 'version depends on it.']} size={34} />
75
+ <line x1={1250} x2={1250} y1={530} y2={808} stroke={ink} strokeWidth={3} strokeDasharray="10 10" />
76
+ <Copy x={100} y={590} lines={['Before cleanup: return to the bridge']} size={38} weight={500} />
77
+ <Copy x={100} y={655} lines={['For read-switch rollback, use the bridge version.', 'Returning to the original old writer means repeating', 'writer drain + reconciliation before another read switch.']} size={34} />
78
+ <Copy x={1350} y={590} lines={['After drop']} size={38} weight={500} />
79
+ <Copy x={1350} y={655} lines={['Restore and repopulate', 'full_name before rolling', 'back to older versions.']} size={34} />
80
+ <Copy x={100} y={870} lines={['Seven days is proposed policy, not proof of safety. No engine or lock duration is supplied.', 'Test DDL and batch size on representative data before scheduling the migration.']} size={32} color={muted} />
81
+ </Frame>;
82
+ }
83
+ export const meta = { title: 'Juniper · Rename a field safely', createdAt: '2026-09-12T00:00:00Z' };
84
+ export const notes = [
85
+ 'This is a fictional, single-primary scenario. Adding the nullable column precedes bridge rollout. Old writers are allowed during rollout but prevent treating the new column as authoritative.',
86
+ 'The UPDATE is conceptual, not engine-specific SQL. Null-aware comparison must include null/value differences. Row locking and current database values avoid a stale application-memory copy. No lock-duration guarantee is implied.',
87
+ 'The read-switch rollback described here is before cleanup. Once application access is removed, compatibility must be checked for the actual running versions. After the column is dropped, older versions require schema restoration and repopulation first.',
88
+ ];
89
+ export default [Coexistence, Reconcile, Rollback];
@@ -0,0 +1,21 @@
1
+ Make three 1200×630 link-preview images for the fictional developer-tools project
2
+ Clearpath: one for an article, one for a release and one for its repository.
3
+ Make each subject understandable at a glance and keep them recognizable as
4
+ one family when shown together.
5
+
6
+ ---
7
+
8
+ Use these exact titles and supporting lines:
9
+
10
+ - Article title: “A green build is not a release”. Supporting line: “Separate
11
+ checks, approval and publication.”
12
+ - Release title: “Clearpath 0.4”. Supporting line: “Preview release notes before
13
+ exporting Markdown.”
14
+ - Repository title: “Clearpath”. Supporting line: “A local release checklist
15
+ for small software teams.”
16
+
17
+ Clearpath is a fictional command-line tool that reads a local checklist and
18
+ prepares a release-note preview. A person reviews it before Markdown export;
19
+ the tool does not publish releases. There is no supplied logo, website address,
20
+ repository URL, adoption count or performance evidence. These images illustrate
21
+ three link types rather than announcing a real project or release.
@@ -0,0 +1,76 @@
1
+ import type { ReactNode } from 'react';
2
+ import '../../lib/typeface.ts';
3
+
4
+ const ink = '#172D28';
5
+ const green = '#16704A';
6
+ const muted = '#52625D';
7
+
8
+ function Canvas({ name, description, children }: { name: string; description: string; children: ReactNode }) {
9
+ return <div style={{ width: 1920, height: 1080, background: '#FFFFFF', display: 'flex', alignItems: 'center' }}>
10
+ <svg data-artwork={name} xmlns="http://www.w3.org/2000/svg" width="1920" height="1008" viewBox="0 0 1200 630" role="img" aria-label={description} fontFamily="IBM Plex Sans" fill={ink}>
11
+ <rect width="1200" height="630" fill="#FFFFFF" />
12
+ {children}
13
+ </svg>
14
+ </div>;
15
+ }
16
+
17
+ function Check({ x, y }: { x: number; y: number }) {
18
+ return <g transform={`translate(${x} ${y})`} fill="none" stroke={green} strokeWidth="4" strokeLinecap="round" strokeLinejoin="round">
19
+ <rect width="36" height="36" rx="8" />
20
+ <path d="M9 18l6 6 13-14" />
21
+ </g>;
22
+ }
23
+
24
+ const Article = () => <Canvas name="article" description="A green build is not a release. Separate checks, approval and publication. Article illustration: checks complete, human approval and publication remain distinct. Fictional Clearpath example.">
25
+ <text x="72" y="132" fontSize="76" fontWeight="600">A green build</text>
26
+ <text x="72" y="218" fontSize="76" fontWeight="600">is not a release</text>
27
+ <text x="74" y="287" fontSize="32" fill={muted}>Separate checks, approval and publication.</text>
28
+ <g transform="translate(74 384)">
29
+ <Check x={0} y={0} />
30
+ <text x="0" y="85" fontSize="30" fontWeight="500">Checks</text>
31
+ <circle cx="383" cy="18" r="18" fill="none" stroke={ink} strokeWidth="3" />
32
+ <text x="365" y="85" fontSize="30" fontWeight="500">Approval</text>
33
+ <rect x="730" y="0" width="36" height="36" rx="4" fill="none" stroke={ink} strokeWidth="3" />
34
+ <text x="730" y="85" fontSize="30" fontWeight="500">Publication</text>
35
+ </g>
36
+ <text x="74" y="569" fontSize="22" fill={muted}>Clearpath · Fictional project</text>
37
+ </Canvas>;
38
+
39
+ const Release = () => <Canvas name="release" description="Clearpath 0.4. Preview release notes before exporting Markdown. Release illustration: a person reviews a local preview before Markdown export; the tool does not publish releases. Fictional release.">
40
+ <text x="72" y="148" fontSize="96" fontWeight="600">Clearpath <tspan fill={green}>0.4</tspan></text>
41
+ <text x="74" y="219" fontSize="32" fill={muted}>Preview release notes before</text>
42
+ <text x="74" y="262" fontSize="32" fill={muted}>exporting Markdown.</text>
43
+ <g transform="translate(74 338)">
44
+ <rect width="650" height="153" rx="12" fill="none" stroke="#B5C5BE" strokeWidth="2" />
45
+ <text x="28" y="49" fontSize="28" fontWeight="500">Release-note preview</text>
46
+ <path d="M28 79H450 M28 104H370 M28 129H490" stroke="#B5C5BE" strokeWidth="6" />
47
+ <Check x={720} y={16} />
48
+ <text x="720" y="90" fontSize="28" fontWeight="500">Human review</text>
49
+ <text x="720" y="129" fontSize="24" fill={muted}>before export</text>
50
+ </g>
51
+ <text x="74" y="569" fontSize="22" fill={muted}>Fictional release · Does not publish releases</text>
52
+ </Canvas>;
53
+
54
+ const Repository = () => <Canvas name="repository" description="Clearpath. A local release checklist for small software teams. Repository illustration: a local checklist prepares a release-note preview for human review and Markdown export, without publishing. Fictional project.">
55
+ <text x="72" y="156" fontSize="110" fontWeight="600">Clearpath</text>
56
+ <text x="74" y="237" fontSize="34" fill={muted}>A local release checklist</text>
57
+ <text x="74" y="283" fontSize="34" fill={muted}>for small software teams.</text>
58
+ <g transform="translate(840 98)">
59
+ <rect width="270" height="340" rx="16" fill="none" stroke="#B5C5BE" strokeWidth="3" />
60
+ <Check x={32} y={46} /><path d="M94 64H230" stroke={green} strokeWidth="7" />
61
+ <Check x={32} y={130} /><path d="M94 148H203" stroke={green} strokeWidth="7" />
62
+ <rect x="32" y="214" width="36" height="36" rx="8" fill="none" stroke={muted} strokeWidth="3" />
63
+ <path d="M94 232H219" stroke="#B5C5BE" strokeWidth="7" />
64
+ </g>
65
+ <text x="74" y="425" fontSize="28" fontWeight="500" fill={green}>Review the release-note preview.</text>
66
+ <text x="74" y="469" fontSize="28" fill={muted}>Markdown export. No publishing.</text>
67
+ <text x="74" y="569" fontSize="22" fill={muted}>Fictional project</text>
68
+ </Canvas>;
69
+
70
+ export const meta = { title: 'Clearpath — link-preview images', createdAt: '2026-09-12T14:20:00Z' };
71
+ export const notes = [
72
+ 'Article link preview. Checks, human approval and publication are separate concerns. Clearpath does not publish releases.',
73
+ 'Fictional release link preview, not a real release announcement. The preview must be reviewed by a person before Markdown export.',
74
+ 'Fictional repository link preview. No repository URL, logo or adoption claims were supplied or invented.',
75
+ ];
76
+ export default [Article, Release, Repository];
@@ -0,0 +1,24 @@
1
+ Make a three-page presentation introducing Patchnote to engineers and product
2
+ managers at a small software team. Show how they can turn selected pull-request
3
+ descriptions into a release-note draft, review it and export it. Use an
4
+ illustrative interface to make the workflow concrete.
5
+
6
+ ---
7
+
8
+ Patchnote is a fictional release-note drafting tool. A user pastes pull-request
9
+ titles and descriptions into a release workspace and selects the entries to
10
+ include. Patchnote groups them into Added, Changed and Fixed, then proposes a
11
+ plain-English draft. A person edits and approves the draft before exporting it
12
+ as Markdown.
13
+
14
+ The draft can contain mistakes. Patchnote does not verify changes, connect to a
15
+ repository or publish releases. We have no customer telemetry or measured time
16
+ savings.
17
+
18
+ For the illustrative release 0.8, use these inputs:
19
+ - PR #142: Add CSV export for filtered results.
20
+ - PR #147: Remember the selected date range between visits.
21
+ - PR #153: Fix duplicate notification emails when a job is retried.
22
+ - PR #155: Refactor internal test helpers.
23
+
24
+ The first three are selected for the release. PR #155 is left out.
@@ -0,0 +1,105 @@
1
+ import type { ReactNode } from 'react';
2
+ import { fontsReady } from '../../lib/typeface.ts';
3
+
4
+ await fontsReady;
5
+
6
+ const ink = '#172B35', muted = '#50616B', accent = '#006D77', line = '#CBD5DA';
7
+ function Copy({ x, y, children, size = 34, color = ink, weight = 400 }: {
8
+ x: number; y: number; children: ReactNode; size?: number; color?: string; weight?: number;
9
+ }) {
10
+ return <text x={x} y={y} fill={color} fontSize={size} fontWeight={weight}>{children}</text>;
11
+ }
12
+ function Frame({ title, subtitle, page, description, children }: {
13
+ title: string; subtitle: string; page: number; description: string; children: ReactNode;
14
+ }) {
15
+ return <svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"
16
+ fontFamily="IBM Plex Sans" role="img" aria-label={`${title}. ${description}`} data-page={page}>
17
+ <rect width="1920" height="1080" fill="#FFFFFF" />
18
+ <Copy x={100} y={145} size={70} weight={600}>{title}</Copy>
19
+ <Copy x={100} y={216} size={36} color={muted}>{subtitle}</Copy>
20
+ {children}
21
+ <Copy x={1710} y={1030} size={28} color={muted}>{page} / 3</Copy>
22
+ </svg>;
23
+ }
24
+ function Window({ x, y, width, height, title, children }: {
25
+ x: number; y: number; width: number; height: number; title: string; children: ReactNode;
26
+ }) {
27
+ return <g transform={`translate(${x} ${y})`}>
28
+ <rect width={width} height={height} rx={16} fill="#FFFFFF" stroke={line} strokeWidth={2} />
29
+ <Copy x={32} y={53} size={30} weight={500}>{title}</Copy>
30
+ <line x1={0} x2={width} y1={80} y2={80} stroke={line} />
31
+ {children}
32
+ </g>;
33
+ }
34
+ const Select = () => <Frame page={1} title="Patchnote turns selected PRs into a draft"
35
+ subtitle="Paste pull-request titles and descriptions, then choose what belongs in the release."
36
+ description="Release 0.8 selects PRs 142, 147 and 153; PR 155, refactoring internal test helpers, is left out. No repository connection.">
37
+ <Copy x={100} y={260} size={26} color={muted}>Fictional product · Illustrative interface</Copy>
38
+ <Window x={100} y={292} width={1180} height={613} title="Release 0.8 / Pasted entries">
39
+ {[
40
+ ['#142', 'Add CSV export for filtered results.', true],
41
+ ['#147', 'Remember the selected date range between visits.', true],
42
+ ['#153', 'Fix duplicate notification emails', true],
43
+ ['#155', 'Refactor internal test helpers.', false],
44
+ ].map(([id, title, selected], i) => <g key={String(id)} transform={`translate(32 ${115 + i * 111})`}>
45
+ <rect x={0} y={0} width={34} height={34} rx={5} fill={selected ? accent : '#FFFFFF'} stroke={selected ? accent : muted} strokeWidth={2} />
46
+ {selected && <path d="M8 17 L14 24 L27 10" stroke="#FFFFFF" strokeWidth={3} fill="none" />}
47
+ <Copy x={58} y={28} size={30} color={muted}>{id}</Copy>
48
+ <Copy x={161} y={28} size={32}>{title}</Copy>
49
+ {i === 2 && <Copy x={161} y={68} size={32}>when a job is retried.</Copy>}
50
+ {i === 3 && <Copy x={161} y={69} size={28} color={muted}>Left out of this release</Copy>}
51
+ </g>)}
52
+ </Window>
53
+ <Copy x={1370} y={361} size={48} weight={600}>3 selected</Copy>
54
+ <Copy x={1370} y={421} size={34} color={muted}>1 left out</Copy>
55
+ <rect x={1370} y={475} width={420} height={84} rx={10} fill={accent} />
56
+ <Copy x={1407} y={529} size={34} color="#FFFFFF" weight={500}>Generate draft →</Copy>
57
+ <Copy x={1370} y={700} size={34} weight={500}>You supply the inputs.</Copy>
58
+ <Copy x={1370} y={756} size={32} color={muted}>No repository connection.</Copy>
59
+ </Frame>;
60
+
61
+ const Review = () => <Frame page={2} title="Review the draft before approving it"
62
+ subtitle="Patchnote groups selected entries into Added, Changed and Fixed, then proposes plain English."
63
+ description="The proposed draft maps Added to PR 142, Changed to PR 147 and Fixed to PR 153. A person checks the input, edits the wording and approves. Drafts can contain mistakes; Patchnote does not verify changes.">
64
+ <Window x={100} y={292} width={1080} height={613} title="Release 0.8 / Proposed draft">
65
+ {[
66
+ ['Added', 'You can now export your filtered results', '#142'],
67
+ ['Changed', 'Your selected date range is remembered', '#147'],
68
+ ['Fixed', 'Fixed duplicate notification emails', '#153'],
69
+ ].map(([category, text, pr], i) => <g key={category} transform={`translate(36 ${142 + i * 155})`}>
70
+ <Copy x={0} y={0} size={30} color={accent} weight={600}>{category}</Copy>
71
+ <Copy x={900} y={0} size={28} color={muted}>{pr}</Copy>
72
+ <Copy x={0} y={52} size={36}>{text}</Copy>
73
+ {i === 0 && <Copy x={0} y={98} size={36}>in CSV format.</Copy>}
74
+ {i === 1 && <Copy x={0} y={98} size={36}>between visits.</Copy>}
75
+ {i === 2 && <Copy x={0} y={98} size={36}>when a job is retried.</Copy>}
76
+ </g>)}
77
+ </Window>
78
+ <Copy x={1280} y={343} size={40} weight={500}>A person decides</Copy>
79
+ <Copy x={1280} y={422} size={34}>Check against the inputs.</Copy>
80
+ <Copy x={1280} y={480} size={34}>Edit the wording.</Copy>
81
+ <Copy x={1280} y={538} size={34}>Approve the draft.</Copy>
82
+ <line x1={1280} y1={600} x2={1820} y2={600} stroke={line} />
83
+ <Copy x={1280} y={673} size={36} weight={500}>Drafts can contain mistakes.</Copy>
84
+ <Copy x={1280} y={736} size={32} color={muted}>Patchnote does not verify</Copy>
85
+ <Copy x={1280} y={782} size={32} color={muted}>the changes described.</Copy>
86
+ </Frame>;
87
+
88
+ const Export = () => <Frame page={3} title="Export the approved draft as Markdown"
89
+ subtitle="The reviewed release note leaves the workspace as text. Publishing remains outside Patchnote."
90
+ description="Illustrative approved release 0.8 Markdown contains Added: export filtered results as CSV; Changed: remember the selected date range between visits; Fixed: duplicate notification emails on job retry. PR 155 is absent. Patchnote does not publish releases. No customer telemetry or measured time savings.">
91
+ <Copy x={100} y={357} size={44} weight={600}>Ready after human review</Copy>
92
+ <Copy x={100} y={430} size={34}>The selected changes are included.</Copy>
93
+ <Copy x={100} y={485} size={34}>Internal test helpers stay out.</Copy>
94
+ <rect x={100} y={552} width={490} height={86} rx={10} fill={accent} />
95
+ <Copy x={140} y={607} size={34} color="#FFFFFF" weight={500}>Export Markdown →</Copy>
96
+ <Copy x={100} y={766} size={32} color={muted}>No customer telemetry or</Copy>
97
+ <Copy x={100} y={813} size={32} color={muted}>measured time savings.</Copy>
98
+ <Window x={770} y={292} width={1050} height={613} title="Release 0.8 / Markdown output">
99
+ {['# Release 0.8', '## Added', '- Export filtered results as CSV.', '## Changed', '- Remember the selected date range', ' between visits.', '## Fixed', '- Fix duplicate notification emails', ' when a job is retried.'].map((text, i) =>
100
+ <Copy key={i} x={38} y={139 + i * 51} size={34} weight={text.startsWith('#') ? 500 : 400} color={text.startsWith('#') ? accent : ink}>{text}</Copy>)}
101
+ </Window>
102
+ </Frame>;
103
+
104
+ export const meta = { title: 'Patchnote — from PRs to release notes', description: 'A fictional product introduction in three pages.' };
105
+ export default [Select, Review, Export];
@@ -0,0 +1,40 @@
1
+ Turn the research report below into a three-page briefing for the product
2
+ and design team at Fern. Explain the observed problems, distinguish them
3
+ from the team's interpretations and propose what to investigate next.
4
+
5
+ Include a horizontal stacked-bar chart comparing completion before and after
6
+ a hint for each task. Label the counts directly, with each bar totaling eight
7
+ participants.
8
+
9
+ ---
10
+
11
+ This is the complete fictional research report for this example. Fern is a
12
+ shared workspace-booking product; all study records are hand-authored.
13
+
14
+ The question was whether first-time users could reserve a desk and understand
15
+ what to do if their plans changed. Eight volunteers from one company tried
16
+ the same desktop prototype in individual moderated sessions. None had used
17
+ Fern before. Everyone first booked a desk for a supplied date, then changed
18
+ the booking date, then looked for cancellation. There was no comparison design.
19
+ For each task, the moderator waited up to three minutes before offering a hint.
20
+
21
+ Six of eight completed booking before a hint; two completed it after a hint.
22
+ Four of eight changed the date before a hint; four completed it after a hint.
23
+ Three of eight found cancellation before a hint; five found it after a hint.
24
+ These are task counts for the same participants, not separate groups. Everyone
25
+ eventually completed all three tasks. No task-time distribution was recorded.
26
+
27
+ Session notes recorded that five participants looked in the calendar for a
28
+ way to change a booking, while the prototype put that action under “My visits”.
29
+ Four said “My visits” sounded like past activity. These observations can overlap;
30
+ the notes do not provide a participant-by-participant mapping between them.
31
+
32
+ The researchers interpret the navigation label and action placement as likely
33
+ sources of confusion. The sessions do not separate their effects, and the
34
+ fixed task order may have influenced later tasks. A proposed next study would
35
+ compare clearer labels and calendar-based actions, vary task order and recruit
36
+ people from more than one company. No alternative has been tested yet.
37
+
38
+ This small volunteer sample cannot establish population completion rates,
39
+ production adoption or mobile usability. The report supports investigating
40
+ booking management, not claiming that a particular redesign will solve it.
@@ -0,0 +1,104 @@
1
+ import type { ReactNode } from 'react';
2
+ import '../../lib/typeface.ts';
3
+
4
+ const ink = '#182B27';
5
+ const muted = '#4F625C';
6
+ const green = '#17634A';
7
+ const pale = '#DAE9E1';
8
+
9
+ function Copy({ x = 112, y, lines, size = 36, 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((line, i) => <tspan key={i} x={x} dy={i ? size * 1.32 : 0}>{line}</tspan>)}
14
+ </text>;
15
+ }
16
+
17
+ function Frame({ title, page, description, children }: {
18
+ title: string; page: number; 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-label={`${title}. ${description}`}>
22
+ <rect width="1920" height="1080" fill="#FFFFFF" />
23
+ <Copy y={150} lines={[title]} size={64} weight={600} />
24
+ {children}
25
+ <Copy x={1770} y={1020} lines={[`${page} / 3`]} size={28} color={muted} />
26
+ </svg>;
27
+ }
28
+
29
+ const tasks = [
30
+ { label: 'Book a desk', before: 6, after: 2 },
31
+ { label: 'Change the date', before: 4, after: 4 },
32
+ { label: 'Find cancellation', before: 3, after: 5 },
33
+ ];
34
+
35
+ function Completion() {
36
+ return <Frame title="Booking management needed more hints" page={1}
37
+ description="Fictional Fern research. Of the same eight participants, booking: six before a hint, two after; date change: four before, four after; cancellation: three before, five after. Everyone eventually completed all tasks.">
38
+ <Copy y={235} lines={['Fern · shared workspace booking · fictional, hand-authored study']} color={muted} />
39
+ <Copy y={320} lines={['8 first-time users from one company; individual moderated desktop sessions.']} />
40
+ <rect x={540} y={385} width={28} height={28} fill={green} />
41
+ <Copy x={585} y={410} lines={['Before a hint']} size={32} />
42
+ <rect x={930} y={385} width={28} height={28} fill={pale} />
43
+ <Copy x={975} y={410} lines={['After a hint']} size={32} />
44
+ <Copy x={1610} y={410} lines={['Total: 8']} size={32} color={muted} />
45
+ {tasks.map((task, i) => {
46
+ const y = 466 + i * 122;
47
+ return <g key={task.label} data-task={task.label}>
48
+ <Copy y={y + 53} lines={[task.label]} size={38} weight={500} />
49
+ <rect x={540} y={y} width={task.before * 150} height={80} fill={green} />
50
+ <rect x={540 + task.before * 150} y={y} width={task.after * 150} height={80} fill={pale} />
51
+ <text x={540 + task.before * 75} y={y + 53} textAnchor="middle" fontSize={38} fontWeight={600} fill="#FFFFFF">{task.before}</text>
52
+ <text x={540 + task.before * 150 + task.after * 75} y={y + 53} textAnchor="middle" fontSize={38} fontWeight={600} fill={ink}>{task.after}</text>
53
+ </g>;
54
+ })}
55
+ <Copy y={870} lines={['Same people, fixed order: book → change date → find cancellation.',
56
+ 'Hints were offered after up to 3 minutes. All 8 eventually completed every task.',
57
+ 'No task-time distribution or comparison design was recorded.']} size={34} color={muted} />
58
+ </Frame>;
59
+ }
60
+
61
+ function Observations() {
62
+ return <Frame title="The notes point to a navigation mismatch" page={2}
63
+ description="Five participants looked in the calendar to change a booking. Four said My visits sounded like past activity. These observations may overlap; no participant mapping is available. Researchers suspect both the label and placement, but cannot separate their effects.">
64
+ <Copy y={270} lines={['Observed in the sessions']} size={40} weight={600} color={green} />
65
+ <Copy x={1050} y={270} lines={['Researchers’ interpretation']} size={40} weight={600} color={green} />
66
+ <Copy y={380} lines={['5 of 8 looked in the calendar']} size={42} weight={500} />
67
+ <Copy y={444} lines={['to change a booking. The prototype', 'put that action under “My visits”.']} />
68
+ <Copy y={610} lines={['4 of 8 questioned “My visits”']} size={42} weight={500} />
69
+ <Copy y={674} lines={['They said it sounded like past activity.']} />
70
+ <Copy x={1050} y={380} lines={['The label and action placement', 'are likely sources of confusion.']} size={40} weight={500} />
71
+ <Copy x={1050} y={535} lines={['The sessions cannot separate', 'their effects. Fixed task order may', 'also have influenced later tasks.']} />
72
+ <Copy y={850} lines={['The two observations can overlap; the notes do not map them to individuals.']} size={34} color={muted} />
73
+ <Copy y={913} lines={['Fictional study evidence, not proof that either factor caused the difficulty.']} size={34} color={muted} />
74
+ </Frame>;
75
+ }
76
+
77
+ function NextStudy() {
78
+ return <Frame title="Test labels and placement before choosing a fix" page={3}
79
+ description="Proposed study, not tested: compare clearer labels and calendar-based actions, vary task order, and recruit from multiple companies. The small volunteer study supports investigating booking management, not claiming a redesign will solve it or estimating population completion, adoption, or mobile usability.">
80
+ <Copy y={255} lines={['Proposed next study · no alternative has been tested yet']} color={green} weight={500} />
81
+ <Copy y={366} lines={['Question to investigate']} size={34} weight={600} color={muted} />
82
+ <Copy x={850} y={366} lines={['What the comparison needs']} size={34} weight={600} color={muted} />
83
+ <line x1={112} x2={1808} y1={400} y2={400} stroke="#BAC9C2" strokeWidth={2} />
84
+ <Copy y={466} lines={['Does clearer wording help?']} size={38} weight={500} />
85
+ <Copy x={850} y={466} lines={['Compare clearer labels with “My visits”;', 'hold action placement constant.*']} />
86
+ <line x1={112} x2={1808} y1={558} y2={558} stroke="#BAC9C2" strokeWidth={2} />
87
+ <Copy y={622} lines={['Does calendar access help?']} size={38} weight={500} />
88
+ <Copy x={850} y={622} lines={['Compare calendar-based actions with the', 'current placement; hold wording constant.*']} />
89
+ <line x1={112} x2={1808} y1={715} y2={715} stroke="#BAC9C2" strokeWidth={2} />
90
+ <Copy y={774} lines={['Vary task order and recruit from more than one company.']} size={38} weight={500} />
91
+ <Copy y={834} lines={['*Suggested comparison structure; the report proposes testing labels and calendar actions.']} size={30} color={muted} />
92
+ <Copy y={909} lines={['Scope: investigate booking management. This small volunteer sample cannot establish',
93
+ 'population completion rates, production adoption, mobile usability or a proven fix.']} size={34} color={muted} />
94
+ </Frame>;
95
+ }
96
+
97
+ export const meta = { title: 'Fern — Research briefing', createdAt: '2026-09-12T14:20:00Z' };
98
+ export const notes = [
99
+ 'Source: the complete fictional report in PROMPT.md. Counts are mutually exclusive before/after-hint outcomes for each task, for the same eight people.',
100
+ 'Observations and interpretations are deliberately separated. No unique combined participant count can be derived.',
101
+ 'Holding one variable constant is an author-proposed operationalization, explicitly marked on the page; no redesign efficacy is claimed.',
102
+ ];
103
+ const pages = [Completion, Observations, NextStudy];
104
+ export default pages;