create-pmos 0.134.0 → 0.136.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 (211) 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/state/discoveries.md +1 -1
  6. package/payload/state/log.md +1 -1
  7. package/workspace/bin/notes-replay.js +235 -0
  8. package/workspace/c3-briefing-test.js +130 -0
  9. package/workspace/c3-foundation-test.js +159 -0
  10. package/workspace/c3-ib-items-test.js +105 -0
  11. package/workspace/c3-ib-more-test.js +120 -0
  12. package/workspace/c3-ib-triage-backlog-test.js +119 -0
  13. package/workspace/c3-ini-harness.js +66 -0
  14. package/workspace/c3-initiatives-test.js +140 -0
  15. package/workspace/c3-one-home-test.js +14 -1
  16. package/workspace/c3-reports-test.js +57 -18
  17. package/workspace/c3-settings-test.js +139 -0
  18. package/workspace/c3-work-test.js +42 -10
  19. package/workspace/fixtures/one-home-baseline.json +1 -9
  20. package/workspace/guide/briefing-cards.md +15 -3
  21. package/workspace/guide/briefing.md +12 -8
  22. package/workspace/guide/calendar-popup.md +2 -2
  23. package/workspace/guide/console-calendar.md +39 -10
  24. package/workspace/guide/index.md +1 -0
  25. package/workspace/guide/initiative-boards.md +65 -0
  26. package/workspace/guide/jira.md +3 -1
  27. package/workspace/guide/layout.md +2 -0
  28. package/workspace/guide/my-work.md +5 -3
  29. package/workspace/guide/notes.md +16 -1
  30. package/workspace/guide/reports-dashboards.md +29 -5
  31. package/workspace/guide/settings.md +68 -0
  32. package/workspace/guide/welcome.md +10 -10
  33. package/workspace/lib/calendar-cli.js +3 -2
  34. package/workspace/lib/calendar-layers.js +21 -10
  35. package/workspace/lib/calendar-tidy.js +230 -0
  36. package/workspace/lib/engine/boards.js +542 -0
  37. package/workspace/lib/engine/breakdown-ops.js +180 -0
  38. package/workspace/lib/engine/calendar-import.js +84 -0
  39. package/workspace/lib/engine/calendar-model.js +185 -0
  40. package/workspace/lib/engine/card-state.js +92 -0
  41. package/workspace/lib/engine/derived/derive.js +92 -0
  42. package/workspace/lib/engine/derived/plan.js +3 -0
  43. package/workspace/lib/engine/derived/trust.js +3 -0
  44. package/workspace/lib/engine/doc-health.js +142 -0
  45. package/workspace/lib/engine/doc-sheet.js +75 -0
  46. package/workspace/lib/engine/intake.js +78 -0
  47. package/workspace/lib/engine/outbox.js +86 -0
  48. package/workspace/lib/engine/plan-forecast.js +342 -0
  49. package/workspace/lib/engine/promises.js +29 -0
  50. package/workspace/lib/engine/records/index.js +114 -0
  51. package/workspace/lib/engine/releases.js +96 -0
  52. package/workspace/lib/engine/replies.js +99 -0
  53. package/workspace/lib/engine/reply-contract/reply-contract.js +372 -0
  54. package/workspace/lib/engine/report-families.js +175 -0
  55. package/workspace/lib/engine/roadmap-ops.js +150 -0
  56. package/workspace/lib/engine/sessions-list.js +27 -0
  57. package/workspace/lib/engine/signal-rules.js +95 -0
  58. package/workspace/lib/engine/signals/cards.js +57 -0
  59. package/workspace/lib/engine/signals/detectors.js +370 -0
  60. package/workspace/lib/engine/signals/facts-cli.js +8 -0
  61. package/workspace/lib/engine/signals/facts.js +197 -0
  62. package/workspace/lib/engine/signals/index.js +33 -0
  63. package/workspace/lib/engine/signals/kinds.js +110 -0
  64. package/workspace/lib/engine/signals/layout-paths.js +44 -0
  65. package/workspace/lib/engine/signals/options.js +24 -0
  66. package/workspace/lib/engine/signals/score.js +72 -0
  67. package/workspace/lib/engine/signals/select.js +47 -0
  68. package/workspace/lib/engine/step-planner.js +65 -0
  69. package/workspace/lib/engine/trust/claim-rules.json +93 -0
  70. package/workspace/lib/engine/trust/import-calendar.js +21 -0
  71. package/workspace/lib/engine/trust/import-guard.js +36 -0
  72. package/workspace/lib/engine/trust/ledger.js +53 -0
  73. package/workspace/lib/engine/trust/outbox.js +45 -0
  74. package/workspace/lib/engine/trust/plan.js +5 -0
  75. package/workspace/lib/engine/trust/trust.js +21 -0
  76. package/workspace/lib/engine/truth-table.js +57 -0
  77. package/workspace/lib/engine/workspace-lib.js +24 -0
  78. package/workspace/lib/factory-join.js +1 -1
  79. package/workspace/lib/facts-async.js +4 -4
  80. package/workspace/lib/headless.js +95 -0
  81. package/workspace/lib/import-overlay.js +2 -1
  82. package/workspace/lib/jira-status.js +30 -3
  83. package/workspace/lib/notes-capture.js +9 -1
  84. package/workspace/lib/notes-final.js +38 -13
  85. package/workspace/lib/notes-replay.js +125 -0
  86. package/workspace/lib/notes-screen.js +180 -0
  87. package/workspace/lib/notes-whisper.js +10 -3
  88. package/workspace/lib/outbox-store.js +10 -6
  89. package/workspace/lib/pmos-verbs.js +3 -1
  90. package/workspace/lib/proactive-cli.js +7 -0
  91. package/workspace/lib/proactive.js +15 -2
  92. package/workspace/lib/records-dir.js +29 -0
  93. package/workspace/lib/repo-script.js +36 -0
  94. package/workspace/lib/report-dashboards.js +5 -2
  95. package/workspace/lib/report-frame.js +122 -0
  96. package/workspace/lib/report-schedules.js +113 -0
  97. package/workspace/lib/route-modules.js +9 -2
  98. package/workspace/lib/routes-boards.js +734 -0
  99. package/workspace/lib/routes-brief-page.js +13 -4
  100. package/workspace/lib/routes-briefing.js +22 -11
  101. package/workspace/lib/routes-c3-init.js +9 -8
  102. package/workspace/lib/routes-c3-life.js +2 -1
  103. package/workspace/lib/routes-c3-reports.js +99 -15
  104. package/workspace/lib/routes-c3-strategy.js +7 -5
  105. package/workspace/lib/routes-c3-timeline.js +3 -2
  106. package/workspace/lib/routes-calendar-import.js +3 -2
  107. package/workspace/lib/routes-calendar-views.js +9 -4
  108. package/workspace/lib/routes-doc-health.js +2 -1
  109. package/workspace/lib/routes-doc-sheet.js +2 -1
  110. package/workspace/lib/routes-forecast.js +2 -1
  111. package/workspace/lib/routes-hooks.js +3 -2
  112. package/workspace/lib/routes-initiatives.js +2 -1
  113. package/workspace/lib/routes-intake.js +2 -1
  114. package/workspace/lib/routes-notes.js +55 -9
  115. package/workspace/lib/routes-outbox.js +23 -3
  116. package/workspace/lib/routes-pm-items.js +19 -6
  117. package/workspace/lib/routes-records.js +10 -7
  118. package/workspace/lib/routes-releases.js +2 -1
  119. package/workspace/lib/routes-report-facts.js +4 -3
  120. package/workspace/lib/routes-report-families.js +2 -1
  121. package/workspace/lib/routes-roadmap-ops.js +17 -14
  122. package/workspace/lib/routes-runs.js +6 -5
  123. package/workspace/lib/routes-settings.js +51 -0
  124. package/workspace/lib/routes-signals.js +3 -2
  125. package/workspace/lib/routes-stage-tools.js +220 -0
  126. package/workspace/lib/run-answers.js +2 -1
  127. package/workspace/lib/settings-store.js +226 -0
  128. package/workspace/lib/stage-tools.js +208 -0
  129. package/workspace/lib/vendor/scripts/signals/layout-paths.js +2 -1
  130. package/workspace/lib/vendor/scripts/workspace-lib.js +24 -0
  131. package/workspace/lib/work-evidence.js +73 -0
  132. package/workspace/package-lock.json +2 -2
  133. package/workspace/package.json +1 -1
  134. package/workspace/public/pm/briefing-deck.js +63 -17
  135. package/workspace/public/pm/briefing-model.js +36 -7
  136. package/workspace/public/pm/c3-ambient.js +100 -0
  137. package/workspace/public/pm/c3-brief-needs.js +26 -6
  138. package/workspace/public/pm/c3-brief-pulse.js +12 -4
  139. package/workspace/public/pm/c3-brief-today.js +56 -21
  140. package/workspace/public/pm/c3-brief.js +2 -1
  141. package/workspace/public/pm/c3-calendar.js +161 -42
  142. package/workspace/public/pm/c3-calpop.js +5 -5
  143. package/workspace/public/pm/c3-demo-boards.js +649 -0
  144. package/workspace/public/pm/c3-demo-brief-page.js +1 -1
  145. package/workspace/public/pm/c3-demo-cal4.js +3 -2
  146. package/workspace/public/pm/c3-demo-cal5.js +52 -0
  147. package/workspace/public/pm/c3-demo-data.js +17 -27
  148. package/workspace/public/pm/c3-demo-rep3.js +46 -5
  149. package/workspace/public/pm/c3-demo.js +2 -1
  150. package/workspace/public/pm/c3-dock.js +1 -1
  151. package/workspace/public/pm/c3-ib-docs.js +80 -0
  152. package/workspace/public/pm/c3-ib-initiatives.js +134 -0
  153. package/workspace/public/pm/c3-ib-items.js +201 -0
  154. package/workspace/public/pm/c3-ib-releases.js +51 -0
  155. package/workspace/public/pm/c3-ib-sprints.js +55 -0
  156. package/workspace/public/pm/c3-ib-triage.js +178 -0
  157. package/workspace/public/pm/c3-ii-tabs.js +240 -0
  158. package/workspace/public/pm/c3-ini-records.js +197 -0
  159. package/workspace/public/pm/c3-ini-timeline.js +278 -0
  160. package/workspace/public/pm/c3-ini-ui.js +229 -0
  161. package/workspace/public/pm/c3-ini-work.js +139 -0
  162. package/workspace/public/pm/c3-initiatives.css +218 -0
  163. package/workspace/public/pm/c3-initiatives.js +741 -0
  164. package/workspace/public/pm/c3-plan.js +2 -2
  165. package/workspace/public/pm/c3-reports.css +47 -47
  166. package/workspace/public/pm/c3-reports.js +223 -185
  167. package/workspace/public/pm/c3-settings.css +74 -0
  168. package/workspace/public/pm/c3-settings.js +703 -0
  169. package/workspace/public/pm/c3-shell.js +102 -36
  170. package/workspace/public/pm/c3-terminal-icons.js +2 -2
  171. package/workspace/public/pm/c3-terminal.js +2 -2
  172. package/workspace/public/pm/calendar-views.js +1 -1
  173. package/workspace/public/pm/components.css +319 -213
  174. package/workspace/public/pm/console3.css +99 -23
  175. package/workspace/public/pm/dest-notes.js +26 -4
  176. package/workspace/public/pm/notes-screen-capture.js +165 -0
  177. package/workspace/public/pm/notes-screen-view.js +98 -0
  178. package/workspace/public/pm/panel-trees.js +5 -2
  179. package/workspace/public/pm/pm-work-view.js +233 -146
  180. package/workspace/public/pm/shell-bell.js +3 -2
  181. package/workspace/public/pm/shell-recording.js +29 -8
  182. package/workspace/public/pm/tokens.css +2 -2
  183. package/workspace/public/pm/view-notes.js +46 -9
  184. package/workspace/public/pm/welcome.js +197 -89
  185. package/workspace/public/welcome.html +6 -4
  186. package/workspace/server.js +105 -6
  187. package/workspace/skills/day-check.skill +1 -1
  188. package/workspace/skills/jira-sync.skill +6 -4
  189. package/workspace/skills/morning-brief.skill +1 -1
  190. package/workspace/test-files.json +12 -3
  191. package/workspace/test-lists/c3-ib-items.json +9 -0
  192. package/workspace/test-lists/c3-ib-more.json +9 -0
  193. package/workspace/test-lists/c3-ib-triage-backlog.json +9 -0
  194. package/workspace/test-lists/c3-initiative-boards.json +6 -0
  195. package/workspace/test-lists/calendar-tidy-up.json +6 -0
  196. package/workspace/test-lists/console3-briefing.json +9 -0
  197. package/workspace/test-lists/console3-foundation.json +9 -0
  198. package/workspace/test-lists/console3-reports.json +3 -3
  199. package/workspace/test-lists/meeting-notes.json +10 -4
  200. package/workspace/test-lists/workspace-headless-jobs.json +5 -0
  201. package/workspace/test-lists/workspace-package-script-closure.json +6 -0
  202. package/workspace/lib/vendor/workspace/lib/actor.js +0 -91
  203. package/workspace/lib/vendor/workspace/lib/board-cache.js +0 -156
  204. package/workspace/lib/vendor/workspace/lib/console-state.js +0 -295
  205. package/workspace/lib/vendor/workspace/lib/factory-join.js +0 -158
  206. package/workspace/lib/vendor/workspace/lib/git-info.js +0 -306
  207. package/workspace/lib/vendor/workspace/lib/plan-items.js +0 -922
  208. package/workspace/lib/vendor/workspace/lib/plan-tree.js +0 -419
  209. package/workspace/lib/vendor/workspace/lib/review-gate.js +0 -39
  210. package/workspace/lib/vendor/workspace/lib/sink.js +0 -321
  211. package/workspace/lib/vendor/workspace/lib/state-root.js +0 -587
@@ -0,0 +1,100 @@
1
+ /* pm/c3-ambient.js — the ambient motion background of the console3 page (ported from the mockup's 90-ambient.js).
2
+ * It also pauses while the tab is hidden. */
3
+ /* Ambient motion background: a few soft accent blobs drift slowly behind the page. It knows where content sits: it reads the
4
+ rects of cards, rows and headings, steers the blobs toward empty space, dims a blob that drifts under a card, and thins
5
+ the colour under text that sits straight on the page (title, tabs, deck pager). Subtle by design (peak alpha about 0.14 light, 0.2 dark). Reduced motion draws one still frame. */
6
+ const Ambient = (() => {
7
+ let rootEl = null;
8
+ const ROOT = () => rootEl;
9
+ let texts = [], cv, cx, W = 0, H = 0, rects = [], blobs = [], cols = [], last = 0, t0 = performance.now(), raf = 0, still = false;
10
+ const SCALE = 0.5; // draw at half resolution; it is all blur anyway
11
+ const CONTENT = '.card,.fc,.sect,.cal3,.c3dock,.sheet'; // opaque surfaces: blobs steer around them
12
+ const TEXT = 'h1,.c3top .sub,.c3tabs,.lh,.stance,.dkd'; // text sitting straight on the page: colour thins under it
13
+ const rgb = (css) => { const c = document.createElement('canvas').getContext('2d'); c.fillStyle = 'rgb(0 0 0)'; c.fillStyle = css; c.fillRect(0, 0, 1, 1); const d = c.getImageData(0, 0, 1, 1).data; return [d[0], d[1], d[2]]; };
14
+ const dark = () => (ROOT().getAttribute('data-pm-theme') || '') === 'dark';
15
+ function readColours() {
16
+ const cs = getComputedStyle(ROOT());
17
+ cols = ['--acc', '--purple', '--acc'].map(v => rgb(cs.getPropertyValue(v).trim() || 'rgb(79 107 237)'));
18
+ }
19
+ function size() {
20
+ W = innerWidth; H = innerHeight;
21
+ cv.width = Math.ceil(W * SCALE); cv.height = Math.ceil(H * SCALE);
22
+ measure();
23
+ }
24
+ function measure() {
25
+ const grab = sel => [...document.querySelectorAll(sel)].map(el => el.getBoundingClientRect()).filter(r => r.width > 4 && r.height > 4 && r.bottom > 0 && r.top < H);
26
+ rects = grab(CONTENT); texts = grab(TEXT);
27
+ }
28
+ /* free space: score a coarse grid by distance from content; the emptiest cells become drift targets */
29
+ function freeTarget() {
30
+ let best = null, bs = -1;
31
+ for (let k = 0; k < 14; k++) {
32
+ const x = Math.random() * W, y = Math.random() * H;
33
+ let d = 1e9;
34
+ for (const r of rects) { const dx = Math.max(r.left - x, 0, x - r.right), dy = Math.max(r.top - y, 0, y - r.bottom); d = Math.min(d, Math.hypot(dx, dy)); }
35
+ if (d > bs) { bs = d; best = { x, y }; }
36
+ }
37
+ return best || { x: W * 0.15, y: H * 0.1 };
38
+ }
39
+ function covered(x, y) {
40
+ for (const r of rects) if (x > r.left && x < r.right && y > r.top && y < r.bottom) return true;
41
+ return false;
42
+ }
43
+ function init() {
44
+ blobs = [0, 1, 2].map(i => ({ x: W * (0.15 + i * 0.35), y: H * (0.1 + (i % 2) * 0.5), r: Math.max(W, H) * (0.32 - i * 0.05), tx: 0, ty: 0, a: 1, ph: i * 2.1, c: i }));
45
+ blobs.forEach(b => { const t = freeTarget(); b.tx = t.x; b.ty = t.y; });
46
+ }
47
+ function frame(now) {
48
+ raf = still ? 0 : requestAnimationFrame(frame);
49
+ if (!still && now - last < 33) return; // ~30 fps is plenty for this
50
+ last = now;
51
+ const tt = still ? 0 : (now - t0) / 1000, peak = dark() ? 0.2 : 0.14;
52
+ cx.setTransform(SCALE, 0, 0, SCALE, 0, 0);
53
+ cx.clearRect(0, 0, W, H);
54
+ cx.globalCompositeOperation = 'source-over';
55
+ for (const b of blobs) {
56
+ // ease toward an empty-space target, with a slow wobble; pick a new target on arrival
57
+ if (!still) { // a still frame (reduced motion) holds the blobs where they began, so a redraw after a layout change looks the same
58
+ b.x += (b.tx - b.x) * 0.004 + Math.sin(tt * 0.21 + b.ph) * 0.35;
59
+ b.y += (b.ty - b.y) * 0.004 + Math.cos(tt * 0.17 + b.ph) * 0.3;
60
+ if (Math.hypot(b.tx - b.x, b.ty - b.y) < 40) { const t = freeTarget(); b.tx = t.x; b.ty = t.y; }
61
+ }
62
+ // a blob whose centre sits under content dims; in open space it comes up to full (slowly)
63
+ const want = covered(b.x, b.y) ? 0.6 : 1;
64
+ b.a += (want - b.a) * 0.02;
65
+ const [r, g, bl] = cols[b.c] || [79, 107, 237], rad = b.r * (1 + 0.06 * Math.sin(tt * 0.3 + b.ph));
66
+ const gr = cx.createRadialGradient(b.x, b.y, 0, b.x, b.y, rad);
67
+ gr.addColorStop(0, `rgba(${r},${g},${bl},${(peak * b.a).toFixed(3)})`);
68
+ gr.addColorStop(0.55, `rgba(${r},${g},${bl},${(peak * b.a * 0.35).toFixed(3)})`);
69
+ gr.addColorStop(1, `rgba(${r},${g},${bl},0)`);
70
+ cx.fillStyle = gr; cx.fillRect(0, 0, W, H);
71
+ }
72
+ // content overlap: knock the colour back to a trace under content, with a soft edge so it never outlines boxes
73
+ cx.globalCompositeOperation = 'destination-out';
74
+ cx.filter = 'blur(18px)';
75
+ cx.fillStyle = 'rgba(0,0,0,0.5)';
76
+ for (const r of texts) cx.fillRect(r.left, r.top, r.width, r.height);
77
+ cx.filter = 'none';
78
+ }
79
+ function mount(root) {
80
+ if (cv && cv.isConnected) return;
81
+ rootEl = root;
82
+ cv = document.createElement('canvas'); cv.className = 'c3amb'; cv.setAttribute('aria-hidden', 'true');
83
+ ROOT().prepend(cv); cx = cv.getContext('2d');
84
+ if (!cx) { cv.remove(); return; } // canvas is not available: the page stays plain white
85
+ still = matchMedia('(prefers-reduced-motion: reduce)').matches;
86
+ readColours(); size(); init();
87
+ addEventListener('resize', size);
88
+ addEventListener('scroll', measure, { passive: true });
89
+ new MutationObserver(() => { clearTimeout(mount._m); mount._m = setTimeout(() => { measure(); readColours(); if (still) frame(performance.now()); }, 120); })
90
+ .observe(ROOT(), { childList: true, subtree: true, attributes: true, attributeFilter: ['data-pm-theme', 'class'] });
91
+ document.addEventListener('visibilitychange', () => { if (still) return; if (document.hidden) { cancelAnimationFrame(raf); raf = 0; } else if (!raf) raf = requestAnimationFrame(frame); });
92
+ if (still) frame(performance.now()); else raf = requestAnimationFrame(frame);
93
+ }
94
+ return { mount };
95
+ })();
96
+
97
+ /* Mount once, after the page root holds its content. Never throws: the background is decoration. */
98
+ export function mountAmbient(root) {
99
+ try { if (root) Ambient.mount(root); } catch { /* no canvas: nothing to draw */ }
100
+ }
@@ -69,7 +69,8 @@ export function itemRow(r, ctx = {}) {
69
69
  offline ? el('span', { class: 'tag', 'data-offline': '' }, 'Outside PMOS') : el('span', { class: 'tag' }, 'Yours'),
70
70
  r.person ? el('span', { class: 'chip2' }, c3ic('user', 'sm'), r.person) : null,
71
71
  r.priority ? el('span', { class: 'fl ' + String(r.priority).toLowerCase() }, r.priority) : null,
72
- r.evidence ? el('span', { class: 'st ok', 'data-looks-done': '' }, c3ic('eye', 'sm'), 'Looks done') : null];
72
+ r.evidence ? el('span', { class: 'st ok', 'data-looks-done': '' }, c3ic('eye', 'sm'), 'Looks done') : null,
73
+ el('span', { class: 'chip2', title: 'Where this lives', 'data-home-chip': '' }, c3ic('repo', 'sm'), 'Work')];
73
74
  row = el('li', { class: 'nrow', 'data-row': '', 'data-id': key, 'data-needs-row': key, 'data-item-id': r.id, 'data-ref-row': r.id, 'data-kind': r.kind, 'data-a': 'expand' },
74
75
  el('span', { class: 'ki ' + tone + (r.kind === 'overdue' ? ' hot' : ''), title: r.kind }, c3ic(r.kind === 'overdue' ? 'alert' : offline ? 'user' : 'clock')),
75
76
  el('div', { class: 'nb' }, el('span', { class: 'tt' }, r.headline), el('div', { class: 'chips' }, ...chips.filter(Boolean)), xp));
@@ -77,9 +78,18 @@ export function itemRow(r, ctx = {}) {
77
78
  return row;
78
79
  }
79
80
 
81
+ /* The Needs you count for the Briefing tab's badge, read without the section: the decisions (the briefing's needs, or its engine cards that carry a move or are
82
+ * Critical) plus the own items of the brief page. The section's own count (recount) takes over once it is on the page. */
83
+ export function needsCount(b, page) {
84
+ const decide = b && Array.isArray(b.needs) && b.needs.length ? b.needs.filter((n) => !BX.gone[n.key]).length : ((b && b.cards) || []).filter((c) => c && (c.move || c.tag === 'Critical')).length;
85
+ const own = page && page.ok !== false ? (page.needs || []).filter((r) => r.kind !== 'decision').length : 0;
86
+ return decide + own;
87
+ }
88
+
80
89
  export function needsSection(S) {
81
90
  const count = el('span', { class: 'bdg g' }, '0');
82
- const quick = el('span', { class: 'chips2 dim mono c3s2' });
91
+ const quick = el('span', { class: 'chips2 dim mono c3s2', 'data-needs-summary': '' });
92
+ const sumState = { dec: 0, own: 0, late: 0, quick: 0 };
83
93
  const own = el('ul', { class: 'list', role: 'list', 'data-needs-own': '' });
84
94
  const ownNote = el('div');
85
95
  const list = el('ul', { class: 'list', role: 'list' });
@@ -88,10 +98,21 @@ export function needsSection(S) {
88
98
  const box = el('section', { class: 'card sect', 'data-c3pg': 'needs', 'aria-label': 'Needs you' },
89
99
  el('div', { class: 'sh2' }, el('h2', null, c3ic('inbox', 'sm'), 'Needs you ', count)), alertHost, ownNote, own, list, extra);
90
100
  let sig = ''; let last = null; let ownSig = ''; let page = null;
91
- const nothing = () => el('li', { class: 'fold' }, c3ic('check', 'sm'), 'Nothing needs you.');
101
+ const nothing = () => el('li', { class: 'fold' }, c3ic('check', 'sm'), 'Nothing needs you right now.');
102
+ /* the header line: "3 decisions · 4 of yours · 2 overdue", then how many take under 2 minutes */
103
+ const summarise = () => {
104
+ const dec = list.querySelectorAll('.nrow[data-needs-row]').length + extra.querySelectorAll('.nrow[data-needs-row]').length;
105
+ const ownRows = [...own.querySelectorAll('.nrow[data-needs-row]')]; const late = ownRows.filter((r) => r.dataset.kind === 'overdue').length;
106
+ if (!quick.isConnected) box.querySelector('.sh2').append(quick);
107
+ quick.replaceChildren(document.createTextNode(dec + (dec === 1 ? ' decision' : ' decisions') + ' · ' + ownRows.length + ' of yours'));
108
+ if (late) quick.append(' · ', el('span', { class: 'tag bad' }, late + ' overdue'));
109
+ if (sumState.quick) quick.append(' · ', c3ic('bolt', 'sm'), sumState.quick + ' under 2 min');
110
+ };
92
111
  const recount = () => {
93
112
  const n = box.querySelectorAll('.nrow[data-needs-row]').length;
113
+ summarise();
94
114
  count.textContent = String(n);
115
+ if (S.setNeedsBdg) S.setNeedsBdg(n);
95
116
  const f = list.querySelector('.fold');
96
117
  if (!n && !f) list.replaceChildren(nothing());
97
118
  else if (n && f && list.children.length === 1 && f.textContent.startsWith('Nothing needs')) f.remove();
@@ -120,15 +141,14 @@ export function needsSection(S) {
120
141
  if (g === sig) return; sig = g;
121
142
  alertHost.replaceChildren(...(inc ? [el('div', { class: 'incident', role: 'status', 'data-c3pg': 'incident' }, c3ic('alert', 'lg'), el('div', null, el('b', null, 'Card payments failing since 07:40'), el('div', { class: 'dim c3s3' }, '12% of checkouts. Sam on call. Only deadlines shown.')))] : []));
122
143
  if (needs) {
123
- if (!quick.isConnected) box.querySelector('.sh2').append(quick);
124
- quick.textContent = ''; quick.append(c3ic('bolt', 'sm'), needs.filter((n) => n.quick).length + ' under 2 min');
144
+ sumState.quick = needs.filter((n) => n.quick).length;
125
145
  list.replaceChildren(...needs.slice(0, 5).map((n, i) => needRow(n, i, ctx)));
126
146
  extra.replaceChildren(...(inc ? [] : belowLine(needs.slice(5), (n, i) => needRow(n, i + 5, ctx), b.kept_out)));
127
147
  recount();
128
148
  return;
129
149
  }
130
150
  const cards = decide.map(cardView);
131
- quick.remove();
151
+ sumState.quick = 0;
132
152
  list.replaceChildren(...cards.map((v, i) => {
133
153
  const T = TAG_STYLE[v.tag] || TAG_STYLE['Just in'];
134
154
  return needRow({ key: v.key || String(i), headline: v.headline, kind: v.tag, icon: T.icon, color: T.color, time: v.deadline && v.deadline.text ? v.deadline.text : null, src: v.tag, view: v }, i, ctx);
@@ -6,9 +6,10 @@
6
6
  import { el } from './dom.js';
7
7
  import { c3ic } from './pm-work-view.js';
8
8
  import { makeXp, attachExpand } from './c3-expand.js';
9
- import { caseFile } from './c3-brief.js';
9
+ import { caseFile, hooks } from './c3-brief.js';
10
10
  import { readQuarter } from './c3-plan.js';
11
- import { row, signal, ensureComponentsCss } from './primitives.js';
11
+ import { row, signal, button, ensureComponentsCss } from './primitives.js';
12
+ import { ensureSprite } from './icons.js';
12
13
 
13
14
  /* tone: ok is neutral, warn is the warning token, bad the destructive one (R28-12) */
14
15
  const PST = { 'on time': ['ok', 'On time'], 'at risk': ['warn', 'At risk'], delayed: ['bad', 'Delayed'], overdue: ['bad', 'Overdue'], 'no plan': ['', 'No plan'] };
@@ -40,9 +41,14 @@ export function pulseCase(r) {
40
41
  export function pulse(S) {
41
42
  ensureComponentsCss();
42
43
  const body = el('div', null, el('p', { class: 'dim mono c3s2' }, 'Reading the initiatives.'));
43
- const box = el('section', { class: 'card pulsecard', 'data-c3pg': 'pulse' },
44
+ const attn = el('span', { class: 'mono dim pulse-attn', 'data-pulse-attn': '' }, 'promised vs forecast');
45
+ ensureSprite();
46
+ const all = button({ label: 'All initiatives', variant: 'ghost', size: 'sm', icon: 'arrow-right', attrs: { 'data-go': 'initiatives' }, onClick: () => { if (hooks.go) hooks.go({ action: 'goinit' }); } });
47
+ const foot = el('div', { class: 'chips2 pulse-foot', 'data-pulse-foot': '' }, el('span', { class: 'dim c3s2' }, 'Tap a row to read why. Open it from there.'), all);
48
+ foot.hidden = true;
49
+ const box = el('section', { class: 'card pulsecard', 'data-c3pg': 'pulse', 'aria-label': 'Portfolio pulse' },
44
50
  el('div', { class: 'pulsehead' },
45
- el('h3', null, c3ic('init', 'sm'), 'Portfolio pulse'), el('span', { class: 'mono dim c3s2' }, 'promised vs forecast')), body);
51
+ el('h3', null, c3ic('init', 'sm'), 'Portfolio pulse'), attn), body, foot);
46
52
  const note = (t, ic) => el('div', { class: 'fold' }, c3ic(ic || 'moon', 'sm'), t);
47
53
  const draw = (rows, err) => {
48
54
  if (err) { body.replaceChildren(el('p', { class: 'dim mono c3s2', role: 'status' }, 'The initiatives could not be read: ' + err)); return; }
@@ -51,6 +57,8 @@ export function pulse(S) {
51
57
  rows = rows.filter((g) => g.pulse || g.status !== 'no plan');
52
58
  if (!rows.length) { body.replaceChildren(note('No initiative has a promised date yet, so there is nothing to compare with the forecast.'), ...(noPlan ? [el('p', { class: 'dim mono c3s2' }, noPlan + ' initiatives have no plan date.')] : [])); return; }
53
59
  const pr = rows.map(pulseRow);
60
+ const need = pr.filter((r) => r.cls === 'bad' || r.cls === 'warn').length;
61
+ attn.textContent = need + ' need attention'; foot.hidden = false;
54
62
  const by = new Map(); pr.forEach((r) => { const k = r.word.replace(/ \+.*/, ''); const o = by.get(k) || { n: 0, cls: r.cls }; o.n += 1; by.set(k, o); });
55
63
  const bar = el('div', { class: 'pbs', role: 'group', 'aria-label': 'Status of initiatives' },
56
64
  ...[...by.entries()].map(([k, v]) => { const seg = el('span', { class: 'pb-seg pb-' + (v.cls || 'mute'), title: v.n + ' ' + k.toLowerCase(), 'aria-label': v.n + ' ' + k.toLowerCase() }); seg.style.setProperty('--fr-grow', String(v.n)); return seg; }));
@@ -1,43 +1,78 @@
1
- /* pm/c3-brief-today.js — the Briefing's "Today": the agenda of today's meetings (PRD briefing-brief R2).
2
- * One row per meeting, in time order, from GET /api/brief-page: its time and length, the people, the initiative, what PMOS holds as PREPARED
3
- * (documents, notes) and what the PM must do BEFORE the meeting (own items that name it). A meeting with nothing held says "Nothing prepared
4
- * yet". A meeting that is over is dimmed; the one under way is marked Now, the next one Next.
1
+ /* pm/c3-brief-today.js — the Briefing's "Today": the agenda of today's meetings (PRD briefing-brief R2, console3-briefing-parity R3).
2
+ * A lead line (the meeting under way or the next one, how many are left, how many are prepped, a small ring), then one row per meeting in time
3
+ * order, from GET /api/brief-page: its time and length, the people, the initiative, what PMOS holds as PREPARED (documents, notes: chips) and what
4
+ * the PM must do BEFORE the meeting (own items that name it: "Before you go: ..." chips). A meeting with nothing held says "nothing prepared".
5
+ * A meeting that is over says "over"; the one under way says "now", the next one "next". "Open calendar" opens the calendar popup.
5
6
  * Rows are REFERENCES (data-ref-row): they carry no control that changes a record. The record's actions are in Needs you and on its own page. */
6
7
  import { el } from './dom.js';
7
8
  import { c3ic } from './pm-work-view.js';
8
9
  import { onBriefPage } from './c3-brief-data.js';
10
+ import { openCalendarPopup } from './c3-calpop.js';
11
+ import { button, ensureComponentsCss } from './primitives.js';
12
+ import { ensureSprite } from './icons.js';
9
13
 
10
14
  const p2 = (n) => String(n).padStart(2, '0');
11
15
  const hm = (iso) => { const d = new Date(iso); return p2(d.getHours()) + ':' + p2(d.getMinutes()); };
16
+ const first = (n) => String(n || '').trim().split(/\s+/)[0];
17
+ const length = (m) => { const x = Math.round(m || 0); return x >= 60 ? Math.floor(x / 60) + 'h' + (x % 60 ? ' ' + (x % 60) + 'm' : '') : x + ' min'; };
12
18
 
13
19
  export function meetingRow(m) {
14
- const marks = { now: 'Now', next: 'Next' };
15
- const prep = m.prepared && m.prepared.length
16
- ? el('ul', { class: 'tl', role: 'list', 'aria-label': 'Prepared' }, ...m.prepared.map((x) => el('li', { 'data-prep': x.kind }, c3ic(x.kind === 'prep' ? 'spark' : 'doc', 'sm'), x.text)))
17
- : el('p', { class: 'dim c3s2', 'data-prep': 'none' }, c3ic('moon', 'sm'), 'Nothing prepared yet');
18
- const bef = m.before && m.before.length
19
- ? el('ul', { class: 'tl', role: 'list', 'aria-label': 'Before the meeting' }, ...m.before.map((x) => el('li', { 'data-before': x.id }, c3ic('todo', 'sm'), x.title, x.due ? el('span', { class: 'mono dim' }, ' · due ' + x.due) : null)))
20
- : el('p', { class: 'dim c3s2', 'data-before': 'none' }, 'Nothing to do before it.');
21
- return el('li', { class: 'trow ' + m.state, 'data-ref-row': '', 'data-today-row': m.id, 'data-state': m.state },
22
- el('div', { class: 'tm mono' }, hm(m.start), el('div', { class: 'dim c3s2' }, m.minutes ? m.minutes + ' min' : 'no end')),
23
- el('div', { class: 'tb' },
24
- el('div', { class: 'chips2' }, el('b', { class: 'tt' }, m.title), marks[m.state] ? el('span', { class: 'st ' + (m.state === 'now' ? 'ok' : ''), 'data-mark': m.state }, marks[m.state]) : null),
25
- el('div', { class: 'chips2 dim c3s2' },
26
- m.people && m.people.length ? el('span', { class: 'chip2', 'data-people': '' }, c3ic('user', 'sm'), m.people.join(', ')) : null,
27
- m.initiative ? el('span', { class: 'chip2', 'data-init': '' }, c3ic('init', 'sm'), m.initiative) : null),
28
- el('div', { class: 'tcols' }, el('div', null, el('div', { class: 'lab mono' }, 'Prepared'), prep), el('div', null, el('div', { class: 'lab mono' }, 'Before the meeting'), bef))));
20
+ const over = m.state === 'past'; const isNow = m.state === 'now'; const isNext = m.state === 'next';
21
+ const chips = [el('span', { class: 'chip2', 'data-when': '' }, c3ic('clock', 'sm'), hm(m.start) + (m.minutes ? ' · ' + length(m.minutes) : ''))];
22
+ if (over) chips.push(el('span', { class: 'tag', 'data-mark': 'over' }, 'over'));
23
+ else if (isNow) chips.push(el('span', { class: 'tag acc', 'data-mark': 'now' }, 'now'));
24
+ else if (isNext) chips.push(el('span', { class: 'tag acc', 'data-mark': 'next' }, 'next'));
25
+ if (m.people && m.people.length) chips.push(el('span', { class: 'dim', 'data-people': '' }, m.people.map(first).join(', ')));
26
+ if (m.initiative) chips.push(el('span', { class: 'chip2', 'data-init': '' }, c3ic('init', 'sm'), m.initiative));
27
+ const prepared = m.prepared || []; const before = m.before || [];
28
+ if (!over) {
29
+ prepared.forEach((x) => chips.push(el('span', { class: 'chip2', 'data-prep': x.kind }, c3ic(x.kind === 'prep' ? 'spark' : 'doc', 'sm'), x.text)));
30
+ if (!prepared.length && !before.length) chips.push(el('span', { class: 'tag', 'data-prep': 'none' }, 'nothing prepared'));
31
+ before.forEach((x) => chips.push(el('span', { class: 'chip2', 'data-before': x.id }, c3ic('todo', 'sm'), 'Before you go: ' + x.title, x.due ? el('span', { class: 'dim' }, ' · due ' + x.due) : null)));
32
+ if (!before.length && prepared.length) chips.push(el('span', { class: 'tag ok', 'data-prep-ready': '' }, 'prep ready'));
33
+ }
34
+ return el('li', { class: 'nrow' + (over ? ' over' : ''), 'data-ref-row': '', 'data-today-row': m.id, 'data-state': m.state },
35
+ el('span', { class: 'ki' + (isNow || isNext ? ' hot' : '') }, c3ic('cal')),
36
+ el('div', { class: 'nb' }, el('span', { class: 'tt' }, m.title), el('div', { class: 'chips' }, ...chips)),
37
+ el('span'));
38
+ }
39
+
40
+ /** The lead line over the list: counts only, from the rows. Pure enough to test: takes the rows of the day. */
41
+ export function leadOf(t) {
42
+ const left = t.filter((m) => m.state !== 'past'); const cur = t.find((m) => m.state === 'now'); const nxt = cur || t.find((m) => m.state === 'next');
43
+ const prepped = left.filter((m) => !(m.before || []).length).length;
44
+ return { left: left.length, prepped, cur: !!cur, nxt: nxt || null, held: t.length - left.length };
45
+ }
46
+
47
+ function lead(t) {
48
+ const L = leadOf(t); const R = 17; const C = 2 * Math.PI * R;
49
+ if (!L.nxt) return el('div', { class: 'lh', 'data-today-lead': '' }, el('span', { class: 'ki' }, c3ic('cal')), el('div', { class: 'lh-main' }, el('b', null, 'No meetings left today'), el('div', { class: 'dim' }, L.held + ' held today.')), el('span'));
50
+ const NS = 'http://www.w3.org/2000/svg';
51
+ const mk = (tag, a) => { const n = document.createElementNS(NS, tag); for (const k of Object.keys(a)) n.setAttribute(k, a[k]); return n; };
52
+ const ring = mk('svg', { width: 44, height: 44, viewBox: '0 0 44 44', role: 'img', 'aria-label': L.prepped + ' of ' + L.left });
53
+ ring.append(mk('circle', { cx: 22, cy: 22, r: R, fill: 'none', stroke: 'var(--muted)', 'stroke-width': 6 }),
54
+ mk('circle', { cx: 22, cy: 22, r: R, fill: 'none', class: 'rg', stroke: 'var(--acc)', 'stroke-width': 6, 'stroke-linecap': 'round', 'stroke-dasharray': (C * L.prepped / Math.max(1, L.left)).toFixed(2) + ' ' + C.toFixed(2), transform: 'rotate(-90 22 22)' }));
55
+ const tx = mk('text', { x: 22, y: 26, 'text-anchor': 'middle', class: 't' }); tx.textContent = L.prepped + '/' + L.left; ring.append(tx);
56
+ return el('div', { class: 'lh', 'data-today-lead': '' }, el('span', { class: 'ki' }, c3ic('cal')),
57
+ el('div', { class: 'lh-main' }, el('b', null, L.nxt.title + (L.cur ? ' is on now' : ' at ' + hm(L.nxt.start) + ' is next')),
58
+ el('div', { class: 'dim' }, L.left + (L.left === 1 ? ' meeting' : ' meetings') + ' left today. ' + L.prepped + ' of ' + L.left + ' prepped.')), ring);
29
59
  }
30
60
 
31
61
  export function todaySection() {
62
+ ensureComponentsCss();
32
63
  const list = el('ul', { class: 'list', role: 'list' });
64
+ const leadHost = el('div');
33
65
  const count = el('span', { class: 'bdg g' }, '0');
66
+ ensureSprite();
67
+ const open = button({ label: 'Open calendar', variant: 'ghost', size: 'sm', icon: 'calendar', attrs: { 'data-pg-opencal': '' }, onClick: (e) => openCalendarPopup(e.currentTarget) });
34
68
  const box = el('section', { class: 'card sect', 'data-c3pg': 'today', 'aria-label': 'Today' },
35
- el('div', { class: 'sh2' }, el('h2', null, c3ic('cal', 'sm'), 'Today ', count)), list);
69
+ el('div', { class: 'sh2' }, el('h2', null, c3ic('cal', 'sm'), 'Today ', count), el('span', { class: 'chips2' }, open)), leadHost, list);
36
70
  list.replaceChildren(el('li', { class: 'fold', 'data-today-state': 'loading' }, c3ic('clock', 'sm'), 'Reading today\'s meetings.'));
37
71
  onBriefPage((j) => {
38
- if (!j || j.ok === false) { count.textContent = '0'; list.replaceChildren(el('li', { class: 'fold', role: 'status', 'data-today-state': 'error' }, c3ic('alert', 'sm'), 'Today\'s meetings could not be read: ' + ((j && j.error) || 'unreadable'))); return; }
72
+ if (!j || j.ok === false) { count.textContent = '0'; leadHost.replaceChildren(); list.replaceChildren(el('li', { class: 'fold', role: 'status', 'data-today-state': 'error' }, c3ic('alert', 'sm'), 'Today\'s meetings could not be read: ' + ((j && j.error) || 'unreadable'))); return; }
39
73
  const t = j.today || [];
40
74
  count.textContent = String(t.length);
75
+ leadHost.replaceChildren(...(t.length ? [lead(t)] : []));
41
76
  list.replaceChildren(...(t.length ? t.map(meetingRow) : [el('li', { class: 'fold', 'data-today-state': 'empty' }, c3ic('moon', 'sm'), 'No meetings today in the calendar PMOS holds.')]));
42
77
  });
43
78
  return box;
@@ -131,7 +131,8 @@ export function needRow(n, i, ctx = {}) {
131
131
  const c0 = build();
132
132
  xp = makeXp(c0); xi = xp.firstChild;
133
133
 
134
- const chips = [ag ? el('span', { class: 'chip2', style: 'gap:4px', title: 'Swipe right to let the agent act' }, c3ic('spark', 'sm'), 'agent move') : null,
134
+ const chips = [el('span', { class: 'tag acc', 'data-decision': '' }, 'decision'), n.hard ? el('span', { class: 'tag', 'data-hard': '' }, 'hard to undo') : null,
135
+ ag ? el('span', { class: 'chip2', title: 'Swipe right to let the agent act' }, c3ic('spark', 'sm'), 'agent move') : null,
135
136
  n.p ? el('span', { class: 'fl ' + n.p.toLowerCase() }, n.p) : null, n.time ? el('span', { class: 'chip2' }, c3ic('clock', 'sm'), n.time) : null,
136
137
  n.who ? avatar(n.who) : null, n.branch ? el('span', { class: 'chip2' }, c3ic('branch', 'sm'), n.branch) : null,
137
138
  n.old ? el('span', { class: 'st warn' }, c3ic('clock', 'sm'), 'old') : null, n.src ? el('span', { class: 'tag' }, n.src) : null,