@protoyard/blueprint 0.0.0-stage → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1145 -2
- package/dist/aggregate.d.ts +83 -0
- package/dist/aggregate.js +268 -0
- package/dist/app-model.d.ts +76 -0
- package/dist/app-model.js +287 -0
- package/dist/archetypes/index.d.ts +1 -0
- package/dist/archetypes/index.js +1 -0
- package/dist/archetypes/schema.d.ts +126 -0
- package/dist/archetypes/schema.js +77 -0
- package/dist/audit.d.ts +52 -0
- package/dist/audit.js +149 -0
- package/dist/authoring/deck-lint.d.ts +32 -0
- package/dist/authoring/deck-lint.js +567 -0
- package/dist/authoring/index.d.ts +1 -0
- package/dist/authoring/index.js +1 -0
- package/dist/authoring/select-deck-layouts.d.ts +50 -0
- package/dist/authoring/select-deck-layouts.js +213 -0
- package/dist/authoring-apis.d.ts +13 -0
- package/dist/authoring-apis.js +13 -0
- package/dist/authoring-validation.d.ts +65 -0
- package/dist/authoring-validation.js +267 -0
- package/dist/avatar.d.ts +25 -0
- package/dist/avatar.js +30 -0
- package/dist/brand.d.ts +70 -0
- package/dist/brand.js +294 -0
- package/dist/brief/archetypes.d.ts +28 -0
- package/dist/brief/archetypes.js +798 -0
- package/dist/brief/content-synthesize.d.ts +31 -0
- package/dist/brief/content-synthesize.js +210 -0
- package/dist/brief/deck-outlines.d.ts +96 -0
- package/dist/brief/deck-outlines.js +561 -0
- package/dist/brief/draft-bodies.d.ts +29 -0
- package/dist/brief/draft-bodies.js +224 -0
- package/dist/brief/fields.d.ts +19 -0
- package/dist/brief/fields.js +229 -0
- package/dist/brief/index.d.ts +8 -0
- package/dist/brief/index.js +8 -0
- package/dist/brief/kind-detection.d.ts +69 -0
- package/dist/brief/kind-detection.js +353 -0
- package/dist/brief/limits.d.ts +117 -0
- package/dist/brief/limits.js +217 -0
- package/dist/brief/page-doc-outlines.d.ts +80 -0
- package/dist/brief/page-doc-outlines.js +485 -0
- package/dist/brief/synthesize.d.ts +59 -0
- package/dist/brief/synthesize.js +511 -0
- package/dist/brief/text.d.ts +13 -0
- package/dist/brief/text.js +59 -0
- package/dist/canvas-screen.d.ts +31 -0
- package/dist/canvas-screen.js +33 -0
- package/dist/canvas-seed.d.ts +58 -0
- package/dist/canvas-seed.js +82 -0
- package/dist/computed/evaluate.d.ts +11 -0
- package/dist/computed/evaluate.js +101 -0
- package/dist/computed/index.d.ts +4 -0
- package/dist/computed/index.js +3 -0
- package/dist/computed/public.d.ts +14 -0
- package/dist/computed/public.js +19 -0
- package/dist/computed/schema.d.ts +10 -0
- package/dist/computed/schema.js +140 -0
- package/dist/computed/types.d.ts +70 -0
- package/dist/computed/types.js +1 -0
- package/dist/computed/validate.d.ts +6 -0
- package/dist/computed/validate.js +146 -0
- package/dist/csv-guard.d.ts +15 -0
- package/dist/csv-guard.js +29 -0
- package/dist/csv.d.ts +85 -0
- package/dist/csv.js +288 -0
- package/dist/files.d.ts +35 -0
- package/dist/files.js +246 -0
- package/dist/form-schema.d.ts +124 -0
- package/dist/form-schema.js +300 -0
- package/dist/generator-version.d.ts +11 -0
- package/dist/generator-version.js +51 -0
- package/dist/generator.d.ts +34 -0
- package/dist/generator.js +445 -0
- package/dist/identity.d.ts +34 -0
- package/dist/identity.js +91 -0
- package/dist/import/csv-shape.d.ts +40 -0
- package/dist/import/csv-shape.js +265 -0
- package/dist/import/json-shape.d.ts +55 -0
- package/dist/import/json-shape.js +491 -0
- package/dist/import/limits.d.ts +13 -0
- package/dist/import/limits.js +14 -0
- package/dist/import/sqlite-schema.d.ts +36 -0
- package/dist/import/sqlite-schema.js +729 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +58 -0
- package/dist/kinds/anchors.d.ts +18 -0
- package/dist/kinds/anchors.js +46 -0
- package/dist/kinds/bodies.d.ts +6 -0
- package/dist/kinds/bodies.js +32 -0
- package/dist/kinds/brand-runtime.d.ts +19 -0
- package/dist/kinds/brand-runtime.js +35 -0
- package/dist/kinds/deck.d.ts +8 -0
- package/dist/kinds/deck.js +34 -0
- package/dist/kinds/doc.d.ts +19 -0
- package/dist/kinds/doc.js +41 -0
- package/dist/kinds/embed/index.d.ts +39 -0
- package/dist/kinds/embed/index.js +91 -0
- package/dist/kinds/envelope.d.ts +159 -0
- package/dist/kinds/envelope.js +78 -0
- package/dist/kinds/index.d.ts +11 -0
- package/dist/kinds/index.js +11 -0
- package/dist/kinds/kind.d.ts +36 -0
- package/dist/kinds/kind.js +55 -0
- package/dist/kinds/limits.d.ts +64 -0
- package/dist/kinds/limits.js +190 -0
- package/dist/kinds/page-doc-runtime.d.ts +37 -0
- package/dist/kinds/page-doc-runtime.js +55 -0
- package/dist/kinds/page.d.ts +16 -0
- package/dist/kinds/page.js +67 -0
- package/dist/kinds/parse.d.ts +18 -0
- package/dist/kinds/parse.js +27 -0
- package/dist/kinds/runtime.d.ts +128 -0
- package/dist/kinds/runtime.js +206 -0
- package/dist/kinds/services.d.ts +49 -0
- package/dist/kinds/services.js +249 -0
- package/dist/kinds/testing.d.ts +9 -0
- package/dist/kinds/testing.js +16 -0
- package/dist/migrations.d.ts +97 -0
- package/dist/migrations.js +351 -0
- package/dist/motion.d.ts +50 -0
- package/dist/motion.js +52 -0
- package/dist/notifications.d.ts +19 -0
- package/dist/notifications.js +177 -0
- package/dist/patch.d.ts +122 -0
- package/dist/patch.js +322 -0
- package/dist/policy.d.ts +23 -0
- package/dist/policy.js +105 -0
- package/dist/runtime-definition.d.ts +39 -0
- package/dist/runtime-definition.js +74 -0
- package/dist/scenarios.d.ts +9 -0
- package/dist/scenarios.js +49 -0
- package/dist/schema/blueprint.schema.json +10191 -0
- package/dist/schema-json.d.ts +6 -0
- package/dist/schema-json.js +36 -0
- package/dist/schema.d.ts +6348 -0
- package/dist/schema.js +1269 -0
- package/dist/search.d.ts +16 -0
- package/dist/search.js +96 -0
- package/dist/services.d.ts +89 -0
- package/dist/services.js +898 -0
- package/dist/share.d.ts +78 -0
- package/dist/share.js +234 -0
- package/dist/sheet-screen.d.ts +49 -0
- package/dist/sheet-screen.js +91 -0
- package/dist/storage.d.ts +43 -0
- package/dist/storage.js +179 -0
- package/dist/stream.d.ts +26 -0
- package/dist/stream.js +173 -0
- package/dist/untrusted.d.ts +63 -0
- package/dist/untrusted.js +502 -0
- package/dist/validation-tasks.d.ts +178 -0
- package/dist/validation-tasks.js +199 -0
- package/dist/validation.d.ts +35 -0
- package/dist/validation.js +312 -0
- package/dist/variants.d.ts +112 -0
- package/dist/variants.js +283 -0
- package/dist/workflows/index.d.ts +3 -0
- package/dist/workflows/index.js +3 -0
- package/dist/workflows/schema.d.ts +50 -0
- package/dist/workflows/schema.js +34 -0
- package/dist/workflows/transition.d.ts +15 -0
- package/dist/workflows/transition.js +25 -0
- package/dist/workflows/validate.d.ts +17 -0
- package/dist/workflows/validate.js +140 -0
- package/package.json +120 -3
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic deck outline templates (ADR-036, ADR-038): pitch, QBR,
|
|
3
|
+
* launch, status, training, talk, report and lesson. A brief picks a template
|
|
4
|
+
* by whole-word cues (or an explicit `outline`) and an optional slide count
|
|
5
|
+
* ("12 slides", "a 10-minute talk"); the result is a deck body of free slides
|
|
6
|
+
* built from `@protoyard/blocks` blocks, plus a per-slide plan (role, pacing,
|
|
7
|
+
* purpose) that layout selection reads.
|
|
8
|
+
*
|
|
9
|
+
* Honest scaffolding, never research: every slide body is a placeholder marked
|
|
10
|
+
* `*Draft:*` that says what to write. No facts, numbers, quotes, names or
|
|
11
|
+
* chart data are invented; metrics show "—", charts are a note asking for
|
|
12
|
+
* real data, quizzes and polls hold placeholder questions.
|
|
13
|
+
*
|
|
14
|
+
* Pacing follows the developer's keynote method: punch (≤ 8 s, one line),
|
|
15
|
+
* idea (≤ 25 s, one concept), diagram (a structure to walk through) and story
|
|
16
|
+
* (an example told in sequence).
|
|
17
|
+
*/
|
|
18
|
+
import { hasPhrase, normalize } from "./text.js";
|
|
19
|
+
export const DECK_OUTLINE_IDS = [
|
|
20
|
+
'pitch',
|
|
21
|
+
'qbr',
|
|
22
|
+
'launch',
|
|
23
|
+
'status',
|
|
24
|
+
'training',
|
|
25
|
+
'lesson',
|
|
26
|
+
'report',
|
|
27
|
+
'talk',
|
|
28
|
+
];
|
|
29
|
+
/** Caps for synthesized outlines (far inside the deck schema's own limits). */
|
|
30
|
+
export const DECK_OUTLINE_LIMITS = Object.freeze({
|
|
31
|
+
/** No template has more slides than this. */
|
|
32
|
+
maxSlides: 16,
|
|
33
|
+
/** Title text taken from the brief. */
|
|
34
|
+
titleLength: 80,
|
|
35
|
+
/** Characters of the brief read when picking a template. */
|
|
36
|
+
briefLength: 5_000,
|
|
37
|
+
});
|
|
38
|
+
const s = (key, role, pacing, title, body, purpose, core = true) => ({ key, role, pacing, title, body, purpose, core });
|
|
39
|
+
const TITLE = s('title', 'title', 'punch', '', 'title', 'Open with the title and who is presenting.');
|
|
40
|
+
export const DECK_OUTLINES = [
|
|
41
|
+
{
|
|
42
|
+
id: 'pitch',
|
|
43
|
+
label: 'Pitch',
|
|
44
|
+
cues: [
|
|
45
|
+
'pitch',
|
|
46
|
+
'pitch deck',
|
|
47
|
+
'investor',
|
|
48
|
+
'investors',
|
|
49
|
+
'fundraising',
|
|
50
|
+
'fundraise',
|
|
51
|
+
'seed round',
|
|
52
|
+
'series a',
|
|
53
|
+
'raise',
|
|
54
|
+
],
|
|
55
|
+
slides: [
|
|
56
|
+
TITLE,
|
|
57
|
+
s('problem', 'problem', 'story', 'The problem', 'statement', 'State the problem in one sentence: who has it, and how often.'),
|
|
58
|
+
s('solution', 'solution', 'idea', 'Our solution', 'bullets', 'Explain how the product solves the problem, in three points.'),
|
|
59
|
+
s('why-now', 'why-now', 'idea', 'Why now', 'bullets', 'Name the change that makes this possible or urgent today.', false),
|
|
60
|
+
s('product', 'demo', 'story', 'How it works', 'steps', 'Walk through the product in three steps a customer takes.', false),
|
|
61
|
+
s('traction', 'metrics', 'punch', 'Traction', 'metrics', 'Show the few numbers that prove demand. Cite a source for each.'),
|
|
62
|
+
s('model', 'business-model', 'idea', 'Business model', 'bullets', 'Explain who pays, how much, and why they keep paying.', false),
|
|
63
|
+
s('competition', 'comparison', 'diagram', 'Competition', 'comparison', 'Compare your approach with the main alternatives, honestly.', false),
|
|
64
|
+
s('team', 'team', 'idea', 'Team', 'people', 'Introduce the people and why they can build this.'),
|
|
65
|
+
s('roadmap', 'roadmap', 'diagram', 'Roadmap', 'timeline', 'Show the next milestones and what each one proves.', false),
|
|
66
|
+
s('ask', 'ask', 'punch', 'The ask', 'statement', 'Say exactly what you are asking for and what it unlocks.'),
|
|
67
|
+
s('close', 'close', 'punch', 'Thank you', 'close', 'Close with how to follow up.'),
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'qbr',
|
|
72
|
+
label: 'Quarterly business review',
|
|
73
|
+
cues: [
|
|
74
|
+
'qbr',
|
|
75
|
+
'quarterly business review',
|
|
76
|
+
'quarterly review',
|
|
77
|
+
'business review',
|
|
78
|
+
'quarter in review',
|
|
79
|
+
'quarterly',
|
|
80
|
+
],
|
|
81
|
+
slides: [
|
|
82
|
+
TITLE,
|
|
83
|
+
s('agenda', 'agenda', 'idea', 'Agenda', 'agenda', 'List what the review covers.'),
|
|
84
|
+
s('highlights', 'metrics', 'punch', 'Quarter highlights', 'metrics', 'Show the headline numbers for the quarter, with sources.'),
|
|
85
|
+
s('trend', 'chart', 'diagram', 'Trend', 'chart-note', 'Chart the key metric over the quarter from your own data.', false),
|
|
86
|
+
s('wins', 'wins', 'idea', 'Wins', 'checklist', 'List what went well and why.'),
|
|
87
|
+
s('misses', 'misses', 'idea', 'Misses', 'bullets', 'List what fell short and what you learned.'),
|
|
88
|
+
s('customer', 'quote', 'story', 'Customer voice', 'quote', 'Share a quote you have permission to use.', false),
|
|
89
|
+
s('risks', 'risks', 'idea', 'Risks', 'risk', 'Name the risks for next quarter and how you will handle them.', false),
|
|
90
|
+
s('next', 'plan', 'diagram', 'Next quarter', 'steps', 'Lay out the priorities for next quarter.'),
|
|
91
|
+
s('asks', 'ask', 'punch', 'What we need', 'statement', 'Ask for the decisions or support you need.', false),
|
|
92
|
+
s('close', 'close', 'punch', 'Questions', 'close', 'Open the floor and say how to follow up.'),
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'launch',
|
|
97
|
+
label: 'Launch',
|
|
98
|
+
cues: [
|
|
99
|
+
'launch',
|
|
100
|
+
'launching',
|
|
101
|
+
'product launch',
|
|
102
|
+
'go to market',
|
|
103
|
+
'gtm',
|
|
104
|
+
'announcement',
|
|
105
|
+
'announce',
|
|
106
|
+
'unveil',
|
|
107
|
+
'release',
|
|
108
|
+
],
|
|
109
|
+
slides: [
|
|
110
|
+
TITLE,
|
|
111
|
+
s('why', 'problem', 'story', 'Why we built it', 'statement', 'Tell the need that led to this launch.'),
|
|
112
|
+
s('what', 'solution', 'idea', 'What is new', 'cards', 'Introduce the main capabilities, one card each.'),
|
|
113
|
+
s('how', 'demo', 'diagram', 'How it works', 'steps', 'Walk through the first use in three steps.'),
|
|
114
|
+
s('who', 'audience', 'idea', 'Who it is for', 'bullets', 'Describe who benefits most.', false),
|
|
115
|
+
s('compare', 'comparison', 'diagram', 'Before and after', 'comparison', 'Compare the old way with the new one.', false),
|
|
116
|
+
s('availability', 'roadmap', 'diagram', 'Availability', 'timeline', 'Say when and where it is available, phase by phase.'),
|
|
117
|
+
s('pricing', 'pricing', 'idea', 'Pricing', 'bullets', 'Summarize plans and prices once they are final.', false),
|
|
118
|
+
s('close', 'close', 'punch', 'Try it', 'close', 'Tell people how to get started.'),
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'status',
|
|
123
|
+
label: 'Status update',
|
|
124
|
+
cues: [
|
|
125
|
+
'status update',
|
|
126
|
+
'status report',
|
|
127
|
+
'weekly update',
|
|
128
|
+
'project update',
|
|
129
|
+
'progress update',
|
|
130
|
+
'progress report',
|
|
131
|
+
'sprint review',
|
|
132
|
+
'standup',
|
|
133
|
+
'status',
|
|
134
|
+
],
|
|
135
|
+
slides: [
|
|
136
|
+
TITLE,
|
|
137
|
+
s('summary', 'metrics', 'punch', 'Summary', 'metrics', 'Give the overall status and the few numbers behind it.'),
|
|
138
|
+
s('progress', 'progress', 'diagram', 'Progress', 'timeline', 'Show where the work stands against the plan.'),
|
|
139
|
+
s('done', 'wins', 'idea', 'Done', 'checklist', 'List what was completed since the last update.'),
|
|
140
|
+
s('next', 'plan', 'idea', 'Next', 'bullets', 'List what happens before the next update.'),
|
|
141
|
+
s('risks', 'risks', 'idea', 'Risks and blockers', 'risk', 'Name each blocker, its owner and the help you need.'),
|
|
142
|
+
s('decisions', 'ask', 'punch', 'Decisions needed', 'statement', 'Ask for the decisions you need, with a deadline.', false),
|
|
143
|
+
s('close', 'close', 'punch', 'Questions', 'close', 'Say when the next update is.', false),
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'training',
|
|
148
|
+
label: 'Training',
|
|
149
|
+
cues: [
|
|
150
|
+
'training',
|
|
151
|
+
'onboarding',
|
|
152
|
+
'workshop',
|
|
153
|
+
'tutorial',
|
|
154
|
+
'enablement',
|
|
155
|
+
'how to',
|
|
156
|
+
'course',
|
|
157
|
+
'bootcamp',
|
|
158
|
+
],
|
|
159
|
+
slides: [
|
|
160
|
+
TITLE,
|
|
161
|
+
s('objectives', 'objectives', 'idea', 'What you will learn', 'checklist', 'List three things people can do after this session.'),
|
|
162
|
+
s('agenda', 'agenda', 'idea', 'Agenda', 'agenda', 'List the parts of the session.'),
|
|
163
|
+
s('concept', 'concept', 'idea', 'Key idea', 'statement', 'Explain the main concept in plain words.'),
|
|
164
|
+
s('walkthrough', 'demo', 'diagram', 'Step by step', 'steps', 'Show the procedure step by step.'),
|
|
165
|
+
s('example', 'example', 'story', 'Example', 'statement', 'Work through one realistic, fictional example.', false),
|
|
166
|
+
s('practice', 'quiz', 'idea', 'Check your understanding', 'quiz', 'Ask one question that shows whether the idea landed.'),
|
|
167
|
+
s('mistakes', 'risks', 'idea', 'Common mistakes', 'risk', 'Warn about the mistakes beginners make.', false),
|
|
168
|
+
s('recap', 'recap', 'idea', 'Recap', 'checklist', 'Repeat the objectives as things people can now do.'),
|
|
169
|
+
s('resources', 'resources', 'idea', 'Resources', 'bullets', 'Point to further reading or help.', false),
|
|
170
|
+
s('close', 'close', 'punch', 'Questions', 'close', 'Say where to ask questions later.'),
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'lesson',
|
|
175
|
+
label: 'Lesson',
|
|
176
|
+
cues: [
|
|
177
|
+
'lesson',
|
|
178
|
+
'lecture',
|
|
179
|
+
'class',
|
|
180
|
+
'classroom',
|
|
181
|
+
'students',
|
|
182
|
+
'teach',
|
|
183
|
+
'teaching',
|
|
184
|
+
'curriculum',
|
|
185
|
+
'syllabus',
|
|
186
|
+
],
|
|
187
|
+
slides: [
|
|
188
|
+
TITLE,
|
|
189
|
+
s('objectives', 'objectives', 'idea', 'Learning objectives', 'checklist', 'List what students will be able to do.'),
|
|
190
|
+
s('warm-up', 'poll', 'punch', 'Warm-up', 'poll', 'Ask a quick question to see what the class already knows.', false),
|
|
191
|
+
s('concept', 'concept', 'idea', 'Concept', 'statement', 'Introduce the concept in one clear sentence.'),
|
|
192
|
+
s('example', 'example', 'story', 'Worked example', 'steps', 'Solve one example step by step.'),
|
|
193
|
+
s('check', 'quiz', 'idea', 'Quick check', 'quiz', 'Ask one question to check understanding.'),
|
|
194
|
+
s('activity', 'activity', 'diagram', 'Activity', 'steps', 'Describe a short activity students do now.', false),
|
|
195
|
+
s('recap', 'recap', 'idea', 'Recap', 'checklist', 'Summarize what was learned.'),
|
|
196
|
+
s('homework', 'homework', 'idea', 'Practice at home', 'bullets', 'Give the follow-up practice.', false),
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'report',
|
|
201
|
+
label: 'Report',
|
|
202
|
+
cues: [
|
|
203
|
+
'report',
|
|
204
|
+
'findings',
|
|
205
|
+
'results',
|
|
206
|
+
'analysis',
|
|
207
|
+
'research',
|
|
208
|
+
'survey results',
|
|
209
|
+
'annual report',
|
|
210
|
+
'retrospective',
|
|
211
|
+
'retro',
|
|
212
|
+
'postmortem',
|
|
213
|
+
],
|
|
214
|
+
slides: [
|
|
215
|
+
TITLE,
|
|
216
|
+
s('summary', 'metrics', 'punch', 'Key findings', 'metrics', 'Lead with the headline results and their sources.'),
|
|
217
|
+
s('context', 'context', 'idea', 'Context', 'statement', 'Say what was studied and why.'),
|
|
218
|
+
s('method', 'method', 'diagram', 'Method', 'steps', 'Describe how the data was gathered.', false),
|
|
219
|
+
s('findings', 'findings', 'idea', 'Findings', 'cards', 'Present each finding on its own card.'),
|
|
220
|
+
s('evidence', 'chart', 'diagram', 'Evidence', 'chart-note', 'Chart the strongest finding from your own data.', false),
|
|
221
|
+
s('recommendations', 'recommendations', 'idea', 'Recommendations', 'checklist', 'Turn the findings into recommendations.'),
|
|
222
|
+
s('next', 'plan', 'diagram', 'Next steps', 'timeline', 'Lay out who does what, and when.'),
|
|
223
|
+
s('limits', 'risks', 'idea', 'Limitations', 'risk', 'State what the data cannot tell you.', false),
|
|
224
|
+
s('close', 'close', 'punch', 'Questions', 'close', 'Say where to find the full report.', false),
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: 'talk',
|
|
229
|
+
label: 'Talk',
|
|
230
|
+
cues: [
|
|
231
|
+
'talk',
|
|
232
|
+
'keynote',
|
|
233
|
+
'conference',
|
|
234
|
+
'meetup',
|
|
235
|
+
'speech',
|
|
236
|
+
'presentation',
|
|
237
|
+
'slides',
|
|
238
|
+
'slide deck',
|
|
239
|
+
'deck',
|
|
240
|
+
],
|
|
241
|
+
slides: [
|
|
242
|
+
TITLE,
|
|
243
|
+
s('hook', 'hook', 'punch', 'The hook', 'statement', 'Open with one surprising line that frames the talk.'),
|
|
244
|
+
s('speaker', 'team', 'idea', 'About me', 'people', 'Say who you are and why you are giving this talk.', false),
|
|
245
|
+
s('agenda', 'agenda', 'idea', 'Agenda', 'agenda', 'List the parts of the talk.', false),
|
|
246
|
+
s('idea-1', 'concept', 'idea', 'First idea', 'statement', 'Make the first point, one idea per slide.'),
|
|
247
|
+
s('story', 'story', 'story', 'A story', 'steps', 'Tell an example in three beats.', false),
|
|
248
|
+
s('idea-2', 'concept', 'idea', 'Second idea', 'bullets', 'Make the second point with up to three supports.'),
|
|
249
|
+
s('poll', 'poll', 'punch', 'Quick poll', 'poll', 'Ask the audience a question to keep them involved.', false),
|
|
250
|
+
s('idea-3', 'concept', 'diagram', 'Third idea', 'steps', 'Show the third point as a sequence.', false),
|
|
251
|
+
s('takeaways', 'recap', 'idea', 'Takeaways', 'checklist', 'List the three things to remember.'),
|
|
252
|
+
s('close', 'close', 'punch', 'Thank you', 'close', 'Say how to reach you.'),
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
const BY_ID = new Map(DECK_OUTLINES.map((template) => [template.id, template]));
|
|
257
|
+
// ── brief reading ─────────────────────────────────────────────────────────
|
|
258
|
+
const NUMBER_WORDS = {
|
|
259
|
+
three: 3,
|
|
260
|
+
four: 4,
|
|
261
|
+
five: 5,
|
|
262
|
+
six: 6,
|
|
263
|
+
seven: 7,
|
|
264
|
+
eight: 8,
|
|
265
|
+
nine: 9,
|
|
266
|
+
ten: 10,
|
|
267
|
+
eleven: 11,
|
|
268
|
+
twelve: 12,
|
|
269
|
+
thirteen: 13,
|
|
270
|
+
fourteen: 14,
|
|
271
|
+
fifteen: 15,
|
|
272
|
+
sixteen: 16,
|
|
273
|
+
twenty: 20,
|
|
274
|
+
thirty: 30,
|
|
275
|
+
};
|
|
276
|
+
/** A slide count the brief asks for: "12 slides", "ten-slide", "a 15-minute talk" (one slide a minute). */
|
|
277
|
+
export function requestedSlideCount(brief) {
|
|
278
|
+
const text = brief.slice(0, DECK_OUTLINE_LIMITS.briefLength).toLowerCase();
|
|
279
|
+
const count = /\b(\d{1,3}|[a-z]+)[\s-]*slides?\b/.exec(text);
|
|
280
|
+
if (count) {
|
|
281
|
+
const value = Number.isNaN(Number(count[1]))
|
|
282
|
+
? NUMBER_WORDS[count[1]]
|
|
283
|
+
: Number(count[1]);
|
|
284
|
+
if (value !== undefined && value > 0)
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
287
|
+
const minutes = /\b(\d{1,3}|[a-z]+)[\s-]*(?:minutes?|min)\b/.exec(text);
|
|
288
|
+
if (minutes) {
|
|
289
|
+
const value = Number.isNaN(Number(minutes[1]))
|
|
290
|
+
? NUMBER_WORDS[minutes[1]]
|
|
291
|
+
: Number(minutes[1]);
|
|
292
|
+
if (value !== undefined && value > 0)
|
|
293
|
+
return value;
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
/** The template a brief asks for, with the cues that matched (longest phrase wins ties). */
|
|
298
|
+
export function chooseDeckOutline(brief) {
|
|
299
|
+
const normalized = normalize(brief.slice(0, DECK_OUTLINE_LIMITS.briefLength));
|
|
300
|
+
let best = null;
|
|
301
|
+
for (const template of DECK_OUTLINES) {
|
|
302
|
+
const cues = template.cues.filter((cue) => hasPhrase(normalized, cue));
|
|
303
|
+
// Multi-word cues count more: "status update" beats "update", "pitch deck" beats "deck".
|
|
304
|
+
const score = cues.reduce((sum, cue) => sum + cue.split(' ').length, 0);
|
|
305
|
+
if (score > 0 && (best === null || score > best.score))
|
|
306
|
+
best = { id: template.id, cues, score };
|
|
307
|
+
}
|
|
308
|
+
return best ? { id: best.id, cues: best.cues } : { id: 'talk', cues: [] };
|
|
309
|
+
}
|
|
310
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point.
|
|
311
|
+
const CONTROL = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;
|
|
312
|
+
/** A deck title from the brief: the topic after "about/on/for", else the first sentence. */
|
|
313
|
+
export function deckTitleFromBrief(brief, fallback = 'Untitled deck') {
|
|
314
|
+
const text = brief
|
|
315
|
+
.slice(0, DECK_OUTLINE_LIMITS.briefLength)
|
|
316
|
+
.replace(CONTROL, ' ')
|
|
317
|
+
.replace(/\s+/g, ' ')
|
|
318
|
+
.trim();
|
|
319
|
+
const sentence = text.split(/(?<=[.!?])\s/)[0] ?? '';
|
|
320
|
+
const topic = /\b(?:about|on|covering|for)\s+(.{3,}?)\s*[.!?]?$/i.exec(sentence)?.[1];
|
|
321
|
+
const raw = (topic ?? sentence).replace(/[.!?]+$/, '').trim();
|
|
322
|
+
if (!raw)
|
|
323
|
+
return fallback;
|
|
324
|
+
const title = raw.charAt(0).toUpperCase() + raw.slice(1);
|
|
325
|
+
if (title.length <= DECK_OUTLINE_LIMITS.titleLength)
|
|
326
|
+
return title;
|
|
327
|
+
const cut = title.slice(0, DECK_OUTLINE_LIMITS.titleLength - 1);
|
|
328
|
+
return `${cut.slice(0, cut.lastIndexOf(' ') > 40 ? cut.lastIndexOf(' ') : cut.length).trimEnd()}…`;
|
|
329
|
+
}
|
|
330
|
+
const draft = (text) => `*Draft:* ${text}`;
|
|
331
|
+
const heading = (text, level = 2) => ({ type: 'heading', text, level });
|
|
332
|
+
function bodyBlocks(slide, deckTitle, agenda) {
|
|
333
|
+
const id = (suffix) => `${slide.key}-${suffix}`;
|
|
334
|
+
const items = (count, style) => ({
|
|
335
|
+
type: 'list',
|
|
336
|
+
...(style ? { style } : {}),
|
|
337
|
+
items: Array.from({ length: count }, (_, i) => ({ text: draft(`point ${i + 1}.`) })),
|
|
338
|
+
});
|
|
339
|
+
switch (slide.body) {
|
|
340
|
+
case 'title':
|
|
341
|
+
return [
|
|
342
|
+
{ type: 'heading', text: deckTitle, level: 1 },
|
|
343
|
+
{ type: 'text', markdown: draft('presenter name and date.'), size: 'lg' },
|
|
344
|
+
];
|
|
345
|
+
case 'agenda':
|
|
346
|
+
return [
|
|
347
|
+
heading(slide.title),
|
|
348
|
+
{ type: 'list', style: 'number', items: agenda.map((text) => ({ text })) },
|
|
349
|
+
];
|
|
350
|
+
case 'statement':
|
|
351
|
+
return [heading(slide.title), { type: 'text', markdown: draft(slide.purpose), size: 'lg' }];
|
|
352
|
+
case 'bullets':
|
|
353
|
+
return [heading(slide.title), { type: 'text', markdown: draft(slide.purpose) }, items(3)];
|
|
354
|
+
case 'checklist':
|
|
355
|
+
return [
|
|
356
|
+
heading(slide.title),
|
|
357
|
+
{ type: 'text', markdown: draft(slide.purpose) },
|
|
358
|
+
items(3, 'check'),
|
|
359
|
+
];
|
|
360
|
+
case 'metrics':
|
|
361
|
+
return [
|
|
362
|
+
heading(slide.title),
|
|
363
|
+
{
|
|
364
|
+
type: 'stat',
|
|
365
|
+
items: [1, 2, 3].map((n) => ({
|
|
366
|
+
value: '—',
|
|
367
|
+
label: `Metric ${n}`,
|
|
368
|
+
context: 'Draft: add a sourced value.',
|
|
369
|
+
})),
|
|
370
|
+
source: 'Draft: cite where each number comes from.',
|
|
371
|
+
},
|
|
372
|
+
];
|
|
373
|
+
case 'chart-note':
|
|
374
|
+
return [
|
|
375
|
+
heading(slide.title),
|
|
376
|
+
{
|
|
377
|
+
type: 'callout',
|
|
378
|
+
tone: 'info',
|
|
379
|
+
title: 'Add your data',
|
|
380
|
+
body: draft(`${slide.purpose} Replace this note with a chart block; no data was generated.`),
|
|
381
|
+
},
|
|
382
|
+
];
|
|
383
|
+
case 'steps':
|
|
384
|
+
return [
|
|
385
|
+
heading(slide.title),
|
|
386
|
+
{
|
|
387
|
+
type: 'steps',
|
|
388
|
+
items: [1, 2, 3].map((n) => ({ title: `Step ${n}`, body: draft('describe this step.') })),
|
|
389
|
+
},
|
|
390
|
+
];
|
|
391
|
+
case 'timeline':
|
|
392
|
+
return [
|
|
393
|
+
heading(slide.title),
|
|
394
|
+
{
|
|
395
|
+
type: 'timeline',
|
|
396
|
+
events: [1, 2, 3].map((n) => ({
|
|
397
|
+
time: `Phase ${n}`,
|
|
398
|
+
title: `Milestone ${n}`,
|
|
399
|
+
body: draft('add the date and outcome.'),
|
|
400
|
+
})),
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
case 'comparison':
|
|
404
|
+
return [
|
|
405
|
+
heading(slide.title),
|
|
406
|
+
{ type: 'text', markdown: draft(slide.purpose) },
|
|
407
|
+
{
|
|
408
|
+
type: 'comparison',
|
|
409
|
+
columns: [{ label: 'Our approach', highlight: true }, { label: 'Alternative' }],
|
|
410
|
+
rows: [1, 2, 3].map((n) => ({ label: `Criterion ${n}`, values: ['—', '—'] })),
|
|
411
|
+
},
|
|
412
|
+
];
|
|
413
|
+
case 'cards':
|
|
414
|
+
return [
|
|
415
|
+
heading(slide.title),
|
|
416
|
+
{
|
|
417
|
+
type: 'cards',
|
|
418
|
+
columns: 3,
|
|
419
|
+
items: [1, 2, 3].map((n) => ({
|
|
420
|
+
title: `Item ${n}`,
|
|
421
|
+
body: draft('one or two sentences.'),
|
|
422
|
+
})),
|
|
423
|
+
},
|
|
424
|
+
];
|
|
425
|
+
case 'quote':
|
|
426
|
+
return [
|
|
427
|
+
heading(slide.title),
|
|
428
|
+
{ type: 'quote', quote: draft('a quote you have permission to use.'), cite: 'Source' },
|
|
429
|
+
];
|
|
430
|
+
case 'people':
|
|
431
|
+
return [
|
|
432
|
+
heading(slide.title),
|
|
433
|
+
{
|
|
434
|
+
type: 'people',
|
|
435
|
+
people: [1, 2, 3].map((n) => ({
|
|
436
|
+
name: `Person ${n}`,
|
|
437
|
+
role: 'Role',
|
|
438
|
+
bio: 'Draft: one line of relevant experience.',
|
|
439
|
+
})),
|
|
440
|
+
},
|
|
441
|
+
];
|
|
442
|
+
case 'risk':
|
|
443
|
+
return [
|
|
444
|
+
heading(slide.title),
|
|
445
|
+
{ type: 'callout', tone: 'warning', title: 'Draft', body: draft(slide.purpose) },
|
|
446
|
+
items(2),
|
|
447
|
+
];
|
|
448
|
+
case 'poll':
|
|
449
|
+
return [
|
|
450
|
+
heading(slide.title),
|
|
451
|
+
{
|
|
452
|
+
type: 'poll',
|
|
453
|
+
id: id('poll'),
|
|
454
|
+
question: 'Draft: replace with your question',
|
|
455
|
+
options: [
|
|
456
|
+
{ id: 'option-a', label: 'Option A' },
|
|
457
|
+
{ id: 'option-b', label: 'Option B' },
|
|
458
|
+
{ id: 'option-c', label: 'Option C' },
|
|
459
|
+
],
|
|
460
|
+
},
|
|
461
|
+
];
|
|
462
|
+
case 'quiz':
|
|
463
|
+
return [
|
|
464
|
+
heading(slide.title),
|
|
465
|
+
{
|
|
466
|
+
type: 'quiz',
|
|
467
|
+
id: id('quiz'),
|
|
468
|
+
questions: [
|
|
469
|
+
{
|
|
470
|
+
id: 'question-1',
|
|
471
|
+
prompt: draft('replace with a question that checks the key idea.'),
|
|
472
|
+
choices: [
|
|
473
|
+
{ id: 'choice-a', label: 'Draft: correct answer' },
|
|
474
|
+
{ id: 'choice-b', label: 'Draft: plausible wrong answer' },
|
|
475
|
+
{ id: 'choice-c', label: 'Draft: another wrong answer' },
|
|
476
|
+
],
|
|
477
|
+
answers: ['choice-a'],
|
|
478
|
+
explanation: draft('explain why the answer is right.'),
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
];
|
|
483
|
+
case 'close':
|
|
484
|
+
return [heading(slide.title), { type: 'text', markdown: draft(slide.purpose), size: 'lg' }];
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const PACING_NOTES = {
|
|
488
|
+
punch: 'Punch slide: one line, about 8 seconds. Say it and move on.',
|
|
489
|
+
idea: 'Idea slide: one concept, up to about 25 seconds.',
|
|
490
|
+
diagram: 'Diagram slide: walk through the structure in order.',
|
|
491
|
+
story: 'Story slide: tell it as a short sequence with a clear point.',
|
|
492
|
+
};
|
|
493
|
+
/** The slide-count range a template supports: its core slides up to all slides. */
|
|
494
|
+
export function deckOutlineRange(id) {
|
|
495
|
+
const template = BY_ID.get(id);
|
|
496
|
+
return { min: template.slides.filter((slide) => slide.core).length, max: template.slides.length };
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Builds a draft deck outline from a brief. Pure and deterministic: the same
|
|
500
|
+
* brief and options give the same slide ids, order, blocks and rationale.
|
|
501
|
+
*/
|
|
502
|
+
export function synthesizeDeckOutline(brief, options = {}) {
|
|
503
|
+
const chosen = options.outline ? { id: options.outline, cues: [] } : chooseDeckOutline(brief);
|
|
504
|
+
const template = BY_ID.get(chosen.id);
|
|
505
|
+
const range = deckOutlineRange(template.id);
|
|
506
|
+
const asked = options.slides ?? requestedSlideCount(brief);
|
|
507
|
+
const target = asked === null ? range.min : Math.min(range.max, Math.max(range.min, Math.floor(asked)));
|
|
508
|
+
// Core slides always; optional slides in template order until the target is met.
|
|
509
|
+
let optionalLeft = target - range.min;
|
|
510
|
+
const slides = template.slides.filter((slide) => {
|
|
511
|
+
if (slide.core)
|
|
512
|
+
return true;
|
|
513
|
+
if (optionalLeft <= 0)
|
|
514
|
+
return false;
|
|
515
|
+
optionalLeft -= 1;
|
|
516
|
+
return true;
|
|
517
|
+
});
|
|
518
|
+
const title = (options.title?.trim() || deckTitleFromBrief(brief, `${template.label} deck`)).slice(0, DECK_OUTLINE_LIMITS.titleLength);
|
|
519
|
+
const agenda = slides
|
|
520
|
+
.filter((slide) => !['title', 'agenda', 'close'].includes(slide.role))
|
|
521
|
+
.map((slide) => slide.title)
|
|
522
|
+
.slice(0, 6);
|
|
523
|
+
const rationale = [
|
|
524
|
+
options.outline
|
|
525
|
+
? `Outline: ${template.label} (chosen explicitly).`
|
|
526
|
+
: chosen.cues.length > 0
|
|
527
|
+
? `Outline: ${template.label} (matched ${chosen.cues.map((cue) => `"${cue}"`).join(', ')}).`
|
|
528
|
+
: `Outline: ${template.label} (no outline cue in the brief; the general talk outline fits any topic).`,
|
|
529
|
+
asked === null
|
|
530
|
+
? `Slides: ${slides.length}, the core of the ${template.label.toLowerCase()} outline (it supports ${range.min}–${range.max}).`
|
|
531
|
+
: asked === slides.length
|
|
532
|
+
? `Slides: ${slides.length}, as asked.`
|
|
533
|
+
: `Slides: ${slides.length}; asked for ${asked}, and this outline supports ${range.min}–${range.max}.`,
|
|
534
|
+
'Draft scaffolding: every slide body is a placeholder marked "Draft:". No facts, numbers, quotes, names or chart data were invented; replace them with sourced content.',
|
|
535
|
+
'Slides use the free layout (blocks); each slide plan carries a role and pacing that layout selection can map to catalog layouts.',
|
|
536
|
+
];
|
|
537
|
+
return {
|
|
538
|
+
outline: template.id,
|
|
539
|
+
label: template.label,
|
|
540
|
+
requestedSlides: asked,
|
|
541
|
+
rationale,
|
|
542
|
+
plan: slides.map((slide) => ({
|
|
543
|
+
id: `slide-${slide.key}`,
|
|
544
|
+
role: slide.role,
|
|
545
|
+
pacing: slide.pacing,
|
|
546
|
+
purpose: slide.purpose,
|
|
547
|
+
core: slide.core,
|
|
548
|
+
})),
|
|
549
|
+
deck: {
|
|
550
|
+
title,
|
|
551
|
+
aspect: '16:9',
|
|
552
|
+
slides: slides.map((slide) => ({
|
|
553
|
+
id: `slide-${slide.key}`,
|
|
554
|
+
title: slide.body === 'title' ? title : slide.title,
|
|
555
|
+
layout: 'free',
|
|
556
|
+
blocks: bodyBlocks(slide, title, agenda),
|
|
557
|
+
notes: `${slide.purpose} ${PACING_NOTES[slide.pacing]}`,
|
|
558
|
+
})),
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn outline draft blocks into page/doc body payloads that pass the wired
|
|
3
|
+
* `@protoyard/pages` schemas (and therefore `safeParseAnyBlueprint`).
|
|
4
|
+
*/
|
|
5
|
+
import type { OutlineBlock, OutlineSection } from './page-doc-outlines.ts';
|
|
6
|
+
/** Lowercase kebab id for document/section anchors (BlockIdSchema). */
|
|
7
|
+
export declare function draftSlugId(value: string, fallback: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Convert outline draft blocks into strict block objects the pages schema accepts.
|
|
10
|
+
* `defaultHref` is a resolved in-document fragment (`#section-id`) for CTA actions.
|
|
11
|
+
*/
|
|
12
|
+
export declare function outlineBlocksToSchema(blocks: readonly OutlineBlock[], defaultHref?: string): Record<string, unknown>[];
|
|
13
|
+
/** Page body: id + title + sections of valid blocks (no draft-only keys). */
|
|
14
|
+
export declare function pageBodyFromOutline(input: {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
sections: readonly OutlineSection[];
|
|
19
|
+
}): Record<string, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* Doc body: continuous block flow. Section titles become headings with stable ids
|
|
22
|
+
* so a TOC has entries and fragment targets stay unique.
|
|
23
|
+
*/
|
|
24
|
+
export declare function docBodyFromOutline(input: {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
sections: readonly OutlineSection[];
|
|
29
|
+
}): Record<string, unknown>;
|