create-pathfinder 4.2.0 → 4.3.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 (39) hide show
  1. package/CLAUDE.md +2 -0
  2. package/package.json +1 -1
  3. package/skills/learn-codebase/SKILL.md +188 -17
  4. package/skills/learn-feature/SKILL.md +136 -15
  5. package/skills/map-system/SKILL.md +293 -0
  6. package/skills/render-artifact/SKILL.md +187 -0
  7. package/skills/render-artifact/engine/bin/render.mjs +225 -0
  8. package/skills/render-artifact/engine/deliver.mjs +197 -0
  9. package/skills/render-artifact/engine/doctor.mjs +96 -0
  10. package/skills/render-artifact/engine/examples/diagram.json +223 -0
  11. package/skills/render-artifact/engine/examples/lesson.json +242 -0
  12. package/skills/render-artifact/engine/references/determinism.md +71 -0
  13. package/skills/render-artifact/engine/references/specification.md +149 -0
  14. package/skills/render-artifact/engine/references/validation.md +268 -0
  15. package/skills/render-artifact/engine/render/behavior.mjs +128 -0
  16. package/skills/render-artifact/engine/render/diagram.mjs +342 -0
  17. package/skills/render-artifact/engine/render/escape.mjs +34 -0
  18. package/skills/render-artifact/engine/render/graph/behavior.mjs +394 -0
  19. package/skills/render-artifact/engine/render/graph/draw.mjs +204 -0
  20. package/skills/render-artifact/engine/render/graph/interaction.mjs +174 -0
  21. package/skills/render-artifact/engine/render/graph/layout.mjs +698 -0
  22. package/skills/render-artifact/engine/render/graph/style.mjs +200 -0
  23. package/skills/render-artifact/engine/render/graph/width.mjs +204 -0
  24. package/skills/render-artifact/engine/render/index.mjs +50 -0
  25. package/skills/render-artifact/engine/render/lesson.mjs +294 -0
  26. package/skills/render-artifact/engine/render/shell.mjs +275 -0
  27. package/skills/render-artifact/engine/render/theme.mjs +592 -0
  28. package/skills/render-artifact/engine/schemas/common.schema.json +101 -0
  29. package/skills/render-artifact/engine/schemas/diagram.schema.json +176 -0
  30. package/skills/render-artifact/engine/schemas/lesson.schema.json +210 -0
  31. package/skills/render-artifact/engine/validate/composition.mjs +395 -0
  32. package/skills/render-artifact/engine/validate/diagnostics.mjs +83 -0
  33. package/skills/render-artifact/engine/validate/diagram-parts.mjs +68 -0
  34. package/skills/render-artifact/engine/validate/evidence.mjs +302 -0
  35. package/skills/render-artifact/engine/validate/index.mjs +132 -0
  36. package/skills/render-artifact/engine/validate/jsonschema.mjs +312 -0
  37. package/skills/render-artifact/engine/validate/structural.mjs +241 -0
  38. package/skills/render-artifact/engine/verification.mjs +76 -0
  39. package/skills/render-artifact/engine/version.mjs +24 -0
@@ -0,0 +1,592 @@
1
+ /**
2
+ * The Pathfinder artifact identity: typography, spacing, colour, and chrome.
3
+ *
4
+ * This lives here, once, and every artifact kind inherits it by rendering into
5
+ * the shell. Adding a kind must never mean restating, forking, or overriding
6
+ * any of it — a kind supplies sections, not styling, and a producer supplies
7
+ * neither.
8
+ *
9
+ * Colour is the project's own: blaze orange #E0611F, the paint on a real trail
10
+ * marker, and bearing grey alongside it. Every pairing below was measured, not
11
+ * eyeballed:
12
+ *
13
+ * light ink on page 17.25:1 muted 7.14:1 link 5.25:1 accent 3.47:1
14
+ * dark ink on page 16.19:1 muted 7.89:1 link 9.51:1 accent 5.25:1
15
+ *
16
+ * WCAG 2.1 AA wants 4.5:1 for body text and 3:1 for interface boundaries and
17
+ * focus indicators. Read the accent row again: 3.47:1 on the light page clears
18
+ * the non-text bar and misses the text one. So `--pf-accent` is for the mark,
19
+ * the focus ring, rules and fills — never for words. Words in accent colour
20
+ * take `--pf-link`, which is the same hue darkened until it passes.
21
+ *
22
+ * Filled accent surfaces carry `--pf-accent-ink` rather than white for the same
23
+ * arithmetic: white on blaze orange is 3.56:1, near-black is 4.97:1.
24
+ *
25
+ * No web font, no network request, no build step. The stack is whatever the
26
+ * reader's system already has, so the artifact opens from `file://` and looks
27
+ * the same tomorrow.
28
+ */
29
+
30
+ export const THEME_CSS = `
31
+ :root {
32
+ color-scheme: light;
33
+ --pf-page: #FDFCFB;
34
+ --pf-surface: #FFFFFF;
35
+ --pf-surface-2: #F5F2EF;
36
+ --pf-ink: #1B1815;
37
+ --pf-muted: #5C5550;
38
+ --pf-line: #E2DCD6;
39
+ --pf-line-strong: #8C837A;
40
+ --pf-accent: #E0611F;
41
+ --pf-accent-ink: #1B1815;
42
+ --pf-link: #B34A13;
43
+ --pf-ok: #1F6F43;
44
+ --pf-no: #A62B1E;
45
+ --pf-shadow: 0 1px 2px rgba(27, 24, 21, .06), 0 8px 24px rgba(27, 24, 21, .05);
46
+
47
+ --pf-space-1: .25rem;
48
+ --pf-space-2: .5rem;
49
+ --pf-space-3: .75rem;
50
+ --pf-space-4: 1rem;
51
+ --pf-space-5: 1.5rem;
52
+ --pf-space-6: 2rem;
53
+ --pf-space-7: 3rem;
54
+ --pf-space-8: 4rem;
55
+
56
+ --pf-radius: 10px;
57
+ --pf-radius-sm: 6px;
58
+ --pf-measure: 68ch;
59
+
60
+ --pf-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
61
+ "Helvetica Neue", Arial, sans-serif;
62
+ --pf-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
63
+ "Liberation Mono", monospace;
64
+ }
65
+
66
+ /* Light is the base, so a reader whose system states no preference gets a
67
+ readable page rather than whichever branch happened to be written first.
68
+ "auto" follows the system; "light" and "dark" are the reader overriding it,
69
+ and both win over the media query because they are on the element. */
70
+ :root[data-pf-theme="dark"] {
71
+ color-scheme: dark;
72
+ --pf-page: #14120F;
73
+ --pf-surface: #1D1A16;
74
+ --pf-surface-2: #262218;
75
+ --pf-ink: #F2EEE9;
76
+ --pf-muted: #B0A79E;
77
+ --pf-line: #35302A;
78
+ --pf-line-strong: #7A7168;
79
+ --pf-accent: #E0611F;
80
+ --pf-accent-ink: #1B1815;
81
+ --pf-link: #F0A97E;
82
+ --pf-ok: #6FD39B;
83
+ --pf-no: #F09085;
84
+ --pf-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .3);
85
+ }
86
+
87
+ @media (prefers-color-scheme: dark) {
88
+ :root[data-pf-theme="auto"] {
89
+ color-scheme: dark;
90
+ --pf-page: #14120F;
91
+ --pf-surface: #1D1A16;
92
+ --pf-surface-2: #262218;
93
+ --pf-ink: #F2EEE9;
94
+ --pf-muted: #B0A79E;
95
+ --pf-line: #35302A;
96
+ --pf-line-strong: #7A7168;
97
+ --pf-accent: #E0611F;
98
+ --pf-accent-ink: #1B1815;
99
+ --pf-link: #F0A97E;
100
+ --pf-ok: #6FD39B;
101
+ --pf-no: #F09085;
102
+ --pf-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .3);
103
+ }
104
+ }
105
+
106
+ *, *::before, *::after { box-sizing: border-box; }
107
+
108
+ html { -webkit-text-size-adjust: 100%; }
109
+
110
+ body {
111
+ margin: 0;
112
+ background: var(--pf-page);
113
+ color: var(--pf-ink);
114
+ font-family: var(--pf-sans);
115
+ font-size: 16px;
116
+ line-height: 1.65;
117
+ text-rendering: optimizeLegibility;
118
+ }
119
+
120
+ h1, h2, h3, h4 {
121
+ line-height: 1.25;
122
+ letter-spacing: -.011em;
123
+ margin: 0;
124
+ font-weight: 650;
125
+ }
126
+
127
+ p { margin: 0 0 var(--pf-space-4); max-width: var(--pf-measure); }
128
+ p:last-child { margin-bottom: 0; }
129
+
130
+ a { color: var(--pf-link); text-underline-offset: .18em; }
131
+ a:hover { text-decoration-thickness: 2px; }
132
+
133
+ :focus-visible {
134
+ outline: 3px solid var(--pf-accent);
135
+ outline-offset: 2px;
136
+ border-radius: var(--pf-radius-sm);
137
+ }
138
+
139
+ code, pre, kbd { font-family: var(--pf-mono); font-size: .875em; }
140
+
141
+ /* ---------- skip link ---------- */
142
+
143
+ .pf-skip {
144
+ position: absolute;
145
+ left: var(--pf-space-3);
146
+ top: var(--pf-space-3);
147
+ z-index: 40;
148
+ padding: var(--pf-space-2) var(--pf-space-4);
149
+ background: var(--pf-accent);
150
+ color: var(--pf-accent-ink);
151
+ border-radius: var(--pf-radius-sm);
152
+ font-weight: 600;
153
+ transform: translateY(-200%);
154
+ }
155
+ .pf-skip:focus { transform: translateY(0); }
156
+
157
+ /* ---------- header ---------- */
158
+
159
+ .pf-header {
160
+ position: sticky;
161
+ top: 0;
162
+ z-index: 30;
163
+ display: flex;
164
+ align-items: center;
165
+ gap: var(--pf-space-4);
166
+ padding: var(--pf-space-4) var(--pf-space-5);
167
+ background: var(--pf-surface);
168
+ border-bottom: 1px solid var(--pf-line);
169
+ }
170
+
171
+ .pf-mark { flex: none; color: var(--pf-accent); display: block; }
172
+
173
+ .pf-identity { min-width: 0; flex: 1 1 auto; }
174
+ .pf-title {
175
+ font-size: 1.0625rem;
176
+ font-weight: 650;
177
+ letter-spacing: -.01em;
178
+ overflow: hidden;
179
+ text-overflow: ellipsis;
180
+ white-space: nowrap;
181
+ }
182
+ .pf-eyebrow {
183
+ font-size: .6875rem;
184
+ font-weight: 700;
185
+ letter-spacing: .1em;
186
+ text-transform: uppercase;
187
+ color: var(--pf-muted);
188
+ }
189
+
190
+ .pf-theme-toggle {
191
+ flex: none;
192
+ display: inline-flex;
193
+ align-items: center;
194
+ gap: var(--pf-space-2);
195
+ padding: var(--pf-space-2) var(--pf-space-3);
196
+ font: inherit;
197
+ font-size: .8125rem;
198
+ font-weight: 600;
199
+ color: var(--pf-ink);
200
+ background: var(--pf-surface-2);
201
+ border: 1px solid var(--pf-line-strong);
202
+ border-radius: 999px;
203
+ cursor: pointer;
204
+ }
205
+ .pf-theme-toggle:hover { border-color: var(--pf-accent); }
206
+
207
+ /* ---------- layout ---------- */
208
+
209
+ .pf-layout {
210
+ display: grid;
211
+ grid-template-columns: minmax(0, 1fr);
212
+ gap: 0;
213
+ max-width: 1180px;
214
+ margin: 0 auto;
215
+ }
216
+
217
+ .pf-main {
218
+ min-width: 0;
219
+ padding: var(--pf-space-6) var(--pf-space-5) var(--pf-space-8);
220
+ }
221
+
222
+ /* ---------- navigation ---------- */
223
+
224
+ .pf-nav {
225
+ position: sticky;
226
+ top: 64px;
227
+ z-index: 20;
228
+ background: var(--pf-page);
229
+ border-bottom: 1px solid var(--pf-line);
230
+ padding: var(--pf-space-3) var(--pf-space-5);
231
+ overflow-x: auto;
232
+ }
233
+
234
+ .pf-nav-label {
235
+ display: none;
236
+ font-size: .6875rem;
237
+ font-weight: 700;
238
+ letter-spacing: .1em;
239
+ text-transform: uppercase;
240
+ color: var(--pf-muted);
241
+ margin-bottom: var(--pf-space-3);
242
+ }
243
+
244
+ .pf-nav-list {
245
+ display: flex;
246
+ gap: var(--pf-space-2);
247
+ list-style: none;
248
+ margin: 0;
249
+ padding: 0;
250
+ }
251
+
252
+ .pf-nav-link {
253
+ display: block;
254
+ white-space: nowrap;
255
+ padding: var(--pf-space-2) var(--pf-space-3);
256
+ border-radius: 999px;
257
+ border: 1px solid var(--pf-line);
258
+ color: var(--pf-ink);
259
+ text-decoration: none;
260
+ font-size: .875rem;
261
+ font-weight: 550;
262
+ }
263
+ .pf-nav-link:hover { border-color: var(--pf-accent); }
264
+ .pf-nav-link[aria-current="true"] {
265
+ border-color: var(--pf-accent);
266
+ background: var(--pf-surface-2);
267
+ font-weight: 650;
268
+ }
269
+ .pf-nav-index {
270
+ color: var(--pf-muted);
271
+ font-variant-numeric: tabular-nums;
272
+ margin-right: var(--pf-space-2);
273
+ }
274
+
275
+ /* ---------- lesson head ---------- */
276
+
277
+ .pf-lead {
278
+ margin: 0 0 var(--pf-space-7);
279
+ padding-bottom: var(--pf-space-6);
280
+ border-bottom: 1px solid var(--pf-line);
281
+ }
282
+ .pf-lead h1 { font-size: clamp(1.75rem, 1.2rem + 2.2vw, 2.5rem); }
283
+ .pf-lead-sub {
284
+ margin-top: var(--pf-space-3);
285
+ font-size: 1.0625rem;
286
+ color: var(--pf-muted);
287
+ max-width: var(--pf-measure);
288
+ }
289
+ .pf-lead-summary { margin-top: var(--pf-space-4); }
290
+
291
+ .pf-objectives {
292
+ margin: var(--pf-space-5) 0 0;
293
+ padding: var(--pf-space-4) var(--pf-space-5);
294
+ background: var(--pf-surface-2);
295
+ border-radius: var(--pf-radius);
296
+ border: 1px solid var(--pf-line);
297
+ max-width: var(--pf-measure);
298
+ }
299
+ .pf-objectives ul { margin: var(--pf-space-2) 0 0; padding-left: 1.25rem; }
300
+ .pf-objectives li { margin-bottom: var(--pf-space-2); }
301
+ .pf-objectives li:last-child { margin-bottom: 0; }
302
+
303
+ /* ---------- modules and sections ---------- */
304
+
305
+ .pf-module { margin-bottom: var(--pf-space-8); scroll-margin-top: 128px; }
306
+ .pf-module-head { margin-bottom: var(--pf-space-5); }
307
+ /* --pf-link, not --pf-accent. This is 11px text, so WCAG 2.1 AA wants 4.5:1
308
+ and the accent gives 3.47:1 on the light page — it clears the 3:1 non-text
309
+ bar the mark and the focus ring rely on, and fails as soon as it becomes
310
+ words. --pf-link is the accent darkened for exactly this: 5.25:1 light,
311
+ 9.51:1 dark. Any future accent-coloured *text* takes this token. */
312
+ .pf-module-index {
313
+ font-size: .6875rem;
314
+ font-weight: 700;
315
+ letter-spacing: .1em;
316
+ text-transform: uppercase;
317
+ color: var(--pf-link);
318
+ }
319
+ .pf-module-title { font-size: 1.5rem; margin-top: var(--pf-space-2); }
320
+ .pf-module-summary { margin-top: var(--pf-space-3); color: var(--pf-muted); }
321
+
322
+ .pf-requires {
323
+ margin-top: var(--pf-space-3);
324
+ font-size: .8125rem;
325
+ color: var(--pf-muted);
326
+ }
327
+ .pf-requires a { color: var(--pf-link); }
328
+
329
+ .pf-section { margin-bottom: var(--pf-space-6); scroll-margin-top: 128px; }
330
+ .pf-section-title { font-size: 1.125rem; margin-bottom: var(--pf-space-3); }
331
+
332
+ .pf-card {
333
+ background: var(--pf-surface);
334
+ border: 1px solid var(--pf-line);
335
+ border-radius: var(--pf-radius);
336
+ padding: var(--pf-space-5);
337
+ box-shadow: var(--pf-shadow);
338
+ }
339
+ .pf-card--concept { border-left: 3px solid var(--pf-accent); }
340
+
341
+ .pf-kicker {
342
+ display: inline-block;
343
+ font-size: .625rem;
344
+ font-weight: 700;
345
+ letter-spacing: .12em;
346
+ text-transform: uppercase;
347
+ color: var(--pf-muted);
348
+ margin-bottom: var(--pf-space-2);
349
+ }
350
+
351
+ /* ---------- evidence ---------- */
352
+
353
+ .pf-evidence {
354
+ margin: var(--pf-space-5) 0 0;
355
+ padding-top: var(--pf-space-4);
356
+ border-top: 1px dashed var(--pf-line);
357
+ }
358
+ .pf-evidence-label {
359
+ font-size: .625rem;
360
+ font-weight: 700;
361
+ letter-spacing: .12em;
362
+ text-transform: uppercase;
363
+ color: var(--pf-muted);
364
+ }
365
+ .pf-evidence ul { list-style: none; margin: var(--pf-space-2) 0 0; padding: 0; }
366
+ .pf-evidence li {
367
+ display: flex;
368
+ flex-wrap: wrap;
369
+ align-items: baseline;
370
+ gap: var(--pf-space-2);
371
+ padding: var(--pf-space-1) 0;
372
+ font-family: var(--pf-mono);
373
+ font-size: .8125rem;
374
+ overflow-wrap: anywhere;
375
+ }
376
+ .pf-cite-path { color: var(--pf-ink); }
377
+ .pf-cite-lines, .pf-cite-commit {
378
+ padding: 0 var(--pf-space-2);
379
+ border: 1px solid var(--pf-line);
380
+ border-radius: var(--pf-radius-sm);
381
+ background: var(--pf-surface-2);
382
+ color: var(--pf-muted);
383
+ font-size: .75rem;
384
+ white-space: nowrap;
385
+ }
386
+
387
+ /* ---------- code ---------- */
388
+
389
+ .pf-code { margin: 0; }
390
+ .pf-code figcaption {
391
+ color: var(--pf-muted);
392
+ font-size: .875rem;
393
+ margin-bottom: var(--pf-space-3);
394
+ max-width: var(--pf-measure);
395
+ }
396
+ .pf-code-frame {
397
+ background: var(--pf-surface-2);
398
+ border: 1px solid var(--pf-line);
399
+ border-radius: var(--pf-radius);
400
+ overflow: hidden;
401
+ }
402
+ .pf-code-lang {
403
+ display: block;
404
+ padding: var(--pf-space-2) var(--pf-space-4);
405
+ border-bottom: 1px solid var(--pf-line);
406
+ font-family: var(--pf-mono);
407
+ font-size: .6875rem;
408
+ letter-spacing: .08em;
409
+ text-transform: uppercase;
410
+ color: var(--pf-muted);
411
+ }
412
+ .pf-code pre {
413
+ margin: 0;
414
+ padding: var(--pf-space-4) 0;
415
+ overflow-x: auto;
416
+ line-height: 1.7;
417
+ tab-size: 2;
418
+ }
419
+ .pf-code-line { display: block; padding: 0 var(--pf-space-4); white-space: pre; }
420
+ .pf-code-no {
421
+ display: inline-block;
422
+ width: 3ch;
423
+ margin-right: var(--pf-space-4);
424
+ text-align: right;
425
+ color: var(--pf-muted);
426
+ user-select: none;
427
+ font-variant-numeric: tabular-nums;
428
+ }
429
+
430
+ /* ---------- flow ---------- */
431
+
432
+ .pf-flow { list-style: none; margin: 0; padding: 0; counter-reset: pf-step; }
433
+ .pf-flow > li {
434
+ position: relative;
435
+ counter-increment: pf-step;
436
+ padding: 0 0 var(--pf-space-5) var(--pf-space-7);
437
+ }
438
+ .pf-flow > li:last-child { padding-bottom: 0; }
439
+ .pf-flow > li::before {
440
+ content: counter(pf-step);
441
+ position: absolute;
442
+ left: 0;
443
+ top: 0;
444
+ width: 1.75rem;
445
+ height: 1.75rem;
446
+ display: grid;
447
+ place-items: center;
448
+ border-radius: 999px;
449
+ background: var(--pf-accent);
450
+ color: var(--pf-accent-ink);
451
+ font-size: .8125rem;
452
+ font-weight: 700;
453
+ font-variant-numeric: tabular-nums;
454
+ }
455
+ .pf-flow > li:not(:last-child)::after {
456
+ content: "";
457
+ position: absolute;
458
+ left: calc(.875rem - 1px);
459
+ top: 2rem;
460
+ bottom: var(--pf-space-2);
461
+ width: 2px;
462
+ background: var(--pf-line);
463
+ }
464
+ .pf-step-title { font-weight: 650; }
465
+ .pf-step-detail { margin-top: var(--pf-space-2); color: var(--pf-muted); }
466
+ .pf-step-next {
467
+ margin-top: var(--pf-space-2);
468
+ font-size: .8125rem;
469
+ color: var(--pf-muted);
470
+ }
471
+
472
+ /* ---------- quiz ---------- */
473
+
474
+ .pf-quiz { display: grid; gap: var(--pf-space-4); }
475
+ .pf-question { border: 0; margin: 0; padding: 0; }
476
+ .pf-question legend {
477
+ padding: 0;
478
+ font-weight: 650;
479
+ margin-bottom: var(--pf-space-3);
480
+ max-width: var(--pf-measure);
481
+ }
482
+ .pf-options { display: grid; gap: var(--pf-space-2); margin-bottom: var(--pf-space-3); }
483
+ .pf-option {
484
+ display: flex;
485
+ align-items: flex-start;
486
+ gap: var(--pf-space-3);
487
+ padding: var(--pf-space-3) var(--pf-space-4);
488
+ border: 1px solid var(--pf-line-strong);
489
+ border-radius: var(--pf-radius-sm);
490
+ background: var(--pf-surface);
491
+ cursor: pointer;
492
+ }
493
+ .pf-option:hover { border-color: var(--pf-accent); }
494
+ .pf-option input { margin: .35rem 0 0; accent-color: var(--pf-accent); flex: none; }
495
+ .pf-option[data-pf-verdict="correct"] {
496
+ border-color: var(--pf-ok);
497
+ box-shadow: inset 0 0 0 1px var(--pf-ok);
498
+ }
499
+ .pf-option[data-pf-verdict="incorrect"] {
500
+ border-color: var(--pf-no);
501
+ box-shadow: inset 0 0 0 1px var(--pf-no);
502
+ }
503
+ .pf-verdict { font-weight: 650; font-size: .8125rem; margin-left: auto; }
504
+ .pf-option[data-pf-verdict="correct"] .pf-verdict { color: var(--pf-ok); }
505
+ .pf-option[data-pf-verdict="incorrect"] .pf-verdict { color: var(--pf-no); }
506
+
507
+ /* ---------- disclosure ---------- */
508
+
509
+ .pf-reveal {
510
+ border: 1px solid var(--pf-line);
511
+ border-radius: var(--pf-radius-sm);
512
+ background: var(--pf-surface-2);
513
+ padding: var(--pf-space-2) var(--pf-space-4);
514
+ }
515
+ .pf-reveal > summary {
516
+ cursor: pointer;
517
+ font-weight: 600;
518
+ font-size: .875rem;
519
+ padding: var(--pf-space-1) 0;
520
+ }
521
+ .pf-reveal[open] > summary { margin-bottom: var(--pf-space-3); }
522
+ .pf-reveal ol { margin: 0; padding-left: 1.25rem; }
523
+ .pf-reveal li { margin-bottom: var(--pf-space-2); }
524
+ .pf-reveal li:last-child { margin-bottom: 0; }
525
+
526
+ /* ---------- provenance ---------- */
527
+
528
+ .pf-provenance {
529
+ max-width: 1180px;
530
+ margin: 0 auto;
531
+ padding: var(--pf-space-6) var(--pf-space-5) var(--pf-space-8);
532
+ border-top: 1px solid var(--pf-line);
533
+ color: var(--pf-muted);
534
+ font-size: .8125rem;
535
+ }
536
+ .pf-provenance dl {
537
+ display: grid;
538
+ grid-template-columns: max-content minmax(0, 1fr);
539
+ gap: var(--pf-space-2) var(--pf-space-4);
540
+ margin: 0 0 var(--pf-space-5);
541
+ }
542
+ .pf-provenance dt { font-weight: 650; color: var(--pf-ink); }
543
+ .pf-provenance dd { margin: 0; font-family: var(--pf-mono); overflow-wrap: anywhere; }
544
+ .pf-caveat { max-width: var(--pf-measure); }
545
+
546
+ /* ---------- wide ---------- */
547
+
548
+ @media (min-width: 900px) {
549
+ .pf-layout {
550
+ grid-template-columns: 248px minmax(0, 1fr);
551
+ gap: var(--pf-space-6);
552
+ padding: 0 var(--pf-space-5);
553
+ }
554
+ .pf-nav {
555
+ top: 76px;
556
+ align-self: start;
557
+ max-height: calc(100vh - 96px);
558
+ overflow-y: auto;
559
+ overflow-x: hidden;
560
+ padding: var(--pf-space-6) 0 var(--pf-space-6);
561
+ border-bottom: 0;
562
+ background: transparent;
563
+ }
564
+ .pf-nav-label { display: block; }
565
+ .pf-nav-list { display: block; }
566
+ .pf-nav-list li { margin-bottom: var(--pf-space-1); }
567
+ .pf-nav-link {
568
+ white-space: normal;
569
+ border: 0;
570
+ border-left: 2px solid var(--pf-line);
571
+ border-radius: 0;
572
+ padding: var(--pf-space-2) var(--pf-space-3);
573
+ }
574
+ .pf-nav-link[aria-current="true"] {
575
+ border-left-color: var(--pf-accent);
576
+ background: var(--pf-surface-2);
577
+ }
578
+ .pf-main { padding: var(--pf-space-7) 0 var(--pf-space-8); }
579
+ .pf-provenance { padding-left: calc(248px + var(--pf-space-6) + var(--pf-space-5)); }
580
+ }
581
+
582
+ @media (prefers-reduced-motion: reduce) {
583
+ html { scroll-behavior: auto; }
584
+ *, *::before, *::after { animation-duration: 1ms !important; transition-duration: 1ms !important; }
585
+ }
586
+
587
+ @media print {
588
+ .pf-header, .pf-nav, .pf-skip, .pf-theme-toggle { display: none; }
589
+ .pf-card { box-shadow: none; break-inside: avoid; }
590
+ .pf-reveal[open] > summary { list-style: none; }
591
+ }
592
+ `.trim();
@@ -0,0 +1,101 @@
1
+ {
2
+ "$id": "common.schema.json",
3
+ "title": "Pathfinder artifact specification — shared contract",
4
+ "description": "Carried by every artifact kind. schema_version, kind, artifact metadata, source identity, and the one citation shape. The kind discriminator selects the kind-specific schema and renderer.",
5
+ "$defs": {
6
+ "schema_version": {
7
+ "description": "The contract version this specification is written against.",
8
+ "const": "1.0"
9
+ },
10
+ "kind": {
11
+ "description": "The artifact kind. Selects the kind schema and the renderer. `lesson` and `diagram` are the supported kinds; anything else is refused rather than improvised.",
12
+ "enum": ["lesson", "diagram"]
13
+ },
14
+ "artifact": {
15
+ "description": "Metadata about the artifact itself. Carries no presentation control: theme, colour, and layout belong to the renderer.",
16
+ "type": "object",
17
+ "required": ["title"],
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "title": { "type": "string", "minLength": 1, "maxLength": 200 },
21
+ "subtitle": { "type": "string", "minLength": 1, "maxLength": 300 },
22
+ "summary": { "type": "string", "minLength": 1, "maxLength": 2000 },
23
+ "locale": {
24
+ "description": "BCP 47 language tag, passed through to the document's lang attribute. Not a translation instruction.",
25
+ "type": "string",
26
+ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
27
+ }
28
+ }
29
+ },
30
+ "source": {
31
+ "description": "Where the artifact's claims come from. `commit` is what evidence resolves against — never the working tree.",
32
+ "type": "object",
33
+ "required": ["repo", "commit"],
34
+ "additionalProperties": false,
35
+ "properties": {
36
+ "repo": {
37
+ "description": "Identifies the repository the evidence belongs to, so a diagnostic can say whose commit is missing.",
38
+ "type": "string",
39
+ "minLength": 1,
40
+ "maxLength": 200
41
+ },
42
+ "commit": {
43
+ "description": "A full or abbreviated Git object name. Evidence resolves against this and nothing else.",
44
+ "type": "string",
45
+ "pattern": "^[0-9a-f]{7,40}$"
46
+ },
47
+ "generated_at": {
48
+ "description": "Supplied by the producer if it exists at all. Part of the deterministic input: the renderer never reads a clock.",
49
+ "type": "string",
50
+ "minLength": 1,
51
+ "maxLength": 64
52
+ }
53
+ }
54
+ },
55
+ "citation": {
56
+ "description": "The one evidence shape, used identically wherever evidence appears, in this and every future kind.",
57
+ "type": "object",
58
+ "required": ["path"],
59
+ "additionalProperties": false,
60
+ "properties": {
61
+ "path": {
62
+ "description": "Repository-relative, forward-slashed. Never absolute, never a machine path.",
63
+ "type": "string",
64
+ "minLength": 1,
65
+ "maxLength": 400,
66
+ "pattern": "^[^/][^\\\\]*$"
67
+ },
68
+ "lines": {
69
+ "description": "Inclusive [start, end] line range, 1-based. Must lie within the file at the resolved commit.",
70
+ "type": "array",
71
+ "minItems": 2,
72
+ "maxItems": 2,
73
+ "items": { "type": "integer", "minimum": 1 }
74
+ },
75
+ "commit": {
76
+ "description": "Overrides source.commit for this citation alone. Present when a claim is about a different point in history.",
77
+ "type": "string",
78
+ "pattern": "^[0-9a-f]{7,40}$"
79
+ }
80
+ }
81
+ },
82
+ "evidence": {
83
+ "description": "A list of citations. Emptiness is legal structurally and rejected by the evidence layer, so the failure is reported as the evidence claim it is.",
84
+ "type": "array",
85
+ "maxItems": 50,
86
+ "items": { "$ref": "common.schema.json#/$defs/citation" }
87
+ },
88
+ "identifier": {
89
+ "description": "A specification-supplied identifier. Every id the renderer emits is derived from one of these, never generated from ambient state.",
90
+ "type": "string",
91
+ "pattern": "^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$"
92
+ },
93
+ "prose": {
94
+ "description": "Plain-text paragraphs. Not Markdown and not HTML: the renderer escapes every character it emits.",
95
+ "type": "array",
96
+ "minItems": 1,
97
+ "maxItems": 40,
98
+ "items": { "type": "string", "minLength": 1, "maxLength": 4000 }
99
+ }
100
+ }
101
+ }