create-pmos 0.133.0 → 0.135.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.
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/bin/notes-replay.js +235 -0
- package/workspace/c3-briefing-test.js +130 -0
- package/workspace/c3-foundation-test.js +159 -0
- package/workspace/c3-ib-items-test.js +105 -0
- package/workspace/c3-ib-more-test.js +120 -0
- package/workspace/c3-ib-triage-backlog-test.js +119 -0
- package/workspace/c3-ini-harness.js +66 -0
- package/workspace/c3-initiatives-test.js +140 -0
- package/workspace/c3-one-home-test.js +14 -1
- package/workspace/c3-reports-test.js +57 -18
- package/workspace/c3-settings-test.js +139 -0
- package/workspace/c3-work-test.js +42 -10
- package/workspace/fixtures/one-home-baseline.json +1 -9
- package/workspace/guide/briefing-cards.md +15 -3
- package/workspace/guide/briefing.md +12 -8
- package/workspace/guide/calendar-popup.md +2 -2
- package/workspace/guide/console-calendar.md +39 -10
- package/workspace/guide/index.md +1 -0
- package/workspace/guide/initiative-boards.md +65 -0
- package/workspace/guide/jira.md +3 -1
- package/workspace/guide/layout.md +2 -0
- package/workspace/guide/my-work.md +5 -3
- package/workspace/guide/notes.md +35 -2
- package/workspace/guide/reports-dashboards.md +29 -5
- package/workspace/guide/settings.md +68 -0
- package/workspace/guide/welcome.md +10 -10
- package/workspace/lib/calendar-cli.js +3 -2
- package/workspace/lib/calendar-layers.js +21 -10
- package/workspace/lib/calendar-tidy.js +230 -0
- package/workspace/lib/engine/boards.js +542 -0
- package/workspace/lib/engine/breakdown-ops.js +180 -0
- package/workspace/lib/engine/calendar-import.js +84 -0
- package/workspace/lib/engine/calendar-model.js +185 -0
- package/workspace/lib/engine/card-state.js +92 -0
- package/workspace/lib/engine/derived/derive.js +92 -0
- package/workspace/lib/engine/derived/plan.js +3 -0
- package/workspace/lib/engine/derived/trust.js +3 -0
- package/workspace/lib/engine/doc-health.js +142 -0
- package/workspace/lib/engine/doc-sheet.js +75 -0
- package/workspace/lib/engine/intake.js +78 -0
- package/workspace/lib/engine/outbox.js +86 -0
- package/workspace/lib/engine/plan-forecast.js +342 -0
- package/workspace/lib/engine/promises.js +29 -0
- package/workspace/lib/engine/records/index.js +114 -0
- package/workspace/lib/engine/releases.js +96 -0
- package/workspace/lib/engine/replies.js +99 -0
- package/workspace/lib/engine/reply-contract/reply-contract.js +372 -0
- package/workspace/lib/engine/report-families.js +175 -0
- package/workspace/lib/engine/roadmap-ops.js +150 -0
- package/workspace/lib/engine/sessions-list.js +27 -0
- package/workspace/lib/engine/signal-rules.js +95 -0
- package/workspace/lib/engine/signals/cards.js +57 -0
- package/workspace/lib/engine/signals/detectors.js +370 -0
- package/workspace/lib/engine/signals/facts-cli.js +8 -0
- package/workspace/lib/engine/signals/facts.js +197 -0
- package/workspace/lib/engine/signals/index.js +33 -0
- package/workspace/lib/engine/signals/kinds.js +110 -0
- package/workspace/lib/engine/signals/layout-paths.js +44 -0
- package/workspace/lib/engine/signals/options.js +24 -0
- package/workspace/lib/engine/signals/score.js +72 -0
- package/workspace/lib/engine/signals/select.js +47 -0
- package/workspace/lib/engine/step-planner.js +65 -0
- package/workspace/lib/engine/trust/claim-rules.json +93 -0
- package/workspace/lib/engine/trust/import-calendar.js +21 -0
- package/workspace/lib/engine/trust/import-guard.js +36 -0
- package/workspace/lib/engine/trust/ledger.js +53 -0
- package/workspace/lib/engine/trust/outbox.js +45 -0
- package/workspace/lib/engine/trust/plan.js +5 -0
- package/workspace/lib/engine/trust/trust.js +21 -0
- package/workspace/lib/engine/truth-table.js +57 -0
- package/workspace/lib/engine/workspace-lib.js +24 -0
- package/workspace/lib/factory-join.js +1 -1
- package/workspace/lib/facts-async.js +4 -4
- package/workspace/lib/headless.js +95 -0
- package/workspace/lib/import-overlay.js +2 -1
- package/workspace/lib/jira-status.js +30 -3
- package/workspace/lib/notes-audio.js +28 -9
- package/workspace/lib/notes-capture.js +136 -0
- package/workspace/lib/notes-final.js +38 -13
- package/workspace/lib/notes-live-guard.js +117 -0
- package/workspace/lib/notes-replay.js +125 -0
- package/workspace/lib/notes-screen.js +180 -0
- package/workspace/lib/notes-whisper.js +139 -16
- package/workspace/lib/notes.js +4 -2
- package/workspace/lib/outbox-store.js +10 -6
- package/workspace/lib/pmos-verbs.js +3 -1
- package/workspace/lib/proactive-cli.js +7 -0
- package/workspace/lib/proactive.js +15 -2
- package/workspace/lib/repo-script.js +36 -0
- package/workspace/lib/report-dashboards.js +5 -2
- package/workspace/lib/report-frame.js +122 -0
- package/workspace/lib/report-schedules.js +113 -0
- package/workspace/lib/route-modules.js +9 -2
- package/workspace/lib/routes-boards.js +734 -0
- package/workspace/lib/routes-brief-page.js +13 -4
- package/workspace/lib/routes-briefing.js +22 -11
- package/workspace/lib/routes-c3-init.js +4 -3
- package/workspace/lib/routes-c3-life.js +2 -1
- package/workspace/lib/routes-c3-reports.js +96 -13
- package/workspace/lib/routes-c3-strategy.js +4 -3
- package/workspace/lib/routes-c3-timeline.js +3 -2
- package/workspace/lib/routes-calendar-import.js +3 -2
- package/workspace/lib/routes-calendar-views.js +9 -4
- package/workspace/lib/routes-doc-health.js +2 -1
- package/workspace/lib/routes-doc-sheet.js +2 -1
- package/workspace/lib/routes-forecast.js +2 -1
- package/workspace/lib/routes-hooks.js +3 -2
- package/workspace/lib/routes-initiatives.js +2 -1
- package/workspace/lib/routes-intake.js +2 -1
- package/workspace/lib/routes-notes.js +113 -15
- package/workspace/lib/routes-outbox.js +23 -3
- package/workspace/lib/routes-pm-items.js +19 -6
- package/workspace/lib/routes-records.js +3 -2
- package/workspace/lib/routes-releases.js +2 -1
- package/workspace/lib/routes-report-facts.js +4 -3
- package/workspace/lib/routes-report-families.js +2 -1
- package/workspace/lib/routes-roadmap-ops.js +5 -4
- package/workspace/lib/routes-runs.js +6 -5
- package/workspace/lib/routes-settings.js +51 -0
- package/workspace/lib/routes-signals.js +3 -2
- package/workspace/lib/routes-stage-tools.js +220 -0
- package/workspace/lib/run-answers.js +2 -1
- package/workspace/lib/settings-store.js +226 -0
- package/workspace/lib/stage-tools.js +208 -0
- package/workspace/lib/vendor/scripts/signals/layout-paths.js +2 -1
- package/workspace/lib/vendor/scripts/workspace-lib.js +24 -0
- package/workspace/lib/work-evidence.js +73 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/briefing-deck.js +63 -17
- package/workspace/public/pm/briefing-model.js +36 -7
- package/workspace/public/pm/c3-ambient.js +100 -0
- package/workspace/public/pm/c3-brief-needs.js +26 -6
- package/workspace/public/pm/c3-brief-pulse.js +12 -4
- package/workspace/public/pm/c3-brief-today.js +56 -21
- package/workspace/public/pm/c3-brief.js +2 -1
- package/workspace/public/pm/c3-calendar.js +161 -42
- package/workspace/public/pm/c3-calpop.js +5 -5
- package/workspace/public/pm/c3-demo-boards.js +649 -0
- package/workspace/public/pm/c3-demo-brief-page.js +1 -1
- package/workspace/public/pm/c3-demo-cal4.js +3 -2
- package/workspace/public/pm/c3-demo-cal5.js +52 -0
- package/workspace/public/pm/c3-demo-data.js +17 -27
- package/workspace/public/pm/c3-demo-rep3.js +46 -5
- package/workspace/public/pm/c3-demo.js +2 -1
- package/workspace/public/pm/c3-dock.js +1 -1
- package/workspace/public/pm/c3-ib-docs.js +80 -0
- package/workspace/public/pm/c3-ib-initiatives.js +134 -0
- package/workspace/public/pm/c3-ib-items.js +201 -0
- package/workspace/public/pm/c3-ib-releases.js +51 -0
- package/workspace/public/pm/c3-ib-sprints.js +55 -0
- package/workspace/public/pm/c3-ib-triage.js +178 -0
- package/workspace/public/pm/c3-ii-tabs.js +240 -0
- package/workspace/public/pm/c3-ini-records.js +197 -0
- package/workspace/public/pm/c3-ini-timeline.js +278 -0
- package/workspace/public/pm/c3-ini-ui.js +229 -0
- package/workspace/public/pm/c3-ini-work.js +139 -0
- package/workspace/public/pm/c3-initiatives.css +218 -0
- package/workspace/public/pm/c3-initiatives.js +741 -0
- package/workspace/public/pm/c3-plan.js +2 -2
- package/workspace/public/pm/c3-reports.css +47 -47
- package/workspace/public/pm/c3-reports.js +223 -185
- package/workspace/public/pm/c3-settings.css +74 -0
- package/workspace/public/pm/c3-settings.js +703 -0
- package/workspace/public/pm/c3-shell.js +102 -36
- package/workspace/public/pm/c3-terminal-icons.js +2 -2
- package/workspace/public/pm/c3-terminal.js +2 -2
- package/workspace/public/pm/calendar-views.js +26 -13
- package/workspace/public/pm/components.css +319 -213
- package/workspace/public/pm/console3.css +118 -23
- package/workspace/public/pm/dest-notes.js +26 -4
- package/workspace/public/pm/notes-screen-capture.js +165 -0
- package/workspace/public/pm/notes-screen-view.js +98 -0
- package/workspace/public/pm/panel-trees.js +5 -2
- package/workspace/public/pm/pm-work-view.js +233 -146
- package/workspace/public/pm/shell-bell.js +3 -2
- package/workspace/public/pm/shell-recording.js +148 -17
- package/workspace/public/pm/tokens.css +2 -2
- package/workspace/public/pm/view-notes.js +53 -11
- package/workspace/public/pm/welcome.js +197 -89
- package/workspace/public/welcome.html +6 -4
- package/workspace/server.js +105 -6
- package/workspace/skills/day-check.skill +1 -1
- package/workspace/skills/jira-sync.skill +6 -4
- package/workspace/skills/morning-brief.skill +1 -1
- package/workspace/test-files.json +14 -3
- package/workspace/test-lists/c3-ib-items.json +9 -0
- package/workspace/test-lists/c3-ib-more.json +9 -0
- package/workspace/test-lists/c3-ib-triage-backlog.json +9 -0
- package/workspace/test-lists/c3-initiative-boards.json +6 -0
- package/workspace/test-lists/calendar-tidy-up.json +6 -0
- package/workspace/test-lists/console3-briefing.json +9 -0
- package/workspace/test-lists/console3-foundation.json +9 -0
- package/workspace/test-lists/console3-reports.json +3 -3
- package/workspace/test-lists/meeting-notes.json +12 -4
- package/workspace/test-lists/workspace-headless-jobs.json +5 -0
- package/workspace/test-lists/workspace-package-script-closure.json +6 -0
- package/workspace/lib/vendor/workspace/lib/actor.js +0 -91
- package/workspace/lib/vendor/workspace/lib/board-cache.js +0 -156
- package/workspace/lib/vendor/workspace/lib/console-state.js +0 -295
- package/workspace/lib/vendor/workspace/lib/factory-join.js +0 -158
- package/workspace/lib/vendor/workspace/lib/git-info.js +0 -306
- package/workspace/lib/vendor/workspace/lib/plan-items.js +0 -922
- package/workspace/lib/vendor/workspace/lib/plan-tree.js +0 -419
- package/workspace/lib/vendor/workspace/lib/review-gate.js +0 -39
- package/workspace/lib/vendor/workspace/lib/sink.js +0 -321
- 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
|
-
|
|
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
|
|
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
|
|
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'),
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
el('
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 = [
|
|
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,
|