create-pmos 0.124.0 → 0.126.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 (129) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +2 -2
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/scripts/nine-stages.js +81 -0
  6. package/payload/scripts/workflow-state.js +2 -0
  7. package/payload/state/discoveries.md +1 -1
  8. package/payload/state/log.md +1 -1
  9. package/workspace/c3-one-home-test.js +131 -0
  10. package/workspace/fixtures/initiatives-api/list.json +171 -3
  11. package/workspace/fixtures/initiatives-api/roadmap.json +228 -4
  12. package/workspace/fixtures/initiatives-api/sheet.json +57 -1
  13. package/workspace/fixtures/one-home-baseline.json +83 -0
  14. package/workspace/guide/briefing-cards.md +40 -0
  15. package/workspace/guide/index.md +1 -0
  16. package/workspace/guide/layout.md +2 -0
  17. package/workspace/lib/engine/nine-stages.js +81 -0
  18. package/workspace/lib/engine/workflow-state.js +2 -0
  19. package/workspace/lib/facts-async.js +51 -0
  20. package/workspace/lib/item-steps.js +52 -0
  21. package/workspace/lib/jira-status.js +8 -3
  22. package/workspace/lib/jobs-to-be-done.js +52 -0
  23. package/workspace/lib/needs-overdue.js +41 -0
  24. package/workspace/lib/outbox-store.js +87 -0
  25. package/workspace/lib/pm-items.js +115 -0
  26. package/workspace/lib/route-modules.js +21 -0
  27. package/workspace/lib/routes-briefing.js +109 -0
  28. package/workspace/lib/routes-c3-init.js +53 -0
  29. package/workspace/lib/routes-c3-life.js +119 -0
  30. package/workspace/lib/routes-c3-strategy.js +169 -0
  31. package/workspace/lib/routes-c3-timeline.js +191 -0
  32. package/workspace/lib/routes-calendar-import.js +60 -0
  33. package/workspace/lib/routes-calendar-views.js +61 -0
  34. package/workspace/lib/routes-doc-health.js +79 -0
  35. package/workspace/lib/routes-doc-sheet.js +169 -0
  36. package/workspace/lib/routes-forecast.js +85 -0
  37. package/workspace/lib/routes-hooks.js +121 -0
  38. package/workspace/lib/routes-initiatives.js +3 -0
  39. package/workspace/lib/routes-intake.js +24 -0
  40. package/workspace/lib/routes-outbox.js +97 -0
  41. package/workspace/lib/routes-pm-items.js +216 -0
  42. package/workspace/lib/routes-records.js +61 -0
  43. package/workspace/lib/routes-releases.js +129 -0
  44. package/workspace/lib/routes-report-facts.js +84 -0
  45. package/workspace/lib/routes-report-families.js +32 -0
  46. package/workspace/lib/routes-roadmap-ops.js +141 -0
  47. package/workspace/lib/routes-runs.js +119 -0
  48. package/workspace/lib/routes-signals.js +50 -0
  49. package/workspace/lib/run-answers.js +69 -0
  50. package/workspace/package-lock.json +2 -2
  51. package/workspace/package.json +1 -1
  52. package/workspace/public/c3.html +26 -0
  53. package/workspace/public/pm/briefing-deck.js +413 -0
  54. package/workspace/public/pm/briefing-model.js +211 -0
  55. package/workspace/public/pm/c3-breakdown.js +240 -0
  56. package/workspace/public/pm/c3-brief.js +278 -0
  57. package/workspace/public/pm/c3-cal-strategy.js +528 -0
  58. package/workspace/public/pm/c3-case.js +185 -0
  59. package/workspace/public/pm/c3-daypart.js +176 -0
  60. package/workspace/public/pm/c3-demo-brief-ix.js +350 -0
  61. package/workspace/public/pm/c3-demo-cal-ix.js +74 -0
  62. package/workspace/public/pm/c3-demo-cal2.js +66 -0
  63. package/workspace/public/pm/c3-demo-cal3.js +173 -0
  64. package/workspace/public/pm/c3-demo-data.js +560 -0
  65. package/workspace/public/pm/c3-demo-init.js +373 -0
  66. package/workspace/public/pm/c3-demo-life.js +77 -0
  67. package/workspace/public/pm/c3-demo-misc2.js +46 -0
  68. package/workspace/public/pm/c3-demo-plan2.js +30 -0
  69. package/workspace/public/pm/c3-demo-rep-ix.js +126 -0
  70. package/workspace/public/pm/c3-demo-rep2.js +92 -0
  71. package/workspace/public/pm/c3-demo-term.js +153 -0
  72. package/workspace/public/pm/c3-demo-time.js +49 -0
  73. package/workspace/public/pm/c3-demo-work-ix.js +80 -0
  74. package/workspace/public/pm/c3-demo-work2.js +13 -0
  75. package/workspace/public/pm/c3-demo.js +113 -0
  76. package/workspace/public/pm/c3-doc-sheet.js +314 -0
  77. package/workspace/public/pm/c3-dock.js +180 -0
  78. package/workspace/public/pm/c3-docs.js +290 -0
  79. package/workspace/public/pm/c3-expand.js +96 -0
  80. package/workspace/public/pm/c3-init-extras.js +170 -0
  81. package/workspace/public/pm/c3-lifecycle-panels.js +476 -0
  82. package/workspace/public/pm/c3-misc-panels.js +89 -0
  83. package/workspace/public/pm/c3-plan-panels.js +163 -0
  84. package/workspace/public/pm/c3-plan.js +192 -0
  85. package/workspace/public/pm/c3-roadmap.js +249 -0
  86. package/workspace/public/pm/c3-sheet.js +107 -0
  87. package/workspace/public/pm/c3-shell.js +674 -0
  88. package/workspace/public/pm/c3-signals.js +196 -0
  89. package/workspace/public/pm/c3-strategy-panels.js +45 -0
  90. package/workspace/public/pm/c3-swipe.js +230 -0
  91. package/workspace/public/pm/c3-terminal-data.js +26 -0
  92. package/workspace/public/pm/c3-terminal-edge.js +91 -0
  93. package/workspace/public/pm/c3-terminal-icons.js +41 -0
  94. package/workspace/public/pm/c3-terminal-results.js +141 -0
  95. package/workspace/public/pm/c3-terminal.js +52 -0
  96. package/workspace/public/pm/c3-tickets.js +163 -0
  97. package/workspace/public/pm/c3-timeline.js +201 -0
  98. package/workspace/public/pm/c3-toast.js +16 -0
  99. package/workspace/public/pm/calendar-views.js +1014 -0
  100. package/workspace/public/pm/console3-lift.md +220 -0
  101. package/workspace/public/pm/console3.css +1547 -0
  102. package/workspace/public/pm/dest-initiatives.js +16 -3
  103. package/workspace/public/pm/dest-mywork.js +8 -1
  104. package/workspace/public/pm/dest-reports.js +47 -1
  105. package/workspace/public/pm/dest-today.js +55 -1
  106. package/workspace/public/pm/doc-health-panel.js +110 -0
  107. package/workspace/public/pm/doc-rail.js +9 -3
  108. package/workspace/public/pm/lifecycle-lens-model.js +228 -0
  109. package/workspace/public/pm/lifecycle-lens.js +261 -0
  110. package/workspace/public/pm/new-initiative-sheet.js +116 -21
  111. package/workspace/public/pm/outbox-panel.js +125 -0
  112. package/workspace/public/pm/pm-work-view.js +895 -0
  113. package/workspace/public/pm/releases-view.js +248 -0
  114. package/workspace/public/pm/report-families-view.js +503 -0
  115. package/workspace/public/pm/run-card.js +244 -0
  116. package/workspace/public/pm/sessions-panel.js +149 -0
  117. package/workspace/public/pm/shell-bell.js +162 -0
  118. package/workspace/public/pm/shell-model.js +83 -0
  119. package/workspace/public/pm/shell-phone.js +124 -0
  120. package/workspace/public/pm/shell-readaloud.js +53 -0
  121. package/workspace/public/pm/shell-recording.js +110 -0
  122. package/workspace/public/pm/shell-ticker.js +90 -0
  123. package/workspace/public/pm/slide-confirm.js +163 -0
  124. package/workspace/public/pm/timeline-forecast-view.js +164 -0
  125. package/workspace/public/pm/today-day.js +8 -2
  126. package/workspace/public/pm/tokens.css +1 -0
  127. package/workspace/server.js +5 -2
  128. package/workspace/test-files.json +21 -3
  129. package/workspace/test-lists/browser-suites-green.json +5 -0
@@ -0,0 +1,164 @@
1
+ /* pm/timeline-forecast-view.js — the Timeline view of Initiatives (console3, read only), built like the mockup's Timeline (console3.html, patch21).
2
+ *
3
+ * One initiative at a time, as the mockup: the LEFT column is the Timeline-history card (the track of checks with Replay, the Gantt of every item with its
4
+ * dependency elbows, the critical path, the promised line, the thin forecast bar, the dashed "where it was before"); the RIGHT column is What changed (who,
5
+ * what, why), the forecast and the scope at each check, and the dates that reach the Calendar. The All scope shows the forecast of every initiative at every
6
+ * check (tap a cell to replay it) and a chooser. The Gantt draws the plan items, the Breakdown tasks and the Jira tickets of the initiative TOGETHER.
7
+ * Every check is a whole frame from GET /api/c3-timeline (the server folds the events and plans each check with plan.js), so a click on a check, and Replay,
8
+ * REDRAW the Gantt from that frame; Replay steps through the checks every 1.3 seconds, as the mockup's isplay does, and stops on any press.
9
+ * Capacity and the rebalance proposal were removed: the mockup's Timeline has neither (its capacity is on Breakdown).
10
+ * Drawing lives in c3-timeline.js. Nothing here writes: the apply control is disabled and no write route is called.
11
+ * Self-registers as the 'forecast' Initiatives view, like Releases. */
12
+ import { el } from './dom.js';
13
+ import { ensureComponentsCss } from './primitives.js';
14
+ import { pmGet } from './http.js';
15
+ import { registerInitiativesView } from './registry.js';
16
+ import { ensureC3Css, c3ic, c3ring, c3Phone } from './pm-work-view.js';
17
+ import { historyCard, whatChangedCard, driftSvg, creepSvg, calSyncCard, calSyncFromCommits, matrixCard, dlabS, short } from './c3-timeline.js';
18
+ import { goInitiative } from './c3-init-extras.js';
19
+ import { progressCard, whySlipsCard, dependenciesCard, slipFacts } from './c3-plan-panels.js';
20
+
21
+ const views = new WeakMap();
22
+ const qs = (V, path, params) => path + '?' + params + (V.ctx.ws ? '&ws=' + encodeURIComponent(V.ctx.ws) : '');
23
+ const titleOf = (V, id) => ((V.ctx.rows || []).find((r) => r.id === id) || {}).title || id;
24
+
25
+ function stopReplay(V) { if (V.timer) { clearInterval(V.timer); V.timer = null; } }
26
+
27
+ function problems(fr) {
28
+ if (!fr || !fr.problems || !fr.problems.length) return null;
29
+ return el('ul', { class: 'chgl', 'data-fc-problems': '' }, fr.problems.map((p) => el('li', { class: 'warn' }, c3ic('alert', 'sm'),
30
+ el('span', null, p.kind === 'cycle' ? 'Dependency cycle: ' + p.path.join(' > ') : p.item + ' waits for ' + p.dependency + ', which is not in this plan'), el('span', { class: 'tag' }, 'plan'))));
31
+ }
32
+
33
+ function handlers(V) {
34
+ return {
35
+ snap: (i) => { stopReplay(V); V.snap = i; render(V); },
36
+ replay: () => {
37
+ stopReplay(V);
38
+ const d = V.data[V.scope]; if (!d || !d.frames || d.frames.length < 2) return;
39
+ V.snap = 0; render(V);
40
+ V.timer = setInterval(() => { if (V.disposed || V.snap >= d.frames.length - 1) { stopReplay(V); return; } V.snap++; render(V); }, 1300);
41
+ },
42
+ };
43
+ }
44
+
45
+ /** The mockup's lh header for one initiative: the answer first. */
46
+ function lens(V, d, fr) {
47
+ const name = titleOf(V, V.scope);
48
+ const slip = fr.slip_days;
49
+ const crit = fr.items.filter((t) => t.critical).slice(0, 3).map((t) => t.title);
50
+ const done = fr.items.filter((t) => t.state === 'done').length;
51
+ return el('div', { class: 'lh', 'data-tl-lh': '' }, el('span', { class: 'ki' }, c3ic('clock')),
52
+ el('div', { style: 'min-width:0' }, el('b', null, slip > 0 ? name + ' ships ' + slip + ' day' + (slip === 1 ? '' : 's') + ' late' : fr.promised ? name + ' holds ' + dlabS(fr.promised) : name + ': ' + fr.items.length + ' item' + (fr.items.length === 1 ? '' : 's')),
53
+ el('div', { class: 'dim' }, crit.length ? 'The critical path runs through ' + crit.join(', ') + '.' : 'No item waits on another, so there is no critical path yet.')),
54
+ c3ring(done, Math.max(1, fr.items.length), 44, done + '/' + fr.items.length));
55
+ }
56
+
57
+ function emptyOne(V, d) {
58
+ return el('div', { class: 'fold', 'data-fc-empty': '' }, c3ic('check', 'sm'), 'Nothing is planned for ' + titleOf(V, V.scope) + ' yet: no plan items, no Breakdown tasks and no Jira tickets. The timeline draws all three once they exist.' + (d && d.notes && d.notes.length ? ' ' + d.notes.join(' ') : ''));
59
+ }
60
+
61
+ function oneBody(V) {
62
+ const d = V.data[V.scope];
63
+ if (!d) return { head: null, body: null };
64
+ const frames = d.frames || [];
65
+ if (!frames.length || !frames.some((f) => f.items.length)) return { head: null, body: emptyOne(V, d) };
66
+ const n = Math.max(0, Math.min(V.snap == null ? frames.length - 1 : V.snap, frames.length - 1));
67
+ const fr = frames[n]; const h = handlers(V);
68
+ const todayFr = frames[frames.length - 1];
69
+ const notes = (d.notes || []).length ? el('div', { class: 'dim', style: 'font-size:var(--fs-2)', 'data-tl-notes': '' }, d.notes.join(' ')) : null;
70
+ const srcs = d.sources || {};
71
+ const where = el('div', { class: 'dim', style: 'font-size:var(--fs-2)', 'data-tl-sources': '' }, `Drawn together: ${srcs.plan || 0} plan item${srcs.plan === 1 ? '' : 's'}, ${srcs.breakdown || 0} Breakdown task${srcs.breakdown === 1 ? '' : 's'}, ${srcs.jira || 0} Jira ticket${srcs.jira === 1 ? '' : 's'}.`);
72
+ const body = el('div', { class: 'brief sbb' }, el('div', { class: 'bcols' },
73
+ el('div', { class: 'bcol l' }, problems(fr), historyCard(frames, n, h, d.today), where, notes),
74
+ el('div', { class: 'bcol r' }, whatChangedCard(frames, n),
75
+ el('section', { class: 'card' }, el('h3', null, c3ic('clock'), 'Forecast at each check'), driftSvg(frames, n)),
76
+ el('section', { class: 'card' }, el('h3', null, c3ic('alert'), 'Scope at each check'), creepSvg(frames, n)),
77
+ calSyncCard(todayFr))));
78
+ return { head: lens(V, d, todayFr), body };
79
+ }
80
+
81
+ function allBody(V) {
82
+ const d = V.data.all;
83
+ if (!d) return { head: null, body: null };
84
+ const titles = {}; (d.rows || []).forEach((r) => { titles[r.id] = titleOf(V, r.id); });
85
+ const rows = (d.rows || []).filter((r) => r.cells.some(Boolean));
86
+ const late = rows.filter((r) => { const c = r.cells[r.cells.length - 1]; return c && c.promised && c.forecast_end > c.promised; }).length;
87
+ const head = el('div', { class: 'lh', 'data-tl-lh': '' }, el('span', { class: 'ki' }, c3ic('clock')), el('div', { style: 'min-width:0' }, el('b', null, rows.length ? (late ? late + ' of ' + rows.length + ' initiatives miss their date' : 'Every initiative holds its date') : 'No initiative has a plan yet'),
88
+ el('div', { class: 'dim' }, 'Forecast dates are derived from the dependencies and never typed.')));
89
+ if (!rows.length) return { head, body: el('div', { class: 'fold', 'data-fc-empty': '' }, c3ic('check', 'sm'), 'No initiative has plan items, Breakdown tasks or Jira tickets yet, so there is no forecast to compare.') };
90
+ const pick = el('section', { class: 'card', 'data-tl-pick': '' }, el('h3', null, c3ic('tree'), 'Pick one to see its path'),
91
+ el('div', { class: 'chips2' }, rows.map((r) => { const c = r.cells[r.cells.length - 1]; const sl = c && c.slip_days > 0 ? ' +' + c.slip_days + ' d' : '';
92
+ return el('button', { type: 'button', class: 'pill', onClick: () => pickScope(V, r.id, null) }, titles[r.id] + sl); })));
93
+ const fx = window.__c3demo && window.__c3planfx;
94
+ const F = fx ? { causes: fx.why, deps: fx.deps } : V.facts;
95
+ const cm = V.commits && V.commits.state === 'ready' && V.commits.rows.length ? V.commits.rows : null;
96
+ const right = F ? [whySlipsCard(F.causes), cm ? calSyncFromCommits(cm) : null, dependenciesCard(F.deps)] : [el('section', { class: 'card', 'data-plan-loading': '' }, el('div', { class: 'fold' }, c3ic('clock', 'sm'), 'Reading the plan of each late initiative…'))];
97
+ return { head, body: el('div', { class: 'brief sbb' }, el('div', { class: 'bcols' }, el('div', { class: 'bcol l' }, progressCard(V, 'All initiatives', 'promised, forecast and progress', { cap: 9, uid: 'all', onOpen: (id) => { if (!goInitiative(id)) pickScope(V, id, null); } }), pick, matrixCard({ checks: d.checks, rows }, titles, (id, i) => pickScope(V, id, i))), el('div', { class: 'bcol r' }, right))) };
98
+ }
99
+
100
+ /** Real mode: the plans of the late initiatives give Why it slips and Dependencies (GET /api/c3-timeline per initiative; at most four). */
101
+ async function loadFacts(V) {
102
+ const d = V.data.all; if (!d || window.__c3demo) return;
103
+ const late = (d.rows || []).map((r) => { const c = r.cells[r.cells.length - 1]; return c && c.slip_days > 0 ? { id: r.id, slip: c.slip_days } : null; }).filter(Boolean).sort((a, b) => b.slip - a.slip).slice(0, 4);
104
+ const seq = V.seq;
105
+ const got = await Promise.all(late.map((l) => pmGet(qs(V, '/api/c3-timeline', 'initiative=' + encodeURIComponent(l.id))).catch(() => null)));
106
+ if (V.disposed) return;
107
+ const by = {}; late.forEach((l, i) => { if (got[i] && got[i].ok) by[l.id] = { title: titleOf(V, l.id), frames: got[i].frames }; });
108
+ V.facts = slipFacts(by);
109
+ if (V.scope === 'all') render(V);
110
+ void seq;
111
+ }
112
+
113
+ function render(V) {
114
+ const pills = el('div', { class: 'chips2', 'data-fc-scope': '', role: 'group', 'aria-label': 'Initiative' }, [['all', 'All'], ...V.ids.map((id) => [id, short(titleOf(V, id), 18)])].map(([id, lab]) => el('button', { type: 'button', class: 'pill', 'aria-pressed': String(V.scope === id), 'data-fc-pill': id, title: id === 'all' ? 'All initiatives' : id,
115
+ onClick: () => pickScope(V, id, null) }, lab)));
116
+ const { head, body } = V.scope === 'all' ? allBody(V) : oneBody(V);
117
+ V.root.replaceChildren(el('div', { class: 'calwrap' }, el('div', { class: 'calbar', style: 'flex-wrap:wrap;gap:8px' }, pills),
118
+ el('div', { class: 'calbody' }, head, V.error ? el('div', { class: 'fold', 'data-fc-error': '' }, c3ic('alert', 'sm'), V.error) : null, body)));
119
+ }
120
+
121
+ async function load(V, scope) {
122
+ const seq = ++V.seq;
123
+ const url = scope === 'all' ? qs(V, '/api/c3-timeline', 'initiatives=' + encodeURIComponent(V.ids.join(','))) : qs(V, '/api/c3-timeline', 'initiative=' + encodeURIComponent(scope));
124
+ const a = await pmGet(url);
125
+ if (V.disposed || seq !== V.seq) return;
126
+ if (a && a.ok) { V.data[scope] = a; V.error = null; } else { V.data[scope] = null; V.error = (a && a.error) || 'The timeline could not be read.'; }
127
+ render(V);
128
+ if (scope === 'all' && a && a.ok) loadFacts(V);
129
+ }
130
+
131
+ function pickScope(V, id, snap) {
132
+ stopReplay(V);
133
+ V.scope = id; V.snap = snap;
134
+ if (V.data[id]) render(V); else { V.error = null; render(V); }
135
+ load(V, id);
136
+ }
137
+
138
+ export function mountForecast(host, ctx) {
139
+ const ids = (ctx.rows || []).map((r) => r.id).filter(Boolean);
140
+ let V = views.get(host);
141
+ if (V && V.ctx.ws !== ctx.ws) { disposeForecast(host); V = null; }
142
+ if (V) { V.ctx = ctx; V.ids = ids; if (host.firstChild !== V.root) host.replaceChildren(V.root); return; }
143
+ ensureComponentsCss();
144
+ ensureC3Css();
145
+ V = { ctx, ids, scope: 'all', snap: null, data: {}, facts: null, root: el('div', { class: 'c3', 'data-in-view': 'forecast' }), error: null, timer: null, seq: 0, disposed: false };
146
+ V.render = () => render(V);
147
+ V.stopPhone = c3Phone(V.root);
148
+ views.set(host, V);
149
+ host.replaceChildren(V.root);
150
+ render(V);
151
+ load(V, V.scope);
152
+ }
153
+
154
+ export function disposeForecast(host) {
155
+ const V = views.get(host);
156
+ if (!V) return;
157
+ V.disposed = true;
158
+ stopReplay(V);
159
+ if (V.stopPhone) V.stopPhone();
160
+ views.delete(host);
161
+ if (V.root.parentNode === host) V.root.remove();
162
+ }
163
+
164
+ registerInitiativesView('forecast', { label: 'Forecast', mount: mountForecast, dispose: disposeForecast });
@@ -62,7 +62,7 @@ const ui = {
62
62
  open: (() => { const v = store.get(OPEN_KEY); return v === 'true' ? true : v === 'false' ? false : null; })(),
63
63
  section: (() => { const v = store.get(SECTION_KEY); return ['agenda', 'goals', 'jobs', 'mentions'].includes(v) ? v : 'agenda'; })(),
64
64
  sel: null, mtab: 'all', openSrc: null, showIgn: {}, showIgnDigests: false,
65
- revising: new Set(), notes: new Map(), siteDraft: '', placeDraft: {}, busy: new Set(), scroll: false,
65
+ revising: new Set(), notes: new Map(), siteDraft: '', placeDraft: {}, busy: new Set(), inflight: new Map(), scroll: false,
66
66
  // today-act-prepare: the folded groups the PM opened, the item whose hold form is open, and its draft
67
67
  cur: new Map(), groupsOpen: new Set(), holding: null, holdDraft: { who: '', when: 'h2' }, compose: new Map(), verdict: new Map(),
68
68
  };
@@ -343,7 +343,10 @@ async function markPress(ws, id, op, said) {
343
343
  const key = 'mark:' + id;
344
344
  if (ui.busy.has(key)) return;
345
345
  ui.busy.add(key); rerender();
346
- const r = await pmPost('/api/proactive/day/mark', { id, ...op });
346
+ const req = pmPost('/api/proactive/day/mark', { id, ...op });
347
+ ui.inflight.set(key, req); // a step press made while this write is out waits for it (below) instead of being dropped as busy
348
+ const r = await req;
349
+ ui.inflight.delete(key);
347
350
  ui.busy.delete(key);
348
351
  if (r && r.ok) {
349
352
  if (op.action !== 'step') ui.holding = null;
@@ -684,6 +687,9 @@ function stepChecks(ctx, id, steps, { refs = [], mark = null, focus = false } =
684
687
  let press = null;
685
688
  if (focus && isCur && !st.done) {
686
689
  press = button({ label: F.press[st.note ? 'note' : r ? 'read' : 'do'], variant: 'primary', size: 'sm', attrs: { 'data-day-step-press': st.t, onMousedown: (ev) => ev.preventDefault() }, onClick: async () => {
690
+ // the box losing focus may have started saving the note already: wait for that write, or this press is dropped as busy
691
+ const prior = ui.inflight.get('mark:' + id);
692
+ if (prior) await prior.catch(() => null);
687
693
  // a note typed in the box is saved first, in this press, so the box losing focus does not redraw the card under the pointer
688
694
  const ta = noteEl && noteEl.querySelector('textarea');
689
695
  if (ta && ta.value !== ((mark && mark.note) || '')) await markPress(ctx.ws, id, { action: 'note', text: ta.value }, A.noteSaved);
@@ -59,6 +59,7 @@
59
59
  --accent:oklch(0.275 0.011 216.9);--border:oklch(1 0 0 / 10%);--input:oklch(1 0 0 / 15%);
60
60
  --ring:oklch(0.56 0.021 213.5);--sidebar:oklch(0.218 0.008 223.9);
61
61
  --primary:var(--foreground);--primary-foreground:var(--background);
62
+ --on-accent:#fff;--mask:#000;
62
63
  --brand-mark:oklch(0.623 0.214 259.815);
63
64
  --brand:var(--foreground);--brand-wash:color-mix(in oklab, var(--foreground) 8%, transparent);
64
65
  --success:oklch(0.76 0.17 152);--warning:oklch(0.82 0.15 82);--info:oklch(0.79 0.11 195);
@@ -3382,6 +3382,9 @@ function spawnSession({ name, agent, cwd, ws, origin, initialInput, profile, ini
3382
3382
  if (!fres.ok) return exitedSession(base, workdir, `launch flag refused — ${fres.error}`);
3383
3383
  if (launchFlags !== undefined) base.launchFlags = fres.applied;
3384
3384
  const argv = [...(mres.state === 'applied' ? mres.args : []), ...fres.args];
3385
+ /* PER-SESSION HOOKS (console3 wave D). Off unless PMOS_AGENT_HOOKS=1: then an agent session starts with --settings whose
3386
+ * hooks post to this workspace (lib/routes-hooks.js, loopback only; the session token travels in the PMOS_SESSION env). */
3387
+ if (process.env.PMOS_AGENT_HOOKS === '1' && agent === 'claude') argv.push('--settings', JSON.stringify(require('./lib/routes-hooks.js').settingsFor(PORT)));
3385
3388
  const launchArgs = argv;
3386
3389
  if (mres.tier) {
3387
3390
  const mc = modelLib.core();
@@ -5463,12 +5466,12 @@ async function handleRequest(req, res) {
5463
5466
  const built = BUILD_RE.exec(url.pathname);
5464
5467
  const p = built ? built[1] : url.pathname;
5465
5468
 
5466
- if (p === '/' || p === '/index.html') {
5469
+ if (p === '/' || p === '/index.html' || p === '/c3.html') {
5467
5470
  // pm-os-absorb Run 2 (R25, X11): the per-launch nonce goes into the base meta line, nowhere else. no-store, so a
5468
5471
  // restarted server never answers a press from a page that holds the previous launch's value. The page code is
5469
5472
  // referenced under this launch's build prefix.
5470
5473
  const n = pmGuard.launchNonce();
5471
- return fs.readFile(path.join(PUB, 'index.html'), 'utf8', (err, html) => {
5474
+ return fs.readFile(path.join(PUB, p === '/c3.html' ? 'c3.html' : 'index.html'), 'utf8', (err, html) => {
5472
5475
  if (err) { res.writeHead(404); res.end('not found'); return; }
5473
5476
  let out = withBuild(html);
5474
5477
  if (n) out = out.replace('<meta name="' + pmGuard.NONCE_META + '" content="" />', '<meta name="' + pmGuard.NONCE_META + '" content="' + n + '" />');
@@ -22,6 +22,10 @@
22
22
  "no-network.test.js",
23
23
  "typesafe-validation.test.js",
24
24
  "claims.test.js",
25
+ "pm-items.test.js",
26
+ "jobs-to-be-done.test.js",
27
+ "pm-items-routes.test.js",
28
+ "item-steps.test.js",
25
29
  "status-facts-panel.test.js",
26
30
  "jev-claims.test.js",
27
31
  "jev-categorize.test.js",
@@ -55,7 +59,10 @@
55
59
  "plan-tree.test.js",
56
60
  "item-cli.test.js",
57
61
  "plan-routes.test.js",
58
- "glossary.test.js"
62
+ "glossary.test.js",
63
+ "c3-daypart.test.js",
64
+ "briefing-icons.test.js",
65
+ "needs-overdue.test.js"
59
66
  ],
60
67
  "panels": [
61
68
  "pm-shell-test.js",
@@ -112,7 +119,9 @@
112
119
  "pm-r30-test.js",
113
120
  "pm-breakdown-test.js",
114
121
  "pm-breakdown-phone-test.js",
115
- "first-paint-count.test.js"
122
+ "first-paint-count.test.js",
123
+ "pm-card-layout-test.js",
124
+ "pm-portfolio-inline-test.js"
116
125
  ],
117
126
  "ui": [
118
127
  "ui-test.js"
@@ -132,6 +141,10 @@
132
141
  "no-network.test.js": 1,
133
142
  "typesafe-validation.test.js": 1,
134
143
  "claims.test.js": 1,
144
+ "pm-items.test.js": 1,
145
+ "jobs-to-be-done.test.js": 1,
146
+ "pm-items-routes.test.js": 1,
147
+ "item-steps.test.js": 1,
135
148
  "status-facts-panel.test.js": 1,
136
149
  "jev-claims.test.js": 3,
137
150
  "jev-categorize.test.js": 1,
@@ -222,7 +235,12 @@
222
235
  "glossary.test.js": 1,
223
236
  "pm-breakdown-test.js": 62,
224
237
  "pm-breakdown-phone-test.js": 19,
225
- "first-paint-count.test.js": 15
238
+ "first-paint-count.test.js": 15,
239
+ "c3-daypart.test.js": 1,
240
+ "briefing-icons.test.js": 1,
241
+ "needs-overdue.test.js": 1,
242
+ "pm-card-layout-test.js": 20,
243
+ "pm-portfolio-inline-test.js": 15
226
244
  },
227
245
  "_comment_seconds": "Measured wall time of each suite run alone (4-CPU sandbox, 2026-10-05). Only the ORDER uses it: the slowest suites start first, and --shard deals suites to the least-loaded shard so the shards finish together. A wrong or missing number never skips a suite; a missing one counts as 10."
228
246
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "_comment": "Initiative browser-suites-green: the one-home check of the console3 feedback plan (section 2). Known duplicates are listed in fixtures/one-home-baseline.json until the Phase 2 restructure.",
3
+ "panels": ["c3-one-home-test.js"],
4
+ "seconds": { "c3-one-home-test.js": 30 }
5
+ }