create-pmos 0.52.0 → 0.53.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 (212) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +2 -2
  3. package/payload/mods/pmos-cockpit/README.md +13 -0
  4. package/payload/mods/pmos-cockpit/hooks/lib/commands.mjs +2 -1
  5. package/payload/mods/pmos-cockpit/hooks/lib/grants.mjs +41 -11
  6. package/payload/mods/pmos-cockpit/hooks/lib/guard.mjs +228 -8
  7. package/payload/mods/pmos-cockpit/hooks/register.tsx +11 -3
  8. package/payload/planning/evals/eval-calibration.md +1 -1
  9. package/payload/planning/okrs/health-budgets.md +1 -1
  10. package/payload/scripts/plan-items-stage.js +271 -0
  11. package/payload/scripts/pmos-log.sh +18 -7
  12. package/payload/scripts/workflow-state.js +28 -2
  13. package/payload/state/discoveries.md +1 -1
  14. package/payload/state/log.md +1 -1
  15. package/workspace/bin/pmos.js +15 -1
  16. package/workspace/cut-stub.js +40 -0
  17. package/workspace/first-paint-audit.js +145 -0
  18. package/workspace/fixtures/capture/requirements/capture-2026-10-04-1.md +10 -0
  19. package/workspace/fixtures/initiatives-api/list.json +176 -0
  20. package/workspace/fixtures/initiatives-api/post-403.json +5 -0
  21. package/workspace/fixtures/initiatives-api/post-ok.json +10 -0
  22. package/workspace/fixtures/initiatives-api/roadmap.json +208 -0
  23. package/workspace/fixtures/initiatives-api/sheet.json +112 -0
  24. package/workspace/fixtures/report-api/draft.json +177 -0
  25. package/workspace/fixtures/reports-page/draft-biweekly-2026-10-16.md +97 -0
  26. package/workspace/fixtures/today-api/provider-rows.json +204 -0
  27. package/workspace/fixtures/today-api/seam-probe-kinds.json +5 -0
  28. package/workspace/guide/board.md +81 -0
  29. package/workspace/guide/breakdown.md +154 -0
  30. package/workspace/guide/command-palette.md +75 -0
  31. package/workspace/guide/index.md +27 -17
  32. package/workspace/guide/initiatives-roadmap.md +130 -0
  33. package/workspace/guide/initiatives-screen.md +89 -0
  34. package/workspace/guide/initiatives-timeline.md +57 -0
  35. package/workspace/guide/integrated-work.md +94 -0
  36. package/workspace/guide/knowledge-graph.md +3 -68
  37. package/workspace/guide/layout.md +98 -32
  38. package/workspace/guide/live-notes.md +3 -3
  39. package/workspace/guide/pm-only-acts.md +89 -0
  40. package/workspace/guide/pm-only-lines.md +61 -0
  41. package/workspace/guide/reports-page.md +80 -0
  42. package/workspace/guide/reports.md +73 -0
  43. package/workspace/guide/timeline.md +70 -0
  44. package/workspace/guide/today.md +85 -0
  45. package/workspace/guide/validate-with-typesafe.md +1 -1
  46. package/workspace/lib/actor.js +91 -0
  47. package/workspace/lib/board-cache.js +32 -2
  48. package/workspace/lib/board-cli.js +108 -0
  49. package/workspace/lib/board-fold.js +232 -0
  50. package/workspace/lib/breakdown-export.js +80 -0
  51. package/workspace/lib/capture.js +98 -0
  52. package/workspace/lib/engine/plan-items-stage.js +271 -0
  53. package/workspace/lib/engine/workflow-state.js +28 -2
  54. package/workspace/lib/factory-join.js +158 -0
  55. package/workspace/lib/grant-mint.js +113 -0
  56. package/workspace/lib/health-rules-factory.js +92 -0
  57. package/workspace/lib/health-rules-plan.js +164 -0
  58. package/workspace/lib/health.js +79 -0
  59. package/workspace/lib/initiative-cli.js +222 -0
  60. package/workspace/lib/initiative-display.js +183 -0
  61. package/workspace/lib/initiative-needs.js +127 -0
  62. package/workspace/lib/initiative-rows.js +206 -0
  63. package/workspace/lib/initiative-writer.js +141 -0
  64. package/workspace/lib/issues.js +206 -0
  65. package/workspace/lib/item-cli.js +195 -0
  66. package/workspace/lib/item-spans.js +137 -0
  67. package/workspace/lib/measure-cli.js +100 -0
  68. package/workspace/lib/measurements.js +189 -0
  69. package/workspace/lib/mobile-nav.js +57 -53
  70. package/workspace/lib/needs-providers.js +120 -0
  71. package/workspace/lib/placements.js +440 -0
  72. package/workspace/lib/plan-items.js +922 -0
  73. package/workspace/lib/plan-ledgers.js +85 -0
  74. package/workspace/lib/plan-rules.js +72 -0
  75. package/workspace/lib/plan-tree.js +367 -0
  76. package/workspace/lib/pm-guard.js +174 -0
  77. package/workspace/lib/pm-lines-needs.js +331 -0
  78. package/workspace/lib/pmos-verbs.js +66 -0
  79. package/workspace/lib/proactive.js +7 -4
  80. package/workspace/lib/report-cli.js +152 -0
  81. package/workspace/lib/report-draft.js +579 -0
  82. package/workspace/lib/report-needs.js +213 -0
  83. package/workspace/lib/route-modules.js +128 -0
  84. package/workspace/lib/routes-board.js +91 -0
  85. package/workspace/lib/routes-capture.js +52 -0
  86. package/workspace/lib/routes-initiatives.js +371 -0
  87. package/workspace/lib/routes-reports.js +163 -0
  88. package/workspace/lib/routes-timeline.js +197 -0
  89. package/workspace/lib/routes-today.js +32 -0
  90. package/workspace/lib/today-cli.js +99 -0
  91. package/workspace/lib/today.js +333 -0
  92. package/workspace/lib/triage.js +163 -0
  93. package/workspace/lib/verify-work-cli.js +81 -0
  94. package/workspace/lib/work-check-needs.js +99 -0
  95. package/workspace/package-lock.json +1 -1
  96. package/workspace/package.json +4 -5
  97. package/workspace/public/index.html +29 -19
  98. package/workspace/public/pm/app.js +299 -30
  99. package/workspace/public/pm/bd-board.js +113 -0
  100. package/workspace/public/pm/bd-drag.js +59 -0
  101. package/workspace/public/pm/bd-model.js +152 -0
  102. package/workspace/public/pm/bd-timeline.js +233 -0
  103. package/workspace/public/pm/bd-tree.js +244 -0
  104. package/workspace/public/pm/board-copy.js +57 -0
  105. package/workspace/public/pm/breakdown-api.js +58 -0
  106. package/workspace/public/pm/breakdown-copy.js +138 -0
  107. package/workspace/public/pm/components.css +3054 -0
  108. package/workspace/public/pm/cycle-review.js +125 -0
  109. package/workspace/public/pm/data.js +40 -187
  110. package/workspace/public/pm/dest-board.js +438 -0
  111. package/workspace/public/pm/dest-initiatives.js +134 -0
  112. package/workspace/public/pm/dest-reports.js +270 -0
  113. package/workspace/public/pm/dest-settings.js +217 -0
  114. package/workspace/public/pm/dest-today.js +176 -0
  115. package/workspace/public/pm/dialogs.js +88 -0
  116. package/workspace/public/pm/display-word.js +56 -0
  117. package/workspace/public/pm/dock.js +105 -0
  118. package/workspace/public/pm/fixture.js +8 -227
  119. package/workspace/public/pm/frame-copy.js +94 -0
  120. package/workspace/public/pm/handoff-box.js +82 -0
  121. package/workspace/public/pm/http.js +88 -0
  122. package/workspace/public/pm/icons.js +53 -0
  123. package/workspace/public/pm/initiative-list.js +218 -0
  124. package/workspace/public/pm/initiative-sheet.js +261 -0
  125. package/workspace/public/pm/initiatives-api.js +59 -0
  126. package/workspace/public/pm/initiatives-copy.js +215 -0
  127. package/workspace/public/pm/initiatives-timeline-copy.js +59 -0
  128. package/workspace/public/pm/initiatives-timeline.js +410 -0
  129. package/workspace/public/pm/initiatives-ui.js +123 -0
  130. package/workspace/public/pm/item-sheet.js +251 -0
  131. package/workspace/public/pm/keep-card.js +204 -0
  132. package/workspace/public/pm/lifecycle.js +7 -1
  133. package/workspace/public/pm/mobile.css +49 -20
  134. package/workspace/public/pm/mobile.js +157 -29
  135. package/workspace/public/pm/modes-live.js +7 -883
  136. package/workspace/public/pm/modes.js +32 -1024
  137. package/workspace/public/pm/new-initiative-sheet.js +74 -0
  138. package/workspace/public/pm/next-step-ui.js +3 -2
  139. package/workspace/public/pm/palette-copy.js +51 -0
  140. package/workspace/public/pm/palette.js +312 -0
  141. package/workspace/public/pm/panel-breakdown.js +702 -0
  142. package/workspace/public/pm/panel-cockpit.js +63 -295
  143. package/workspace/public/pm/panel-detail.js +25 -172
  144. package/workspace/public/pm/panel-initiatives.js +1 -19
  145. package/workspace/public/pm/panel-repos.js +31 -81
  146. package/workspace/public/pm/panel-tabs.js +197 -110
  147. package/workspace/public/pm/panel-trees.js +5 -2
  148. package/workspace/public/pm/portfolio-report.js +61 -0
  149. package/workspace/public/pm/primitives.js +656 -0
  150. package/workspace/public/pm/print.css +33 -0
  151. package/workspace/public/pm/provider-cards.js +136 -0
  152. package/workspace/public/pm/registry.js +194 -0
  153. package/workspace/public/pm/release-card.js +110 -0
  154. package/workspace/public/pm/report-view.js +309 -0
  155. package/workspace/public/pm/reports-copy.js +114 -0
  156. package/workspace/public/pm/roadmap.js +271 -0
  157. package/workspace/public/pm/sig-watch.js +41 -0
  158. package/workspace/public/pm/state.js +8 -4
  159. package/workspace/public/pm/status-banner.js +13 -5
  160. package/workspace/public/pm/tab-doc.js +2 -4
  161. package/workspace/public/pm/tab-term.js +12 -2
  162. package/workspace/public/pm/tab-web.js +3 -2
  163. package/workspace/public/pm/timeline-copy.js +48 -0
  164. package/workspace/public/pm/today-cards.js +244 -0
  165. package/workspace/public/pm/today-copy.js +97 -0
  166. package/workspace/public/pm/tokens.css +16 -18
  167. package/workspace/public/pm/topbar.js +102 -0
  168. package/workspace/public/pm/triage-card.js +60 -0
  169. package/workspace/public/pm/undo-hold.js +103 -0
  170. package/workspace/public/pm/view-addrepo.js +2 -1
  171. package/workspace/public/pm/view-livenotes.js +3 -1
  172. package/workspace/public/pm/view-today.js +7 -162
  173. package/workspace/public/vendor/Sortable.min.js +2 -0
  174. package/workspace/public/vendor/lucide-sprite.js +79 -0
  175. package/workspace/replaced-tests.json +2864 -0
  176. package/workspace/run-tests.js +56 -5
  177. package/workspace/server.js +312 -36
  178. package/workspace/sheet-answer.js +61 -0
  179. package/workspace/skills/measure.skill +41 -0
  180. package/workspace/skills/report-biweekly.skill +23 -25
  181. package/workspace/skills/tracker-refresh.skill +50 -0
  182. package/workspace/test-files.json +19 -3
  183. package/workspace/test-lists/base.json +6 -0
  184. package/workspace/test-lists/run2-a.json +6 -0
  185. package/workspace/test-lists/run2-b.json +13 -0
  186. package/workspace/test-lists/run2-c.json +19 -0
  187. package/workspace/test-lists/run2-d.json +22 -0
  188. package/workspace/test-lists/run3-0.json +5 -0
  189. package/workspace/test-lists/run3-a.json +5 -0
  190. package/workspace/test-lists/run3-b.json +6 -0
  191. package/workspace/test-lists/run3-c.json +15 -0
  192. package/workspace/test-lists/run3-d.json +6 -0
  193. package/workspace/test-lists/run4-a.json +8 -0
  194. package/workspace/test-lists/run4-b.json +6 -0
  195. package/workspace/test-lists/run4-c.json +13 -0
  196. package/workspace/test-lists/run4-d.json +9 -0
  197. package/workspace/test-lists/run5-a.json +6 -0
  198. package/workspace/test-lists/run5-b.json +6 -0
  199. package/workspace/test-lists/run5-c.json +15 -0
  200. package/workspace/test-run.js +27 -1
  201. package/workspace/third-party/README.md +44 -0
  202. package/workspace/third-party/lucide-static-1.52.0.LICENSE +43 -0
  203. package/workspace/third-party/sortablejs-1.15.7.LICENSE +21 -0
  204. package/workspace/design-parity.js +0 -673
  205. package/workspace/public/pm/tab-task.js +0 -86
  206. package/workspace/public/pm/view-guide.js +0 -141
  207. package/workspace/public/pm/view-knowledge.js +0 -225
  208. package/workspace/public/pm/view-memory.js +0 -122
  209. package/workspace/public/pm/view-portfolio.js +0 -153
  210. package/workspace/public/pm/view-reports.js +0 -190
  211. package/workspace/public/pm/view-scheduled.js +0 -161
  212. package/workspace/public/pm/view-skills.js +0 -216
@@ -8,9 +8,8 @@
8
8
  *
9
9
  * ── What this panel is ──────────────────────────────────────────────────────────────────────
10
10
  * Panel 3 answers one question about one initiative: what is the state of the work, and what can
11
- * I open? Top to bottom: the slug + tag + pills header, the TASK PHASES (collapsible, each phase's
12
- * status derived from its own tasks), LINKS (external references that are deliberately NOT repo
13
- * files), ARTIFACTS (repo files, each optionally linked to an external destination with a
11
+ * I open? Top to bottom: the slug + tag + pills header, LINKS (external references that are deliberately
12
+ * NOT repo files), ARTIFACTS (repo files, each optionally linked to an external destination with a
14
13
  * repo-is-source sync panel), and the derived footer line.
15
14
  *
16
15
  * ── Two structural facts the port has to honour ─────────────────────────────────────────────
@@ -22,11 +21,10 @@
22
21
  * reproduces the wrapper; bare text is used exactly where the template has bare text — `+`,
23
22
  * `⇄`, `·`, `→ repo`, `adopt`, `link`, `next`, the note lines and the two headings.
24
23
  *
25
- * 2. NOTHING DERIVABLE IS STORED (D33). Phases, task statuses, phase counts, the active-row
26
- * highlight, link lists and the footer line are all computed at read time, every render, from
27
- * the initiative record plus the four state maps the prototype keeps (`collapsedPhases`,
28
- * `initLinks`, `artLinks`, `artSyncOpen`/`artSyncState`). A cached phase status is a board that
29
- * lies the moment a task moves.
24
+ * 2. NOTHING DERIVABLE IS STORED (D33). Link lists and the footer line are all computed at read time,
25
+ * every render, from the initiative record plus the state maps the prototype keeps (`initLinks`,
26
+ * `artLinks`, `artSyncOpen`/`artSyncState`). The work itself (items, their state) is the Breakdown's;
27
+ * the phases with synthetic rung tasks are cut (R29), and so is the Task tab that opened them.
30
28
  *
31
29
  * ── Where the data comes from ───────────────────────────────────────────────────────────────
32
30
  * Fixture mode reads `fixture.js` directly. Normal mode is the same code path with the optional
@@ -53,12 +51,15 @@ import { el, setChildren } from './dom.js';
53
51
  import { state, setState, FIXTURE, LEGACY_LAYOUT_FLAG } from './state.js';
54
52
  import { isMobile } from './mobile.js';
55
53
  import * as FX from './fixture.js';
56
- import { openDoc, openTab, openTerm, fileNewModel } from './panel-initiatives.js';
54
+ import { openDoc, openTerm, fileNewModel } from './panel-initiatives.js';
57
55
  import { addProductBody } from './panel-repos.js';
58
56
  import { lifecycleRow } from './lifecycle.js';
59
57
  import { statusBanner, lifecycleNotes } from './status-banner.js';
60
58
  import { stepActions } from './next-step-ui.js';
61
59
  import { boardOf, records, currentProduct } from './board-read.js';
60
+ import { breakdownEntry } from './panel-breakdown.js';
61
+ import { askConfirm } from './dialogs.js';
62
+ import { button } from './primitives.js';
62
63
 
63
64
  /** The cockpit's own look of this panel (workspace-cockpit-nav): the mockup's header, chips and phase rows. A phone and `?layout=legacy`
64
65
  * keep the prototype's look, which the older suites and the phone still read. */
@@ -92,13 +93,6 @@ function sibling(key, path) {
92
93
  return null;
93
94
  }
94
95
 
95
- /** Open a task tab: panel 4's own opener when it is reachable, the shared one otherwise. */
96
- function openTask(tab) {
97
- const tabs = sibling('tabs', './panel-tabs.js');
98
- if (tabs && typeof tabs.openTask === 'function') { tabs.openTask(tab); return; }
99
- openTab(tab);
100
- }
101
-
102
96
  /**
103
97
  * Record a link declaration in file-mode state, if the data layer offers that write. A link is a
104
98
  * DECLARATION — `{artifact_path, kind, target}` — never an outbound call: PMOS holds no credential
@@ -310,8 +304,10 @@ function lanePicker(slug, rec) {
310
304
  onChange: async (e) => {
311
305
  const next = e.target.value;
312
306
  if (next === rec.lane) return;
313
- const ok = typeof window === 'undefined' || !window.confirm ? true
314
- : window.confirm('Change “' + slug + '” from the ' + rec.lane + ' lane to the ' + next + ' lane?\n\nThe lane decides which steps the workflow ladder requires (spec, rubric, explainer, …). Only the lane: line of the record changes; the steps are re-derived and some may show as missing.');
307
+ const ok = await askConfirm({
308
+ title: 'Change “' + slug + '” to the ' + next + ' lane?', confirmLabel: 'Change lane',
309
+ body: 'Change “' + slug + '” from the ' + rec.lane + ' lane to the ' + next + ' lane?\n\nThe lane decides which steps the workflow ladder requires (spec, rubric, explainer, …). Only the lane: line of the record changes; the steps are re-derived and some may show as missing.',
310
+ });
315
311
  if (!ok) { e.target.value = rec.lane; return; }
316
312
  const data = sibling('data', './data.js');
317
313
  const out = data && data.setInitiativeLane ? await data.setInitiativeLane(slug, next) : null;
@@ -369,16 +365,6 @@ function noLadderBlock(ansBg, ansC, ansText, noLadderText) {
369
365
  ];
370
366
  }
371
367
 
372
- /* ── 4 · TASK PHASES (lines 357–378, derivation 1719–1756) ──────────────────────────────────
373
- * The phase set is the initiative's workflow — one phase per engine rung. Its tasks are the
374
- * record's authored `tasks[<phase>]` when present, otherwise the three derived from the rung's own
375
- * evidence (`synthTasks`). Nothing is invented per initiative: a rung with no evidence reads
376
- * `todo`, never `done`.
377
- *
378
- * Phase status is derived from its tasks, in this order: any blocked → blocked; any doing → doing;
379
- * all done → done; some done → doing; else todo. Collapse is per phase PER INITIATIVE (`pkey`), so
380
- * folding `build` on one initiative does not fold it on the next.
381
- */
382
368
  /**
383
369
  * What the documents say, beside the ladder and never in it (status-from-artifacts, Run 1). One plain
384
370
  * line each, with its basis. A flag colours its own dot only; no phase, count or total reads it.
@@ -397,55 +383,6 @@ function factsBlock(rec) {
397
383
  )));
398
384
  }
399
385
 
400
- function phaseBlock(slug, rec) {
401
- const phases = FX.phasesFor(rec, slug);
402
- if (!phases.length) return null;
403
- const collP = state.collapsedPhases || {};
404
- const repo = repoOf(slug);
405
-
406
- return el('div', { style: 'padding:2px 0 6px' }, phases.map((ph) => {
407
- const pkey = slug + '|' + ph.key;
408
- const open = !collP[pkey];
409
- const [phDot, phColor] = FX.TDOT[ph.status];
410
-
411
- const head = el('button', {
412
- onClick: () => setState((s) => ({
413
- collapsedPhases: { ...s.collapsedPhases, [pkey]: !((s.collapsedPhases || {})[pkey]) },
414
- })),
415
- style: 'display:flex;align-items:center;gap:8px;width:100%;padding:9px 14px 8px',
416
- hover: 'background:var(--accent)',
417
- },
418
- el('span', { style: 'flex:none;width:10px;font-size:9px;color:var(--muted-foreground)' }, iv(open ? '▾' : '▸')),
419
- el('span', { style: 'flex:none;width:13px;text-align:center;font-size:11px;color:' + phColor }, iv(phDot)),
420
- el('span', { style: 'font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--foreground)' }, iv(ph.label)),
421
- el('span', { style: 'margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, iv(ph.count)),
422
- );
423
-
424
- const rows = open ? ph.tasks.map((t, ti) => {
425
- const tid = FX.taskTabId(slug, ph.key, ti);
426
- const active = state.active === tid;
427
- const [dot, color] = FX.TDOT[t.status] || FX.TDOT.todo;
428
- return el('button', {
429
- onClick: () => openTask({
430
- id: tid, kind: 'task', role: 'TASK', title: t.title, slug, repo, phase: ph.key,
431
- status: t.status, instruction: t.instruction, input: t.input, expect: t.expect,
432
- evalNote: t.evalNote, output: t.output,
433
- }),
434
- style: 'display:flex;align-items:center;gap:9px;width:100%;padding:6px 14px 6px 32px;min-height:34px;border-top:1px solid oklch(1 0 0 / 4%);background:'
435
- + (active ? 'var(--accent)' : 'transparent')
436
- + ';border-left:2px solid ' + (active ? 'var(--brand)' : 'transparent'),
437
- hover: 'background:var(--accent)',
438
- },
439
- el('span', { style: 'flex:none;width:13px;text-align:center;font-size:11px;color:' + color }, iv(dot)),
440
- el('span', { style: 'flex:1;min-width:0;font-size:12.5px;color:var(--foreground);overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, iv(t.title)),
441
- el('span', { style: 'flex:none;font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' }, '↗'),
442
- );
443
- }) : [];
444
-
445
- return el('div', { style: 'border-bottom:1px solid oklch(1 0 0 / 5%)' }, head, rows);
446
- }));
447
- }
448
-
449
386
  /* ── 5a · LINKS (lines 380–398, derivation 1757–1766, 1797–1798) ────────────────────────────
450
387
  * External references — NOT repo files.
451
388
  *
@@ -633,10 +570,7 @@ function linksRow(slug) {
633
570
  style: 'width:100%;font-family:var(--mono);font-size:11.5px;padding:6px 8px;background:var(--secondary);border:1px solid var(--border);color:var(--foreground)',
634
571
  }),
635
572
  el('div', { style: 'display:flex;gap:6px;margin-top:8px;flex-wrap:wrap' },
636
- el('button', {
637
- onClick: () => submitTyped(openSrc, read()),
638
- style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-weight:500',
639
- }, 'record this reference'),
573
+ button({ label: 'record this reference', variant: 'primary', size: 'sm', onClick: () => submitTyped(openSrc, read()) }),
640
574
  resolver && el('button', {
641
575
  onClick: () => askSessionToResolve(slug, openSrc, resolver, read()),
642
576
  style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;background:var(--secondary);border:1px solid var(--border);color:var(--foreground)',
@@ -829,10 +763,7 @@ function artifactRow(slug, a, noHead) {
829
763
  style: 'width:100%;font-family:var(--mono);font-size:11px;padding:5px 7px;background:var(--secondary);border:1px solid var(--border);color:var(--foreground)',
830
764
  }),
831
765
  el('div', { style: 'display:flex;gap:6px;margin-top:7px;flex-wrap:wrap' },
832
- el('button', {
833
- onClick: () => submit(kind),
834
- style: 'font-family:var(--mono);font-size:10px;padding:4px 9px;min-height:26px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-weight:500',
835
- }, 'record'),
766
+ button({ label: 'record', variant: 'primary', size: 'sm', onClick: () => submit(kind) }),
836
767
  el('button', {
837
768
  onClick: () => setState((s) => ({ artLinkForm: { ...s.artLinkForm, [key]: null } })),
838
769
  style: 'font-family:var(--mono);font-size:10px;padding:4px 9px;min-height:26px;background:transparent;border:1px solid var(--border);color:var(--muted-foreground)',
@@ -917,10 +848,7 @@ function artifactRow(slug, a, noHead) {
917
848
  el('div', { style: 'font-size:12px;line-height:1.5;color:var(--muted-foreground)' },
918
849
  iv(live ? FX.artDiffText(dest) : NO_CONNECTOR)),
919
850
  el('div', { style: 'display:flex;gap:6px;margin-top:8px;flex-wrap:wrap' },
920
- el('button', {
921
- onClick: decide(FX.artSyncPushed), disabled: inert,
922
- style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-weight:500' + off,
923
- }, 'repo is source → push'),
851
+ button({ label: 'repo is source → push', variant: 'primary', size: 'sm', disabled: inert, onClick: decide(FX.artSyncPushed) }),
924
852
  el('button', {
925
853
  onClick: decide(FX.artSyncPulled), disabled: inert,
926
854
  style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;background:var(--secondary);border:1px solid var(--border);color:var(--foreground)' + off,
@@ -972,14 +900,7 @@ function realSyncPanel(slug, a, key, dest) {
972
900
  })));
973
901
  };
974
902
 
975
- const btn = (label, primary, onClick) => el('button', {
976
- onClick,
977
- style: 'font-family:var(--mono);font-size:10.5px;padding:5px 10px;min-height:28px;'
978
- + (primary
979
- ? 'background:var(--brand);color:oklch(0.987 0.002 197.1);font-weight:500'
980
- : 'background:var(--secondary);border:1px solid var(--border);color:var(--foreground)'),
981
- hover: primary ? null : 'border-color:var(--ring)',
982
- }, label);
903
+ const btn = (label, primary, onClick) => button({ label, variant: primary ? 'primary' : 'outline', size: 'sm', onClick });
983
904
 
984
905
  return el('div', { style: 'margin-top:6px;padding:9px 11px;border:1px solid var(--border);background:var(--background)' },
985
906
  el('div', { style: 'font-size:12px;line-height:1.5;color:var(--muted-foreground)' },
@@ -1177,58 +1098,6 @@ function cockpitTitle(name, tag, pills) {
1177
1098
  ];
1178
1099
  }
1179
1100
 
1180
- const CK_PH_DOT = { done: 'var(--success)', blocked: 'var(--destructive)', doing: 'var(--info)', todo: 'var(--muted-foreground)' };
1181
-
1182
- /** The phases as the mockup draws them: a row per phase with its count, its tasks beneath while it is open. */
1183
- function phaseBlockCockpit(slug, rec) {
1184
- const phases = FX.phasesFor(rec, slug);
1185
- if (!phases.length) return null;
1186
- const collP = state.collapsedPhases || {};
1187
- const repo = repoOf(slug);
1188
- /* Run 4 (the mockup): only the phase the initiative is at opens by itself — the same pointer the breadcrumb uses (the first
1189
- * blocked step, else the current one). Every other phase shows its row and its count, and opens on a click. A phase the PM has
1190
- * opened or folded keeps that choice (`collapsedPhases` holds true or false for it). */
1191
- const stepAt = Array.isArray(rec.steps) ? FX.targetStepIndex(rec.steps) : -1;
1192
- const atKey = stepAt >= 0 && rec.steps[stepAt] ? rec.steps[stepAt].k : null;
1193
- const anyTaskTab = (state.tabs || []).some((t) => t.id === state.active && t.kind === 'task' && t.slug === slug);
1194
- return el('div', { 'data-cockpit': 'phases', style: 'display:flex;flex-direction:column;gap:2px' }, phases.map((ph) => {
1195
- const pkey = slug + '|' + ph.key;
1196
- const open = Object.prototype.hasOwnProperty.call(collP, pkey) ? !collP[pkey] : ph.key === atKey;
1197
- // the task the initiative is at: the first blocked (else in-progress) task of that phase, marked while no task tab is in front
1198
- const atTask = ph.key === atKey && !anyTaskTab ? ph.tasks.findIndex((t) => t.status === 'blocked') : -1;
1199
- const atIdx = atTask >= 0 ? atTask : (ph.key === atKey && !anyTaskTab ? ph.tasks.findIndex((t) => t.status === 'doing') : -1);
1200
- const head = el('button', {
1201
- onClick: () => setState((s) => ({ collapsedPhases: { ...s.collapsedPhases, [pkey]: open } })),
1202
- style: 'display:flex;align-items:center;gap:8px;min-height:32px;font:500 11px var(--mono);letter-spacing:.08em;text-transform:uppercase;text-align:left;width:100%',
1203
- },
1204
- el('span', null, open ? '▾' : '▸'),
1205
- el('span', { style: 'width:8px;height:8px;border-radius:50%;display:inline-block;flex-shrink:0;background:' + (CK_PH_DOT[ph.status] || CK_PH_DOT.todo) }),
1206
- ph.label.charAt(0).toUpperCase() + ph.label.slice(1),
1207
- el('span', { style: 'margin-left:auto;color:var(--muted-foreground)' }, iv(ph.count)),
1208
- );
1209
- const rows = open ? ph.tasks.map((t, ti) => {
1210
- const tid = FX.taskTabId(slug, ph.key, ti);
1211
- const active = state.active === tid || ti === atIdx;
1212
- const [glyph, color] = FX.TDOT[t.status] || FX.TDOT.todo;
1213
- return el('button', {
1214
- onClick: () => openTask({
1215
- id: tid, kind: 'task', role: 'TASK', title: t.title, slug, repo, phase: ph.key,
1216
- status: t.status, instruction: t.instruction, input: t.input, expect: t.expect,
1217
- evalNote: t.evalNote, output: t.output,
1218
- }),
1219
- 'aria-current': ti === atIdx ? 'step' : null,
1220
- style: 'display:flex;align-items:center;gap:10px;min-height:34px;padding:0 8px 0 24px;border-radius:8px;font:400 13px var(--sans);text-align:left;width:100%;background:' + (active ? 'var(--accent)' : 'transparent'),
1221
- hover: 'background:var(--accent)',
1222
- },
1223
- el('span', { style: 'color:' + color }, glyph),
1224
- el('span', { style: 'flex-grow:1' }, iv(t.title)),
1225
- el('span', { style: 'color:var(--muted-foreground)' }, '↗'),
1226
- );
1227
- }) : [];
1228
- return [head, rows];
1229
- }));
1230
- }
1231
-
1232
1101
  /** The blocker card (workspace-cockpit-nav): drawn only when a rung of the ladder is blocking, in the engine's own words. The cockpit
1233
1102
  * draws it as the mockup's row: the words on the left, and on the right the slot of the Grant button, which stays empty until the
1234
1103
  * grants ledger behind it exists (PRD Q3) — the slot keeps the card's height, so nothing moves when the button arrives. */
@@ -1275,6 +1144,7 @@ function initBody(slug) {
1275
1144
  out.push(headerBlock(slug, rec.tag || '', rec.pills || [], rec.crumb || null, lanePicker(slug, rec)));
1276
1145
  if (rec.blocker) out.push(el('div', { style: 'margin:12px 14px 0' }, blockerCard(rec.blocker)));
1277
1146
  }
1147
+ { const bd = breakdownEntry(slug, rec, repoOf(slug)); if (bd) out.push(bd); }
1278
1148
  { const sb = statusBanner(repoOf(slug), slug, rec); if (sb) out.push(sb); }
1279
1149
  { const lc = lifecycleRow(repoOf(slug), slug); if (lc) { out.push(lc); const ln = lifecycleNotes(rec); if (ln) out.push(ln); } }
1280
1150
 
@@ -1291,8 +1161,6 @@ function initBody(slug) {
1291
1161
  const nextB = nextStepsBlock(slug, rec);
1292
1162
  if (nextB) out.push(nextB);
1293
1163
 
1294
- if (!ck) { const phases = phaseBlock(slug, rec); if (phases) out.push(phases); }
1295
-
1296
1164
  { const fb = factsBlock(rec); if (fb) out.push(fb); }
1297
1165
 
1298
1166
  out.push(el('div', { style: 'border-top:1px solid var(--border)' },
@@ -1312,10 +1180,9 @@ function initBody(slug) {
1312
1180
  * column, so they keep it (`FULL`). */
1313
1181
  const FULL = 'margin:0 -16px';
1314
1182
  function cockpitInitBody(slug, rec) {
1315
- const ph = phaseBlockCockpit(slug, rec);
1316
1183
  const out = [...cockpitTitle(slug, rec.tag || '', rec.pills || [])];
1317
1184
  if (rec.blocker) out.push(blockerCard(rec.blocker));
1318
- if (ph) out.push(CK_SEP(), ph);
1185
+ { const bd = breakdownEntry(slug, rec, repoOf(slug)); if (bd) out.push(bd); }
1319
1186
  const more = [crumbBlock(rec.crumb || null), lanePicker(slug, rec)].filter(Boolean);
1320
1187
  if (more.length) out.push(el('div', null, more));
1321
1188
  const wide = [];
@@ -1567,10 +1434,7 @@ function fileNewBody() {
1567
1434
  }
1568
1435
 
1569
1436
  out.push(el('div', { style: 'padding:8px 14px 12px;border-bottom:1px solid var(--border)' },
1570
- el('button', {
1571
- onClick: nw.onCreate,
1572
- style: 'padding:5px 12px;min-height:30px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-size:12px;font-weight:500',
1573
- }, iv(nw.createLabel)),
1437
+ button({ label: nw.createLabel, variant: 'primary', onClick: nw.onCreate }),
1574
1438
  ));
1575
1439
 
1576
1440
  out.push(el('div', { style: 'margin-top:auto;padding:8px 14px;border-top:1px solid var(--border);font-family:var(--mono);font-size:10px;color:var(--muted-foreground)' },
@@ -1625,25 +1489,14 @@ export function renderDetail(host) {
1625
1489
  else if (kind === 'none') body = [noneBody()];
1626
1490
  else body = modeDetailBody(kind);
1627
1491
 
1628
- // the cockpit draws an initiative without the panel's own title bar (the top bar's breadcrumb names it); its fold control returns with Focus
1492
+ // the cockpit draws an initiative without the panel's own title bar (the top bar's breadcrumb names it)
1629
1493
  const bare = cockpitLook() && kind === 'init';
1630
- /* The cockpit's column (outside Focus) is the mockup's: the page's own background, and for an initiative 16px of padding and a 12px
1631
- * gap between sections. The legacy shell, a phone and Focus keep the panel's sidebar fill and draw their own padding. */
1632
- const ckCol = cockpitLook() && !state.layoutFocus;
1494
+ /* The cockpit's column is the mockup's: the page's own background, and for an initiative 16px of padding and a 12px
1495
+ * gap between sections. The legacy shell and a phone keep the panel's sidebar fill and draw their own padding. */
1496
+ const ckCol = cockpitLook();
1633
1497
  host.style.background = ckCol ? 'transparent' : 'var(--sidebar)';
1634
1498
  host.style.padding = ckCol && bare ? '16px' : '';
1635
1499
  host.style.gap = ckCol && bare ? '12px' : '';
1636
- if (bare && state.layoutFocus) {
1637
- // Focus (Run 2): the detail is a 360px slice with a sticky bar that names what is asked of the PM and leads back to the lists
1638
- const slug = selectedSlug();
1639
- const rows = (state.statuses && state.statuses[state.product]) || {};
1640
- const key = window.PMOSStatusGroups ? window.PMOSStatusGroups.keyOf(rows[slug]) : 'unknown';
1641
- const ask = key === 'needs-you' ? 'Needs you' : key === 'awaiting-acceptance' ? 'Awaiting your acceptance' : 'Nothing is asked of you';
1642
- const bar = el('div', { 'data-focus': 'bar', style: 'position:sticky;top:0;z-index:2;display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--border);background:var(--sidebar)' },
1643
- el('span', { style: 'font:500 12px var(--sans);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap' }, ask),
1644
- el('button', { 'data-focus': 'navigate', onClick: () => setState({ layoutFocus: false }), style: 'margin-left:auto;font:500 11px var(--sans);border:1px solid var(--border);border-radius:6px;padding:2px 8px', hover: 'background:var(--accent)' }, 'Navigate'));
1645
- return setChildren(host, [bar, ...body]);
1646
- }
1647
1500
  /* The cockpit's initiative has no title bar, so its fold control floats at the column's top right. The column folds to the 36px
1648
1501
  * strip app.js draws ("initiative ⟩"), which opens it again. Nothing in the column moves (design-parity names the control). */
1649
1502
  host.style.position = bare ? 'relative' : '';
@@ -866,27 +866,9 @@ export function renderInitiatives(host) {
866
866
  chip('objective', !byStatus, () => setState({ groupBy: 'objective' }), 'by-objective'),
867
867
  chip('status', byStatus, () => setState({ groupBy: 'status' }), 'by-status'),
868
868
  byStatus ? chip('active only', !!state.activeOnly, () => setState({ activeOnly: !state.activeOnly }), 'active-only') : null,
869
- SG ? chip('what do these mean?', !!state.legendOpen, () => setState({ legendOpen: !state.legendOpen }), 'legend-toggle') : null,
870
869
  byStatus && statusErr ? el('span', { 'data-r16': 'status-error', style: 'flex-basis:100%;font-size:10.5px;color:var(--destructive)' }, 'status could not be read — ' + statusErr + ' (every initiative is under Unknown)') : null);
871
870
 
872
- /* THE LEGEND (status-clarity R1). One row per group, read from the same table that files the rows, so what this
873
- * says and where a row lands cannot disagree: what it means, the fact it is read from, who acts, what moves it,
874
- * and what the PM can do by hand. */
875
- const OWNER_WORD = { pm: 'you', agent: 'the agent', system: 'nobody (derived)' };
876
- const legend = !FIXTURE && SG && state.legendOpen ? el('div', { 'data-r18': 'legend',
877
- style: 'display:flex;flex-direction:column;gap:8px;margin:0 12px 8px;padding:8px 10px;border:1px solid var(--border);background:var(--secondary)' },
878
- el('div', { style: 'font-size:11px;color:var(--muted-foreground);line-height:1.5' },
879
- 'A status is never typed. It is worked out again each time from the ledgers and git. You can only hold, close, cancel or reopen an initiative by hand.'),
880
- SG.GROUPS.map((g) => el('div', { 'data-r18': 'legend-row', 'data-key': g.key, style: 'display:flex;flex-direction:column;gap:2px;padding-top:6px;border-top:1px solid var(--border)' },
881
- el('div', { style: 'display:flex;gap:7px;align-items:baseline' },
882
- el('span', { style: 'font-size:12px;font-weight:600;color:' + (STATUS_TONE[g.key] || 'var(--foreground)') }, g.label),
883
- el('span', { style: 'font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted-foreground)' }, 'acts: ' + (OWNER_WORD[g.owner] || g.owner))),
884
- el('div', { style: 'font-size:11.5px;line-height:1.5;color:var(--foreground)' }, g.meaning),
885
- el('div', { style: 'font-size:10.5px;line-height:1.5;color:var(--muted-foreground)' }, 'read from: ' + g.source),
886
- el('div', { style: 'font-size:10.5px;line-height:1.5;color:var(--muted-foreground)' }, 'moves: ' + g.moves),
887
- el('div', { style: 'font-size:10.5px;line-height:1.5;color:var(--muted-foreground)' }, 'you can: ' + g.pmCan)))) : null;
888
-
889
- const kids = [header, searchBox, groupBar, legend];
871
+ const kids = [header, searchBox, groupBar];
890
872
  /* THE WORKING SET, pinned above the Objectives.
891
873
  *
892
874
  * It is `groupNode` — the same element, the same collapse key, the same forced-open-on-search
@@ -28,9 +28,11 @@ import { syncButton } from './lifecycle.js';
28
28
  import { treesBlock, branchForm, openBranchForm, treeNoteFor } from './panel-trees.js';
29
29
  import { state, setState, FIXTURE } from './state.js';
30
30
  import {
31
- productsList, repoRow, firstSlugOf, MODE_META, SETTINGS_GROUPS, ADDABLE_PRODUCTS,
31
+ productsList, repoRow, firstSlugOf, ADDABLE_PRODUCTS,
32
32
  INGESTED_KNOW, INGEST_LOG_GITHUB, INGEST_LOG_LOCAL, INGEST_ALREADY,
33
33
  } from './fixture.js';
34
+ import { askConfirm, askFields } from './dialogs.js';
35
+ import { toast } from './primitives.js';
34
36
 
35
37
  /** An interpolated value, in the inline span the prototype's runtime puts it in. */
36
38
  const ip = (v) => el('span', null, v);
@@ -161,69 +163,6 @@ export function selectRepo(id) {
161
163
  }).catch(() => { /* an unreadable repository still selects; panel 3 reports the read */ });
162
164
  }
163
165
 
164
- /* ── settings & tools ────────────────────────────────────────────────────────────────────────
165
- * EXACTLY two groups, and no `workflow` entry. The prototype's `modeMeta` still carries `workflow`
166
- * because the mode exists in code, but it is deliberately absent from the drawer: a workflow is
167
- * decided by the agent at runtime, not configured by hand (PRD, panel-1 scope). The filter below is
168
- * belt and braces — it makes the absence a property of THIS module, so it cannot regress by someone
169
- * adding an id to the seed list.
170
- */
171
- /* overhaul-r1 R6.1 — in LIVE mode, a mode with no server behind it is not offered. `agents`,
172
- * `memory` and `connections` start empty and every edit is a client-only setState that no server
173
- * receives (modes.js `seedOr`), so they looked broken because they were. They come back in a later
174
- * release wired or replaced. Fixture mode keeps them: it is the prototype's recorded state, and the
175
- * design comparison reads it. Exported so the live-mode test can assert the list, not a screenshot. */
176
- export const UNBACKED_MODES = ['agent', 'connect']; // mode ids (labels: agents · connections). `memory` left this list in R9 (D72): the repository's ledger backs it.
177
- /* overhaul-r4 — the PORTFOLIO mode joins "all repositories" in live mode only (the fixture drawer is the
178
- * prototype's, and the design comparison reads it). Its meta lives here, not in fixture.js, for the
179
- * same reason. */
180
- const META = Object.assign({}, MODE_META, { portfolio: ['▦', 'portfolio'], guide: ['?', 'guide'], validate: ['◉', 'validate'], today: ['☀', 'today'] });
181
- const LIVE_EXTRA = { 'all repositories': ['today', 'portfolio', 'guide', 'validate'] };
182
- const DRAWER_GROUPS = SETTINGS_GROUPS.map((g) => ({
183
- label: g.label,
184
- items: (FIXTURE ? [] : LIVE_EXTRA[g.label] || []).concat(
185
- g.items.filter((id) => id !== 'workflow' && MODE_META[id] && (FIXTURE || !UNBACKED_MODES.includes(id)))),
186
- })).filter((g) => g.items.length);
187
-
188
- function drawerButton(id) {
189
- const [glyph, label] = META[id];
190
- const on = state.mode === id;
191
- return el('button', {
192
- onClick: () => setState({ mode: id, settingsOpen: false, col1: true }),
193
- style: 'display:flex;align-items:center;gap:9px;width:100%;padding:7px 14px;min-height:38px;border-left:2px solid '
194
- + (on ? 'var(--brand)' : 'transparent') + ';background:' + (on ? 'var(--accent)' : 'transparent')
195
- + ';color:' + (on ? 'var(--foreground)' : 'var(--muted-foreground)'),
196
- hover: 'background:var(--accent)',
197
- },
198
- el('span', { style: 'flex:none;width:16px;text-align:center;font-size:13px;line-height:1' }, ip(glyph)),
199
- el('span', { style: 'font-size:12px' }, ip(label)),
200
- );
201
- }
202
-
203
- function drawer() {
204
- const kids = [];
205
- if (state.settingsOpen) {
206
- const groups = [];
207
- for (const g of DRAWER_GROUPS) {
208
- groups.push(el('div', {
209
- style: 'padding:8px 14px 3px;font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-foreground)',
210
- }, ip(g.label)));
211
- for (const id of g.items) groups.push(drawerButton(id));
212
- }
213
- kids.push(el('div', { style: 'max-height:44vh;overflow-y:auto;border-bottom:1px solid var(--border)' }, groups));
214
- }
215
- kids.push(el('button', {
216
- onClick: () => setState((s) => ({ settingsOpen: !s.settingsOpen })),
217
- style: 'display:flex;align-items:center;gap:9px;width:100%;padding:11px 14px;min-height:44px;color:var(--muted-foreground)',
218
- hover: 'color:var(--foreground);background:var(--accent)',
219
- },
220
- el('span', { style: 'flex:none;width:14px;text-align:center;font-size:14px' }, '⚙'),
221
- el('span', { style: 'font-size:12px' }, 'settings & tools'),
222
- el('span', { style: 'margin-left:auto;font-size:10px' }, ip(state.settingsOpen ? '▾' : '▸')),
223
- ));
224
- return el('div', { style: 'border-top:1px solid var(--border);flex:none' }, kids);
225
- }
226
-
227
166
  /* ── the rows ────────────────────────────────────────────────────────────────────────────────── */
228
167
 
229
168
  function repoButton(tuple, product) {
@@ -259,17 +198,29 @@ export function treesOf(name) {
259
198
  return dataMod && typeof dataMod.productTrees === 'function' ? dataMod.productTrees(name) : [];
260
199
  }
261
200
 
201
+ /* A refusal is told twice: the legacy list draws repoNotice, and the frame's Settings page, which draws no notice, gets the toast. */
202
+ function refused(name, text) {
203
+ setState({ repoNotice: { name, tone: 'bad', text } });
204
+ toast({ text, tone: 'error' });
205
+ }
206
+
262
207
  export async function renameRepo(name) {
263
- const win = typeof window !== 'undefined' && window.prompt ? window : null;
264
- const label = win ? win.prompt('Rename “' + name + '” to:', name) : null;
265
- if (label === null || !String(label).trim()) return;
266
- // the collapsed rail shows an abbreviation; empty keeps (or restores) the one derived from the name
208
+ // the legacy rail shows an abbreviation; empty keeps (or restores) the one derived from the name
267
209
  const cur = dataMod.repoInitials ? dataMod.repoInitials(name) : null;
268
- const ini = win.prompt('Abbreviation on the collapsed rail (up to 3 letters or digits; empty = derived from the name):', cur || '');
269
- if (ini === null) return;
210
+ const answer = await askFields({
211
+ title: 'Rename “' + name + '”', confirmLabel: 'Rename',
212
+ fields: [
213
+ { key: 'label', label: 'Name', value: name },
214
+ { key: 'ini', label: 'Short name for the legacy rail: up to 3 letters or digits. Leave it empty to use the first letters of the name.', value: cur || '' },
215
+ ],
216
+ });
217
+ if (!answer) return;
218
+ const label = answer.label;
219
+ const ini = answer.ini;
220
+ if (!String(label).trim()) return;
270
221
  if (String(label).trim() === name && String(ini).trim() === (cur || '')) return;
271
222
  const out = await dataMod.renameRepository(name, String(label).trim(), String(ini).trim());
272
- if (!out || out.ok === false) { setState({ repoNotice: { name, tone: 'bad', text: (out && out.error) || 'rename refused' } }); return; }
223
+ if (!out || out.ok === false) { refused(name, (out && out.error) || 'The rename was refused.'); return; }
273
224
  await connectData();
274
225
  setState({ product: String(label).trim(), repoNotice: null });
275
226
  }
@@ -312,19 +263,21 @@ async function switchProfile(name) {
312
263
  ? 'Switch “' + name + '” to document work?\n\nLanes offered: ' + lanesText(name, 'pm-artifacts') + '.\nThe ladder becomes route, anchor, brief, draft, review, approved, handed off — no contract, build, eval or explainer.'
313
264
  : 'Switch “' + name + '” to code work?\n\nLanes offered: ' + lanesText(name, 'product-code') + '.\nThe ladder returns to spec, contract, build, eval, explainer, acceptance.';
314
265
  const tail = '\n\nAn initiative whose lane is not in the new set is treated as the strictest lane (' + (toDocs ? 'a governed prd' : 'feature') + '). No initiative record is edited.\n\nThis ' + (toDocs ? 'writes' : 'removes') + ' the file .pmos-profile in the repository. It is a change for you to commit.';
315
- const ok = typeof window === 'undefined' || !window.confirm ? true : window.confirm(msg + tail);
266
+ const ok = await askConfirm({ title: toDocs ? 'Switch “' + name + '” to document work?' : 'Switch “' + name + '” to code work?', body: msg + tail, confirmLabel: 'Switch' });
316
267
  if (!ok) return;
317
268
  const out = await dataMod.setRepoProfile(name, toDocs ? 'pm-artifacts' : 'product-code');
318
269
  if (!out || out.ok === false) { setState({ repoNotice: { name, tone: 'bad', text: (out && out.error) || 'the profile was not changed' } }); return; }
319
270
  setState({ repoNotice: { name, tone: null, text: 'now ' + (toDocs ? 'document work (pm-artifacts)' : 'code work (product-code)') + ' — .pmos-profile ' + (toDocs ? 'written' : 'removed') + '; commit it' } });
320
271
  }
321
272
 
322
- async function removeRepo(name) {
323
- const ok = typeof window === 'undefined' || !window.confirm ? true
324
- : window.confirm('Remove “' + name + '” from PMOS?\n\nThis forgets the repository and its worktree rows. No folder is deleted.');
273
+ export async function removeRepo(name) {
274
+ const ok = await askConfirm({
275
+ title: 'Remove “' + name + '” from PMOS?', confirmLabel: 'Remove', danger: true,
276
+ body: 'This forgets the repository and its worktree rows. No folder is deleted.',
277
+ });
325
278
  if (!ok) return;
326
279
  const out = await dataMod.removeRepository(name);
327
- if (!out || out.ok === false) { setState({ repoNotice: { name, tone: 'bad', text: (out && out.error) || 'remove refused' } }); return; }
280
+ if (!out || out.ok === false) { refused(name, (out && out.error) || 'The removal was refused.'); return; }
328
281
  realRepos = (realRepos || []).filter((t) => t[0] !== name);
329
282
  setState({ repoNotice: null });
330
283
  }
@@ -502,7 +455,7 @@ export function renderRepos(host) {
502
455
  ),
503
456
  );
504
457
 
505
- setChildren(host, [header, scroll, drawer()]);
458
+ setChildren(host, [header, scroll]);
506
459
  return host;
507
460
  }
508
461
 
@@ -521,7 +474,7 @@ export function renderRepos(host) {
521
474
  const inputStyle = 'font-family:var(--mono);font-size:11.5px;padding:6px 9px;border:1px solid var(--input);min-height:32px';
522
475
  const labelStyle = 'font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-foreground);padding-top:8px';
523
476
  const rowStyle = 'display:grid;grid-template-columns:76px 1fr;gap:10px;padding:10px 14px';
524
- const goStyle = 'padding:5px 12px;min-height:30px;background:var(--brand);color:oklch(0.987 0.002 197.1);font-size:12px;font-weight:500';
477
+ const goStyle = 'padding:5px 12px;min-height:30px;background:var(--primary);color:var(--primary-foreground);font:500 12px var(--sans)';
525
478
 
526
479
  /**
527
480
  * A form field. `oninput` writes the value straight onto the state object WITHOUT a render — a
@@ -786,6 +739,3 @@ export function renderAddProduct(host) {
786
739
  return host;
787
740
  }
788
741
 
789
- /* Exposed for the browser suite: the drawer's exact shape is an acceptance criterion (two groups,
790
- * no `workflow`), and an assertion should be able to read it without scraping the DOM. */
791
- export const settingsGroups = () => DRAWER_GROUPS.map((g) => ({ label: g.label, items: [...g.items] }));