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,272 @@
|
|
|
1
|
+
/* pm/premium.js — the premium Briefing page (initiative briefing-premium, slice 1). Reads GET /api/briefing-premium and draws the model
|
|
2
|
+
* lib/briefing-premium.js builds: the hero, "What you need to do", Today, the dock and the standard dialog. The read is one GET; the only writes are the PM's own presses (slice 2, below). A row clears when the repository's record changes, so there is nothing to press to clear it.
|
|
3
|
+
* Slice 2 (initiative briefing-premium-writes): the buttons that record the PM's own calls post to /api/briefing-premium/{decide,put-off,return}.
|
|
4
|
+
* Slice 3: the reply box asks a read-only agent. A control with nothing honest to write stays disabled and says why. */
|
|
5
|
+
import { pmPost } from './http.js';
|
|
6
|
+
import { $, $$, esc, store, THEME_KEY, RM, AI, S, theme, toast, kv, H, openDlg, closeDlg, setTab, setMode, CHECK, CHEV, ptx, turnA, turnU, H1, H2, P1, UL, logHtml, loadThread, onScroll, wireRail } from './premium-shell.js';
|
|
7
|
+
const ICON = {
|
|
8
|
+
meeting: '<rect x="4" y="5" width="16" height="15" rx="3"/><path d="M4 10h16M9 3v4M15 3v4"/>',
|
|
9
|
+
merge: '<circle cx="6" cy="6" r="2"/><circle cx="6" cy="18" r="2"/><circle cx="18" cy="9" r="2"/><path d="M6 8v8M18 11c0 4-6 3-12 5"/>',
|
|
10
|
+
run: '<path d="M13 3 5 14h6l-1 7 8-11h-6z"/>',
|
|
11
|
+
};
|
|
12
|
+
const SRC = { meeting: 'Calendar', merge: 'Merges', run: 'Agent runs' };
|
|
13
|
+
let M = null; // the model
|
|
14
|
+
const on = new Set(Object.keys(SRC));
|
|
15
|
+
const pinned = new Set();
|
|
16
|
+
|
|
17
|
+
wireRail('brief');
|
|
18
|
+
|
|
19
|
+
/* ── a day's baseline, kept on this device, so "N of M done" counts rows that cleared since the page first read today ────── */
|
|
20
|
+
const dayKey = () => { const d = new Date(); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); };
|
|
21
|
+
function baseline(keys) {
|
|
22
|
+
const k = 'pmos.premium.base.' + dayKey();
|
|
23
|
+
let seen = [];
|
|
24
|
+
try { seen = JSON.parse(store.get(k) || '[]'); } catch { seen = []; }
|
|
25
|
+
const all = [...new Set([...seen, ...keys])];
|
|
26
|
+
store.set(k, JSON.stringify(all));
|
|
27
|
+
return all;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ── hero ───────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
31
|
+
function drawHero() {
|
|
32
|
+
$('#date').textContent = M.date;
|
|
33
|
+
$('#brief').innerHTML = M.hero.map((s) => s.parts.map((p) => (p.s ? `<span class="k">${esc(p.t)}</span>` : esc(p.t))).join('')).join(' ');
|
|
34
|
+
$('#snap').textContent = M.header.text;
|
|
35
|
+
$('#snap').title = M.header.missing.length ? 'A source that cannot be read adds nothing. It is named here, and the page does not guess.' : 'Every figure on this page comes from a record in the repository.';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ── rows ───────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
39
|
+
const STAMP = (n) => String(n).padStart(2, '0');
|
|
40
|
+
function drawRows() {
|
|
41
|
+
const rows = M.list.rows, L = $('#rank');
|
|
42
|
+
const keys = rows.map((r) => r.key), off = (M.list.putOff || []).map((x) => x.key); // a row put off is not done: it leaves both counts and is named below the list
|
|
43
|
+
const base = (M.pending ? keys : baseline(keys)).filter((k) => !off.includes(k)), done = base.filter((k) => !keys.includes(k)).length, total = base.length;
|
|
44
|
+
const order = rows.slice().sort((a, b) => (pinned.has(b.key) - pinned.has(a.key)) || (a.n - b.n));
|
|
45
|
+
L.innerHTML = order.map((r, i) => `<div class="rr${i < 3 ? ' lead' : ''}${i === 0 ? ' first' : ''}" data-key="${esc(r.key)}"${linkOf(r) ? ` data-link="${esc(linkOf(r))}"` : ''}><button class="rhd" aria-haspopup="dialog"><span class="rn">${STAMP(i + 1)}</span><span><span class="rt">${esc(r.title)}${i === 0 ? '<em class="tgf">Do this first</em>' : ''}</span><span class="rs">${r.product ? `<b class="pn">${esc(r.product)}</b>` : ''}${esc(r.subject)}</span><span class="sug"><span>${esc(r.prepared)}</span></span><span class="skip">${esc(r.skip)}</span></span></button><button class="rpin" data-pin="${esc(r.key)}" aria-pressed="${pinned.has(r.key)}" aria-label="Pin ${esc(r.title)} to the top" title="Pin to the top"><svg class="i" viewBox="0 0 24 24"><path d="M9 4h6l-1 6 3 3H7l3-3zM12 13v7"/></svg></button></div>`).join('')
|
|
46
|
+
+ (M.list.more ? `<p class="note">${M.list.more} more ${M.list.more === 1 ? 'row is' : 'rows are'} not shown here.</p>` : '');
|
|
47
|
+
const n = M.list.count;
|
|
48
|
+
$('#movesCap').innerHTML = M.pending ? `${n} ${n === 1 ? 'row' : 'rows'} so far · still reading` : n ? `<b>${done} of ${total}</b> done · ${n} ${n === 1 ? 'row' : 'rows'} left` : 'All clear';
|
|
49
|
+
$('#movesCap').title = 'Counts rows that cleared since this page first read today. A row clears when its record changes.';
|
|
50
|
+
const p = $('#mprog'); p.hidden = !total || !!M.pending; p.setAttribute('aria-valuemax', total); p.setAttribute('aria-valuenow', done); $('b', p).style.width = (total ? done / total * 100 : 0) + '%';
|
|
51
|
+
$('#calm').hidden = n > 0 || !!M.pending; $('#calmNext').textContent = '';
|
|
52
|
+
$('#order').textContent = M.pending ? M.list.pendingNote : n ? M.list.order : '';
|
|
53
|
+
$('#order').hidden = !n && !M.pending;
|
|
54
|
+
const po = M.list.putOff || [];
|
|
55
|
+
$('#putoff').textContent = po.length ? 'Put off by you: ' + po.map((x) => x.text).join('; ') + '. Not counted as done.' : '';
|
|
56
|
+
$('#putoff').hidden = !po.length;
|
|
57
|
+
}
|
|
58
|
+
$('#rank').addEventListener('click', (e) => {
|
|
59
|
+
const pin = e.target.closest('.rpin');
|
|
60
|
+
if (pin) { const k = pin.dataset.pin; pinned.has(k) ? pinned.delete(k) : pinned.add(k); drawRows(); toast(pinned.has(k) ? 'Pinned to the top for this session.' : 'Unpinned.'); return; }
|
|
61
|
+
const h = e.target.closest('.rhd');
|
|
62
|
+
if (h) openRow(M.list.rows.find((r) => r.key === h.closest('.rr').dataset.key));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/* ── today ──────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
66
|
+
const linkOf = (r) => ((r.from || []).find((x) => /^meeting:/.test(x)) || '').replace(/^meeting:/, '');
|
|
67
|
+
function evHtml(e, i) {
|
|
68
|
+
const lnk = e.kind === 'meeting' && (M.list.rows || []).some((r) => linkOf(r) === e.title) ? e.title : '';
|
|
69
|
+
return `<button class="ev src-${e.kind}"${lnk ? ` data-link="${esc(lnk)}"` : ''} data-i="${i}" style="animation:rise .6s var(--ease) both"><time>${esc(e.t)}</time><span class="gl-t"><svg class="i" viewBox="0 0 24 24">${ICON[e.kind]}</svg></span><span><b>${esc(e.title)}</b><span class="d">${esc(e.sub)}${e.product ? ' · ' + esc(e.product) : ''}</span></span></button>`;
|
|
70
|
+
}
|
|
71
|
+
const ALL = () => M.today.ahead.concat(M.today.earlier);
|
|
72
|
+
function drawChips() {
|
|
73
|
+
$('#srcChips').innerHTML = Object.entries(SRC).map(([k, n]) => `<button class="fchip" data-k="${k}" aria-pressed="${on.has(k)}">${n}<em>${M.today.counts[k] || 0}</em></button>`).join('');
|
|
74
|
+
}
|
|
75
|
+
function drawTl() {
|
|
76
|
+
const T = M.today, all = ALL(), ahead = T.ahead.filter((e) => on.has(e.kind)), early = T.earlier.filter((e) => on.has(e.kind));
|
|
77
|
+
$('#todayCap').textContent = `${early.length} earlier · ${ahead.length} ahead`;
|
|
78
|
+
$('#tl').innerHTML = `<div class="grp">Ahead</div>${ahead.length ? ahead.map((e) => evHtml(e, all.indexOf(e))).join('') : '<div class="empty">Nothing ahead in these sources.</div>'}<div class="nowl">${esc(T.now.t)} · ${esc(T.now.label)}</div><div class="grp" style="padding-top:6px">Earlier today</div><div class="early">${early.length ? early.map((e) => evHtml(e, all.indexOf(e))).join('') : `<div class="empty">${esc(T.note || 'Nothing yet in these sources.')}</div>`}</div>`;
|
|
79
|
+
$$('#tl .ev').forEach((x, i) => { x.tabIndex = i ? -1 : 0; });
|
|
80
|
+
const next = ahead[0];
|
|
81
|
+
$('#dNext').innerHTML = next ? `<i></i>Next <b>${esc(next.t)}</b> ${esc(next.title)}` : (M.header.missing.includes('calendar') ? 'Calendar not read' : 'Nothing more today');
|
|
82
|
+
}
|
|
83
|
+
$('#srcChips').addEventListener('click', (e) => { const b = e.target.closest('button'); if (!b) return; const k = b.dataset.k; on.has(k) ? on.delete(k) : on.add(k); b.setAttribute('aria-pressed', on.has(k)); drawTl(); });
|
|
84
|
+
$('#tl').addEventListener('keydown', (e) => {
|
|
85
|
+
if (!/^(ArrowUp|ArrowDown|Home|End)$/.test(e.key)) return;
|
|
86
|
+
const L = $$('#tl .ev'), i = L.indexOf(document.activeElement); if (i < 0) return;
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
const j = e.key === 'ArrowDown' ? Math.min(L.length - 1, i + 1) : e.key === 'ArrowUp' ? Math.max(0, i - 1) : e.key === 'Home' ? 0 : L.length - 1;
|
|
89
|
+
L.forEach((x) => { x.tabIndex = -1; }); L[j].tabIndex = 0; L[j].focus();
|
|
90
|
+
});
|
|
91
|
+
$('#tl').addEventListener('click', (e) => { const b = e.target.closest('.ev'); if (b) openEvent(ALL()[+b.dataset.i]); });
|
|
92
|
+
document.addEventListener('click', (e) => {
|
|
93
|
+
const fb = e.target.closest('.fbtn');
|
|
94
|
+
if (fb) { const o = $('#' + fb.dataset.opts), v = !o.classList.contains('on'); o.classList.toggle('on', v); o.inert = !v; fb.setAttribute('aria-expanded', v); }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/* ── dock ───────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
98
|
+
function drawDock() { $('#dN').textContent = M.dock.needs == null ? '·' : M.dock.needs; $('#dLab').textContent = M.dock.needs == null ? 'still reading' : M.dock.needs === 0 ? 'need you' : M.dock.label; }
|
|
99
|
+
|
|
100
|
+
const SEL = {}; // the option chosen on each row, for this session
|
|
101
|
+
const RET = {}; // the reason chosen for returning a run, by row key
|
|
102
|
+
const WHY = {}; // what the PM typed in the reason box, by row key (kept across a redraw and across a refused press)
|
|
103
|
+
const RET_REASONS = ['wrong facts', 'wrong scope', 'not now', 'too long'];
|
|
104
|
+
const holdNote = (t) => `<p class="nodef" id="holdWhy">${esc(t)}</p>`;
|
|
105
|
+
const whyBox = (r, label) => `<label class="whyl" for="why">${esc(label)}</label><textarea id="why" class="why" rows="3" maxlength="1200" aria-describedby="cardErr">${esc(WHY[r.key] || '')}</textarea>`;
|
|
106
|
+
function ctaState(r) {
|
|
107
|
+
const d = r.dialog, w = d.write, b = $('#recBtn');
|
|
108
|
+
if (!b || !w) return;
|
|
109
|
+
const why = ($('#why') || {}).value || '';
|
|
110
|
+
if (w.kind === 'decision') b.disabled = !(SEL[r.key] || d.sel) || why.trim().length < 3;
|
|
111
|
+
else if (w.kind === 'return') b.disabled = !RET[r.key];
|
|
112
|
+
}
|
|
113
|
+
function cardHtml(r) {
|
|
114
|
+
const d = r.dialog, sel = SEL[r.key] || d.sel, w = d.write;
|
|
115
|
+
if (d.items) return `<div class="acard" id="card"><p class="ct">${esc(d.head)}</p>${d.items.map((x) => `<div class="tri"><span><b>${esc(x.t)}</b><small>${esc(x.m)}</small><em>${esc(x.s)}${x.lead ? ' · ' + esc(x.lead) : ''}</em></span></div>`).join('')}</div>`;
|
|
116
|
+
const o = d.opts.find((x) => x.k === sel);
|
|
117
|
+
const err = '<p class="nodef err" id="cardErr" role="alert" hidden></p>';
|
|
118
|
+
const facts = d.facts.length ? `<div class="kv">${d.facts.map(([a, b]) => `<span>${esc(a)}</span><span>${esc(b)}</span>`).join('')}</div>` : '';
|
|
119
|
+
const opts = d.opts.length ? `<div class="opts2">${d.opts.map((x) => `<button class="op" data-op="${esc(x.k)}" aria-pressed="${x.k === sel}"><i></i><span><b>${esc(x.t)}</b><small>${esc(x.d)}</small></span></button>`).join('')}</div>` : '';
|
|
120
|
+
const head = `<div class="acard" id="card"><p class="ct">${esc(d.head)}</p>${facts}`;
|
|
121
|
+
if (w && w.kind === 'decision') {
|
|
122
|
+
const label = o ? d.prim[o.k].replace(/^Hold to record: /, 'Record: ') : 'Your call: choose one';
|
|
123
|
+
return `${head}${opts}${d.nodef ? `<p class="nodef">${esc(d.nodef)}</p>` : ''}${whyBox(r, 'Why (required, kept in the journal)')}${err}<div class="cact" id="cardAct"><button class="cta" id="recBtn" data-rec="decision" disabled>${CHECK}<span>${esc(label)}</span></button></div><div class="mv2">Not yet <span class="seg2">${[['later', 'Later today'], ['tomorrow', 'Tomorrow'], ['nextweek', 'Next week']].map(([k, t]) => `<button data-until="${k}">${t}</button>`).join('')}</span></div></div>`;
|
|
124
|
+
}
|
|
125
|
+
if (w && w.kind === 'return') {
|
|
126
|
+
return `${head}<p class="ct">Return this run: why</p><div class="seg2" id="retWhy">${RET_REASONS.map((x) => `<button data-ret="${esc(x)}" aria-pressed="${RET[r.key] === x}">${esc(x)}</button>`).join('')}</div>${err}<div class="cact"><button class="cta" id="recBtn" data-rec="return" disabled>${CHECK}<span>${esc(d.action)}</span></button></div></div>`;
|
|
127
|
+
}
|
|
128
|
+
if (d.opts.length) return `${head}${opts}${d.nodef ? `<p class="nodef">${esc(d.nodef)}</p>` : ''}<div class="cact" id="cardAct"><button class="cta" disabled title="${esc(d.hold || 'Nothing here can be recorded yet.')}">${o ? esc(d.prim[o.k]) : 'Your call: choose one'}</button></div>${d.hold ? holdNote(d.hold) : ''}</div>`;
|
|
129
|
+
return `${head}${d.nodef ? `<p class="nodef">${esc(d.nodef)}</p>` : ''}${d.action ? `<div class="cact"><button class="cta" disabled title="${esc(d.hold || 'Nothing here can be recorded.')}">${CHECK}${esc(d.action)}</button></div>${d.hold ? holdNote(d.hold) : ''}` : ''}</div>`;
|
|
130
|
+
}
|
|
131
|
+
function paneHtml(r) {
|
|
132
|
+
// the dialog heading above already names the row and what it is about, so the pane starts with the record itself, as a grid (no repeated title or intro)
|
|
133
|
+
const p = r.dialog.pane, sel = SEL[r.key] || r.dialog.sel, o = (r.dialog.opts || []).find((x) => x.k === sel);
|
|
134
|
+
let h = `<p class="dpath">${esc(p.file || '')}</p>`;
|
|
135
|
+
if (p.list) h = `<p class="dpath">planning/runs/evals.jsonl</p><ol class="stp">${r.dialog.items.map((x, k) => `<li><i>${k + 1}</i><span><b>${esc(x.t)}</b><small>${esc(x.m + ' · ' + x.s)}${x.lead ? '. ' + esc(x.lead) : ''}</small></span></li>`).join('')}</ol>`;
|
|
136
|
+
else if (p.optsDraft) h += H2('Evidence') + kv(p.evidence) + H2('Decision') + P1(o ? o.t + '. ' + o.draft : 'Choose an option to draft the entry.') + `<p class="xn">A draft. Nothing is written to the journal until you press Record.</p>`;
|
|
137
|
+
else h += (p.rows && p.rows.length ? kv(p.rows) : '') + (p.paras && p.paras.length ? (p.parasTitle ? H2(p.parasTitle) : '') + p.paras.map(P1).join('') : '') + `<p class="xn">The record as it stands. Nothing is drafted here.</p>`;
|
|
138
|
+
return `<article class="paper doc">${h}</article>`;
|
|
139
|
+
}
|
|
140
|
+
let openR = null;
|
|
141
|
+
/* A record whose dialog could not be built still opens something true: what the row itself says. */
|
|
142
|
+
function plainDialog(r) {
|
|
143
|
+
return { eye: 'Detail', logTitle: 'Read from the repository', log: ['Read the record'], say: [r.prepared, r.skip].filter(Boolean).join(' '), head: 'What is known', facts: [['Subject', r.subject], ['Next', r.prepared], ['If you skip', r.skip && r.skip.replace(/^If you skip: /, '')]].filter((x) => x[1]), opts: [], sel: null, nodef: '', pane: { label: 'Detail', file: '', h: r.title, intro: r.subject || '', rows: [], paras: [] } };
|
|
144
|
+
}
|
|
145
|
+
function openRow(r) {
|
|
146
|
+
if (!r) return;
|
|
147
|
+
if (!r.dialog) r = { ...r, dialog: plainDialog(r) };
|
|
148
|
+
openR = r; const d = r.dialog;
|
|
149
|
+
openDlg({ eye: d.eye + ' · real data', title: d.title || r.title, sub: d.sub || r.subject, say: d.say, stream: turnA(logHtml(d)) + turnA(ptx(d.say)) + cardHtml(r), noteKey: r.key, pane: paneHtml(r), label: d.pane.label });
|
|
150
|
+
ctaState(r);
|
|
151
|
+
loadThread(r.key);
|
|
152
|
+
setTimeout(() => { const b = $('#dBody'); b.scrollTop = b.scrollHeight; }, 120);
|
|
153
|
+
}
|
|
154
|
+
function showErr(msg) { const e = $('#cardErr'); if (!e) { toast(msg); return; } e.textContent = msg; e.hidden = false; }
|
|
155
|
+
/* A press that records. The page waits for the server's answer: a refusal shows its reason and keeps what the PM typed. */
|
|
156
|
+
async function write(path, body, done) {
|
|
157
|
+
const ws = new URLSearchParams(location.search).get('ws');
|
|
158
|
+
const btns = $$('#card button, #askF button'); btns.forEach((b) => { b.disabled = true; });
|
|
159
|
+
const res = await pmPost('/api/briefing-premium/' + path + (ws ? '?ws=' + encodeURIComponent(ws) : ''), body);
|
|
160
|
+
if (!res || res.ok !== true) {
|
|
161
|
+
btns.forEach((b) => { b.disabled = false; }); if (openR) ctaState(openR);
|
|
162
|
+
const m = (res && res.error) || 'The record was not written.';
|
|
163
|
+
showErr(m); return false;
|
|
164
|
+
}
|
|
165
|
+
$$('#askF button').forEach((b) => { b.disabled = false; });
|
|
166
|
+
done(res); return true;
|
|
167
|
+
}
|
|
168
|
+
$('#dBody').addEventListener('click', (e) => {
|
|
169
|
+
const lt = e.target.closest('#logT');
|
|
170
|
+
if (lt) { const c = $('#alog').classList.toggle('col'); lt.setAttribute('aria-expanded', !c); return; }
|
|
171
|
+
if (!openR) return;
|
|
172
|
+
const op = e.target.closest('[data-op]');
|
|
173
|
+
if (op) { WHY[openR.key] = ($('#why') || {}).value || ''; SEL[openR.key] = op.dataset.op; $('#card').outerHTML = cardHtml(openR); $('#oMain').innerHTML = paneHtml(openR); ctaState(openR); return; }
|
|
174
|
+
const rt = e.target.closest('[data-ret]');
|
|
175
|
+
if (rt) { WHY[openR.key] = ($('#why') || {}).value || ''; RET[openR.key] = rt.dataset.ret; $('#card').outerHTML = cardHtml(openR); ctaState(openR); return; }
|
|
176
|
+
const un = e.target.closest('[data-until]');
|
|
177
|
+
if (un) { write('put-off', { key: openR.key, until: un.dataset.until }, async () => { toast('Put off. It comes back by itself, and it is not counted as done.'); closeDlg(); await load(); }); return; }
|
|
178
|
+
const rec = e.target.closest('#recBtn');
|
|
179
|
+
if (rec && !rec.disabled) {
|
|
180
|
+
const w = openR.dialog.write, why = ($('#why') || {}).value || '', key = openR.key;
|
|
181
|
+
WHY[key] = why;
|
|
182
|
+
if (w.kind === 'decision') write('decide', { key, choice: SEL[key] || openR.dialog.sel, why }, async (res) => { delete WHY[key]; toast('Recorded in pm-decisions.md.'); closeDlg(); await load(); });
|
|
183
|
+
else write('return', { run: w.run, reason: RET[key], ...(w.ws ? { ws: w.ws } : {}) }, async () => { toast('Returned to the agent. It counts as a correction.'); closeDlg(); await load(); });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
$('#dBody').addEventListener('input', (e) => { if (e.target.id === 'why' && openR) { WHY[openR.key] = e.target.value; ctaState(openR); } });
|
|
187
|
+
function openEvent(e) {
|
|
188
|
+
if (!e) return;
|
|
189
|
+
openRow({ key: 'ev:' + e.kind + ':' + e.at, title: e.title, subject: e.sub, prepared: e.sub, dialog: e.dialog });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
/* ── a decision and the meeting that needs it are one thing, shown twice ─────────────────────────────────── */
|
|
194
|
+
{
|
|
195
|
+
const lk = (el, v) => { const id = el.dataset.link; if (!id) return; $$(`[data-link="${CSS.escape(id)}"]`).forEach((x) => x.classList.toggle('hl-k', v)); };
|
|
196
|
+
['pointerenter', 'focusin'].forEach((ev) => document.addEventListener(ev, (e) => { const t = e.target.closest && e.target.closest('[data-link]'); if (t) lk(t, true); }, true));
|
|
197
|
+
['pointerleave', 'focusout'].forEach((ev) => document.addEventListener(ev, (e) => { const t = e.target.closest && e.target.closest('[data-link]'); if (t) lk(t, false); }, true));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
/* ── the products the Briefing reads (initiative briefing-premium-products) ──────────────────────────────────────────────────────────── */
|
|
202
|
+
const wsParam = () => new URLSearchParams(location.search).get('ws');
|
|
203
|
+
function drawPick() {
|
|
204
|
+
const w = $('#pkw'), P = M.products, one = wsParam();
|
|
205
|
+
if (!w) return;
|
|
206
|
+
if (one) { w.hidden = false; $('#pick').hidden = true; $('#pickMenu').hidden = true; let a = $('#pickAll'); if (!a) { a = document.createElement('a'); a.id = 'pickAll'; a.className = 'fchip'; a.href = '/premium.html'; a.textContent = 'All picked products'; w.appendChild(a); } return; }
|
|
207
|
+
if ($('#pickAll')) $('#pickAll').remove();
|
|
208
|
+
$('#pick').hidden = false;
|
|
209
|
+
if (!P) { w.hidden = true; return; }
|
|
210
|
+
w.hidden = false;
|
|
211
|
+
const n = P.available.filter((x) => x.on).length;
|
|
212
|
+
$('#pickN').textContent = n + ' of ' + P.available.length;
|
|
213
|
+
$('#pickMenu').innerHTML = P.available.length
|
|
214
|
+
? P.available.map((x) => `<button role="menuitemcheckbox" aria-checked="${x.on}" data-ws="${esc(x.ws)}"><i class="bx"></i><span>${esc(x.name)}</span>${x.home ? '<em>home</em>' : ''}</button>`).join('') + '<button role="menuitem" data-reset="1" class="pkreset"><span>Back to the default</span><em>all but home</em></button>'
|
|
215
|
+
: '<p class="note pkempty">No product workspace is registered.</p>';
|
|
216
|
+
}
|
|
217
|
+
function closePick() { $('#pickMenu').hidden = true; $('#pick').setAttribute('aria-expanded', 'false'); }
|
|
218
|
+
$('#pick').addEventListener('click', (e) => {
|
|
219
|
+
e.stopPropagation();
|
|
220
|
+
const m = $('#pickMenu'), open = m.hidden;
|
|
221
|
+
m.hidden = !open; $('#pick').setAttribute('aria-expanded', open);
|
|
222
|
+
if (open) { const f = $('button', m); if (f) f.focus(); }
|
|
223
|
+
});
|
|
224
|
+
document.addEventListener('click', (e) => { if (!e.target.closest('#pkw')) closePick(); });
|
|
225
|
+
$('#pkw').addEventListener('keydown', (e) => {
|
|
226
|
+
if (e.key === 'Escape') { closePick(); $('#pick').focus(); return; }
|
|
227
|
+
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;
|
|
228
|
+
const L = $$('#pickMenu button'), i = L.indexOf(document.activeElement); if (i < 0) return;
|
|
229
|
+
e.preventDefault(); L[(i + (e.key === 'ArrowDown' ? 1 : L.length - 1)) % L.length].focus();
|
|
230
|
+
});
|
|
231
|
+
$('#pickMenu').addEventListener('click', async (e) => {
|
|
232
|
+
const b = e.target.closest('button'); if (!b || !M || !M.products) return;
|
|
233
|
+
const on = M.products.available.filter((x) => x.on).map((x) => x.ws);
|
|
234
|
+
let body;
|
|
235
|
+
if (b.dataset.reset) body = { key: 'ws.briefing.include', reset: true };
|
|
236
|
+
else { const ws = b.dataset.ws, next = on.includes(ws) ? on.filter((x) => x !== ws) : on.concat(ws); body = { key: 'ws.briefing.include', value: next }; }
|
|
237
|
+
const res = await pmPost('/api/settings', body);
|
|
238
|
+
if (!res || res.ok !== true) { toast((res && res.error) || 'The choice was not saved.'); return; }
|
|
239
|
+
toast(b.dataset.reset ? 'Back to the default: every product except the home one.' : 'Saved. The Briefing now reads the products you ticked.');
|
|
240
|
+
await load();
|
|
241
|
+
const again = b.dataset.ws ? $(`#pickMenu [data-ws="${CSS.escape(b.dataset.ws)}"]`) : null; if (again) again.focus();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
/* ── load ───────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
245
|
+
let readTimer = null;
|
|
246
|
+
const ago = (ms) => { const m = Math.round(ms / 60000); return m < 1 ? 'just now' : m < 90 ? m + ' min ago' : Math.round(m / 60) + ' h ago'; };
|
|
247
|
+
async function load() {
|
|
248
|
+
let r = null, status = 0;
|
|
249
|
+
const ws = new URLSearchParams(location.search).get('ws');
|
|
250
|
+
try { const res = await fetch('/api/briefing-premium' + (ws ? '?ws=' + encodeURIComponent(ws) : ''), { credentials: 'same-origin' }); status = res.status; r = await res.json(); } catch { r = null; }
|
|
251
|
+
clearInterval(readTimer);
|
|
252
|
+
if (r && r.state === 'reading') {
|
|
253
|
+
// the first read has no end I can promise: say how long it has run, and ask again
|
|
254
|
+
const t0 = Date.now() - (r.elapsed_ms || 0), tick = () => { $('#brief').textContent = 'Reading the products for the first time. ' + Math.round((Date.now() - t0) / 1000) + ' s so far. This page shows nothing until it has read, rather than guess.'; };
|
|
255
|
+
tick(); readTimer = setInterval(tick, 1000); setTimeout(load, 3000);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!r || !r.ok) {
|
|
259
|
+
$('#snap').textContent = 'The repository could not be read';
|
|
260
|
+
$('#brief').textContent = 'The Briefing could not read the repository, so it shows nothing rather than guess.' + (r && r.error ? ' The reason: ' + r.error : status ? ' (the server answered ' + status + ')' : ' The server did not answer.');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
M = r.model; drawHero(); drawRows(); drawChips(); drawTl(); drawDock(); drawPick(); onScroll();
|
|
264
|
+
const age = Date.now() - r.as_of;
|
|
265
|
+
if (age > 5 * 60000) $('#snap').textContent += ' · read ' + ago(age) + (r.refreshing ? ', refreshing' : '');
|
|
266
|
+
if (r.error) $('#snap').textContent += ' · the latest read failed: ' + r.error.slice(0, 120);
|
|
267
|
+
if (r.error && !r.pending) setTimeout(load, 150000);
|
|
268
|
+
else if (r.pending) setTimeout(load, 3000);
|
|
269
|
+
else if (r.refreshing) setTimeout(load, 10000);
|
|
270
|
+
}
|
|
271
|
+
$('#brief').textContent = 'Reading the repository.';
|
|
272
|
+
load();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="pmos-nonce" content="" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
7
|
+
<title>PMOS · Board</title>
|
|
8
|
+
<!-- The premium Board (initiative briefing-premium-board, slice 5). The third place on the rail, in the Briefing's shell. It reads GET /api/board-premium, built from the routes the production Initiatives page reads. The only press is Draft it on a core document, through the existing /api/core-docs/draft route. -->
|
|
9
|
+
<link rel="preload" href="/vendor/geist-var.woff2" as="font" type="font/woff2" crossorigin />
|
|
10
|
+
<link rel="stylesheet" href="/pm/premium.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div class="sky"><i class="a"></i><i class="b"></i><i class="c"></i></div>
|
|
14
|
+
<nav class="rail glass" aria-label="Places">
|
|
15
|
+
<i class="ind"></i>
|
|
16
|
+
<button class="pl" data-go="brief"><svg class="i" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>Briefing</button>
|
|
17
|
+
<button class="pl" data-go="work"><svg class="i" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="m8.5 12 2.5 2.5 4.5-5"/></svg>My work</button>
|
|
18
|
+
<button class="pl" data-go="init" aria-current="page"><svg class="i" viewBox="0 0 24 24"><path d="M12 3 3 8l9 5 9-5z"/><path d="m3 13 9 5 9-5"/></svg>Board</button>
|
|
19
|
+
<button class="pl" data-go="rep"><svg class="i" viewBox="0 0 24 24"><path d="M4 20V10M10 20V4M16 20v-8M22 20H2"/></svg>Insights</button>
|
|
20
|
+
</nav>
|
|
21
|
+
|
|
22
|
+
<div class="dock glass" id="dock" inert><span class="nm">Board</span><span class="sep"></span><span class="cnt"><b id="dN">·</b><span id="dLab">need a look</span></span><span class="sep hide-s"></span><span class="pl2 hide-s" id="dNext"></span><span class="sp"></span><button class="round" id="theme2" aria-label="Switch light or dark" style="width:44px;height:44px"><svg class="i" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z"/></svg></button></div>
|
|
23
|
+
<div class="veil" id="veil"></div>
|
|
24
|
+
<div class="dlg" id="dlg" role="dialog" aria-modal="true" aria-labelledby="dTitle" tabindex="-1" data-mode="split" inert>
|
|
25
|
+
<div class="grab"></div>
|
|
26
|
+
<div class="dhead"><div class="dht"><p class="label" id="dEye"></p><h2 id="dTitle"></h2><p class="dsub" id="dSub"></p></div>
|
|
27
|
+
<div class="dtools"><button class="round dx2" id="dPane" aria-label="Hide the conversation" aria-pressed="true" title="Hide the conversation"><svg class="i" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="3"/><path d="M9 4v16"/></svg></button><button class="round dx2" id="dExpand" aria-label="Full screen" aria-pressed="false" title="Full screen"><svg class="i" viewBox="0 0 24 24"><path id="dExpP" d="M14 4h6v6M10 20H4v-6M20 4l-7 7M4 20l7-7"/></svg></button><button class="round dx2" id="dClose" aria-label="Close"><svg class="i" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></button></div></div>
|
|
28
|
+
<div class="dtabs" id="dTabs"><div class="seg" role="group" aria-label="Sections"><i class="pill"></i><button data-t="out" aria-pressed="true">Detail</button><button data-t="chat" aria-pressed="false">Conversation</button></div></div>
|
|
29
|
+
<div class="dgrid" id="dGrid" data-tab="out"><section class="pane p-work" id="dBody" aria-label="Conversation"></section>
|
|
30
|
+
<section class="pane p-out" id="dOut" aria-label="Detail"><div class="otop"><span class="ol" id="oLab">Detail</span></div><div class="obody" id="oMain"></div></section></div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="toast glass" id="toast" role="status"></div>
|
|
33
|
+
<main class="stage" id="stage">
|
|
34
|
+
<header class="top rise" style="--d:0">
|
|
35
|
+
<h1 class="vh">Board</h1><div id="crumbHost"></div>
|
|
36
|
+
<div class="tools tools-top"><span class="snap" id="snap">Reading the board</span>
|
|
37
|
+
<span class="seg modeseg" id="modeSeg" role="group" aria-label="How an initiative opens"><i class="pill"></i><button data-m="page" aria-pressed="true">Page</button><button data-m="popup" aria-pressed="false">Popup</button></span>
|
|
38
|
+
<button class="glass round" id="theme" aria-label="Switch light or dark"><svg class="i" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z"/></svg></button></div>
|
|
39
|
+
</header>
|
|
40
|
+
<section class="hero hero-slim"><p class="brief rise" style="--d:2" id="brief" aria-live="polite"></p></section>
|
|
41
|
+
<div class="bcols">
|
|
42
|
+
<div>
|
|
43
|
+
<section class="card rise" style="--d:5" id="cIni" aria-labelledby="hIni">
|
|
44
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><path d="M12 3 3 8l9 5 9-5z"/><path d="m3 13 9 5 9-5"/></svg></span><h2 id="hIni">Initiatives</h2><small id="iniCap"></small></div>
|
|
45
|
+
<div class="mprog" id="iniBar" role="progressbar" aria-label="Open initiatives on track" aria-valuemin="0" hidden><b></b></div>
|
|
46
|
+
<div class="seg" id="ibTabs" role="group" aria-label="Initiatives view"><i class="pill"></i><button data-v="list" aria-pressed="true">List</button><button data-v="stages" aria-pressed="false">By stage</button><button data-v="time" aria-pressed="false">Timeline</button></div>
|
|
47
|
+
<div id="ibBody"></div>
|
|
48
|
+
<p class="note" id="iniNote" style="margin-top:12px"></p>
|
|
49
|
+
</section>
|
|
50
|
+
</div>
|
|
51
|
+
<div>
|
|
52
|
+
<section class="card rise" style="--d:6" id="cSpr" aria-labelledby="hSpr">
|
|
53
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span><h2 id="hSpr">Sprint</h2><small id="sprCap"></small></div>
|
|
54
|
+
<div id="sprBody"></div>
|
|
55
|
+
</section>
|
|
56
|
+
<section class="card rise" style="--d:7" id="cRel" aria-labelledby="hRel">
|
|
57
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="3"/><path d="M3 10h18M8 3v4M16 3v4"/></svg></span><h2 id="hRel">Coming up</h2><small id="relCap"></small></div>
|
|
58
|
+
<div id="relBody"></div>
|
|
59
|
+
</section>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<section class="card rise" style="--d:8;margin-top:24px" id="cDocs" aria-labelledby="hDocs">
|
|
63
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6z"/><path d="M14 3v5h5M9 13h6M9 17h4"/></svg></span><h2 id="hDocs">Core documents</h2><small id="docCap"></small></div>
|
|
64
|
+
<div id="docTop"></div>
|
|
65
|
+
<div class="dmg two" id="docBody" role="list"></div>
|
|
66
|
+
<p class="note" id="docNote" style="margin-top:12px"></p>
|
|
67
|
+
</section>
|
|
68
|
+
<p class="foot" id="foot">This page reads the board's own records, the same ones the Initiatives page reads. Its one press is Draft it on a core document, which starts the existing read-only run that writes a draft and sends nothing. Issue, Drop and moving an initiative are not built here. The Metrics charts are not drawn, because PMOS holds no metric series for a board. A sprint appears only when a session has read Jira.</p>
|
|
69
|
+
</main>
|
|
70
|
+
<noscript>This page needs JavaScript.</noscript>
|
|
71
|
+
<script type="module" src="/pm/premium-board.js"></script>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="pmos-nonce" content="" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
7
|
+
<title>PMOS · Initiative</title>
|
|
8
|
+
<!-- The premium initiative page (initiative briefing-premium-initiative, slice 6). Opened from a Board click. It reads GET /api/initiative-premium, built from the routes the production Initiatives sheet reads. The only press is Draft it on a core document; a document row opens the read-only document sheet. -->
|
|
9
|
+
<link rel="preload" href="/vendor/geist-var.woff2" as="font" type="font/woff2" crossorigin />
|
|
10
|
+
<link rel="stylesheet" href="/pm/premium.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div class="sky"><i class="a"></i><i class="b"></i><i class="c"></i></div>
|
|
14
|
+
<nav class="rail glass" aria-label="Places">
|
|
15
|
+
<i class="ind"></i>
|
|
16
|
+
<button class="pl" data-go="brief"><svg class="i" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>Briefing</button>
|
|
17
|
+
<button class="pl" data-go="work"><svg class="i" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="m8.5 12 2.5 2.5 4.5-5"/></svg>My work</button>
|
|
18
|
+
<button class="pl" data-go="init" aria-current="page"><svg class="i" viewBox="0 0 24 24"><path d="M12 3 3 8l9 5 9-5z"/><path d="m3 13 9 5 9-5"/></svg>Board</button>
|
|
19
|
+
<button class="pl" data-go="rep"><svg class="i" viewBox="0 0 24 24"><path d="M4 20V10M10 20V4M16 20v-8M22 20H2"/></svg>Insights</button>
|
|
20
|
+
</nav>
|
|
21
|
+
|
|
22
|
+
<div class="dock glass" id="dock" inert><span class="nm">Initiative</span><span class="sep"></span><span class="cnt"><b id="dN">·</b><span id="dLab">need a look</span></span><span class="sep hide-s"></span><span class="pl2 hide-s" id="dNext"></span><span class="sp"></span><button class="round" id="theme2" aria-label="Switch light or dark" style="width:44px;height:44px"><svg class="i" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z"/></svg></button></div>
|
|
23
|
+
<div class="veil" id="veil"></div>
|
|
24
|
+
<div class="dlg" id="dlg" role="dialog" aria-modal="true" aria-labelledby="dTitle" tabindex="-1" data-mode="split" inert>
|
|
25
|
+
<div class="grab"></div>
|
|
26
|
+
<div class="dhead"><div class="dht"><p class="label" id="dEye"></p><h2 id="dTitle"></h2><p class="dsub" id="dSub"></p></div>
|
|
27
|
+
<div class="dtools"><button class="round dx2" id="dPane" aria-label="Hide the conversation" aria-pressed="true" title="Hide the conversation"><svg class="i" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="3"/><path d="M9 4v16"/></svg></button><button class="round dx2" id="dExpand" aria-label="Full screen" aria-pressed="false" title="Full screen"><svg class="i" viewBox="0 0 24 24"><path id="dExpP" d="M14 4h6v6M10 20H4v-6M20 4l-7 7M4 20l7-7"/></svg></button><button class="round dx2" id="dClose" aria-label="Close"><svg class="i" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></button></div></div>
|
|
28
|
+
<div class="dtabs" id="dTabs"><div class="seg" role="group" aria-label="Sections"><i class="pill"></i><button data-t="out" aria-pressed="true">Detail</button><button data-t="chat" aria-pressed="false">Conversation</button></div></div>
|
|
29
|
+
<div class="dgrid" id="dGrid" data-tab="out"><section class="pane p-work" id="dBody" aria-label="Conversation"></section>
|
|
30
|
+
<section class="pane p-out" id="dOut" aria-label="Detail"><div class="otop"><span class="ol" id="oLab">Detail</span></div><div class="obody" id="oMain"></div></section></div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="toast glass" id="toast" role="status"></div>
|
|
33
|
+
<main class="stage" id="stage">
|
|
34
|
+
<header class="top rise" style="--d:0">
|
|
35
|
+
<h1 class="vh" id="iH1">Initiative</h1><div id="crumbHost"></div>
|
|
36
|
+
<div class="tools tools-top"><span class="snap" id="snap">Reading the initiative</span>
|
|
37
|
+
<button class="glass round" id="theme" aria-label="Switch light or dark"><svg class="i" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z"/></svg></button></div>
|
|
38
|
+
</header>
|
|
39
|
+
<section class="hero hero-slim"><p class="brief rise" style="--d:2" id="brief" aria-live="polite"></p></section>
|
|
40
|
+
<div id="iMain"><p class="empty3 plain" id="iRead">The initiative is read from its records. This page shows nothing until it has read, rather than guess.</p></div>
|
|
41
|
+
<p class="foot" id="foot">This page reads the initiative's own records, the same ones the Initiatives sheet reads. Its one press is Draft it on a core document, which starts the existing read-only run that writes a draft and sends nothing. Opening a document only reads it. Editing, Issue, Drop and adding a task are not built on this page.</p>
|
|
42
|
+
</main>
|
|
43
|
+
<noscript>This page needs JavaScript.</noscript>
|
|
44
|
+
<script type="module" src="/pm/premium-initiative.js"></script>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="pmos-nonce" content="" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
7
|
+
<title>PMOS · My work</title>
|
|
8
|
+
<!-- The premium My work (initiative briefing-premium-work, slice 4). The second place on the rail, in the Briefing's shell. It reads GET /api/my-work-premium, built from the list the Work page reads, and presses through the existing /api/pm-items routes. -->
|
|
9
|
+
<link rel="preload" href="/vendor/geist-var.woff2" as="font" type="font/woff2" crossorigin />
|
|
10
|
+
<link rel="stylesheet" href="/pm/premium.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div class="sky"><i class="a"></i><i class="b"></i><i class="c"></i></div>
|
|
14
|
+
<nav class="rail glass" aria-label="Places">
|
|
15
|
+
<i class="ind"></i>
|
|
16
|
+
<button class="pl" data-go="brief"><svg class="i" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>Briefing</button>
|
|
17
|
+
<button class="pl" data-go="work" aria-current="page"><svg class="i" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="m8.5 12 2.5 2.5 4.5-5"/></svg>My work</button>
|
|
18
|
+
<button class="pl" data-go="init"><svg class="i" viewBox="0 0 24 24"><path d="M12 3 3 8l9 5 9-5z"/><path d="m3 13 9 5 9-5"/></svg>Board</button>
|
|
19
|
+
<button class="pl" data-go="rep"><svg class="i" viewBox="0 0 24 24"><path d="M4 20V10M10 20V4M16 20v-8M22 20H2"/></svg>Insights</button>
|
|
20
|
+
</nav>
|
|
21
|
+
|
|
22
|
+
<div class="dock glass" id="dock" inert><span class="nm">My work</span><span class="sep"></span><span class="cnt"><b id="dN">·</b><span id="dLab">to clear</span></span><span class="sep hide-s"></span><span class="pl2 hide-s" id="dNext"></span><span class="sp"></span><button class="round" id="theme2" aria-label="Switch light or dark" style="width:44px;height:44px"><svg class="i" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z"/></svg></button></div>
|
|
23
|
+
<div class="veil" id="veil"></div>
|
|
24
|
+
<div class="dlg" id="dlg" role="dialog" aria-modal="true" aria-labelledby="dTitle" tabindex="-1" data-mode="split" inert>
|
|
25
|
+
<div class="grab"></div>
|
|
26
|
+
<div class="dhead"><div class="dht"><p class="label" id="dEye"></p><h2 id="dTitle"></h2><p class="dsub" id="dSub"></p></div>
|
|
27
|
+
<div class="dtools"><button class="round dx2" id="dPane" aria-label="Hide the conversation" aria-pressed="true" title="Hide the conversation"><svg class="i" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="3"/><path d="M9 4v16"/></svg></button><button class="round dx2" id="dExpand" aria-label="Full screen" aria-pressed="false" title="Full screen"><svg class="i" viewBox="0 0 24 24"><path id="dExpP" d="M14 4h6v6M10 20H4v-6M20 4l-7 7M4 20l7-7"/></svg></button><button class="round dx2" id="dClose" aria-label="Close"><svg class="i" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></button></div></div>
|
|
28
|
+
<div class="dtabs" id="dTabs"><div class="seg" role="group" aria-label="Sections"><i class="pill"></i><button data-t="out" aria-pressed="true">Detail</button><button data-t="chat" aria-pressed="false">Conversation</button></div></div>
|
|
29
|
+
<div class="dgrid" id="dGrid" data-tab="out"><section class="pane p-work" id="dBody" aria-label="Conversation"></section>
|
|
30
|
+
<section class="pane p-out" id="dOut" aria-label="Detail"><div class="otop"><span class="ol" id="oLab">Detail</span></div><div class="obody" id="oMain"></div></section></div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="toast glass" id="toast" role="status"></div>
|
|
33
|
+
<main class="stage" id="stage">
|
|
34
|
+
<header class="top rise" style="--d:0">
|
|
35
|
+
<h1 class="vh">My work</h1><span class="glass name" aria-hidden="true">My work</span>
|
|
36
|
+
<div class="tools tools-top"><span class="snap" id="snap">Reading your list</span>
|
|
37
|
+
<button class="glass round" id="theme" aria-label="Switch light or dark"><svg class="i" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z"/></svg></button></div>
|
|
38
|
+
</header>
|
|
39
|
+
<section class="hero">
|
|
40
|
+
<p class="date rise" style="--d:1" id="date"></p>
|
|
41
|
+
<p class="brief rise" style="--d:2" id="brief" aria-live="polite"></p>
|
|
42
|
+
</section>
|
|
43
|
+
<form class="cap glass rise" style="--d:3" id="cap" autocomplete="off">
|
|
44
|
+
<svg class="i" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
|
|
45
|
+
<input id="capIn" placeholder="Add anything to your list" aria-label="Add an item to your list" maxlength="300">
|
|
46
|
+
<button class="capb" type="submit" aria-label="Add it to your list" title="Add it to your list"><svg class="i" viewBox="0 0 24 24"><path d="M5 12h14M13 6l6 6-6 6"/></svg></button>
|
|
47
|
+
</form>
|
|
48
|
+
<div class="tx rise" style="--d:4" id="tx"><span class="hint" id="capHint" aria-live="polite">One line. The date and priority are read from it, for example "Reply to Rina about SSO by Friday".</span></div>
|
|
49
|
+
<div class="cols2" style="margin-top:24px">
|
|
50
|
+
<div>
|
|
51
|
+
<section class="card rise" style="--d:5" id="plate" aria-labelledby="hPlate">
|
|
52
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="m8.5 12 2.5 2.5 4.5-5"/></svg></span><h2 id="hPlate">On your plate</h2><small id="plateCap"></small></div>
|
|
53
|
+
<div id="plateBody"></div>
|
|
54
|
+
<p class="note" id="plateNote" style="margin-top:14px"></p>
|
|
55
|
+
</section>
|
|
56
|
+
</div>
|
|
57
|
+
<div>
|
|
58
|
+
<section class="card rise" style="--d:7" id="agCard" aria-labelledby="hAg">
|
|
59
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><path d="M12 3l1.8 4.7L18.5 9.5l-4.7 1.8L12 16l-1.8-4.7L5.5 9.5l4.7-1.8z"/></svg></span><h2 id="hAg">With agents</h2><small id="agCap"></small></div>
|
|
60
|
+
<div class="pipe" id="pipe"></div>
|
|
61
|
+
<div class="agl" id="agl"></div>
|
|
62
|
+
<p class="note" id="agHold" style="margin-top:12px"></p>
|
|
63
|
+
</section>
|
|
64
|
+
<section class="card rise" style="--d:8" id="waitCard" aria-labelledby="hWait">
|
|
65
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span><h2 id="hWait">Waiting on others</h2><small id="waitCap"></small></div>
|
|
66
|
+
<div id="waitBody"></div>
|
|
67
|
+
</section>
|
|
68
|
+
<section class="card rise" style="--d:9" id="revCard" aria-labelledby="hRev" hidden>
|
|
69
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><path d="M4 12a8 8 0 0 1 14-5.3M20 12a8 8 0 0 1-14 5.3"/><path d="M18 3v4h-4M6 21v-4h4"/></svg></span><h2 id="hRev">Untouched</h2><small id="revCap"></small></div>
|
|
70
|
+
<div class="revc"><p id="revTxt"></p><button id="revBtn" type="button">Look at them</button></div>
|
|
71
|
+
</section>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<section class="card rise" style="--d:5;margin-top:24px" id="trayCard" aria-labelledby="hTray">
|
|
75
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><path d="M4 8h16l-1.5 11a2 2 0 0 1-2 1.7H7.5a2 2 0 0 1-2-1.7z"/><path d="M8 8V6a4 4 0 0 1 8 0v2"/></svg></span><h2 id="hTray">Your tray</h2><small id="trayCap"></small></div>
|
|
76
|
+
<div class="tgrid" id="trayBody"></div>
|
|
77
|
+
<p class="note" id="trayNote" style="margin-top:14px"></p>
|
|
78
|
+
</section>
|
|
79
|
+
<p class="foot" id="foot">This page reads your list, the same one the Work page reads. It writes only when you press a button that says what it does, and each press uses the Work page's own route. The shelf, the capacity bars and starting an agent are not built here. Jira and Slack items appear only when an agent session has read them.</p>
|
|
80
|
+
</main>
|
|
81
|
+
<noscript>This page needs JavaScript.</noscript>
|
|
82
|
+
<script type="module" src="/pm/premium-work.js"></script>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="pmos-nonce" content="" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
7
|
+
<title>PMOS · Briefing</title>
|
|
8
|
+
<!-- The premium Briefing (initiative briefing-premium, slice 1). A page of its own beside the console: the console stays the default page.
|
|
9
|
+
The rail's other three places open the matching console tab until their slices land. Slice 2 adds the PM's own presses (record a decision, put a row off, return a run, save a note); the page never writes on its own. -->
|
|
10
|
+
<link rel="preload" href="/vendor/geist-var.woff2" as="font" type="font/woff2" crossorigin />
|
|
11
|
+
<link rel="stylesheet" href="/pm/premium.css" />
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div class="sky"><i class="a"></i><i class="b"></i><i class="c"></i></div>
|
|
15
|
+
<nav class="rail glass" aria-label="Places">
|
|
16
|
+
<i class="ind"></i>
|
|
17
|
+
<button class="pl" data-go="brief" aria-current="page"><svg class="i" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>Briefing</button>
|
|
18
|
+
<button class="pl" data-go="work"><svg class="i" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="m8.5 12 2.5 2.5 4.5-5"/></svg>My work</button>
|
|
19
|
+
<button class="pl" data-go="init"><svg class="i" viewBox="0 0 24 24"><path d="M12 3 3 8l9 5 9-5z"/><path d="m3 13 9 5 9-5"/></svg>Board</button>
|
|
20
|
+
<button class="pl" data-go="rep"><svg class="i" viewBox="0 0 24 24"><path d="M4 20V10M10 20V4M16 20v-8M22 20H2"/></svg>Insights</button>
|
|
21
|
+
</nav>
|
|
22
|
+
|
|
23
|
+
<div class="dock glass" id="dock" inert><span class="nm">Briefing</span><span class="sep"></span><span class="cnt"><b id="dN">·</b><span id="dLab">for you</span></span><span class="sep hide-s"></span><span class="pl2 hide-s" id="dNext"></span><span class="sp"></span><button class="round" id="theme2" aria-label="Switch light or dark" style="width:44px;height:44px"><svg class="i" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z"/></svg></button></div>
|
|
24
|
+
<div class="veil" id="veil"></div>
|
|
25
|
+
<div class="dlg" id="dlg" role="dialog" aria-modal="true" aria-labelledby="dTitle" tabindex="-1" data-mode="split" inert>
|
|
26
|
+
<div class="grab"></div>
|
|
27
|
+
<div class="dhead"><div class="dht"><p class="label" id="dEye"></p><h2 id="dTitle"></h2><p class="dsub" id="dSub"></p></div>
|
|
28
|
+
<div class="dtools"><button class="round dx2" id="dPane" aria-label="Hide the conversation" aria-pressed="true" title="Hide the conversation"><svg class="i" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="3"/><path d="M9 4v16"/></svg></button><button class="round dx2" id="dExpand" aria-label="Full screen" aria-pressed="false" title="Full screen"><svg class="i" viewBox="0 0 24 24"><path id="dExpP" d="M14 4h6v6M10 20H4v-6M20 4l-7 7M4 20l7-7"/></svg></button><button class="round dx2" id="dClose" aria-label="Close"><svg class="i" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></button></div></div>
|
|
29
|
+
<div class="dtabs" id="dTabs"><div class="seg" role="group" aria-label="Sections"><i class="pill"></i><button data-t="out" aria-pressed="true">Detail</button><button data-t="chat" aria-pressed="false">Conversation</button></div></div>
|
|
30
|
+
<div class="dgrid" id="dGrid" data-tab="out"><section class="pane p-work" id="dBody" aria-label="Conversation"></section>
|
|
31
|
+
<section class="pane p-out" id="dOut" aria-label="Detail"><div class="otop"><span class="ol" id="oLab">Detail</span></div><div class="obody" id="oMain"></div></section></div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="toast glass" id="toast" role="status"></div>
|
|
34
|
+
<main class="stage" id="stage">
|
|
35
|
+
<header class="top rise" style="--d:0">
|
|
36
|
+
<h1 class="vh">Briefing</h1><span class="glass name" aria-hidden="true">Briefing</span>
|
|
37
|
+
<div class="tools tools-top"><span class="snap" id="snap">Reading the repository</span>
|
|
38
|
+
<span class="pkw" id="pkw" hidden><button class="fchip" id="pick" aria-haspopup="menu" aria-expanded="false" aria-controls="pickMenu">Products<em id="pickN"></em></button><div class="crmenu pkmenu" id="pickMenu" role="menu" hidden></div></span>
|
|
39
|
+
<button class="glass round" id="theme" aria-label="Switch light or dark"><svg class="i" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z"/></svg></button></div>
|
|
40
|
+
</header>
|
|
41
|
+
<section class="hero">
|
|
42
|
+
<p class="date rise" style="--d:1" id="date"></p>
|
|
43
|
+
<p class="brief rise" style="--d:2" id="brief" aria-live="polite"></p>
|
|
44
|
+
</section>
|
|
45
|
+
<div class="cols2">
|
|
46
|
+
<div>
|
|
47
|
+
<section class="card rise" style="--d:6" aria-labelledby="hMoves">
|
|
48
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><path d="M5 12l4.5 4.5L19 7"/></svg></span><h2 id="hMoves">What you need to do</h2><small id="movesCap"></small></div>
|
|
49
|
+
<div class="mprog" id="mprog" role="progressbar" aria-label="Done today" aria-valuemin="0" hidden><b></b></div>
|
|
50
|
+
<div class="rank" id="rank"></div>
|
|
51
|
+
<div class="calm" id="calm" hidden><i><svg class="i" viewBox="0 0 24 24"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg></i><div><b>Nothing needs you right now.</b><small id="calmNext"></small></div></div>
|
|
52
|
+
<p class="note" id="order" style="margin-top:14px"></p>
|
|
53
|
+
<p class="note" id="putoff" hidden></p>
|
|
54
|
+
</section>
|
|
55
|
+
</div>
|
|
56
|
+
<div>
|
|
57
|
+
<section class="card rise" id="todayCard" style="--d:7" aria-labelledby="hToday">
|
|
58
|
+
<div class="ch"><span class="ic"><svg class="i" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span><h2 id="hToday">Today</h2><small id="todayCap"></small><button class="fbtn" data-opts="todayOpts" aria-expanded="false" aria-controls="todayOpts" aria-label="Filter sources"><svg class="i" viewBox="0 0 24 24"><path d="M4 7h9M17 7h3M4 17h3M11 17h9"/><circle cx="15" cy="7" r="2"/><circle cx="9" cy="17" r="2"/></svg></button></div>
|
|
59
|
+
<div class="opts" id="todayOpts" inert><div><div class="chips" id="srcChips" role="group" aria-label="Sources"></div></div></div>
|
|
60
|
+
<div class="tl" id="tl" role="group" aria-label="Today. Use the up and down arrow keys to move between rows."></div>
|
|
61
|
+
</section>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<p class="foot" id="foot">This page reads the repository's records. It writes only when you press a button that says it records: a decision goes to pm-decisions.md, a returned run to the corrections ledger, a put-off row and a note to the workspace's state folder. It keeps its last answer in the workspace's state folder, so a reload is quick. Counts come from derived state at the time shown above.</p>
|
|
65
|
+
</main>
|
|
66
|
+
<noscript>This page needs JavaScript.</noscript>
|
|
67
|
+
<script type="module" src="/pm/premium.js"></script>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
Binary file
|
|
Binary file
|