create-pmos 0.514.0 → 0.515.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.
@@ -134,12 +134,13 @@ function periods() {
134
134
  const d = new Date(today()), y = d.getUTCFullYear(), q = Math.floor(d.getUTCMonth() / 3), h = d.getUTCMonth() < 6 ? 0 : 1;
135
135
  const span = (y0, m0, y1, m1) => ({ from: Date.UTC(y0, m0, 1), to: Date.UTC(y1, m1, 0) });
136
136
  const lq = q ? { y, q: q - 1 } : { y: y - 1, q: 3 };
137
- return [
137
+ const F = M && M.okrFile && M.okrFile.period; // Intelligence redesign A: the OKR file's own period opens first (H2 2026 for IDPF)
138
+ return (F ? [{ key: 'okr', label: F.label, short: F.short, name: 'The OKR period', said: 'this OKR period', from: F.from, to: F.to }] : []).concat([
138
139
  Object.assign({ key: 'q', label: 'Q' + (q + 1) + ' ' + y, short: 'Q' + (q + 1), name: 'This quarter', said: 'this quarter' }, span(y, q * 3, y, q * 3 + 3)),
139
140
  Object.assign({ key: 'lq', label: 'Q' + (lq.q + 1) + ' ' + lq.y, short: 'Q' + (lq.q + 1), name: 'Last quarter', said: 'last quarter' }, span(lq.y, lq.q * 3, lq.y, lq.q * 3 + 3)),
140
141
  Object.assign({ key: 'h', label: 'H' + (h + 1) + ' ' + y, short: 'H' + (h + 1), name: 'This half', said: 'this half' }, span(y, h * 6, y, h * 6 + 6)),
141
142
  Object.assign({ key: 'y', label: String(y), short: String(y), name: 'This year', said: 'this year' }, span(y, 0, y + 1, 0)),
142
- ];
143
+ ]);
143
144
  }
144
145
  const inPer = (g, p = per) => {
145
146
  if (!p) return true;
@@ -256,10 +257,37 @@ async function openPortfolio(only = pickedWs(), keepTab = false) {
256
257
  for (const c of m.charts || []) charts[m.board.ws + '~' + c.key] = c;
257
258
  for (const g of m.goals || []) goals.push(Object.assign({}, g, { id: m.board.ws + '~' + g.id, rid: g.id, key: g.key ? m.board.ws + '~' + g.key : null, prod: m.board.name || others[i].name, ws: m.board.ws, moves: g.moves || [] }));
258
259
  });
259
- PF = { goals, charts, products, unread };
260
+ const rows = [prodRow(M)].concat(got.filter(Boolean).map(prodRow));
261
+ PF = { goals, charts, products, unread, rows };
260
262
  dash = 'all'; if (!keepTab) setTab('goals'); redraw();
261
263
  if (unread.length) toast('Not read: ' + unread.join(', ') + '. Their goals are not in Portfolio.');
262
264
  }
265
+ /** All products (IgA6Portfolio): one row per product, each number from that product's own model: its North Star, its key results read of the
266
+ * OKR file's total, its late initiatives, its outcome checks overdue, and the one next step Claude would take. PURE over a model. */
267
+ function prodRow(m) {
268
+ const F = m.okrFile, ns = (m.goals || []).find((g) => g.role === 'north-star' && !g.program && !g.initiative) || null;
269
+ const late = (m.lateRows || []).length, over = (m.outcomes || []).filter((r) => r.promise && r.promise.overdue).length;
270
+ const wait = F ? Object.entries(F.needs || {}).sort((a, b) => b[1] - a[1]) : [];
271
+ const next = !F ? 'No OKR file is read here, so there is no key result to list.'
272
+ : wait.length ? 'Connect ' + wait[0][0] + ': it unlocks ' + (wait[0][1] === 1 ? '1 key result.' : wait[0][1] + ' key results.')
273
+ : over ? 'Judge the outcome check that is overdue.' : late ? 'Chase the ' + (late === 1 ? 'late initiative.' : late + ' late initiatives.') : '';
274
+ return { ws: m.board.ws, name: m.board.name || m.board.ws, initials: String(m.board.name || '?').split(/\s+/).map((w) => w[0]).join('').slice(0, 2).toUpperCase(),
275
+ ns: ns ? ns.title + (ns.nowTxt ? ' · ' + ns.nowTxt : ' · not read') : 'none', total: F ? F.total : null, read: F ? F.read : null, late, over, next };
276
+ }
277
+ function allProductsHtml() {
278
+ if (!PF || !PF.rows || PF.rows.length < 2) return '';
279
+ const R = PF.rows, cell = (n) => (n ? `<b>${n}</b>` : '<span class="note">—</span>');
280
+ return `<div class="ins-tbl ins-allp" role="table" aria-label="All products"><div class="ins-tr ins-th ins-pcols" role="row"><div role="columnheader">All products <span>${R.length}</span></div><div role="columnheader">North Star</div><div role="columnheader">Key results read</div><div role="columnheader">Late</div><div role="columnheader">Checks overdue</div><div role="columnheader">Next, by Claude</div></div>`
281
+ + R.map((r) => `<div class="ins-tr ins-row ins-pcols" role="row" data-prod="${esc(r.ws)}" tabindex="0" aria-label="${esc(r.name)}"><div class="nm" role="cell"><span class="ins-pav" aria-hidden="true">${esc(r.initials)}</span><b>${esc(r.name)}</b></div>`
282
+ + `<div role="cell" class="note">${esc(r.ns)}</div><div role="cell">${r.total === null ? '<span class="note">no OKR file</span>' : `<b>${r.read}</b> <span class="note">of ${r.total}</span>`}</div><div role="cell">${cell(r.late)}</div><div role="cell">${cell(r.over)}</div>`
283
+ + `<div role="cell">${r.next ? `<span class="ins-dv"><span class="sp" aria-hidden="true">${SV(I.spark)}</span><span>${esc(r.next)}</span></span>` : ''}</div></div>`).join('') + '</div>';
284
+ }
285
+ /** The ✦ line of All products: the totals of the rows, said once. */
286
+ function allProductsLine() {
287
+ const R = (PF && PF.rows) || [], withF = R.filter((r) => r.total !== null), T = withF.reduce((a, r) => a + r.total, 0), rd = withF.reduce((a, r) => a + r.read, 0);
288
+ const late = R.reduce((a, r) => a + r.late, 0), over = R.reduce((a, r) => a + r.over, 0);
289
+ return 'Across your ' + R.length + ' products: ' + [withF.length ? rd + ' of ' + T + ' key results have a reading' : '', late ? late + (late === 1 ? ' initiative is late' : ' initiatives are late') : '', over ? over + (over === 1 ? ' outcome check is overdue' : ' outcome checks are overdue') : ''].filter(Boolean).join(', ') + '.';
290
+ }
263
291
  /** With several products ticked, Outcomes and Library are still read one product at a time: say which (principle 7, honest state). */
264
292
  const oneNote = () => (PF ? `<p class="note ins-onenote">Shows ${esc(M.board.name || 'the first product')} only: this tab reads one product at a time.</p>` : '');
265
293
  const allGoals = () => (PF ? PF.goals : M.goals);
@@ -441,16 +469,18 @@ function stripList() {
441
469
  // Your impact (batch 16 item 7, GmA4Moments): a key result that moved after your work, as one more ✦ line, with its source to open
442
470
  const im = PF ? null : IM.intelItem(), mine = im ? [im] : [];
443
471
  const fcg = PF ? null : fcGrillItem();
472
+ // Intelligence redesign A (IgA1Open): after the first line, the outcome checks that are due and the late initiatives, one at a time
473
+ const more = PF ? [] : ((M.okr && M.okr.more) || []).map((x) => ({ text: x.text, why: x.why || '', outcome: x.outcome || null }));
444
474
  const ok = PF ? [] : [Object.assign({ okr: true, text: '', why: '' }, (() => { const L = okrLine(); return { text: L.head, why: L.sub, tone: L.tone, keepAll: L.keepAll, start: !!L.start, retry: !!L.retry }; })())].concat(fcg ? [fcg] : []);
445
- if (I0.none) return ok.concat(PF ? [] : RI.stripItems(), mine);
475
+ if (I0.none) return ok.concat(more, PF ? [] : RI.stripItems(), mine);
446
476
  const finds = [{ text: I0.text, why: I0.why || '', chart: I0.chart, move: I0.move || null, calm: !!I0.calm }].concat((I0.more || []).map((x) => ({ text: x.text, why: x.why || '', chart: x.chart, move: x.move || null })));
447
477
  // a calm finding ("Nothing stands out") says what the ✦ line already says: it is left out
448
- return ok.concat(finds.filter((f) => !(ok.length && f.calm)), PF ? [] : RI.stripItems(), mine);
478
+ return ok.concat(finds.filter((f) => !(ok.length && f.calm)), more, PF ? [] : RI.stripItems(), mine); // the outcome check due and the late work follow the readings' own findings
449
479
  }
450
480
  function drawStrip() {
451
481
  const G = goalsShown(), behind = G.filter((g) => g.tone === 'late' || g.tone === 'red').length;
452
482
  $('#insEye').textContent = ''; $('#insAct').innerHTML = ''; $('#insPg').innerHTML = ''; $('#insMv').hidden = true; $('#insMv').innerHTML = ''; $('#insHero').classList.remove('mv', 'crit');
453
- if (PF) { $('#brief').textContent = 'Portfolio: ' + nGoals(G.length) + ' across ' + PF.products.length + (PF.products.length === 1 ? ' product' : ' products') + (behind ? ', ' + behind + ' behind.' : '.'); $('#insWhy').textContent = 'The finding under it is read one product at a time.'; $('#askWhy').href = homeAsk('insights', 'Which product needs me most this quarter?'); $('#askWhy').hidden = false; return; }
483
+ if (PF) { $('#brief').textContent = PF.rows && PF.rows.length > 1 ? allProductsLine() : 'Portfolio: ' + nGoals(G.length) + ' across ' + PF.products.length + (PF.products.length === 1 ? ' product' : ' products') + (behind ? ', ' + behind + ' behind.' : '.'); $('#insWhy').textContent = 'The finding under it is read one product at a time.'; $('#askWhy').href = homeAsk('insights', 'Which product needs me most this quarter?'); $('#askWhy').hidden = false; return; }
454
484
  const L = stripList(), open = L.map((f, i) => i).filter((i) => !insSkip.has(i));
455
485
  if (!open.includes(insIdx)) insIdx = open.length ? open[0] : 0;
456
486
  const f = L[insIdx];
@@ -949,7 +979,7 @@ const nowOf = (g) => ((g.failed || (g.held || []).length) && g.lastRead && g.las
949
979
  function goalRow(g) {
950
980
  const sel = selecting && picked.includes(g.id), warn = gcSay(g), crit = OK.dotOf(g) === 'broken';
951
981
  return `<div class="ins-tr ins-row ${COLS}${sel ? ' sel' : ''}${crit ? ' crit' : ''}" role="row" data-goal="${esc(g.id)}" tabindex="0" aria-label="${esc(g.title)}"${selecting ? ` aria-selected="${sel}"` : ''}>`
952
- + `<div class="nm" role="cell">${crit ? OK.dotHtml('broken') : ''}<span><b>${markHtml(g.title)}</b><small><span class="cz-pill q ins-type">${esc(g.type)}</span><span class="ins-sub" title="${esc(goalSub(g))}">${esc(PF && g.prod ? [g.prod, goalSub(g)].filter(Boolean).join(' · ') : goalSub(g))}</span></small></span></div>`
982
+ + `<div class="nm" role="cell">${crit ? OK.dotHtml('broken') : ''}<span><b>${g.krTok ? `<span class="ins-krt">${esc(g.krTok)}</span>` : ''}${markHtml(g.title)}</b><small><span class="cz-pill q ins-type">${esc(g.type)}</span><span class="ins-sub" title="${esc(goalSub(g))}">${esc(PF && g.prod ? [g.prod, goalSub(g)].filter(Boolean).join(' · ') : goalSub(g))}</span></small></span></div>`
953
983
  + `<div class="val" role="cell"><b>${esc(nowOf(g))}</b> <span class="note">${esc(g.targetTxt ? 'of ' + g.targetTxt : 'no target')}</span>${warn ? `<div class="note ins-warn">${esc(warn)}</div>` : GC.firstOf(g) ? '' : barHtml(g)}</div>`
954
984
  + `<div class="sofar" role="cell">${soFarHtml(g)}</div>`
955
985
  + forecastHtml(g)
@@ -977,12 +1007,62 @@ const GRP_PILL = (L) => {
977
1007
  const scored = L.filter((g) => g.tone !== 'q'), bad = scored.filter((g) => g.tone === 'late' || g.tone === 'red').length;
978
1008
  return !scored.length ? '' : bad ? `<span class="cz-pill late">${bad} of ${L.length} behind</span>` : '<span class="cz-pill ok">on track</span>';
979
1009
  };
1010
+ /* ── the OKR file as the goal list (Intelligence redesign A, IgA1Open): the file's key results by objective. A key result with a reader (a KR
1011
+ * metric record attached by its kr field or its id) is that goal's row; one without says what it needs, never a number. A record that reads no
1012
+ * key result of the file keeps its row under "Other goals" (principle 13: nothing is dropped). ── */
1013
+ const okrOn = () => !!(!PF && M.okrFile && M.okrFile.objectives && M.okrFile.objectives.length);
1014
+ const okrKrs = () => (okrOn() ? M.okrFile.objectives.flatMap((o) => o.krs.map((k) => Object.assign({ obj: o }, k))) : []);
1015
+ const krGoal = (k) => (k.goal ? M.goals.find((g) => g.id === k.goal) || null : null);
1016
+ const krHit = (k) => hit(k.token + ' ' + k.title, k.obj.title, ...k.moves.map((m) => m.title));
1017
+ /** The rows of one key result: its reader's goal row, with the key result's id before the title and the file's movers when the record has none. */
1018
+ function krRow(k) {
1019
+ const g = krGoal(k);
1020
+ if (g) return goalRow(Object.assign({}, g, { krTok: k.token, moves: g.moves && g.moves.length ? g.moves : k.moves }));
1021
+ const decision = k.decision && !k.need ? true : k.decision;
1022
+ const now = decision ? `<b>Not yet</b>${k.due ? `<div class="note">due ${esc(md(day(k.due)))}</div>` : ''}`
1023
+ : `<span class="ins-need">${OK.dotHtml('q')}<span class="note">${esc(k.need ? 'needs ' + k.need : 'needs a source')}</span></span>`;
1024
+ const mv = k.moves.length ? k.moves.filter((r) => r.status !== 'cancelled').slice(0, 3).map((r) => initRow(r)).join('') + (k.moves.length > 3 ? `<span class="note">and ${k.moves.length - 3} more</span>` : '') : `<span class="note">${M.initiativesRead ? 'No initiative yet' : 'The initiatives are not read yet'}</span>`;
1025
+ return `<div class="ins-tr ins-row ${COLS} ins-krq" role="row" data-kr="${esc(k.key)}" tabindex="0" aria-label="${esc(k.token + ' ' + k.title)}">`
1026
+ + `<div class="nm" role="cell"><span><b><span class="ins-krt">${esc(k.token)}</span>${esc(k.title)}</b><small><span class="ins-sub" title="${esc(k.line || '')}">${esc(k.line || '')}</span></small></span></div>`
1027
+ + `<div class="val" role="cell">${now}</div><div class="sofar" role="cell"><span class="note">—</span></div><div class="pace" role="cell"><span class="note">—</span></div>`
1028
+ + `<div class="mv" role="cell">${mv}</div></div>`;
1029
+ }
1030
+ /** The key-result table from the OKR file: by objective, then the records that read none of its key results. */
1031
+ function okrTableHtml(head) {
1032
+ const P = M.okrFile.period, shown = !P || !per || (P.from <= per.to && P.to >= per.from), filtered = !noFilter();
1033
+ const readers = new Set(M.okrFile.readers || []);
1034
+ const K = shown ? okrKrs().filter((k) => krHit(k) && (!filtered || (krGoal(k) && goalsShown().some((g) => g.id === k.goal)))) : [];
1035
+ const other = goalsShown().filter((g) => !readers.has(g.id));
1036
+ const by = new Map(); for (const k of K) { if (!by.has(k.obj.id)) by.set(k.obj.id, []); by.get(k.obj.id).push(k); }
1037
+ const groups = [...by.values()].map((L) => `<div class="ins-grp" role="row"><span role="cell"><b>${esc(L[0].obj.token)}</b> ${esc(L[0].obj.title)}</span></div>` + L.map(krRow).join('')).join('')
1038
+ + (other.length ? `<div class="ins-grp" role="row"><span role="cell">Other goals</span> <span class="q">recorded here, not in the OKR file</span></div>` + other.map(goalRow).join('') : '');
1039
+ const n = K.length + other.length;
1040
+ return head(n) + (n ? groups : `<div class="ins-empty">No key result matches${find ? ' “' + esc(find) + '”' : ''}${filtered ? ' this filter' : ''} in ${esc(per.label)}. <button type="button" class="lk" id="goalsAll">Show every goal</button></div>`);
1041
+ }
1042
+ /** A key result with no reader, in the Viewer: its words from the OKR file, what it needs, and the initiatives that move it. */
1043
+ function openKr(key) {
1044
+ const k = okrKrs().find((x) => x.key === key); if (!k) return;
1045
+ viewer({ kind: 'Intelligence · ' + k.obj.token, title: k.title, icon: 'chart', id: 'kr:' + key, eye: k.token, render: (el) => {
1046
+ el.innerHTML = `<div class="ins-gd"><div class="ins-sayc"><p class="ins-sayv">${esc(k.line || k.title)}</p>`
1047
+ + `<p class="note">${esc(k.decision && !k.need ? 'A decision: it is done when it is decided' + (k.due ? ', due ' + md(day(k.due)) : '') + '.' : k.need ? 'Nothing reads it yet: it needs ' + k.need + '. I will not guess a number.' : 'The OKR file names no source for it.')}</p></div>`
1048
+ + (k.source ? `<h3 class="ins-h3">How I would read it</h3><p class="note">${esc('The OKR file says: ' + k.source + '.')}</p>` : '')
1049
+ + (k.need && !k.decision ? `<div class="ins-krcon"><span class="sp" aria-hidden="true">${SV(I.spark)}</span><span>${esc('Connect ' + k.need + ' and I test a read before I keep it.')}</span><button type="button" class="cz-ib sm solid" data-krcon aria-label="Open Settings, Connectors" title="Open Connectors">${SV(I.chk)}</button></div>` : '')
1050
+ + `<h3 class="ins-h3">Moved by</h3>${k.moves.length ? `<div class="ins-mv">${k.moves.map((r) => initRow(r)).join('')}</div>` : '<p class="note">No initiative is anchored to it yet.</p>'}</div>`;
1051
+ el.addEventListener('click', (e) => { if (e.target.closest('[data-krcon]')) import('./cozy-settings.js').then((S) => S.openSettings('connectors')).catch(() => toast('Open Settings › Connectors.')); });
1052
+ } });
1053
+ }
980
1054
  function drawGoals() {
981
1055
  const G = goalsShown(), T = $('#goalTbl');
982
1056
  document.body.classList.toggle('ins-trv', dash === TRENDS);
983
1057
  if (dash === TRENDS) { T.innerHTML = bmHtml(true); T.hidden = false; $('#goalCards').innerHTML = ''; return; }
984
1058
  const head = `<div class="ins-tr ins-th ${COLS}" role="row"><div role="columnheader">Key results <span>${G.length} · ${esc(per ? per.label : '')}</span></div><div role="columnheader">Now, of target <span class="ins-lg"><span><i class="ins-tdk"></i>today</span></span></div><div role="columnheader">So far</div><div role="columnheader">By its date</div><div role="columnheader" class="ins-mvh">Moved by <span class="ins-lg"><span><i class="ins-trk"><i class="k"></i></i>Skipped</span><span><i class="ins-trk"><i class="p m"></i></i>Milestone</span></span></div></div>`;
985
1059
  const D = curDash();
1060
+ if (okrOn() && D.id === 'all') {
1061
+ T.hidden = false;
1062
+ const head2 = (n) => head.replace(/Key results <span>[^<]*<\/span>/, `Key results <span>${n} · ${esc(per ? per.label : '')}</span>`);
1063
+ T.innerHTML = okrTableHtml(head2) + budgetsHtml(D);
1064
+ drawCards(G, true); IP2.drawn(); return;
1065
+ }
986
1066
  if (!M.goals.filter(isKr).length) {
987
1067
  // the drafts table above says what is drafted: no empty card under it (zero is silence)
988
1068
  const B = budgetsHtml(D);
@@ -996,7 +1076,7 @@ function drawGoals() {
996
1076
  const objs = [...new Set(M.goals.filter(isKr).map((g) => g.pillar || ''))];
997
1077
  const head1 = (k) => (PF ? esc(k || 'A product') : k ? 'Objective ' + (objs.indexOf(k) + 1) + ' · ' + esc(k) : 'No objective');
998
1078
  const groups = [...by.entries()].map(([k, L]) => `<div class="ins-grp" role="row"><span role="cell">${head1(k)}</span> <span class="q">${esc(per ? per.label : '')}</span>${GRP_PILL(L)}</div>` + L.map(goalRow).join('')).join('');
999
- T.innerHTML = head + (G.length ? groups : `<div class="ins-empty">No goal matches${find ? ' “' + esc(find) + '”' : ''}${noFilter() ? '' : ' this filter'} in ${esc(per.label)}. <button type="button" class="lk" id="goalsAll">Show every goal</button></div>`) + budgetsHtml(D);
1079
+ T.innerHTML = allProductsHtml() + head + (G.length ? groups : `<div class="ins-empty">No goal matches${find ? ' “' + esc(find) + '”' : ''}${noFilter() ? '' : ' this filter'} in ${esc(per.label)}. <button type="button" class="lk" id="goalsAll">Show every goal</button></div>`) + budgetsHtml(D);
1000
1080
  drawCards(G);
1001
1081
  IP2.drawn();
1002
1082
  }
@@ -1016,24 +1096,38 @@ function cardPill(g) {
1016
1096
  if (t === 'ok') return g.done !== null && g.timeGone != null && g.done > g.timeGone ? ['ok', 'Ahead'] : ['ok', 'On track'];
1017
1097
  return null;
1018
1098
  }
1019
- function drawCards(G) {
1020
- $('#goalCards').innerHTML = G.map((g) => {
1099
+ /** A key result with no reader, as a phone card: its id and name, what it needs (or Not yet for a decision), its first mover. */
1100
+ const krCard = (k) => `<div class="ins-card" role="listitem" data-kr="${esc(k.key)}" tabindex="0" aria-label="${esc(k.token + ' ' + k.title)}"><div class="ins-ch"><b><span class="ins-krt">${esc(k.token)}</span>${esc(k.title)}</b></div>`
1101
+ + `<div class="ins-cs"><span class="note">${esc(k.decision && !k.need ? 'Not yet' + (k.due ? ' · due ' + md(day(k.due)) : '') : k.need ? 'needs ' + k.need : 'needs a source')}</span></div>`
1102
+ + (k.moves[0] ? `<div class="ins-c1">${initRow(k.moves[0], false)}</div>` : '') + '</div>';
1103
+ function drawCards(G, okr = false) {
1104
+ if (okr) { // the phone (IgA7Phone): a card per key result that is read or due as a decision; the ones that wait on a source fold under one line
1105
+ const K = okrKrs().filter(krHit), shown = K.filter((k) => krGoal(k) || (k.decision && !k.need)), wait = K.filter((k) => !shown.includes(k));
1106
+ const nw = wait.filter((k) => k.need).length;
1107
+ $('#goalCards').innerHTML = shown.map((k) => (krGoal(k) ? cardOf(Object.assign({}, krGoal(k), { krTok: k.token })) : krCard(k))).join('')
1108
+ + (wait.length ? `<details class="ins-cfold"><summary><b>${wait.length === 1 ? '1 more key result' : wait.length + ' more key results'}</b> <span class="note">${nw ? nw + ' wait on a source' : ''}</span></summary>${wait.map(krCard).join('')}</details>` : '');
1109
+ return;
1110
+ }
1111
+ $('#goalCards').innerHTML = (PF ? allProductsHtml() : '') + G.map(cardOf).join('') || (M.goals.filter(isKr).length ? '<p class="note">No goal in this period.</p>' : '');
1112
+ }
1113
+ function cardOf(g) {
1021
1114
  const p = cardPill(g), tg = g.targetTxt ? (g.type === 'Count' ? 'of ' : 'target ') + g.targetTxt + (g.byAt ? ' by ' + md(day(g.byAt)) : '') : 'no target', first = GC.firstOf(g);
1022
1115
  const gr = first ? `<span class="note">${esc(GC.firstMeta(g).split(' · ').slice(1).join(' · '))}</span>` : isBlocks(g) ? blocksHtml(g, 190, false) : g.type === 'Stay within' && g.series.length && g.target !== null ? bandSvg(g.series, g.target, g.limit !== 'floor', g.tone, g.title, 190, 30) : g.key ? sparkHtml(g, 190, 30) : '';
1023
- return `<div class="ins-card" role="listitem" data-goal="${esc(g.id)}" tabindex="0" aria-label="${esc(g.title)}"><div class="ins-ch"><b>${esc(g.title)}</b>${p ? `<span class="cz-pill ${p[0]}">${p[1]}</span>` : ''}</div>`
1116
+ return `<div class="ins-card" role="listitem" data-goal="${esc(g.id)}" tabindex="0" aria-label="${esc(g.title)}"><div class="ins-ch"><b>${g.krTok ? `<span class="ins-krt">${esc(g.krTok)}</span>` : ''}${esc(g.title)}</b>${p ? `<span class="cz-pill ${p[0]}">${p[1]}</span>` : ''}</div>`
1024
1117
  + `<div class="ins-cs"><span class="cz-pill q ins-type">${esc(g.type)}</span><span class="note">${esc(tg)}</span></div>`
1025
1118
  + `<div class="ins-cv"><b>${esc(nowOf(g))}</b><span class="ins-cg">${gr}</span></div>${first || gcSay(g) ? '' : barHtml(g, ' ins-wide')}`
1026
1119
  + (g.moves[0] ? `<div class="ins-c1">${initRow(g.moves[0], false)}</div>` : '') + '</div>' + (PF ? '' : draftsFor(g.id).map(drHtml).join(''));
1027
- }).join('') || (M.goals.filter(isKr).length ? '<p class="note">No goal in this period.</p>' : '');
1028
1120
  }
1029
1121
  IP2.mount({ page: $('#stage'), table: $('#goalTbl'), selecting: () => selecting });
1030
- $('#goalCards').addEventListener('click', (e) => { if (e.target.closest('a, .okr-dr')) return; const c = e.target.closest('[data-goal]'); if (c) pressGoal(c.dataset.goal); });
1122
+ $('#goalCards').addEventListener('click', (e) => { if (e.target.closest('a, .okr-dr, summary')) return; const pp = e.target.closest('[data-prod]'); if (pp) { location.assign(setQ({ ws: pp.dataset.prod, board: null, goal: null })); return; } const q = e.target.closest('[data-kr]'); if (q) { openKr(q.dataset.kr); return; } const c = e.target.closest('[data-goal]'); if (c) pressGoal(c.dataset.goal); });
1031
1123
  $('#goalTbl').addEventListener('click', (e) => {
1032
- if (e.target.closest('#goalsAll')) { find = ''; $('#findIn').value = ''; clearFilter(); per = periods()[3]; redraw(); return; }
1124
+ if (e.target.closest('#goalsAll')) { find = ''; $('#findIn').value = ''; clearFilter(); per = periods().find((p) => p.key === 'y'); redraw(); return; }
1033
1125
  if (e.target.closest('a')) return;
1034
1126
  const tile = e.target.closest('[data-tile], [data-mt]'); if (tile) { if (tgOpen) closeTarget(); openTile(tile.dataset.tile || tile.dataset.mt); return; }
1035
1127
  if (e.target.closest('.okr-dr')) return; // a draft under a row has its own presses
1036
1128
  const bu = e.target.closest('[data-budget]'); if (bu) { openBudget(bu.dataset.budget); return; }
1129
+ const kq = e.target.closest('[data-kr]'); if (kq) { openKr(kq.dataset.kr); return; }
1130
+ const pr = e.target.closest('[data-prod]'); if (pr) { location.assign(setQ({ ws: pr.dataset.prod, board: null, goal: null })); return; }
1037
1131
  const r = e.target.closest('[data-goal]'); if (r) pressGoal(r.dataset.goal);
1038
1132
  });
1039
1133
  $('#goalTbl').addEventListener('keydown', (e) => { if (e.key !== 'Enter' && e.key !== ' ') return; const r = e.target.closest && e.target.closest('.ins-row, [data-tile]'); if (!r || e.target !== r) return; e.preventDefault(); r.click(); });
@@ -1342,6 +1436,8 @@ $('#goalCards').addEventListener('click', (e) => { if (e.target.closest('a')) re
1342
1436
  /* ── Delivery trends: six tiles from the Board's measures (AgInsGoals, IsTarget): a label, the value, a small line with its end dot and the
1343
1437
  * target's line under it. A press opens the target popover over the tiles. In the Delivery trends view the tiles stand three to a row. ─── */
1344
1438
  const TILES = [['blocked', 'Blocked tickets'], ['late', 'Late initiatives'], ['idle', 'Idle 30+ days'], ['scope', 'Scope added'], ['merges', 'Merged a week'], ['corrections', 'Corrections / 100 runs']];
1439
+ /* Intelligence redesign A (IgA4Trends): merges and corrections measure PMOS's own agents, so they show only on the PMOS workspace's Intelligence. */
1440
+ const tilesOn = () => TILES.filter(([k]) => (M && M.self) || (k !== 'merges' && k !== 'corrections'));
1345
1441
  const TILE_DIR = { merges: 'at least' }; // more merges is better; every other measure is better lower
1346
1442
  const TILE_DEF = { late: 'Open initiatives the workflow engine reports as late today. Earlier weeks count the initiatives late today whose end date had already passed; the Board keeps no history of a status.', scope: 'Initiatives whose planned start falls in the week.' };
1347
1443
  const numsOf = (ser) => (ser || []).filter((v) => typeof v === 'number' && Number.isFinite(v));
@@ -1350,7 +1446,7 @@ const numsOf = (ser) => (ser || []).filter((v) => typeof v === 'number' && Numbe
1350
1446
  function targetOf(key, t) {
1351
1447
  const TS = ((M.trends || {}).tiles || {})[key] || null, dir = (TS && TS.dir) || TILE_DIR[key] || 'at most';
1352
1448
  const tg = dir === 'none' || t.tg == null || !Number.isFinite(Number(t.tg)) ? null : Number(t.tg);
1353
- if (tg === null) return { dir, tg: null, met: null, miss2: false, line: 'No target', set: TS };
1449
+ if (tg === null) return { dir, tg: null, met: null, miss2: false, line: '', set: TS }; // zero is silence: no "No target" (principle 3)
1354
1450
  const miss = (v) => (dir === 'at least' ? v < tg : v > tg), S = numsOf(t.ser), v0 = S.length ? S[S.length - 1] : Number(t.v);
1355
1451
  const met = Number.isFinite(v0) ? !miss(v0) : null, miss2 = S.length >= 2 ? miss(S[S.length - 1]) && miss(S[S.length - 2]) : met === false;
1356
1452
  return { dir, tg, met, miss2, set: TS, line: 'Target: ' + dir + ' ' + tg + (met ? ' · met' : TS && TS.at ? ' · set ' + md(day(TS.at)) : '') };
@@ -1369,6 +1465,8 @@ function tileOf(key) {
1369
1465
  const b = (BM.tiles || []).find((x) => x.key === key);
1370
1466
  if (!b) return { v: 'Not read', s: '', abs: true, T: { line: '' } };
1371
1467
  if (b.absent) return { v: b.absent, s: '', abs: true, why: b.why || '', def: b.def, T: { line: '' } };
1468
+ // a month of history makes "no activity on record" mean idle; before that the tile says when it is known, not a count (IgA4Trends)
1469
+ if (b.knownFrom) return { v: '—', s: 'known from ' + md(day(b.knownFrom)), abs: true, why: 'History starts ' + md(day(b.historyFrom)) + ', so the first month is known from ' + md(day(b.knownFrom)) + '.', def: b.def, T: { line: 'known from ' + md(day(b.knownFrom)) } };
1372
1470
  t = { v: b.value, ser: b.ser, s: b.targetTxt || '', note: b.text || '', tg: b.tg != null ? b.tg : null, base: !!b.look || b.kind === 'warn' || b.kind === 'bad',
1373
1471
  change: b.change || null, wk: b.wk || null, nt: b.nt || null, def: b.def, dec: b.dec || 0 };
1374
1472
  }
@@ -1385,7 +1483,7 @@ function miniSpark(ser, W = 150, H = 18) {
1385
1483
  }
1386
1484
  let tgOpen = null; // the tile whose target popover is open
1387
1485
  /** The six tiles with their words (label, value, change) as the trends view reads them. */
1388
- const trendTiles = () => TILES.map(([k, l]) => Object.assign({ key: k, label: l }, tileOf(k)));
1486
+ const trendTiles = () => tilesOn().map(([k, l]) => Object.assign({ key: k, label: l }, tileOf(k)));
1389
1487
  const CHAT = 'M4 5h16v11H9l-5 4z';
1390
1488
  /** The ✦ month line (canvas MxA): the Board's measures in one sentence; the chat icon takes it to Strategist. Nothing read, no line. */
1391
1489
  function monthStrip() {
@@ -1418,7 +1516,7 @@ function bmHtml(big) {
1418
1516
  : '<div class="ins-grp" role="row"><h2 id="hBm" role="cell">Delivery trends</h2> <span class="q">the Board shows today’s counts as chips · press a tile to set its target</span></div>';
1419
1517
  const mv = (t) => (big && !t.abs && t.change && t.change.d !== 0 ? `<span class="mv">${esc(t.change.txt)}</span>` : '');
1420
1518
  return `<section class="ins-trends${big ? ' big' : ''}" id="cBm" aria-labelledby="hBm">${head}${big ? monthStrip() : ''}`
1421
- + `<div class="ins-tiles" id="bmBody">${TILES.map(([k, l]) => { const t = tileOf(k), on = tgOpen === k; return `<div class="ins-tile${t.warn ? ' warn' : ''}${t.abs ? ' abs' : ''}${on ? ' sel' : ''}" data-tile="${k}" role="button" tabindex="0" aria-expanded="${on}" aria-label="${esc(l + ', ' + t.v + (t.s ? ', ' + t.s : '') + (t.change && t.change.d !== 0 ? ', ' + t.change.txt : '') + (t.T.line ? ', ' + t.T.line : ''))}"${t.s ? ` title="${esc(t.s)}"` : ''}><span class="l">${esc(l)}</span><span class="v">${esc(t.v)}</span>${big ? miniSpark(t.ser, 300, 34) : miniSpark(t.ser)}${mv(t)}<span class="tg">${on ? 'Setting target…' : esc(t.T.line)}</span></div>`; }).join('')}</div>`
1519
+ + `<div class="ins-tiles" id="bmBody">${tilesOn().map(([k, l]) => { const t = tileOf(k), on = tgOpen === k; return `<div class="ins-tile${t.warn ? ' warn' : ''}${t.abs ? ' abs' : ''}${on ? ' sel' : ''}" data-tile="${k}" role="button" tabindex="0" aria-expanded="${on}" aria-label="${esc(l + ', ' + t.v + (t.s ? ', ' + t.s : '') + (t.change && t.change.d !== 0 ? ', ' + t.change.txt : '') + (t.T.line ? ', ' + t.T.line : ''))}"${t.s ? ` title="${esc(t.s)}"` : ''}><span class="l">${esc(l)}</span><span class="v">${esc(t.v)}</span>${big ? miniSpark(t.ser, 300, 34) : miniSpark(t.ser)}${mv(t)}<span class="tg">${on ? 'Setting target…' : esc(t.T.line)}</span></div>`; }).join('')}</div>`
1422
1520
  + (big ? movedCard() + movedGoals() : '') + '</section>';
1423
1521
  }
1424
1522
  /** A tile in the Viewer (MxB): its weeks as bars, a blank week dashed with its reason, the definition, where it is counted from, and the target. */
@@ -1547,21 +1645,26 @@ function draftOf(r) {
1547
1645
  let decOpen = false;
1548
1646
  function outRow(r) {
1549
1647
  const due = dueOf(r), P = promiseOf(r), dr = draftOf(r), ship = day(r.endAt);
1550
- const promised = P ? `<b>${esc(P.g.title + (P.promised !== null ? ' ' + sgn(P.promised, P.g.unit) : ''))}</b><div class="note">${esc(P.g.byAt ? 'by ' + md(day(P.g.byAt)) : 'no date')}</div>` : `<span class="note">${r.anchor ? 'Anchored to ' + esc(r.anchor) + ', which is not a goal with readings.' : 'No goal is anchored, so nothing was promised to measure.'}</span>`;
1648
+ const pr = r.promise || null;
1649
+ const promised0 = P ? `<b>${esc(P.g.title + (P.promised !== null ? ' ' + sgn(P.promised, P.g.unit) : ''))}</b><div class="note">${esc(P.g.byAt ? 'by ' + md(day(P.g.byAt)) : 'no date')}</div>` : `<span class="note">${r.anchor ? 'Anchored to ' + esc(r.anchor) + ', which is not a goal with readings.' : 'No goal is anchored, so nothing was promised to measure.'}</span>`;
1650
+ // the D57 promise (outcome_metric) is what the row promised; the anchor goal's numbers stay under it when there is one
1651
+ const promised = pr && pr.metric ? `<b>${esc(pr.metric)}</b>${P ? `<div class="note">${esc(P.g.title + (P.promised !== null ? ' ' + sgn(P.promised, P.g.unit) : ''))}</div>` : ''}` : P ? promised0 : '<span class="cz-pill q">No outcome promised</span>';
1652
+ const check = pr && pr.horizon ? `<span class="ins-chk">${OK.dotHtml(pr.overdue ? 'behind' : 'q')}<b>${esc(md(day(pr.horizon)))}</b></span>${pr.overdue ? '<div class="note">overdue</div>' : ''}` : '<span class="note">No check date</span>';
1551
1653
  const got = P ? `<span class="ins-gotw">${bulletHtml(P)}<b>${esc(P.moved === null ? 'no reading yet' : sgn(P.moved, P.g.unit))}</b></span>${guardOf(r) ? `<div class="note">${esc(guardOf(r).name + ': ' + guardOf(r).text)}</div>` : ''}` : '';
1552
1654
  let verdict, act = '';
1553
1655
  if (r.group === 'verdict') {
1554
- verdict = dr ? `<span class="ins-dv"><span class="sp" aria-hidden="true">${SV(I.spark)}</span><span><b>${esc(dr.verdict)}.</b> ${esc(dr.text)}</span></span>` : `<span class="cz-pill you">To judge${due !== null ? ' · due ' + esc(md(due)) : ''}</span>`;
1656
+ const dd = pr && pr.horizon ? day(pr.horizon) : due;
1657
+ verdict = dr ? `<span class="ins-dv"><span class="sp" aria-hidden="true">${SV(I.spark)}</span><span><b>${esc(dr.verdict)}.</b> ${esc(dr.text)}</span></span>` : `<span class="cz-pill you">To judge${dd !== null ? (pr && pr.overdue ? ' · was due ' : ' · due ') + esc(md(dd)) : ''}</span>`;
1555
1658
  act = `<button type="button" class="cz-ib sm" data-verdict="${esc(r.id)}" aria-haspopup="dialog" aria-label="Decide: Keep, Iterate or Stop" title="Decide">${SV(I.pen)}</button>`
1556
1659
  + (dr ? `<button type="button" class="cz-ib sm solid" data-keep="${esc(r.id)}" aria-label="Record the verdict: Keep" title="Record the verdict: Keep">${SV(I.chk)}</button>` : '');
1557
1660
  } else if (r.group === 'decided') {
1558
1661
  const v = r.verdict;
1559
1662
  verdict = v ? `<span class="cz-pill ${v.verdict === 'Stop' ? 'q' : 'ok'}">${v.verdict === 'Keep' ? SV(I.chk) + 'Kept' : v.verdict === 'Iterate' ? 'Changed' : 'Stopped'}${v.at ? ' · ' + esc(md(day(v.at))) : ''}</span><div class="note">${esc(v.learned)}</div>`
1560
- : (r.status === 'done' ? `<span class="cz-pill ok">${SV(I.chk)}Kept</span>` : '<span class="cz-pill q">Stopped</span>') + '<div class="note">No learning recorded.</div>';
1663
+ : '<span class="cz-pill q">Stopped</span><div class="note">No learning recorded.</div>'; // only a stopped initiative is decided without a verdict
1561
1664
  if (v && v.at === M.today) act = `<button type="button" class="cz-ib sm" data-vundo="${esc(r.id)}" aria-label="Undo the verdict" title="Undo">${SV(I.undo)}</button>`;
1562
1665
  } else verdict = `<span class="cz-pill q">${esc(due !== null ? 'Judged ' + md(due) : r.idx === 6 ? 'Being monitored' : 'Too early')}</span>`;
1563
1666
  return `<div class="ins-tr ins-row ins-ocols" role="row" data-ini="${esc(r.id)}"><div class="nm" role="cell"><a href="${iniHref(r.id)}"><b>${esc(r.title)}</b></a><small>${esc(ship !== null ? 'shipped ' + md(ship) : 'no ship date recorded')}</small></div>`
1564
- + `<div role="cell" class="pace">${promised}</div><div role="cell" class="pace">${got}</div><div role="cell" class="pace">${verdict}</div><div role="cell" class="ins-act">${act}</div></div>`;
1667
+ + `<div role="cell" class="pace">${promised}</div><div role="cell" class="pace">${check}</div><div role="cell" class="pace">${got}</div><div role="cell" class="pace">${verdict}</div><div role="cell" class="ins-act">${act}</div></div>`;
1565
1668
  }
1566
1669
  /** Did it work? (Iq8Overview): only the work to judge and the work in its window; the decided ones fold under one line that counts them. The
1567
1670
  * section is not drawn when nothing has shipped. */
@@ -1571,9 +1674,10 @@ function drawOutcomes() {
1571
1674
  if (!M.initiativesRead || (!live.length && !dec.length)) { sec.hidden = true; $('#outTbl').innerHTML = ''; return; }
1572
1675
  sec.hidden = false;
1573
1676
  const nj = live.filter((r) => r.group === 'verdict').length, nw = live.length - nj;
1574
- const vOf = (r) => (r.verdict ? r.verdict.verdict : r.status === 'done' ? 'Keep' : 'Stop'), cnt = (k) => dec.filter((r) => vOf(r) === k).length;
1575
- const head = `<div class="ins-tr ins-th ins-ocols" role="row"><div role="columnheader">Did it work? <span>${esc([nj ? nj + ' to judge' : '', nw ? nw + ' in window' : ''].filter(Boolean).join(' · '))}</span></div><div role="columnheader">Promised</div><div role="columnheader">Got</div><div role="columnheader">Verdict</div><div></div></div>`;
1576
- const foot = dec.length ? `<button type="button" class="ins-foot" id="decFold" aria-expanded="${decOpen}">Decided <b>${dec.length}</b> · ${esc([cnt('Keep') + ' kept', cnt('Iterate') + ' changed', cnt('Stop') + ' stopped'].join(', '))}${SV(decOpen ? I.up : I.down)}</button>` : '';
1677
+ const vOf = (r) => (r.verdict ? r.verdict.verdict : 'Stop'), cnt = (k) => dec.filter((r) => vOf(r) === k).length; // kept only from a recorded verdict
1678
+ const np = live.filter((r) => !(r.promise && r.promise.metric) && !r.goal).length;
1679
+ const head = `<div class="ins-tr ins-th ins-ocols" role="row"><div role="columnheader">Did it work? <span>${esc([nj ? nj + ' to judge' : '', nw ? nw + ' in window' : '', np ? np + ' without a promise' : ''].filter(Boolean).join(' · '))}</span></div><div role="columnheader">Promised</div><div role="columnheader">Check</div><div role="columnheader">Got</div><div role="columnheader">Verdict</div><div></div></div>`;
1680
+ const foot = dec.length ? `<button type="button" class="ins-foot" id="decFold" aria-expanded="${decOpen}">Decided <b>${dec.length}</b> · ${esc([cnt('Keep') ? cnt('Keep') + ' kept' : '', cnt('Iterate') ? cnt('Iterate') + ' changed' : '', cnt('Stop') ? cnt('Stop') + ' stopped' : ''].filter(Boolean).join(', '))}${SV(decOpen ? I.up : I.down)}</button>` : '';
1577
1681
  $('#outTbl').innerHTML = oneNote() + head + live.map(outRow).join('') + foot + (decOpen ? dec.map(outRow).join('') : '');
1578
1682
  }
1579
1683
  $('#outTbl').addEventListener('click', async (e) => {
@@ -1635,9 +1739,19 @@ function goalPeriods() {
1635
1739
  out.push({ kind: 'Year', label: String(y), by: Date.UTC(y, 12, 0) }, { kind: 'Year', label: String(y + 1), by: Date.UTC(y + 1, 12, 0) });
1636
1740
  return out;
1637
1741
  }
1742
+ /** + drafts the goal (IgA1Open): the first key result of the OKR file that nothing reads yet, its numbers from the file's line, its due date and
1743
+ * its objective. It is a draft in the same form (the form is the ✎ editor); Save keeps it as that key result's reader, never a second goal. */
1744
+ function krDraft() {
1745
+ const k = okrKrs().find((x) => !x.goal && !x.decision && /\d/.test(x.line || '')); if (!k) return null;
1746
+ const nums = String(k.line).split('→').map((x) => { const m = /(-?−?\d[\d,]*(?:\.\d+)?)\s*(%)?/.exec(x); return m ? { v: Number(m[1].replace(/,/g, '').replace('−', '-')), pct: !!m[2] } : null; });
1747
+ const two = nums.length >= 2 && nums[0] && nums[nums.length - 1];
1748
+ return { k, name: k.title, obj: (k.obj.token + ' ' + k.obj.title).trim(), base: two ? nums[0].v : null, target: two ? nums[nums.length - 1].v : null, pct: !!(nums[0] && nums[0].pct), due: k.due };
1749
+ }
1638
1750
  function openNewGoal() {
1639
1751
  const objs = [...new Set(M.goals.map((g) => g.pillar).filter(Boolean))];
1640
- const GP = goalPeriods(), others = M.goals.map((g) => g.title);
1752
+ const DR0 = krDraft();
1753
+ if (DR0 && !objs.includes(DR0.obj)) objs.unshift(DR0.obj);
1754
+ const GP = goalPeriods(), others = M.goals.filter((g) => g.type !== 'Done or not').map((g) => g.title); // a done-or-not goal is never a guardrail
1641
1755
  /* the objective is eyebrow text with a quiet chevron (AgInsNewGoal: "New goal · Objective 1"); its menu names each objective */
1642
1756
  const objSel = `<span class="ins-mw"><button type="button" class="ins-objb" id="ngObjB" aria-haspopup="menu" aria-expanded="false" aria-controls="ngObjMenu" aria-label="Objective: ${objs.length ? 'Objective 1 · ' + esc(objs[0]) : 'No objective'}">${objs.length ? 'Objective 1' : 'No objective'}<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 10l5 5 5-5"/></svg></button>`
1643
1757
  + `<div class="ins-menu" id="ngObjMenu" role="menu" aria-label="Objective" hidden>${objs.map((o, i) => `<button type="button" role="menuitemradio" aria-checked="${i === 0}" data-obj="${esc(o)}" data-said="Objective ${i + 1}">Objective ${i + 1} · ${esc(o)}</button>`).join('')}<button type="button" role="menuitemradio" aria-checked="${!objs.length}" data-obj="" data-said="No objective">No objective</button><button type="button" role="menuitemradio" aria-checked="false" data-obj="+" data-said="A new objective">A new objective…</button></div></span>`
@@ -1663,6 +1777,19 @@ function openNewGoal() {
1663
1777
  });
1664
1778
  $('#ngPer').dataset.kind = 'Quarter';
1665
1779
  goalHint();
1780
+ if (DR0) fillDraft(DR0);
1781
+ }
1782
+ /** Fill the drafted key result into the form; the PM checks it and saves, or edits any field first. */
1783
+ function fillDraft(D) {
1784
+ const all = Array.from(document.querySelectorAll('#sideBody button')), btn = (t) => all.find((x) => x.textContent.trim() === t);
1785
+ $('#ngName').value = D.name; $('#ngName').dataset.kr = D.k.obj.token + '.' + D.k.token;
1786
+ if (D.pct && btn('Percentage')) btn('Percentage').click();
1787
+ if (D.base !== null) $('#ngBase').value = String(D.base);
1788
+ if (D.target !== null) $('#ngTgt').value = String(D.target);
1789
+ if (D.base !== null && D.target !== null && D.target < D.base && btn('Down')) btn('Down').click();
1790
+ if (D.due && btn('Custom')) { btn('Custom').click(); $('#ngBy').value = D.due; }
1791
+ const t = all.find((x) => x.dataset && x.dataset.src === 'typed'); if (t) t.click(); // no reader is tested yet: you type the reading in
1792
+ $('#sideMsg').textContent = '✦ Drafted from your OKR file (' + D.k.token + '). Check it, then save.' + (D.k.need ? ' It needs ' + D.k.need + ' to be read by itself.' : '');
1666
1793
  }
1667
1794
  /** The form follows its type: a measured goal has Better is, a baseline and a target; Stay within a limit and the reading now; Done or not its steps. */
1668
1795
  function goalHint() {
@@ -1688,7 +1815,7 @@ async function saveGoal() {
1688
1815
  const src = $('#sideBody [data-src][aria-pressed="true"]');
1689
1816
  const body = { ws: M.board.ws, name: v('#ngName'), type: ty, direction: segVal('Better is'), baseline: v('#ngBase'), target: v('#ngTgt'),
1690
1817
  due: pk === 'Custom' ? v('#ngBy') : $('#ngPer').value, period_kind: always ? null : pk, period_label: pk === 'Custom' ? 'Custom' : ($('#ngPer').selectedOptions[0] || {}).textContent || pk,
1691
- objective: obj || '', source: src ? src.dataset.src : '', owner: v('#ngOwner'), guardrail: $('#ngGuard').value,
1818
+ objective: obj || '', source: src ? src.dataset.src : '', owner: v('#ngOwner'), guardrail: $('#ngGuard').value, kr: $('#ngName').dataset.kr || undefined,
1692
1819
  steps: ty === 'Done or not' ? $('#ngSteps').value.split('\n').map((x) => x.trim()).filter(Boolean) : undefined,
1693
1820
  limit: ty === 'Stay within' ? (segVal('Limit') === 'Floor' ? 'floor' : 'ceiling') : undefined, unit: ty === 'Stay within' ? v('#ngUnit') : undefined, always_on: always };
1694
1821
  $('#sideGo').disabled = true; m.textContent = 'Saving.';
@@ -1931,15 +2058,23 @@ function drawList() {
1931
2058
  + found.map((f) => `<div class="ins-fcard" data-f="${esc(f.id)}"><button type="button" class="nm" data-fx="open" aria-label="${esc(f.name)}: what PMOS found"><b>${esc(f.name)}</b><small>${esc(f.why)}</small></button><button type="button" class="ins-btn sm dark" data-fx="setup">Set it up</button><button type="button" class="ins-btn sm ghost" data-fx="dismiss">Not a report</button></div>`).join('') + '</div>' : '';
1932
2059
  const empty = R.missing ? '<div class="ins-empty"><b>The report files could not be read</b>, so none is listed.</div>'
1933
2060
  : R.setupsOk === false ? '<div class="ins-empty"><b>The report setups could not be read</b>, so what you set up is not listed.</div>' : '';
1934
- const table = nRep ? `<div class="ins-rtbl" id="repAsks"><div class="ins-rl ins-rlh" role="presentation"><span>Report</span><span>Next</span><span>Status</span></div><div role="list">${reps.map(repRowHtml).join('')}${asks.map(askRowHtml).join('')}</div></div>`
1935
- : !empty ? '<div class="ins-empty"><b>No report is set up and no report file is on record.</b> Reports are made in Home.</div>' : '';
1936
- const dashBlock = `<div class="ins-sec"><h3>Dashboards</h3><span class="q">${dashes.length} · a dashboard is a saved view of Goals</span></div><div class="ins-dgrid" role="list">${dashes.map(dashCard).join('')}</div>`;
1937
- $('#repBody').innerHTML = `${oneNote()}<div id="readyLib">${RL.libHtml(LIB.L, { open: LIB.open, yours: true })}</div><div id="listRows" aria-label="Dashboards and reports">${fbox}${empty}${table}${dashBlock}</div>`;
2061
+ /* Intelligence redesign A (IgA5Reports): what needs you first (a draft waiting for your ✓, a proposal ready), then the ready-made set folded
2062
+ * (its switches stay under the fold), then Yours, then the dashboards. */
2063
+ const waits = reps.filter((G) => repState(G).draft), waitAsks = asks.filter((a) => a.state === 'ready');
2064
+ const rest = reps.filter((G) => !waits.includes(G)), restAsks = asks.filter((a) => !waitAsks.includes(a));
2065
+ const head3 = '<div class="ins-rl ins-rlh" role="presentation"><span>Report</span><span>Next</span><span>Status</span></div>';
2066
+ const needs = waits.length + waitAsks.length ? `<div class="ins-sec"><h3>Needs you</h3><span class="q">${waits.length + waitAsks.length}</span></div><div class="ins-rtbl" id="repNeeds">${head3}<div role="list">${waits.map(repRowHtml).join('')}${waitAsks.map(askRowHtml).join('')}</div></div>` : '';
2067
+ const table = rest.length + restAsks.length ? `<div class="ins-sec"><h3>Yours</h3><span class="q">${rest.length + restAsks.length}</span></div><div class="ins-rtbl" id="repAsks">${head3}<div role="list">${rest.map(repRowHtml).join('')}${restAsks.map(askRowHtml).join('')}</div></div>`
2068
+ : !empty && !needs ? '<div class="ins-empty"><b>No report is set up and no report file is on record.</b> Reports are made in Home.</div>' : '';
2069
+ const dashBlock = `<div class="ins-sec"><h3>Dashboards</h3><span class="q">${dashes.length ? dashes.length + ' · a dashboard is a saved view of Goals' : 'none saved · select rows, then Save'}</span></div><div class="ins-dgrid" role="list">${dashes.map(dashCard).join('')}</div>`;
2070
+ const lib = RL.libHtml(LIB.L, { open: LIB.open, yours: false });
2071
+ $('#repBody').innerHTML = `${oneNote()}${needs}<details class="ins-libfold"${LIB.fold ? ' open' : ''}><summary><b>Drafting on their moment</b> <span class="note">ready-made reports, nothing to set up</span></summary><div id="readyLib">${lib}</div></details><div id="listRows" aria-label="Dashboards and reports">${fbox}${empty}${table}${dashBlock}</div>`;
2072
+ const fd = $('#repBody .ins-libfold'); if (fd) fd.addEventListener('toggle', () => { LIB.fold = fd.open; });
1938
2073
  if (!LIB.read && M.board && M.board.ws) { LIB.read = true; RL.readLib(M.board.ws).then((L) => { LIB.L = L; if (!$('#libSide').hidden) drawLib(); }); }
1939
2074
  }
1940
2075
  /* the ready-made group before Yours (batch 17 item 1, PbA6): read once per open of the panel, again after a switch */
1941
- const LIB = { L: null, read: false, open: new Set() };
1942
- const drawLib = () => { const el = $('#readyLib'); if (el) el.innerHTML = RL.libHtml(LIB.L, { open: LIB.open, yours: true }); };
2076
+ const LIB = { L: null, read: false, open: new Set(), fold: false };
2077
+ const drawLib = () => { const el = $('#readyLib'); if (el) el.innerHTML = RL.libHtml(LIB.L, { open: LIB.open, yours: false }); };
1943
2078
  RL.wireLib($('#repBody'), { get ws() { return M && M.board ? M.board.ws : ''; }, open: LIB.open, toast: (t) => toast(t), redraw: drawLib,
1944
2079
  reload: async () => { LIB.L = await RL.readLib(M.board.ws); drawLib(); } });
1945
2080
  $('#repBody').addEventListener('click', async (e) => {
@@ -51,6 +51,7 @@
51
51
  "briefing-premium-board.test.js",
52
52
  "board-trail.test.js",
53
53
  "briefing-premium-insights.test.js",
54
+ "okr-goals.test.js",
54
55
  "briefing-premium-initiative.test.js",
55
56
  "briefing-premium-adddoc.test.js",
56
57
  "briefing-premium-reports.test.js",