create-pmos 0.252.0 → 0.254.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 +2 -2
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/workflow-state.js +6 -1
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/briefing-premium-board-test.js +275 -0
- package/workspace/briefing-premium-initiative-test.js +304 -0
- package/workspace/briefing-premium-products-test.js +164 -0
- package/workspace/briefing-premium-test.js +438 -0
- package/workspace/briefing-premium-work-test.js +281 -0
- package/workspace/fixtures/board-premium-facts.js +38 -0
- package/workspace/fixtures/briefing-premium-facts.js +32 -0
- package/workspace/fixtures/initiative-premium-facts.js +42 -0
- package/workspace/fixtures/my-work-premium-facts.js +31 -0
- package/workspace/guide/briefing-premium.md +151 -0
- package/workspace/guide/index.md +1 -0
- package/workspace/lib/board-premium.js +133 -0
- package/workspace/lib/briefing-premium-agent.js +134 -0
- package/workspace/lib/briefing-premium-products.js +121 -0
- package/workspace/lib/briefing-premium-writes.js +138 -0
- package/workspace/lib/briefing-premium.js +581 -0
- package/workspace/lib/engine/workflow-state.js +6 -1
- package/workspace/lib/headless.js +1 -1
- package/workspace/lib/initiative-premium.js +147 -0
- package/workspace/lib/my-work-premium.js +151 -0
- package/workspace/lib/route-modules.js +4 -0
- package/workspace/lib/routes-board-premium.js +61 -0
- package/workspace/lib/routes-briefing-premium.js +115 -0
- package/workspace/lib/routes-initiative-premium.js +68 -0
- package/workspace/lib/routes-my-work-premium.js +31 -0
- package/workspace/lib/settings-store.js +3 -0
- package/workspace/lib/vendor/scripts/workflow-state.js +6 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/premium-board.js +180 -0
- package/workspace/public/pm/premium-initiative.js +336 -0
- package/workspace/public/pm/premium-shell.js +184 -0
- package/workspace/public/pm/premium-work.js +198 -0
- package/workspace/public/pm/premium.css +1212 -0
- package/workspace/public/pm/premium.js +272 -0
- package/workspace/public/premium-board.html +73 -0
- package/workspace/public/premium-initiative.html +46 -0
- package/workspace/public/premium-work.html +84 -0
- package/workspace/public/premium.html +69 -0
- package/workspace/public/vendor/geist-mono-var.woff2 +0 -0
- package/workspace/public/vendor/geist-var.woff2 +0 -0
- package/workspace/server.js +5 -3
- package/workspace/test-files.json +14 -0
- package/workspace/test-lists/briefing-premium.json +19 -0
- package/workspace/third-party/README.md +12 -0
- package/workspace/third-party/geist-OFL.LICENSE +93 -0
- package/workspace/third-party/geist-mono-OFL.LICENSE +93 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/* pm/premium-work.js — the premium My work page (initiative briefing-premium-work, slice 4). Reads GET /api/my-work-premium and draws the model
|
|
2
|
+
* lib/my-work-premium.js builds from the list the Work page reads: the hero, "On your plate", With agents, Waiting on others, the dock and the standard dialog.
|
|
3
|
+
* Every press goes to the Work page's own route under /api/pm-items; this file adds no writer. A control with nothing honest to write stays off and says why. */
|
|
4
|
+
import { pmPost } from './http.js';
|
|
5
|
+
import { $, $$, esc, openDlg, closeDlg, toast, loadThread, onScroll, wireRail, logHtml, turnA, turnU, ptx, CHECK, S } from './premium-shell.js';
|
|
6
|
+
|
|
7
|
+
let M = null; // the model
|
|
8
|
+
let openR = null; // the row whose dialog is open
|
|
9
|
+
const ICO = (p) => `<svg class="i" viewBox="0 0 24 24">${p}</svg>`;
|
|
10
|
+
const GLYPH = {
|
|
11
|
+
Jira: '<path d="M12 3l8 8-8 8-8-8z"/><path d="M9 11l3 3 3-3"/>',
|
|
12
|
+
Slack: '<path d="M9 4 7 20M17 4l-2 16M4.5 9h15M4 15h15"/>',
|
|
13
|
+
Board: '<path d="M12 3 3 8l9 5 9-5z"/><path d="m3 13 9 5 9-5"/>',
|
|
14
|
+
Yours: '<path d="M4 20l4-1 11-11-3-3L5 16z"/>',
|
|
15
|
+
};
|
|
16
|
+
const glyph = (src) => GLYPH[src] || '<circle cx="12" cy="12" r="4"/>';
|
|
17
|
+
wireRail('work');
|
|
18
|
+
|
|
19
|
+
/* ── hero and header ───────────────────────────────────────────────────────────────────────────────────── */
|
|
20
|
+
function drawHero() {
|
|
21
|
+
$('#date').textContent = M.date;
|
|
22
|
+
$('#brief').innerHTML = M.hero.map((s) => s.parts.map((p) => (p.s ? `<span class="k">${esc(p.t)}</span>` : esc(p.t))).join('')).join(' ');
|
|
23
|
+
$('#snap').textContent = M.header.text;
|
|
24
|
+
$('#snap').title = 'Every figure on this page comes from an item on your list. A date shows only when one was set.';
|
|
25
|
+
}
|
|
26
|
+
function drawDock() { $('#dN').textContent = M.dock.needs; $('#dLab').textContent = M.dock.needs === 0 ? 'to clear' : M.dock.label; $('#dNext').innerHTML = M.dock.next ? `<i></i>Next <b>${esc(M.dock.next)}</b>` : 'Nothing more on your list'; }
|
|
27
|
+
|
|
28
|
+
/* ── the plate ─────────────────────────────────────────────────────────────────────────────────────────── */
|
|
29
|
+
function rowHtml(r, o = {}) {
|
|
30
|
+
const inbox = r.list === 'inbox' || r.list === 'tray', sm = o.sm;
|
|
31
|
+
let lead;
|
|
32
|
+
if (sm === 'ag') lead = `<span class="ags${r.agent === 'ready' ? ' ready' : ''}">${r.agent === 'ready' ? ICO('<path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/>') : r.agent === 'failed' ? ICO('<path d="M12 8v5M12 16.5v.5"/><circle cx="12" cy="12" r="9"/>') : '<span class="spin"></span>'}</span>`;
|
|
33
|
+
else if (sm === 'wait') lead = `<span class="avp" aria-hidden="true">${esc(((r.sub.match(/waiting on (\S)/) || [])[1] || '·'))}</span>`;
|
|
34
|
+
else if (inbox) lead = `<span class="wgl" title="${esc(r.src)}">${ICO(glyph(r.src))}</span>`;
|
|
35
|
+
else if (r.agent === 'ready') lead = `<button class="wck dec" data-open="${esc(r.key)}" aria-label="Review the agent draft: ${esc(r.title)}" title="An agent draft is ready. Open it to review.">${ICO('<path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/>')}</button>`;
|
|
36
|
+
else lead = `<button class="wck" data-ck="${esc(r.id)}" aria-pressed="false" aria-label="Mark done: ${esc(r.title)}">${CHECK}</button>`;
|
|
37
|
+
const meta = r.chip ? `<span class="wm"><span class="due${r.overdue ? ' od' : ''}">${esc(r.chip)}</span></span>` : '';
|
|
38
|
+
const state = sm === 'ag' ? ' · ' + ({ ready: 'draft ready to review', failed: 'the run failed', queued: 'queued', working: 'working', sending: 'sending' }[r.agent] || '') : '';
|
|
39
|
+
return `<div class="wrw"><div class="wrow${sm ? ' sm' : ''}${inbox ? ' inb' : ''}${o.first ? ' first' : ''}" data-key="${esc(r.key)}">${lead}<button class="wrh" aria-haspopup="dialog"><span class="wtx"><span class="wtit">${esc(r.title)}${o.first ? '<em class="tgf">Do this first</em>' : ''}</span><span class="ws">${esc(r.sub)}${esc(state)}</span></span>${meta}</button></div></div>`;
|
|
40
|
+
}
|
|
41
|
+
function drawPlate() {
|
|
42
|
+
const P = M.plate;
|
|
43
|
+
$('#plateCap').innerHTML = `<b>${P.n}</b> to clear`;
|
|
44
|
+
$('#plateNote').textContent = P.note;
|
|
45
|
+
let h = '';
|
|
46
|
+
for (const s of P.sections) {
|
|
47
|
+
if (!s.n) {
|
|
48
|
+
h += `<div class="wsec" data-lane="${s.key}"><span class="label">${esc(s.label)}</span><em>empty</em></div>` + (s.key === 'inbox' ? `<p class="empty3">${CHECK}Nothing waits to be sorted.</p>` : '<p class="empty3 plain">Nothing here.</p>');
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
h += `<div class="wsec" data-lane="${s.key}"><span class="label">${esc(s.label)}</span><em>${s.n} open</em></div>`;
|
|
52
|
+
const first = s.key === 'today' ? s.rows.find((r) => r.overdue) || null : null;
|
|
53
|
+
h += s.rows.map((r) => rowHtml(r, { first: first && r.key === first.key })).join('');
|
|
54
|
+
if (s.more) h += `<p class="note">${s.more} more ${s.more === 1 ? 'item is' : 'items are'} not shown here.</p>`;
|
|
55
|
+
}
|
|
56
|
+
$('#plateBody').innerHTML = h;
|
|
57
|
+
}
|
|
58
|
+
function drawTray() {
|
|
59
|
+
const T = M.tray;
|
|
60
|
+
$('#trayCap').textContent = T.cap;
|
|
61
|
+
$('#trayBody').innerHTML = T.rows.length ? T.rows.map((r) => rowHtml(r)).join('') + (T.more ? `<p class="note">${T.more} more not shown here.</p>` : '') : '<p class="empty3 plain">Nothing in your tray. Add a line above and it lands here first.</p>';
|
|
62
|
+
$('#trayNote').textContent = T.note;
|
|
63
|
+
}
|
|
64
|
+
function drawAgents() {
|
|
65
|
+
const A = M.agents, c = A.counts;
|
|
66
|
+
$('#agCap').textContent = A.cap;
|
|
67
|
+
$('#pipe').innerHTML = `<div class="pst"><b>${c.running}</b><span>Running</span></div><div class="pst"><b>${c.failed}</b><span>Failed</span></div><div class="pst${c.ready ? ' hot' : ''}"><b>${c.ready}</b><span>To review</span></div>`;
|
|
68
|
+
$('#agl').innerHTML = A.rows.length ? A.rows.map((r) => rowHtml(r, { sm: 'ag' })).join('') : '<p class="empty3 plain">No agent is working on your items.</p>';
|
|
69
|
+
$('#agHold').textContent = A.hold;
|
|
70
|
+
}
|
|
71
|
+
function drawWaiting() {
|
|
72
|
+
const W = M.waiting;
|
|
73
|
+
$('#waitCap').textContent = W.cap;
|
|
74
|
+
$('#waitBody').innerHTML = W.rows.length ? W.rows.map((r) => rowHtml(r, { sm: 'wait' })).join('') + (W.more ? `<p class="note">${W.more} more not shown here.</p>` : '') : '<p class="empty3 plain">Nobody owes you anything.</p>';
|
|
75
|
+
}
|
|
76
|
+
function drawReview() {
|
|
77
|
+
const R = M.review; $('#revCard').hidden = !R.n;
|
|
78
|
+
$('#revCap').textContent = R.n + ' ' + (R.n === 1 ? 'item' : 'items');
|
|
79
|
+
$('#revTxt').innerHTML = `<b>${R.n}</b> ${R.n === 1 ? 'item has' : 'items have'} sat untouched for ${R.days} days. Open one to keep it or let it go.`;
|
|
80
|
+
}
|
|
81
|
+
function draw() { drawHero(); drawPlate(); drawTray(); drawAgents(); drawWaiting(); drawReview(); drawDock(); onScroll(); }
|
|
82
|
+
const rowOf = (key) => (M && M.rows || []).find((r) => r.key === key) || null;
|
|
83
|
+
|
|
84
|
+
/* ── the dialog: detail first, the presses of the Work page, the read-only agent beside it ─────────────────── */
|
|
85
|
+
const NOTYET = [['later-today', 'Later today'], ['tomorrow', 'Tomorrow'], ['next-week', 'Next week']];
|
|
86
|
+
function cardHtml(r) {
|
|
87
|
+
const d = r.dialog, w = d.write, a = w.actions, err = '<p class="nodef err" id="cardErr" role="alert" hidden></p>';
|
|
88
|
+
const move = [a.includes('flag') ? ['flag', 'Do today'] : null, a.includes('park') ? ['park', 'Later'] : null].filter(Boolean);
|
|
89
|
+
return `<div class="acard" id="card"><p class="ct">What you can do</p>`
|
|
90
|
+
+ `<div class="cact two"><button class="cta" data-p="done">${CHECK}<span>Done</span></button>${a.includes('keep') ? '<button class="cta quiet" data-p="keep"><span>Keep it</span></button>' : ''}</div>`
|
|
91
|
+
+ `<div class="mv2">Not yet <span class="seg2">${NOTYET.map(([k, t]) => `<button data-p="notyet" data-when="${k}">${t}</button>`).join('')}</span></div>`
|
|
92
|
+
+ (move.length ? `<div class="mv2">Move to <span class="seg2">${move.map(([k, t]) => `<button data-p="${k}">${t}</button>`).join('')}</span></div>` : '')
|
|
93
|
+
+ (a.includes('waiting') ? `<label class="whyl" for="wOn">Waiting on someone</label><div class="cact two"><input id="wOn" class="why one" maxlength="80" placeholder="A name" autocomplete="off"><button class="cta quiet" data-p="waiting" id="wBtn" disabled><span>Wait for them</span></button></div>` : '')
|
|
94
|
+
+ `<div class="cact"><button class="cta quiet" data-p="remove"><span>Drop it</span></button></div>`
|
|
95
|
+
+ `${r.agent ? `<p class="nodef" id="holdWhy">${esc(d.hold)}</p>` : ''}${err}</div>`;
|
|
96
|
+
}
|
|
97
|
+
function paneHtml(r) {
|
|
98
|
+
const p = r.dialog.pane;
|
|
99
|
+
let h = `<p class="dpath">${esc(p.file)}</p>`; // the dialog heading above already names the item and its source
|
|
100
|
+
if (p.rows && p.rows.length) h += `<div class="kv2">${p.rows.map(([a, b]) => `<span>${esc(a)}</span><span>${esc(b)}</span>`).join('')}</div>`;
|
|
101
|
+
if (p.paras && p.paras.length) h += `<h3 class="d2">${esc(p.parasTitle)}</h3>${p.paras.map((x) => `<p>${esc(x)}</p>`).join('')}`;
|
|
102
|
+
return `<article class="paper doc">${h}<p class="xn">The item as your list holds it.</p></article>`;
|
|
103
|
+
}
|
|
104
|
+
function openRow(r) {
|
|
105
|
+
if (!r) return;
|
|
106
|
+
openR = r; const d = r.dialog;
|
|
107
|
+
openDlg({ eye: d.eye + ' · real data', title: r.title, sub: r.sub, say: d.say, stream: turnA(logHtml(d)) + turnA(ptx(d.say)) + cardHtml(r), noteKey: r.key, pane: paneHtml(r), label: d.pane.label });
|
|
108
|
+
loadThread(r.key);
|
|
109
|
+
setTimeout(() => { const b = $('#dBody'); b.scrollTop = b.scrollHeight; }, 120);
|
|
110
|
+
}
|
|
111
|
+
function showErr(msg) { const e = $('#cardErr'); if (!e) { toast(msg); return; } e.textContent = msg; e.hidden = false; }
|
|
112
|
+
|
|
113
|
+
/* a press: the page waits for the route's answer; a refusal shows its reason and changes nothing */
|
|
114
|
+
const ROUTE = { done: 'done', notyet: 'notyet', flag: 'flag', park: 'park', waiting: 'waiting', remove: 'remove', keep: 'keep' };
|
|
115
|
+
async function press(kind, id, extra) {
|
|
116
|
+
const body = { id, ...(extra || {}) };
|
|
117
|
+
if (kind === 'flag') body.flagged = true;
|
|
118
|
+
return pmPost('/api/pm-items/' + ROUTE[kind], body);
|
|
119
|
+
}
|
|
120
|
+
const SAID = { done: 'Marked done.', notyet: 'Put off. It comes back by itself.', flag: 'Moved to Today.', park: 'Moved to Later.', waiting: 'Now waiting.', remove: 'Dropped.', keep: 'Kept.' };
|
|
121
|
+
function toastUndo(msg, undo) {
|
|
122
|
+
const t = $('#toast'); t.innerHTML = `${esc(msg)} <button class="undo" type="button">Undo</button>`; t.classList.add('on');
|
|
123
|
+
const b = $('.undo', t); b.onclick = async () => { t.classList.remove('on'); const r = await undo(); if (!r || r.ok !== true) toast((r && r.error) || 'It could not be undone.'); await load(); };
|
|
124
|
+
clearTimeout(toastUndo.t); toastUndo.t = setTimeout(() => t.classList.remove('on'), 8000);
|
|
125
|
+
}
|
|
126
|
+
async function doPress(kind, id, extra, undo) {
|
|
127
|
+
const btns = $$('#card button'); btns.forEach((b) => { b.disabled = true; });
|
|
128
|
+
const res = await press(kind, id, extra);
|
|
129
|
+
if (!res || res.ok !== true) { btns.forEach((b) => { b.disabled = false; }); wSync(); showErr((res && res.error) || 'The press was not recorded.'); return false; }
|
|
130
|
+
closeDlg(); openR = null;
|
|
131
|
+
if (undo) toastUndo(SAID[kind], undo); else toast(SAID[kind]);
|
|
132
|
+
await load(); return true;
|
|
133
|
+
}
|
|
134
|
+
const wSync = () => { const b = $('#wBtn'); if (b) b.disabled = !(($('#wOn') || {}).value || '').trim(); };
|
|
135
|
+
$('#dBody').addEventListener('input', (e) => { if (e.target.id === 'wOn') wSync(); });
|
|
136
|
+
$('#dBody').addEventListener('click', (e) => {
|
|
137
|
+
const lt = e.target.closest('#logT');
|
|
138
|
+
if (lt) { const c = $('#alog').classList.toggle('col'); lt.setAttribute('aria-expanded', !c); return; }
|
|
139
|
+
const b = e.target.closest('[data-p]');
|
|
140
|
+
if (!b || b.disabled || !openR) return;
|
|
141
|
+
const id = openR.id, k = b.dataset.p;
|
|
142
|
+
if (k === 'notyet') doPress('notyet', id, { when: b.dataset.when });
|
|
143
|
+
else if (k === 'waiting') doPress('waiting', id, { on: ($('#wOn').value || '').trim() });
|
|
144
|
+
else if (k === 'done') doPress('done', id, null, () => pmPost('/api/pm-items/reopen', { id }));
|
|
145
|
+
else if (k === 'remove') doPress('remove', id, null, () => pmPost('/api/pm-items/undo', { id }));
|
|
146
|
+
else doPress(k, id);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
/* ── the list: a check marks done, a row opens its dialog ────────────────────────────────────────────────── */
|
|
150
|
+
$('#plateBody').addEventListener('click', async (e) => { await listClick(e); });
|
|
151
|
+
$('#agl').addEventListener('click', (e) => { listClick(e); });
|
|
152
|
+
$('#waitBody').addEventListener('click', (e) => { listClick(e); });
|
|
153
|
+
$('#trayBody').addEventListener('click', (e) => { listClick(e); });
|
|
154
|
+
async function listClick(e) {
|
|
155
|
+
const ck = e.target.closest('.wck');
|
|
156
|
+
if (ck && ck.dataset.open) { openRow(rowOf(ck.dataset.open)); return; }
|
|
157
|
+
if (ck) {
|
|
158
|
+
const id = ck.dataset.ck;
|
|
159
|
+
ck.setAttribute('aria-pressed', 'true');
|
|
160
|
+
const res = await pmPost('/api/pm-items/done', { id });
|
|
161
|
+
if (!res || res.ok !== true) { ck.setAttribute('aria-pressed', 'false'); toast((res && res.error) || 'The press was not recorded.'); return; }
|
|
162
|
+
toastUndo(SAID.done, () => pmPost('/api/pm-items/reopen', { id })); await load(); return;
|
|
163
|
+
}
|
|
164
|
+
const row = e.target.closest('.wrow'); if (row && e.target.closest('.wrh')) openRow(rowOf(row.dataset.key));
|
|
165
|
+
}
|
|
166
|
+
$('#revBtn').onclick = () => { const id = (M.review.ids || [])[0]; const r = (M.rows || []).find((x) => x.id === id); if (r) openRow(r); };
|
|
167
|
+
|
|
168
|
+
/* ── add: one line, read by the Work page's own parser ─────────────────────────────────────────────────────── */
|
|
169
|
+
$('#cap').addEventListener('submit', async (e) => {
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
const inp = $('#capIn'), line = inp.value.trim();
|
|
172
|
+
if (!line) { $('#capHint').textContent = 'Write one line to add.'; return; }
|
|
173
|
+
const go = $('.capb'); go.disabled = true;
|
|
174
|
+
const res = await pmPost('/api/pm-items/add', { line });
|
|
175
|
+
go.disabled = false;
|
|
176
|
+
if (!res || res.ok !== true) { $('#capHint').textContent = (res && res.error) || 'It was not added.'; return; }
|
|
177
|
+
inp.value = '';
|
|
178
|
+
const it = res.item || {};
|
|
179
|
+
const bits = [it.due ? 'due ' + it.due : null, it.priority].filter(Boolean);
|
|
180
|
+
$('#capHint').textContent = 'Added: ' + (it.title || line) + (bits.length ? ' (' + bits.join(', ') + ')' : '') + '. It is in your tray until you place it.';
|
|
181
|
+
await load();
|
|
182
|
+
const fresh = it.id ? rowOf('work:' + it.id) : null; // a new item opens at once, so it can be placed
|
|
183
|
+
if (fresh) openRow(fresh);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
/* ── load ────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
187
|
+
async function load() {
|
|
188
|
+
let r = null, status = 0;
|
|
189
|
+
try { const res = await fetch('/api/my-work-premium', { credentials: 'same-origin' }); status = res.status; r = await res.json(); } catch { r = null; }
|
|
190
|
+
if (!r || !r.ok) {
|
|
191
|
+
$('#snap').textContent = 'Your list could not be read';
|
|
192
|
+
$('#brief').textContent = 'My work could not read your list, so it shows nothing rather than guess.' + (r && r.error ? ' The reason: ' + r.error : status ? ' (the server answered ' + status + ')' : ' The server did not answer.');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
M = r.model; draw();
|
|
196
|
+
}
|
|
197
|
+
$('#brief').textContent = 'Reading your list.';
|
|
198
|
+
load();
|