create-pmos 0.512.0 → 0.514.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 (104) hide show
  1. package/package.json +27 -95
  2. package/payload/KIT-VERSION +1 -1
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/state/discoveries.md +1 -1
  6. package/workspace/briefing-premium-adddoc-test.js +3 -3
  7. package/workspace/briefing-premium-board-test.js +2 -2
  8. package/workspace/briefing-premium-initiative-test.js +3 -3
  9. package/workspace/briefing-premium-insights-test.js +2 -2
  10. package/workspace/briefing-premium-sources-test.js +4 -5
  11. package/workspace/briefing-premium-test.js +2 -2
  12. package/workspace/changelog.json +26 -0
  13. package/workspace/guide/home.md +1 -1
  14. package/workspace/intel-report-ui-test.js +1 -1
  15. package/workspace/intelligence-gaps-ui-test.js +1 -1
  16. package/workspace/lib/premium-console-type.js +15 -12
  17. package/workspace/lib/routes-boards.js +18 -6
  18. package/workspace/metric-drafts-ui-test.js +2 -2
  19. package/workspace/package-lock.json +2 -2
  20. package/workspace/package.json +1 -1
  21. package/workspace/premium-menu-rows-test.js +1 -1
  22. package/workspace/premium-vw-doc-test.js +1 -1
  23. package/workspace/public/pm/bd-pin-card.css +4 -4
  24. package/workspace/public/pm/bd-publish.css +11 -11
  25. package/workspace/public/pm/bd-shared.css +17 -17
  26. package/workspace/public/pm/c3-capture.css +9 -9
  27. package/workspace/public/pm/c3-initiatives.css +23 -23
  28. package/workspace/public/pm/c3-listen.css +1 -1
  29. package/workspace/public/pm/components.css +572 -572
  30. package/workspace/public/pm/console3.css +374 -373
  31. package/workspace/public/pm/cozy-adler.css +4 -4
  32. package/workspace/public/pm/cozy-aud.css +1 -1
  33. package/workspace/public/pm/cozy-board.css +328 -328
  34. package/workspace/public/pm/cozy-ev.css +78 -78
  35. package/workspace/public/pm/cozy-home-rows.css +32 -32
  36. package/workspace/public/pm/cozy-home.css +240 -240
  37. package/workspace/public/pm/cozy-insights.css +200 -200
  38. package/workspace/public/pm/cozy-live-pill.css +5 -5
  39. package/workspace/public/pm/cozy-mtg-present.css +28 -28
  40. package/workspace/public/pm/cozy-onboard.css +15 -15
  41. package/workspace/public/pm/cozy-panel.css +39 -39
  42. package/workspace/public/pm/cozy-pick.css +5 -5
  43. package/workspace/public/pm/cozy-rec-now.css +23 -23
  44. package/workspace/public/pm/cozy-rec-present.css +11 -11
  45. package/workspace/public/pm/cozy-rec-tab.css +13 -13
  46. package/workspace/public/pm/cozy-steps.css +14 -14
  47. package/workspace/public/pm/cozy-today.css +95 -95
  48. package/workspace/public/pm/cozy-viewer.css +27 -27
  49. package/workspace/public/pm/cozy-vw-mtg.css +113 -113
  50. package/workspace/public/pm/cozy-vw-rp.css +79 -79
  51. package/workspace/public/pm/cozy-vw-shell.css +43 -43
  52. package/workspace/public/pm/cozy-vw.css +129 -129
  53. package/workspace/public/pm/cozy-xv.css +15 -15
  54. package/workspace/public/pm/cozy.css +53 -21
  55. package/workspace/public/pm/dash-view.css +27 -27
  56. package/workspace/public/pm/hi-pop.css +26 -26
  57. package/workspace/public/pm/home-impact.css +8 -8
  58. package/workspace/public/pm/home-ini.css +36 -36
  59. package/workspace/public/pm/home-line.css +7 -7
  60. package/workspace/public/pm/impact.css +9 -9
  61. package/workspace/public/pm/ini-view.css +243 -243
  62. package/workspace/public/pm/load-shapes.css +1 -1
  63. package/workspace/public/pm/loose-props.css +14 -14
  64. package/workspace/public/pm/metric-drafts.css +20 -20
  65. package/workspace/public/pm/ms-suggest.css +9 -9
  66. package/workspace/public/pm/ov-view.css +14 -14
  67. package/workspace/public/pm/premium-insights.js +3 -3
  68. package/workspace/public/pm/premium-report-frame.css +1 -1
  69. package/workspace/public/pm/premium-report-intel.css +17 -17
  70. package/workspace/public/pm/premium-report-print.css +1 -1
  71. package/workspace/public/pm/premium-vw-backlog.css +13 -13
  72. package/workspace/public/pm/premium-vw-cal.css +32 -32
  73. package/workspace/public/pm/premium-vw-copy.css +7 -7
  74. package/workspace/public/pm/premium-vw-doc.css +69 -69
  75. package/workspace/public/pm/premium-vw-item.css +12 -12
  76. package/workspace/public/pm/premium-vw-jira.css +39 -39
  77. package/workspace/public/pm/premium-vw-meet.css +28 -28
  78. package/workspace/public/pm/premium-vw-notepad.css +13 -13
  79. package/workspace/public/pm/premium-vw-notes.css +11 -11
  80. package/workspace/public/pm/premium-vw-rec.css +27 -27
  81. package/workspace/public/pm/premium-vw-report.css +21 -21
  82. package/workspace/public/pm/premium-vw-slides.css +28 -28
  83. package/workspace/public/pm/premium.css +618 -618
  84. package/workspace/public/pm/prog-order.css +6 -6
  85. package/workspace/public/pm/prog-page.css +17 -17
  86. package/workspace/public/pm/program-charter.css +33 -33
  87. package/workspace/public/pm/ready-lib.css +6 -6
  88. package/workspace/public/pm/sort-order.css +1 -1
  89. package/workspace/public/pm/split-merge.css +18 -18
  90. package/workspace/public/pm/task-card.css +24 -24
  91. package/workspace/public/pm/tk-tasks.css +42 -42
  92. package/workspace/public/pm/tk-view.css +6 -6
  93. package/workspace/public/pm/wf-page.css +31 -31
  94. package/workspace/public/premium-aud.html +1 -1
  95. package/workspace/public/premium-board.html +1 -1
  96. package/workspace/public/premium-intelligence.html +1 -1
  97. package/workspace/public/premium-rec.html +1 -1
  98. package/workspace/public/premium-viewer.html +1 -1
  99. package/workspace/public/premium-workflows.html +1 -1
  100. package/workspace/public/premium.html +1 -1
  101. package/workspace/public/vendor/plus-jakarta-sans-var.woff2 +0 -0
  102. package/workspace/server.js +1 -0
  103. package/workspace/third-party/README.md +2 -1
  104. package/workspace/third-party/plus-jakarta-sans-OFL.LICENSE +93 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.512.0",
3
+ "version": "0.514.0",
4
4
  "description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -32,6 +32,32 @@
32
32
  "audit"
33
33
  ],
34
34
  "pmosChanges": [
35
+ {
36
+ "v": "0.514.0",
37
+ "at": "2026-11-03T16:19:12.685Z",
38
+ "f": 4,
39
+ "prs": [
40
+ {
41
+ "n": 1205,
42
+ "t": "Closed also reads the Done category and terminal stage names",
43
+ "s": "Closed also reads the Done category and terminal stage names",
44
+ "k": "fix"
45
+ }
46
+ ]
47
+ },
48
+ {
49
+ "v": "0.513.0",
50
+ "at": "2026-11-03T15:46:14.696Z",
51
+ "f": 4,
52
+ "prs": [
53
+ {
54
+ "n": 1203,
55
+ "t": "Typography roles with Plus Jakarta Sans, two weights, four sizes",
56
+ "s": "Typography roles with Plus Jakarta Sans, two weights, four sizes",
57
+ "k": "new"
58
+ }
59
+ ]
60
+ },
35
61
  {
36
62
  "v": "0.512.0",
37
63
  "at": "2026-11-03T15:33:10.629Z",
@@ -470,100 +496,6 @@
470
496
  "d": "Every new initiative needed its workflow chosen by hand. Now you star one in Settings and new work uses it."
471
497
  }
472
498
  ]
473
- },
474
- {
475
- "v": "0.494.0",
476
- "at": "2026-11-02T15:38:38.702Z",
477
- "f": 4,
478
- "prs": [
479
- {
480
- "n": 1156,
481
- "t": "A report change from the chat edits the existing draft in place",
482
- "s": "A report change from the chat edits the existing draft in place",
483
- "k": "fix",
484
- "h": "Report edits from chat change the draft",
485
- "d": "Asking the chat to change a report could start a new draft. Now it edits the draft you already have."
486
- },
487
- {
488
- "n": 1150,
489
- "t": "A report's chat icon opens its own conversation beside it",
490
- "s": "A report's chat icon opens its own conversation beside it",
491
- "k": "new",
492
- "h": "Talk about a report beside it",
493
- "d": "A report had no place of its own to discuss it. Now its chat icon opens a conversation next to the report."
494
- },
495
- {
496
- "n": 1149,
497
- "t": "Product groups use the product name, not the folder name",
498
- "s": "Product groups use the product name, not the folder name",
499
- "k": "fix",
500
- "h": "Products show their real names",
501
- "d": "Board groups showed the folder name of a product. Now they show the product's own name."
502
- },
503
- {
504
- "n": 1157,
505
- "t": "Weekly impact check as thank-you bubbles, counted by default",
506
- "s": "Weekly impact check as thank-you bubbles, counted by default",
507
- "k": "new",
508
- "h": "Your weekly impact, as thank-yous",
509
- "d": "The weekly impact check was easy to miss. Now it arrives as small thank-you bubbles and is counted by default."
510
- },
511
- {
512
- "n": 1158,
513
- "t": "Grill every initiative with open work, not only late work; Home says how many",
514
- "s": "Grill every initiative with open work, not only late work; Home says how many",
515
- "k": "fix",
516
- "h": "Every open initiative gets a grill",
517
- "d": "Claude only grilled initiatives that were late. Now it grills every initiative with open work, and Home says how many."
518
- },
519
- {
520
- "n": 1159,
521
- "t": "Pages open faster after a break and after a start",
522
- "s": "Pages open faster after a break and after a start",
523
- "k": "imp",
524
- "h": "Pages open faster",
525
- "d": "Pages were slow after a break or a fresh start. Now they open quicker in both cases."
526
- },
527
- {
528
- "n": 1161,
529
- "t": "A report template keeps its look and sections",
530
- "s": "A report template keeps its look and sections",
531
- "k": "new",
532
- "h": "Report templates keep their look",
533
- "d": "A report made from a template could lose the template's look and sections. Now it keeps both."
534
- },
535
- {
536
- "n": 1162,
537
- "t": "Finished report slides, the Pyramid deck",
538
- "s": "Finished report slides, the Pyramid deck",
539
- "k": "new",
540
- "h": "Finished report slides",
541
- "d": "Reports could not become a finished slide deck. Now a report can come out as clean slides in the Pyramid style."
542
- },
543
- {
544
- "n": 1160,
545
- "t": "Tighter phone initiative page, one 16 px inset",
546
- "s": "Tighter phone initiative page, one 16 px inset",
547
- "k": "new",
548
- "h": "A tidier initiative page on the phone",
549
- "d": "On a phone the initiative page had uneven edges. Now everything lines up on one even margin."
550
- }
551
- ]
552
- },
553
- {
554
- "v": "0.493.0",
555
- "at": "2026-11-02T14:41:05.438Z",
556
- "f": 4,
557
- "prs": [
558
- {
559
- "n": 1152,
560
- "t": "Retire Now / Next / Later; the Roadmap is one ranked list",
561
- "s": "Retire Now / Next / Later; the Roadmap is one ranked list",
562
- "k": "new",
563
- "h": "One ranked roadmap",
564
- "d": "The roadmap split work into Now, Next and Later. Now it is one ranked list, and each initiative's stage says where it is."
565
- }
566
- ]
567
499
  }
568
500
  ]
569
501
  }
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@55dc3bc3
1
+ kernel-source: pmos@cdccf707
2
2
  assembled: 2026-11-03
3
3
  mode: vendor
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Eval calibration log"
4
4
  description: "Append-only record of every PM override of an evaluator verdict."
5
- generated: {by: process:create-pmos, at: "2026-11-03T15:33:07Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-03T16:19:09Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Health budgets"
4
4
  description: "Closed registry of health-budget anchors (D59), edited only by the PM."
5
- generated: {by: process:create-pmos, at: "2026-11-03T15:33:07Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-03T16:19:09Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Discoveries and friction log"
4
4
  description: "Append-only record of what runs discovered and where the process rubbed."
5
- generated: {by: process:create-pmos, at: "2026-11-03T15:33:07Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-03T16:19:09Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -145,7 +145,7 @@ const PAGES = {
145
145
  ok('A3 the add route takes POST only', wrong === 405);
146
146
  }
147
147
 
148
- /* A8 fidelity: the form follows the Agreed design (premium-agreed-design): the Viewer is one cozy card in Nunito, its presses pills, the main one
148
+ /* A8 fidelity: the form follows the Agreed design (premium-agreed-design): the Viewer is one cozy card in Plus Jakarta Sans, its presses pills, the main one
149
149
  * the solid colour. The colours are read from the page's own tokens, so a theme is checked against itself. */
150
150
  for (const [w, h] of [[1280, 900], [390, 844]]) for (const th of ['light', 'dark']) {
151
151
  const R = await open('initiative', { w, h, theme: th });
@@ -159,8 +159,8 @@ const PAGES = {
159
159
  return { vw: cs('#czViewer').backgroundColor, card: tok('--cz-card', 'background-color'), vwR: cs('#czViewer').borderTopLeftRadius, ff: cs('#czViewer').fontFamily, go: [cs(go).backgroundColor, '700', cs(go).borderTopLeftRadius], solid: tok('--cz-solid', 'background-color'), no: bk ? getComputedStyle(bk).backgroundColor : 'rgba(0, 0, 0, 0)', dlg: !!(document.querySelector('#dlg') && !!document.querySelector('#dlg.on')) };
160
160
  });
161
161
  const d = [];
162
- if (!/^Nunito/.test(btn.ff) || btn.br !== '999px') d.push('button ' + JSON.stringify(btn));
163
- if (g.vw !== g.card || !/^Nunito/.test(g.ff) || (w > 760 && parseFloat(g.vwR) < 20)) d.push('viewer ' + [g.vw, g.card, g.vwR, g.ff].join(' '));
162
+ if (!/^"?Plus Jakarta Sans/.test(btn.ff) || btn.br !== '999px') d.push('button ' + JSON.stringify(btn));
163
+ if (g.vw !== g.card || !/^"?Plus Jakarta Sans/.test(g.ff) || (w > 760 && parseFloat(g.vwR) < 20)) d.push('viewer ' + [g.vw, g.card, g.vwR, g.ff].join(' '));
164
164
  if (g.go[0] !== g.solid || +g.go[1] < 700 || g.go[2] !== '999px') d.push('Add document ' + g.go.join(' ') + ' want ' + g.solid);
165
165
  if (g.no !== g.card && g.no !== 'rgba(0, 0, 0, 0)') d.push('Back ' + g.no); // ← cancels (batch 18 item 2-C): the header's quiet round press
166
166
  if (g.dlg) d.push('the old dialog opened');
@@ -645,11 +645,11 @@ const IVREAD = () => {
645
645
  await R.ctx.close();
646
646
  }
647
647
 
648
- /* B13 the Agreed design's look: Nunito, the soft panel, the chips and the track's rows */
648
+ /* B13 the Agreed design's look: Plus Jakarta Sans, the soft panel, the chips and the track's rows */
649
649
  for (const th of ['light', 'dark']) {
650
650
  const R = await open({ w: 1440, h: 900, theme: th }), q = R.page;
651
651
  const s = await q.evaluate(() => { const cs = (sel) => getComputedStyle(document.querySelector(sel)); const main = cs('main.bd-main'), row = document.querySelector('.bt-l').getBoundingClientRect(); return { font: cs('body').fontFamily, rad: main.borderTopLeftRadius, bg: main.backgroundColor, chip: Math.round(document.querySelector('#chJira').getBoundingClientRect().height), row: Math.round(row.height), tab: cs('#tabIni').fontWeight, fab: cs('#czFab').borderTopLeftRadius }; });
652
- ok(`B13 the look of the Agreed design at 1440 px, ${th}: Nunito, a 28 px panel, 30 px chips, 54 px rows`, /^"?Nunito/.test(s.font) && s.rad === '28px' && s.chip === 30 && s.row >= 54 && s.row <= 62 && s.tab === '700' && (th === 'light' ? s.bg === 'rgb(255, 255, 255)' : s.bg !== 'rgb(255, 255, 255)'), JSON.stringify(s));
652
+ ok(`B13 the look of the Agreed design at 1440 px, ${th}: Plus Jakarta Sans, a 28 px panel, 30 px chips, 54 px rows`, /^"?Plus Jakarta Sans/.test(s.font) && s.rad === '28px' && s.chip === 30 && s.row >= 54 && s.row <= 62 && s.tab === '600' && (th === 'light' ? s.bg === 'rgb(255, 255, 255)' : s.bg !== 'rgb(255, 255, 255)'), JSON.stringify(s));
653
653
  await R.ctx.close();
654
654
  }
655
655
  return finish();
@@ -252,7 +252,7 @@ const WRITES = /^\/api\/(?:boards\/(?:move|tasks\/(?:add|state))|lifecycle|core-
252
252
  const post = await fetch(BASE + '/api/initiative-premium?id=x', { method: 'POST' }); ok('I10 the route answers GET only', post.status === 405);
253
253
  }
254
254
 
255
- /* I12 fidelity: the Agreed design (premium-agreed-design, Inside one initiative): Nunito, the cozy card, the CTA in the solid colour, the floating
255
+ /* I12 fidelity: the Agreed design (premium-agreed-design, Inside one initiative): Plus Jakarta Sans, the cozy card, the CTA in the solid colour, the floating
256
256
  * menu. The colours are read from the page's own tokens, so each theme is checked against itself. */
257
257
  for (const [w, h] of [[1440, 900], [390, 844]]) for (const th of ['light', 'dark']) {
258
258
  const R = await open({ w, h, theme: th });
@@ -264,7 +264,7 @@ const WRITES = /^\/api\/(?:boards\/(?:move|tasks\/(?:add|state))|lifecycle|core-
264
264
  return { font: cs('body').fontFamily, cta: (cs('#czViewer .iv-go') || {}).backgroundColor, ctaAlt: !document.querySelector('#czViewer .iv-go'), solid: tok('--cz-solid'), blue: tok('--cz-blue'), well: tok('--cz-well'), card: tok('--cz-card'), box: cs('#czViewer .iv-mc').backgroundColor, rad: parseFloat(cs(innerWidth <= 760 ? '#czViewer .iv-tel' : '#czViewer .iv-mc').borderTopLeftRadius), fab: fr ? [Math.round(fr.width), Math.round(fr.height)] : null, over: document.documentElement.scrollWidth - innerWidth, cardW: Math.round(c.width) };
265
265
  });
266
266
  const d = [];
267
- if (!/^Nunito/.test(g.font)) d.push('font ' + g.font);
267
+ if (!/^"?Plus Jakarta Sans/.test(g.font)) d.push('font ' + g.font);
268
268
  if (!g.ctaAlt && ![g.solid, g.blue].includes(g.cta)) d.push('cta ' + g.cta + ' want ' + g.solid + ' or ' + g.blue);
269
269
  if (![g.well, g.card].includes(g.box) && !/rgba/.test(g.box)) d.push('card box ' + g.box);
270
270
  // a phone has no wrapper card (batch 23 item 5, canvas PmA): the mission is the first card there
@@ -272,7 +272,7 @@ const WRITES = /^\/api\/(?:boards\/(?:move|tasks\/(?:add|state))|lifecycle|core-
272
272
  // on a phone the initiative's page is the Viewer, full screen over the list: the floating menu waits under it (HbCPhone)
273
273
  if (w > 760 && (!g.fab || g.fab[0] !== 56 || g.fab[1] !== 56)) d.push('fab ' + JSON.stringify(g.fab));
274
274
  if (g.over > 0) d.push('sideways scroll ' + g.over);
275
- ok(`I12 the initiative follows the Agreed design at ${w} px, ${th}: Nunito, the cozy card, the CTA, the floating menu`, d.length === 0, d.join(' ; '));
275
+ ok(`I12 the initiative follows the Agreed design at ${w} px, ${th}: Plus Jakarta Sans, the cozy card, the CTA, the floating menu`, d.length === 0, d.join(' ; '));
276
276
  ok(`I12 no page error at ${w} px, ${th}`, R.errors.length === 0, R.errors.join(' | '));
277
277
  await R.ctx.close();
278
278
  }
@@ -603,13 +603,13 @@ const BMR = { ok: true, state: 'ready', model: { tiles: [
603
603
  await R.ctx.close();
604
604
  }
605
605
 
606
- /* I11 fidelity to the Agreed design (AgInsGoals, AgInsPhone): Nunito, the cozy panel, the pill tabs, the table, the strip and the six tiles */
606
+ /* I11 fidelity to the Agreed design (AgInsGoals, AgInsPhone): Plus Jakarta Sans, the cozy panel, the pill tabs, the table, the strip and the six tiles */
607
607
  for (const [w, h] of [[1440, 900], [390, 844]]) {
608
608
  const R = await open({ w, h }); await sleep(300);
609
609
  const f = await R.page.evaluate(() => { const cs = (s) => getComputedStyle(document.querySelector(s)); const main = cs('main.ins'), tab = cs('#viewChip'), strip = cs('#insHero'), row = document.querySelector('#goalTbl .ins-row').getBoundingClientRect();
610
610
  return { font: cs('body').fontFamily, radius: main.borderTopLeftRadius, bg: main.backgroundColor, tabBg: tab.backgroundColor, tabR: tab.borderTopLeftRadius, tabW: tab.fontWeight, stripR: strip.borderTopLeftRadius, stripBg: strip.backgroundColor, rowH: Math.round(row.height), over: document.documentElement.scrollWidth > innerWidth, fab: document.querySelector('#czFab').getBoundingClientRect().width, fabX: Math.round(document.querySelector('#czFab').getBoundingClientRect().left) }; });
611
611
  const phone = w < 720;
612
- ok(`I11 the page wears the Agreed design at ${w} px: Nunito, the white panel, the pill View chip, the well strip, 56 px floating button`, /^Nunito/.test(f.font) && f.radius === (phone ? '0px' : '28px') && (phone || f.bg === 'rgb(255, 255, 255)') && f.tabBg === 'rgb(244, 243, 240)' && f.tabR === '999px' && +f.tabW >= 700 && (phone || (f.stripR === '16px' && f.stripBg === 'rgb(244, 243, 240)' && f.rowH >= 60)) && !f.over && f.fab === 56 && (phone ? f.fabX > 300 : f.fabX === 36), JSON.stringify(f));
612
+ ok(`I11 the page wears the Agreed design at ${w} px: Plus Jakarta Sans, the white panel, the pill View chip, the well strip, 56 px floating button`, /^"?Plus Jakarta Sans/.test(f.font) && f.radius === (phone ? '0px' : '28px') && (phone || f.bg === 'rgb(255, 255, 255)') && f.tabBg === 'rgb(244, 243, 240)' && f.tabR === '999px' && +f.tabW >= 600 && (phone || (f.stripR === '16px' && f.stripBg === 'rgb(244, 243, 240)' && f.rowH >= 60)) && !f.over && f.fab === 56 && (phone ? f.fabX > 300 : f.fabX === 36), JSON.stringify(f));
613
613
  if (!phone) {
614
614
  await toTrends(R.page);
615
615
  const t = await R.page.evaluate(() => { const tiles = [...document.querySelectorAll('#bmBody [data-tile]')].map((x) => x.getBoundingClientRect()); return { n: tiles.length, tileH: tiles.length ? Math.round(tiles[0].height) : 0, over: document.documentElement.scrollWidth > innerWidth }; });
@@ -193,7 +193,7 @@ const sh = (cwd, args) => spawnSync('git', args, { cwd, encoding: 'utf8' });
193
193
  {
194
194
  // re-pointed again for the Agreed design's Home: the source row is a Needs-you row of Home's list, and the digest is a row of New for you.
195
195
  // Re-pointed for Home A + C (HvAC, HvACDesk): a row is one line, its mark and its title; the subject moved to the row's tooltip and
196
- // accessible name, and the title is 14 px on a desktop, 16 px on a phone, weight 600 (the type scale, feedback batch 15 item 2)
196
+ // accessible name, and the title is the row title role, 15 px at 600 (feedback batch 26 item 6)
197
197
  const ROWS = [['row', '.hm-rm'], ['number', '.hv-d'], ['title', '.hm-rt b']];
198
198
  const DIGS = [['event', ''], ['eventTime', '.ev-tx .ev-mut'], ['eventTitle', '.ev-tx b']];
199
199
  const SANS = 'var(--cz-sans)';
@@ -201,10 +201,10 @@ const sh = (cwd, args) => spawnSync('git', args, { cwd, encoding: 'utf8' });
201
201
  const WANT = {
202
202
  row: { fontFamily: SANS, color: 'var(--cz-ink)', backgroundColor: 'transparent' },
203
203
  number: { backgroundColor: 'var(--cz-blue)', borderRadius: '50%', width: '8px', height: '8px' }, // the row's dot is blue: it waits on you (HvAC .d)
204
- title: { fontFamily: SANS, fontSize: '14px', fontWeight: '600', color: 'var(--cz-ink)' }, // HvACDesk .t; 16px on a phone (HvAC .t b)
204
+ title: { fontFamily: SANS, fontSize: 'var(--fs-row)', fontWeight: 'var(--fw-strong)', color: 'var(--cz-ink)' }, // the row title role (batch 26 item 6), desktop and phone
205
205
  event: { fontFamily: SANS, color: 'var(--cz-ink)', backgroundColor: 'transparent' }, // a row of New for you's table (cozy-ev.css)
206
- eventTime: { fontSize: '12px', color: 'var(--cz-ink3)' },
207
- eventTitle: { fontFamily: SANS, fontSize: '14px', fontWeight: '700', color: 'var(--cz-ink)' },
206
+ eventTime: { fontSize: 'var(--fs-meta)', color: 'var(--cz-ink3)' },
207
+ eventTitle: { fontFamily: SANS, fontSize: 'var(--fs-row)', fontWeight: 'var(--fw-strong)', color: 'var(--cz-ink)' },
208
208
  };
209
209
  const grab = (pg, pairs, base, col) => pg.evaluate(([pairs, want, base, col]) => {
210
210
  const o = {};
@@ -235,7 +235,6 @@ const sh = (cwd, args) => spawnSync('git', args, { cwd, encoding: 'utf8' });
235
235
  await allNeeds(R.page);
236
236
  // on a phone "Not in an initiative" starts folded (HvAC): open it to reach the source row
237
237
  if (w === 390) { const lh = await R.page.$('#hmList .hv-lh[aria-expanded="false"]'); if (lh) await lh.click(); }
238
- WANT.title.fontSize = w === 390 ? '16px' : '14px';
239
238
  await sleep(500);
240
239
  const rowSel = '#hmList .hm-r[data-id^="n:src:"]';
241
240
  const diffs = diff(await grab(R.page, ROWS, rowSel, '.hm-pg'), ROWS, 4); // the row fills its group (the group's own inset aside)
@@ -127,7 +127,7 @@ const vw = () => { const v = document.querySelector('#czViewer:not([hidden])');
127
127
  const selBg = await p.evaluate(() => getComputedStyle(document.querySelector('.hm-r.sel')).backgroundColor);
128
128
  ok('R12 the selected row is tinted (HvACDesk .sel)', selBg && !/rgba\(0, 0, 0, 0\)|transparent/.test(selBg), selBg);
129
129
  const hd = await p.evaluate(() => { const h = document.querySelector('#hmCh'); return h && !h.hidden ? { t: (h.querySelector('.hm-chb') || {}).textContent || '', mark: !!h.querySelector('.hv-mk .hv-d, .hv-mk .hv-ck'), y: Math.round(h.getBoundingClientRect().top), size: getComputedStyle(h.querySelector('.hm-chb')).fontSize } : null; });
130
- ok('R12 the chat\'s header line names the open row: its mark and its title, 16 px (the type scale, batch 15), at the top of the chat card (HvACDesk .ch)', !!hd && hd.t === r0.t && hd.mark && hd.y === 32 && hd.size === '16px', JSON.stringify(hd)); }
130
+ ok('R12 the chat\'s header line names the open row: its mark and its title, 15 px (the row title role, batch 26 item 6), at the top of the chat card (HvACDesk .ch)', !!hd && hd.t === r0.t && hd.mark && hd.y === 32 && hd.size === '15px', JSON.stringify(hd)); }
131
131
 
132
132
  /* R10 */
133
133
  ok('R10 a page where nothing was pressed to record sends no POST', A.posts.filter((x) => /briefing-premium\/(decide|put-off|return|start)/.test(x.path)).length === 0, JSON.stringify(A.posts.map((x) => x.path)));
@@ -273,7 +273,7 @@ const vw = () => { const v = document.querySelector('#czViewer:not([hidden])');
273
273
  const desk = document.querySelector('#hmDesk'), list = document.querySelector('#hmList'), cs = getComputedStyle(document.querySelector('#hmChat')), r = list.getBoundingClientRect();
274
274
  return { inset: Math.round(r.left), radius: cs.borderTopLeftRadius, list: Math.round(list.getBoundingClientRect().width), font: getComputedStyle(document.body).fontFamily, theme: document.documentElement.dataset.theme || '', bg: getComputedStyle(document.body).backgroundColor, rail: !!document.querySelector('nav.rail'), menu: !!document.querySelector('#czMenu, .cz-nav') };
275
275
  });
276
- ok(`R12 Home follows the Home A + C design at 1440 px (HvACDesk), ${theme}: the list at 16 px and 360 px wide, the chat a card with a 24 px radius, Nunito, the floating place menu, no rail`, f.inset === 16 && f.radius === '24px' && f.list === 360 && /Nunito/.test(f.font) && f.menu && !f.rail, JSON.stringify(f));
276
+ ok(`R12 Home follows the Home A + C design at 1440 px (HvACDesk), ${theme}: the list at 16 px and 360 px wide, the chat a card with a 24 px radius, Plus Jakarta Sans, the floating place menu, no rail`, f.inset === 16 && f.radius === '24px' && f.list === 360 && /Plus Jakarta Sans/.test(f.font) && f.menu && !f.rail, JSON.stringify(f));
277
277
  await D.ctx.close();
278
278
  }
279
279
  return finish();
@@ -1,4 +1,30 @@
1
1
  [
2
+ {
3
+ "v": "0.514.0",
4
+ "at": "2026-11-03T16:19:12.685Z",
5
+ "f": 4,
6
+ "prs": [
7
+ {
8
+ "n": 1205,
9
+ "t": "Closed also reads the Done category and terminal stage names",
10
+ "s": "Closed also reads the Done category and terminal stage names",
11
+ "k": "fix"
12
+ }
13
+ ]
14
+ },
15
+ {
16
+ "v": "0.513.0",
17
+ "at": "2026-11-03T15:46:14.696Z",
18
+ "f": 4,
19
+ "prs": [
20
+ {
21
+ "n": 1203,
22
+ "t": "Typography roles with Plus Jakarta Sans, two weights, four sizes",
23
+ "s": "Typography roles with Plus Jakarta Sans, two weights, four sizes",
24
+ "k": "new"
25
+ }
26
+ ]
27
+ },
2
28
  {
3
29
  "v": "0.512.0",
4
30
  "at": "2026-11-03T15:33:10.629Z",
@@ -9,7 +9,7 @@ timestamp: 2026-11-03
9
9
 
10
10
  Home is the page at `/premium.html`. It follows the Agreed design and the "Uncovered features" canvas (initiative `premium-uncovered-features`). The left side is one list. The middle is the chat. The right side is the Viewer. A row opens its item in the Viewer. On a phone the list is the page, and a row opens its conversation with a back bar.
11
11
 
12
- Home, the Board, Insights and the Viewer use one type scale: 11, 12, 14, 16, 20 and 28 px. A list row's name is 14 px on a desktop and 16 px on a phone, a group head is 16 px, the quiet meta line and a chip are 12 px, a badge is 11 px, a page title is 20 px and a large number is 28 px. The same kind of text has the same size on every page; weight and colour make the difference, not a half pixel.
12
+ Home, the Board, Insights and the Viewer use one typeface, Plus Jakarta Sans, with two weights (regular and semibold) and four sizes: 13, 15, 18 and 26 px. Each kind of text has one role, and the role sets its size, weight and line height: a large number and a page title are 26 px, a panel title is 18 px, a group head, a row's name and a sentence are 15 px, and the quiet meta line, a label and a badge are 13 px. The same kind of text looks the same on every page. Rows have room to breathe, so a list row is about 52 to 58 px high. There is no compact setting. Slides and report templates keep their own sizes.
13
13
 
14
14
  ## The list
15
15
 
@@ -88,7 +88,7 @@ const FINDING = { id: 'rf-0123456789', name: 'Partner numbers to Lee', why: 'You
88
88
  };
89
89
  const vClose = async (pg) => { for (let k = 0; k < 4; k++) { const shown = await pg.evaluate(() => { const v = document.querySelector('#czViewer'); return !!v && !v.hidden; }); if (!shown) break; await pg.evaluate(() => window.czViewer && window.czViewer.close()); await sleep(250); } };
90
90
  const sizes = (p, sel) => p.evaluate((s) => [...new Set([...document.querySelectorAll(s + ', ' + s + ' *')].filter((e) => e.childNodes.length && [...e.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim())).map((e) => getComputedStyle(e).fontSize))], sel);
91
- const SCALE = new Set(['11px', '12px', '14px', '16px', '20px', '28px']);
91
+ const SCALE = new Set(['13px', '15px', '18px', '26px']); // the typography roles (batch 26 item 6)
92
92
  const panel = (p) => p.evaluate(() => {
93
93
  const b = document.querySelector('#czViewer:not([hidden]) .ins-rpd') || document.querySelector('#side:not([hidden]) .ins-rpd'); if (!b) return null;
94
94
  return { title: (document.querySelector('#czViewer .cz-vw-title') || {}).textContent || '', kind: (document.querySelector('#czViewer .cz-vw-kind') || {}).textContent || '', sum: (b.querySelector('.ins-rps b') || {}).textContent || '',
@@ -157,7 +157,7 @@ const WEEKLY = { view: 'view', weeks: 3, day: 'Mon', opens: 4 };
157
157
  await p.evaluate(() => document.querySelector('#mdHost').remove());
158
158
 
159
159
  /* ── T: the type scale ── */
160
- const SCALE = new Set(['11px', '12px', '14px', '16px', '20px', '28px']);
160
+ const SCALE = new Set(['13px', '15px', '18px', '26px']); // the typography roles (batch 26 item 6)
161
161
  const sizes = await p.evaluate(() => [...new Set([...document.querySelectorAll('#insHero, #insHero *, #nsRow, #nsRow *')].filter((e) => e.childNodes.length && [...e.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim())).map((e) => getComputedStyle(e).fontSize))]);
162
162
  ok('T the strip and the drafts use the type scale only', sizes.every((s) => SCALE.has(s)), sizes.join());
163
163
  ok('no page errors', !A.errors.length, A.errors.join(' | '));
@@ -3,25 +3,25 @@
3
3
  * The premium stylesheets were cut from the approved mockup, which set its own type: Geist and Geist Mono, a 15 px body and sizes from 10 to
4
4
  * 44 px written as plain pixels, and weights from 550 to 700. The console (pm/console3.css + pm/tokens.css) sets Space Grotesk and IBM Plex
5
5
  * Mono, a 14 px / 1.5 body, a six-step scale (--fs-1 to --fs-6: 11, 12, 14, 16, 20, 28 px) and three weights (400, 500, 600).
6
+ * Since feedback batch 26 item 6 (2026-11-03) the steps are the typography roles (cozy.css): sizes go to a role token (13, 15, 18 or 26 px)
7
+ * and weights to the two weight tokens (400, 600).
6
8
  * transform(css) maps a premium stylesheet onto the console's type and changes nothing else: every font size goes to the nearest console
7
9
  * step, every heavy weight to 600 (550 to 500), the two font families to the console's, and the body line height to 1.5. It is idempotent,
8
10
  * so a stylesheet already on console type comes back unchanged (premium-console-type.test.js holds the committed stylesheets to that).
9
11
  * The browser suites also run it over the mockup before they compare computed styles, so the mockup stays the reference for the look and
10
12
  * the console is the reference for the type. */
11
13
 
12
- const FS = '--fs-1:11px;--fs-2:12px;--fs-3:14px;--fs-4:16px;--fs-5:20px;--fs-6:28px';
14
+ const FS = '--fw-regular:400;--fw-strong:600;--fs-field:16px;--fs-display:26px;--fs-page:26px;--fs-panel:18px;--fs-section:15px;--fs-row:15px;--fs-body:15px;--fs-meta:13px;--fs-label:13px;--fs-badge:13px;--lh-display:1.2;--lh-page:1.2;--lh-panel:1.3;--lh-section:1.35;--lh-row:1.4;--lh-body:1.6;--lh-meta:1.5;--lh-label:1.35;--lh-badge:1.2';
13
15
 
14
- /* a pixel size to the console step it reads as */
16
+ /* a pixel size to the role size it reads as: 13 meta, 15 body, 18 panel, 26 display */
15
17
  function step(px) {
16
18
  const n = parseFloat(px);
17
- if (n <= 11.5) return 'var(--fs-1)';
18
- if (n <= 13) return 'var(--fs-2)';
19
- if (n <= 15) return 'var(--fs-3)';
20
- if (n <= 20) return 'var(--fs-4)';
21
- if (n <= 30) return 'var(--fs-5)';
22
- return 'var(--fs-6)';
19
+ if (n < 13.5) return 'var(--fs-meta)';
20
+ if (n < 17.5) return 'var(--fs-body)';
21
+ if (n < 23) return 'var(--fs-panel)';
22
+ return 'var(--fs-display)';
23
23
  }
24
- const weight = (w) => (w === '550' ? '500' : (+w > 600 ? '600' : w));
24
+ const weight = (w) => (+w >= 600 ? 'var(--fw-strong)' : 'var(--fw-regular)');
25
25
 
26
26
  /* the console's font files; base is where /vendor/ is served ('/vendor/' on the server, a file:// path for the mockup) */
27
27
  function fontFaces(base = '/vendor/') {
@@ -32,7 +32,9 @@ function fontFaces(base = '/vendor/') {
32
32
  }
33
33
 
34
34
  function transform(css, { base = '/vendor/', faces = true } = {}) {
35
- let out = String(css)
35
+ // an @font-face's weight is a descriptor of the file (400, 300 700), not a style: set aside, then put back unchanged
36
+ const kept = [];
37
+ let out = String(css).replace(/@font-face\s*\{[^}]*\}/g, (m) => (/Geist/.test(m) ? m : '\u0000' + (kept.push(m) - 1) + '\u0000'))
36
38
  .replace(/@font-face\s*\{[^}]*Geist[^}]*\}\s*/g, '')
37
39
  .replace(/--sans:[^;}]*/g, '--sans:"Space Grotesk",ui-sans-serif,system-ui,sans-serif')
38
40
  .replace(/--mono:[^;}]*/g, '--mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace')
@@ -49,8 +51,9 @@ function transform(css, { base = '/vendor/', faces = true } = {}) {
49
51
  .replace(/font:\s*((?:italic\s+)?)(\d{3})(\s)/g, (m, it, w, sp) => 'font:' + it + weight(w) + sp)
50
52
  // the body reads at the console's line height; the Briefing paragraph gets the width its smaller type needs
51
53
  .replace(/(\bbody\{[^}]*?)line-height:1\.45/g, '$1line-height:1.5')
52
- .replace(/(\.brief\{[^}]*?)max-width:42ch/g, '$1max-width:64ch');
53
- if (!/--fs-1:/.test(out)) out = ':root{' + FS + '}\n' + out;
54
+ .replace(/(\.brief\{[^}]*?)max-width:42ch/g, '$1max-width:64ch')
55
+ .replace(/\u0000(\d+)\u0000/g, (m, k) => kept[+k]);
56
+ if (!/--fs-body:/.test(out)) out = ':root{' + FS + '}\n' + out;
54
57
  if (faces && !/space-grotesk-var\.woff2/.test(out)) out = fontFaces(base) + out;
55
58
  return out;
56
59
  }
@@ -477,15 +477,27 @@ const gapFields = (spans) => ({ gaps: holdSpans.planGaps(spans, 14), stageSpans:
477
477
  const minDay = (a, b) => (!a ? b || null : !b ? a : a < b ? a : b);
478
478
  const maxDay = (a, b) => (!a ? b || null : !b ? a : a > b ? a : b);
479
479
 
480
- /** batch 26 item 5: whether an initiative stands in its workflow's terminal stage, read from the workflow, never from a list of names. On a
481
- * D99 workflow: the stage is in the Done category, or it is the workflow's last stage. On a board's own column workflow: its last column. */
480
+ /** batch 26 item 5: whether an initiative stands in its workflow's terminal stage. Read from the workflow first: the stage is in the Done
481
+ * category, or it is the last stage (stages after it that only end work, such as Won't do or Cancelled, do not count). A stage the workflow
482
+ * does not hold (a Jira-read status such as "Closed") is terminal when its name says so, as tkStatus reads a ticket (done, closed, resolved,
483
+ * released). Follow-up (CAS rows still late after #1202): the board column branch ignored the Done category and any stage outside the list. */
484
+ const TERMINAL_RE = /\b(done|closed|resolved|released|complete|completed|finished)\b/i;
485
+ const END_RE = /\b(won'?t do|cancel+ed|archived|abandoned|dropped|rejected)\b/i;
486
+ const isDoneCat = (c) => String(c || '').toLowerCase() === 'done';
482
487
  function closedOf(row, wx) {
483
- if (wx.own) {
484
- const st = wx.stages.find((x) => x.key === wx.stage);
485
- return !!st && (st.cat === 'done' || (st.of > 1 && st.n === st.of));
488
+ const cur = (wx && wx.stages || []).find((x) => x.key === wx.stage);
489
+ if (cur && (wx.own || cur.name === row.stage)) {
490
+ if (isDoneCat(cur.cat) || (cur.of > 1 && cur.n === cur.of)) return true;
491
+ if (wx.own) return false;
486
492
  }
493
+ if (isDoneCat(row.cat)) return true;
487
494
  const wf = Array.isArray(row.workflow) ? row.workflow : [];
488
- return wf.length > 1 && wf.findIndex((x) => x.name === row.stage) === wf.length - 1;
495
+ const name = String(row.stage || '');
496
+ const i = wf.findIndex((x) => x && x.name === name);
497
+ if (i < 0) return TERMINAL_RE.test(name);
498
+ if (isDoneCat(wf[i].cat)) return true;
499
+ // the last stage, not counting the ones after it that only end work
500
+ return wf.length > 1 && wf.slice(i + 1).every((x) => END_RE.test(String(x && x.name || '')));
489
501
  }
490
502
  function boardRow(board, base, folded, lib) {
491
503
  const s = B.stageOf(board, base.id, base.reading, folded);
@@ -108,9 +108,9 @@ const INITIATIVE = () => ({
108
108
  why: c.querySelector('.md-why').textContent,
109
109
  presses: [...c.querySelectorAll('.md-ft .md-ib')].map((b) => ({ label: b.getAttribute('aria-label'), title: b.getAttribute('title'), text: b.textContent.trim(), svg: !!b.querySelector('svg[aria-hidden="true"]'), solid: b.classList.contains('solid'), href: b.getAttribute('href') || '' })) };
110
110
  });
111
- // every font size the parts draw: the type scale only (11, 12, 14, 16, 20, 28 px)
111
+ // every font size the parts draw: the type roles only (13, 15, 18, 26 px)
112
112
  const sizes = (p, sel) => p.evaluate((s) => [...new Set([...document.querySelectorAll(s + ', ' + s + ' *')].filter((e) => e.childNodes.length && [...e.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim())).map((e) => getComputedStyle(e).fontSize))], sel);
113
- const SCALE = new Set(['11px', '12px', '14px', '16px', '20px', '28px']);
113
+ const SCALE = new Set(['13px', '15px', '18px', '26px']); // the typography roles (batch 26 item 6)
114
114
 
115
115
  /* ── P: the program's proposed goal ── */
116
116
  const D = await open();
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.512.0",
3
+ "version": "0.514.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.512.0",
9
+ "version": "0.514.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@anthropic-ai/sdk": "^1.3.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.512.0",
3
+ "version": "0.514.0",
4
4
  "private": true,
5
5
  "description": "PMOS Terminal Workspace — local companion hosting real PTYs beside the library and gates (D62)",
6
6
  "license": "MIT",
@@ -90,7 +90,7 @@ const ONE = { '.crmenu': 42, '.crmenu.tbmenu': 44, '.dsmn': 44 };
90
90
  await page.goto(ORIGIN + '/menu.html');
91
91
  const c1 = await page.evaluate(() => { const t = getComputedStyle(document.querySelector('.tag')), p = getComputedStyle(document.querySelector('.lsn-help')), c = getComputedStyle(document.querySelector('.c3'));
92
92
  return { tag: t.backgroundColor, fs: p.fontSize, muted: c.getPropertyValue('--muted').trim(), fg: c.getPropertyValue('--foreground').trim() }; });
93
- ok(`C1 a console sheet at ${w} ${theme}: its tokens are set, a tag has a fill and the body is 14px text`, c1.muted && c1.fg && !/rgba\(0, 0, 0, 0\)|transparent/.test(c1.tag) && c1.fs === '14px', JSON.stringify(c1));
93
+ ok(`C1 a console sheet at ${w} ${theme}: its tokens are set, a tag has a fill and the body is 15px text (the body role)`, c1.muted && c1.fg && !/rgba\(0, 0, 0, 0\)|transparent/.test(c1.tag) && c1.fs === '15px', JSON.stringify(c1));
94
94
  // H1: a long highlight keeps its tag on one line
95
95
  ctx._html = page0(`<section class="rvsec" style="overflow-wrap:anywhere;max-width:${Math.min(w - 32, 560)}px"><ul class="rvhl"><li><time>0:00</time><span>${LONG.join(' ').repeat(3)}</span><em>decision</em></li></ul></section>`, theme);
96
96
  await page.goto(ORIGIN + '/menu.html');
@@ -232,7 +232,7 @@ const prop = (o) => Object.assign({ id: 'prop-a', state: 'proposed', prompt: 'ad
232
232
  const fs = [...root.querySelectorAll('*')].map((e) => getComputedStyle(e).fontSize).filter((x, i, a) => a.indexOf(x) === i);
233
233
  return { sx: document.documentElement.scrollWidth - innerWidth, bad, out, fs };
234
234
  }, [V, w < 600]);
235
- ok(`D8 at ${w} px ${theme}: no sideways scroll, no small target, nothing past the edge, console sizes only`, f.sx <= 0 && f.bad.length === 0 && f.out === 0 && f.fs.every((x) => ['11px', '12px', '14px', '16px', '20px', '28px'].includes(x)), JSON.stringify(f));
235
+ ok(`D8 at ${w} px ${theme}: no sideways scroll, no small target, nothing past the edge, role sizes only`, f.sx <= 0 && f.bad.length === 0 && f.out === 0 && f.fs.every((x) => ['13px', '15px', '16px', '18px', '26px'].includes(x)), JSON.stringify(f));
236
236
  const c = await R.page.evaluate((v) => { const e = document.querySelector(v + ' .cf'); return [getComputedStyle(document.querySelector('#czViewer')).backgroundColor, getComputedStyle(e).color]; }, V); // the page sits on the Viewer's card
237
237
  ok(`D8 at ${w} px ${theme}: the page follows the theme`, theme === 'dark' ? c[0] === 'rgb(36, 38, 43)' && c[1] === 'rgb(236, 238, 241)' : c[0] === 'rgb(255, 255, 255)' && c[1] === 'rgb(42, 46, 53)', JSON.stringify(c)); // the initiative page is cozy (premium-agreed-design): the card is the cozy white or dark card
238
238
  await R.ctx.close();
@@ -1,13 +1,13 @@
1
1
  /* pm/bd-pin-card.css — the ◇ card on the Board timeline (feedback batch 13 item 4, XcalmA5Milestones): the initiative's milestones with a pin each.
2
2
  * The card is a pm/bd-pop.js popover; the Filter's "Milestones on the timeline" group uses the Filter's own chips. */
3
3
  .bd-pp.bd-pc { padding: 12px 12px 6px; }
4
- .bd-pc-h { margin: 0; font: 800 var(--fs-3)/1.3 var(--cz-sans); color: var(--cz-ink); }
5
- .bd-pc-sub { margin: 2px 0 6px; font-size: var(--fs-2); font-weight: 600; }
4
+ .bd-pc-h { margin: 0; font: var(--fw-strong) var(--fs-section)/1.3 var(--cz-sans); color: var(--cz-ink); }
5
+ .bd-pc-sub { margin: 2px 0 6px; font-size: var(--fs-label); font-weight: var(--fw-strong); line-height: var(--lh-label); }
6
6
  .bd-pc-r { display: flex; align-items: center; gap: 10px; min-height: 44px; border-top: 1px solid var(--cz-hair); }
7
7
  .bd-pc-r .tg-tm-i { margin-left: 4px; }
8
- .bd-pc-n { flex: 1; min-width: 0; font: 700 var(--fs-2)/1.25 var(--cz-sans); color: var(--cz-ink2); overflow-wrap: anywhere; }
8
+ .bd-pc-n { flex: 1; min-width: 0; font: var(--fw-strong) var(--fs-label)/1.25 var(--cz-sans); color: var(--cz-ink2); overflow-wrap: anywhere; }
9
9
  .bd-pc-r.on .bd-pc-n { color: var(--cz-ink); }
10
- .bd-pc-n small { display: block; font-size: var(--fs-1); font-weight: 600; color: var(--cz-ink3); }
10
+ .bd-pc-n small { display: block; font-size: var(--fs-badge); font-weight: var(--fw-strong); color: var(--cz-ink3); line-height: var(--lh-badge); }
11
11
  .bd-pc-n small.late { color: var(--cz-late); }
12
12
  .bd-pc-pin { all: unset; flex: none; width: 32px; height: 32px; border-radius: 9px; display: inline-flex; align-items: center; justify-content: center; color: var(--cz-ink3); cursor: pointer; }
13
13
  .bd-pc-pin svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }