create-pmos 0.514.0 → 0.516.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 +35 -95
- 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/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/briefing-premium-calendar-test.js +6 -3
- package/workspace/briefing-premium-insights-test.js +12 -12
- package/workspace/briefing-premium-metrics-test.js +1 -1
- package/workspace/changelog.json +34 -0
- package/workspace/fixtures/premium-viewer-fixtures.js +4 -2
- package/workspace/guide/agent-capabilities.md +73 -0
- package/workspace/guide/index.md +4 -1
- package/workspace/guide/intelligence-okr-goals.md +39 -0
- package/workspace/guide/reports-needs-you.md +34 -0
- package/workspace/lib/board-metrics-premium.js +4 -1
- package/workspace/lib/insights-goals.js +3 -1
- package/workspace/lib/insights-premium.js +53 -7
- package/workspace/lib/okr-goals.js +200 -0
- package/workspace/lib/report-acts.js +189 -0
- package/workspace/lib/routes-insights-premium.js +44 -5
- package/workspace/lib/routes-ready-reports.js +8 -1
- package/workspace/lib/routes-report-setups.js +21 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/premium-vw-meet-test.js +9 -5
- package/workspace/public/pm/cozy-insights.css +60 -1
- package/workspace/public/pm/home-app.js +3 -1
- package/workspace/public/pm/premium-insights.js +269 -36
- package/workspace/public/premium-intelligence.html +1 -1
- package/workspace/test-files.json +1 -0
- package/workspace/test-lists/reports-needs-you.json +5 -0
|
@@ -21,6 +21,8 @@ const { recordsDirFor } = require('./records-dir.js');
|
|
|
21
21
|
const reports = require('./routes-reports.js');
|
|
22
22
|
const setups = require('./routes-report-setups.js');
|
|
23
23
|
const PD = require('./premium-dashboards.js');
|
|
24
|
+
/** The PMOS repository's own workspace: the agent measures (merges, corrections) are read only there. */
|
|
25
|
+
const isSelf = (ctx, b) => { try { return !!(ctx.selfRoot && b && require('path').resolve(ctx.getWs(b.ws).root) === require('path').resolve(ctx.selfRoot)); } catch { return false; } };
|
|
24
26
|
|
|
25
27
|
/** GET `pathAndQuery` from a route module's own handler, as the Board page's route does, and return { status, body }. */
|
|
26
28
|
async function via(mod, pathAndQuery, req, ctx) {
|
|
@@ -32,19 +34,40 @@ async function via(mod, pathAndQuery, req, ctx) {
|
|
|
32
34
|
}
|
|
33
35
|
/** The opening sentence of each listed report file (insights-one-list L6), read from the workspace's repository. A file that cannot be read has none. */
|
|
34
36
|
const LEADS_MAX = 60;
|
|
35
|
-
function leadsOf(ctx, ws, list) {
|
|
37
|
+
function leadsOf(ctx, ws, list, shas) {
|
|
36
38
|
const out = {};
|
|
37
39
|
let root = null;
|
|
38
40
|
try { root = require('path').resolve(ctx.getWs(ws).root); } catch { return out; }
|
|
39
41
|
for (const r of list.slice().sort((a, b) => String(b.slot).localeCompare(String(a.slot))).slice(0, LEADS_MAX)) {
|
|
40
42
|
const rel = String(r.path || '');
|
|
41
43
|
if (!rel || rel.includes('..') || require('path').isAbsolute(rel)) continue;
|
|
42
|
-
try {
|
|
44
|
+
try {
|
|
45
|
+
const buf = require('fs').readFileSync(require('path').join(root, rel));
|
|
46
|
+
// Reports and dashboards redesign R: the headline is the preview, never the line that says the definition is a draft
|
|
47
|
+
out[rel] = require('./report-acts.js').leadPastWarning(buf.toString('utf8').slice(0, 20000), Insights.leadOf);
|
|
48
|
+
if (shas) shas[rel] = require('crypto').createHash('sha1').update(`blob ${buf.length}\0`).update(buf).digest('hex'); // what Issue sends: the file as read
|
|
49
|
+
} catch { /* an unreadable file has no lead */ }
|
|
43
50
|
}
|
|
44
51
|
return out;
|
|
45
52
|
}
|
|
46
53
|
/** The product's open drafts: the ✦ line's ✓✓ count (lib/metric-drafts.js). */
|
|
47
54
|
const openDrafts = (dir, ws) => { const MD = require('./metric-drafts.js'), S = MD.read(dir).scopes.get(MD.keyOf('product', ws)); return S ? [...S.rows.values()].filter((r) => r.state === 'open').length : 0; };
|
|
55
|
+
/** The health budget registry (D59): planning/okrs/health-budgets.md, or pmos/planning/okrs/ in a product repository (the kit layout). Without
|
|
56
|
+
* the second path every product said "not in the budget registry" although it declares one (Intelligence grill, 3 Nov). null when neither reads. */
|
|
57
|
+
function readBudgets(root) {
|
|
58
|
+
const P = require('path'), IG = require('./insights-goals.js');
|
|
59
|
+
for (const f of [P.join(root, 'planning', 'okrs', 'health-budgets.md'), P.join(root, 'pmos', 'planning', 'okrs', 'health-budgets.md')]) {
|
|
60
|
+
try { return IG.parseBudgets(require('fs').readFileSync(f, 'utf8')); } catch { /* the next place */ }
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/** Each initiative's D57 promise: { [id]: { metric, horizon, disposition } } from lib/outcomes.js scan. */
|
|
65
|
+
function promisesOf(S) {
|
|
66
|
+
const out = {};
|
|
67
|
+
for (const it of (S && S.subjects) || []) out[it.id] = { metric: it.metric || '', horizon: it.horizon || '', disposition: null };
|
|
68
|
+
for (const it of (S && S.disposed) || []) if (out[it.id]) out[it.id].disposition = it.disposition;
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
48
71
|
const qs = (o) => '?' + Object.entries(o).filter(([, v]) => v != null && v !== '').map(([k, v]) => k + '=' + encodeURIComponent(v)).join('&');
|
|
49
72
|
|
|
50
73
|
module.exports = {
|
|
@@ -78,7 +101,11 @@ module.exports = {
|
|
|
78
101
|
const su = await via(setups, '/api/report-setups' + q, req, ctx).catch(() => ({ status: 500, body: null }));
|
|
79
102
|
const setupsOk = su.status === 200 && !!su.body && Array.isArray(su.body.setups);
|
|
80
103
|
if (!setupsOk) missing.push('report setups');
|
|
81
|
-
const
|
|
104
|
+
const shas = {};
|
|
105
|
+
const leads = reportsOk ? leadsOf(ctx, ws, rp.body.reports || [], shas) : {};
|
|
106
|
+
// Reports and dashboards redesign R: each report's definition (its title names it; status: draft is "Confirm the definition")
|
|
107
|
+
let defs = [];
|
|
108
|
+
if (pick) { try { defs = require('./report-setups.js').definitionsOf(require('path').resolve(ctx.getWs(pick.ws).root)).map((d) => ({ id: d.id, title: d.title, status: d.status })); } catch { defs = []; } }
|
|
82
109
|
// premium-agreed-design (G1, G2): the board's initiatives, so a goal can name what moves it and Outcomes can list shipped work. Read-only.
|
|
83
110
|
let initiatives = null;
|
|
84
111
|
if (pick && pick.id) {
|
|
@@ -91,7 +118,14 @@ module.exports = {
|
|
|
91
118
|
if (pick) {
|
|
92
119
|
const root = require('path').resolve(ctx.getWs(pick.ws).root);
|
|
93
120
|
try { ins = require('./insights-records.js').read(root); } catch { missing.push('insights records'); }
|
|
94
|
-
|
|
121
|
+
budgets = readBudgets(root);
|
|
122
|
+
}
|
|
123
|
+
// Intelligence redesign A: the OKR file is the goal list (lib/okr-goals.js), and each initiative's promise is its D57 outcome (lib/outcomes.js scan)
|
|
124
|
+
let okr = null, promises = null;
|
|
125
|
+
if (pick) {
|
|
126
|
+
const root = require('path').resolve(ctx.getWs(pick.ws).root);
|
|
127
|
+
try { okr = require('./okr-goals.js').read(root); } catch { okr = null; }
|
|
128
|
+
try { promises = promisesOf(require('./outcomes.js').scan(root)); } catch { promises = null; }
|
|
95
129
|
}
|
|
96
130
|
// premium-uncovered-features (IsMenus): the product picker says how many goals each product's workspace records; a workspace that cannot be read says none
|
|
97
131
|
const counts = new Map();
|
|
@@ -110,8 +144,13 @@ module.exports = {
|
|
|
110
144
|
watch,
|
|
111
145
|
now: Date.now(), board: pick ? { ws: pick.ws, id: pick.id, name: pick.name } : { ws, id: null, name: 'Intelligence' }, boards: all.map((x) => Object.assign({ ws: x.ws, id: x.id, name: x.name }, goalsIn(x.ws))),
|
|
112
146
|
outcomes, reports: reportsOk ? rp.body.reports || [] : [], reportsOk, missing, saved, mine,
|
|
113
|
-
setups: setupsOk ? su.body : null, pinned, leads, archived, initiatives, goalMeta, ins, budgets,
|
|
147
|
+
setups: setupsOk ? su.body : null, pinned, leads, archived, initiatives, goalMeta, ins, budgets, okr, promises, self: isSelf(ctx, pick),
|
|
114
148
|
});
|
|
149
|
+
// Reports and dashboards redesign R (RgR1Panel): each waiting report's one ✓, its display name, and the other product it is about (lib/report-acts.js)
|
|
150
|
+
if (model && model.reports) {
|
|
151
|
+
const R = require('./report-acts.js').reportActsOf({ rows: model.reports.rows || [], setups: model.reports.setups || [], defs, boards: (model.board && model.board.switcher) || [], here: model.board ? model.board.ws : null, shas });
|
|
152
|
+
Object.assign(model.reports, { acts: R.acts, names: R.names, about: R.about });
|
|
153
|
+
}
|
|
115
154
|
// batch 16 item 1: each ✦ strip finding about a goal that is behind carries one drafted move (lib/insight-moves.js), done ones marked
|
|
116
155
|
if (pick) {
|
|
117
156
|
try {
|
|
@@ -225,7 +225,14 @@ async function dashboardAnswer(req, ctx, w, scope, from) {
|
|
|
225
225
|
}
|
|
226
226
|
if (!X.model) X.f.goalDraft = null;
|
|
227
227
|
else if (!RR.goalsOf(X.f.intel, scope, Date.parse(X.f.today + 'T00:00:00Z'), false).goals.length) X.f.goalDraft = { title: '', sub: '', anchor: scope };
|
|
228
|
-
|
|
228
|
+
const d = RR.dashboardOf(X.f);
|
|
229
|
+
// Reports and dashboards redesign R (RgR4Make): a product page's ✦ line is Intelligence's line for that product (lib/report-acts.js). A held
|
|
230
|
+
// source keeps its own warning; "ready to send" follows only in a week with news (something behind, a ticket closed, an update drafted)
|
|
231
|
+
if (d && d.level === 'product' && !d.heldBy) {
|
|
232
|
+
const R = d.reports || [], news = (d.goals || []).some((g) => g.dot === 'behind') || !!d.done || R.some((r) => r.kind === 'weekly' && r.state === 'drafted' && !r.desk);
|
|
233
|
+
d.handled = require('./report-acts.js').productLineOfIntel(X.f.intel, { deskReady: R.some((r) => r.kind === 'weekly' && r.state === 'drafted' && r.desk), news, fallback: d.handled });
|
|
234
|
+
}
|
|
235
|
+
return { status: 200, body: { ok: true, dashboard: d } };
|
|
229
236
|
}
|
|
230
237
|
|
|
231
238
|
/** The scopes of a workspace: its product, its programs (the ones whose charter is here) and its live initiatives. */
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
* POST /api/report-setups/pause | resume { ws, id } → { ok, setup }
|
|
18
18
|
* POST /api/report-setups/edit { ws, id, schedule?, name?, audience?, output? } → { ok, setup } change a TEMPLATE (premium-reports-from-chat L1).
|
|
19
19
|
* schedule { cadence: 'off' } pauses it; any other schedule changes it and resumes it
|
|
20
|
+
* POST /api/report-setups/confirm { ws, id } → { ok, path, sha } ✓ Confirm the definition: status: draft → stable (redesign R)
|
|
21
|
+
* POST /api/report-setups/move { ws, id, to } → { ok, id, to, moved } ✦ Move to <product>: definition, drafts and setup go to
|
|
22
|
+
* the workspace `to`; a report with an issued issue is 409 (redesign R)
|
|
20
23
|
* POST /api/report-setups/remove { ws, id } → { ok } delete a TEMPLATE: its schedule stops; its drafts and issued reports stay (L2)
|
|
21
24
|
* POST /api/report-setups/from-dashboard { ws, key, name, output, schedule, audience } → { ok, setup }
|
|
22
25
|
* POST /api/report-setups/finder { ws, cadence?, sources? } → { ok, finder }
|
|
@@ -62,7 +65,7 @@ const headless = require('./headless.js');
|
|
|
62
65
|
|
|
63
66
|
const text = (v) => (typeof v === 'string' ? v : '');
|
|
64
67
|
const bad = (status, error) => ({ status, body: { ok: false, error } });
|
|
65
|
-
const POSTS = ['ask', 'change', 'cancel', 'setup', 'draft', 'dismiss', 'pause', 'resume', 'from-dashboard', 'finder', 'edit', 'remove', 'documents/discard', 'documents/archive', 'strip-dismiss'];
|
|
68
|
+
const POSTS = ['ask', 'change', 'cancel', 'setup', 'draft', 'dismiss', 'pause', 'resume', 'from-dashboard', 'finder', 'edit', 'remove', 'documents/discard', 'documents/archive', 'strip-dismiss', 'confirm', 'move'];
|
|
66
69
|
const IR = require('./intel-report.js');
|
|
67
70
|
const MADE_BY = ['schedule', 'you', 'agent'];
|
|
68
71
|
|
|
@@ -508,6 +511,23 @@ async function handle(req, res, url, ctx) {
|
|
|
508
511
|
const now = new Date();
|
|
509
512
|
return send({ status: 200, body: { ok: true, setup: setupOut(RS.fold(RS.readLines(file)).setups.get(id), now, documentsOf(ctx, w), copyIndex(root), root) } });
|
|
510
513
|
}
|
|
514
|
+
if (sub === 'confirm') {
|
|
515
|
+
// Reports and dashboards redesign R: ✓ Confirm the definition (status: draft → stable). Only the PM's press confirms one (report-daily.skill)
|
|
516
|
+
const r = require('./report-acts.js').confirmDefinition(root, text(body.id), opts);
|
|
517
|
+
if (!r.ok) return fail(r);
|
|
518
|
+
if (ctx.invalidateNeedsAgg) ctx.invalidateNeedsAgg();
|
|
519
|
+
return send({ status: 200, body: r });
|
|
520
|
+
}
|
|
521
|
+
if (sub === 'move') {
|
|
522
|
+
// Reports and dashboards redesign R: ✦ Move to <product>. The target is a workspace this server knows; an issued report is never moved (409)
|
|
523
|
+
const t = wsOf(ctx, text(body.to));
|
|
524
|
+
if (!t || !text(body.to)) return send(bad(404, 'no such product to move it to'));
|
|
525
|
+
if (t.id === w.id) return send(bad(400, 'it is in that product already'));
|
|
526
|
+
const r = require('./report-acts.js').moveReportTo(root, text(body.id), t.root, Object.assign({}, opts, { toWsId: t.id }));
|
|
527
|
+
if (!r.ok) return fail(r);
|
|
528
|
+
if (ctx.invalidateNeedsAgg) ctx.invalidateNeedsAgg();
|
|
529
|
+
return send({ status: 200, body: Object.assign({ to: t.id }, r) });
|
|
530
|
+
}
|
|
511
531
|
if (sub === 'remove') {
|
|
512
532
|
const r = RS.remove(root, text(body.id), opts);
|
|
513
533
|
if (!r.ok) return fail(r);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.516.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.516.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@anthropic-ai/sdk": "^1.3.0",
|
package/workspace/package.json
CHANGED
|
@@ -39,6 +39,10 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
39
39
|
srv = spawn(process.execPath, [path.join(__dirname, 'server.js')], { cwd: __dirname, stdio: 'ignore', env: T.env({ PMOS_WS_PORT: String(T.PORT) }) });
|
|
40
40
|
await T.waitForServer(BASE);
|
|
41
41
|
browser = await chromium.launch({ executablePath: exe });
|
|
42
|
+
/* One clock for the page and the stand-ins: today at 11:00, so "Rail review" (30 minutes on) is today at any hour the suite runs.
|
|
43
|
+
* Unpinned, after 23:30 it fell on tomorrow and S6 lost its Record. */
|
|
44
|
+
const NOW = new Date(); NOW.setHours(11, 0, 0, 0);
|
|
45
|
+
const open = (u, o) => F.open(browser, BASE, u, { now: NOW, ...o });
|
|
42
46
|
const body = (p) => p.evaluate(() => (document.querySelector('#czViewer .cz-vw-body') || {}).innerText || '');
|
|
43
47
|
const press = (p, sel) => p.$eval('#czViewer ' + sel, (b) => b.click());
|
|
44
48
|
const folds = (p) => p.evaluate(() => [...document.querySelectorAll('#czViewer [data-czs-fold]')].map((x) => x.dataset.czsFold + (x.getAttribute('aria-expanded') === 'true' ? '*' : '')).join());
|
|
@@ -46,7 +50,7 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
46
50
|
const sum = (p) => p.evaluate(() => { const s = [...document.querySelectorAll('#czViewer .czs-sum')].find((x) => x.offsetParent) || document.querySelector('#czViewer .czs-sum'); return s ? { line: (s.querySelector('.czs-line') || {}).innerText || '', text: (s.querySelector('.czs-p') || {}).innerText || '', meta: (s.querySelector('.czs-meta') || {}).innerText || '', pri: (s.querySelector('[data-czs-act]') || {}).textContent || '' } : null; });
|
|
47
51
|
|
|
48
52
|
/* an ended meeting with notes and a recording */
|
|
49
|
-
const A = await
|
|
53
|
+
const A = await open('/premium-viewer.html?open=meeting¬e=n1');
|
|
50
54
|
const p = A.page;
|
|
51
55
|
const up = await until(() => p.evaluate(() => !!document.querySelector('#czViewer .vz-mtg[data-mk="notes"]') && !!document.querySelector('#czViewer .czs-sum')), 15000);
|
|
52
56
|
ok('0 the meeting opens in the Viewer on its own page', up);
|
|
@@ -128,7 +132,7 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
128
132
|
await A.ctx.close();
|
|
129
133
|
|
|
130
134
|
/* S6 Before: a meeting to come, no note */
|
|
131
|
-
const B = await
|
|
135
|
+
const B = await open('/premium-viewer.html?open=meeting&event=ev-rail&date=' + F.dayKey(F.world(NOW).RAIL), { scene: 'prep' });
|
|
132
136
|
const q = B.page;
|
|
133
137
|
const prepUp = await until(() => q.evaluate(() => !!document.querySelector('#czViewer .vz-mtg[data-mk="prep"]') && !!document.querySelector('#czViewer .czs-sum')), 15000);
|
|
134
138
|
const s6 = await sum(q);
|
|
@@ -169,7 +173,7 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
169
173
|
await B.ctx.close();
|
|
170
174
|
|
|
171
175
|
/* S7 During: a note that records */
|
|
172
|
-
const C = await
|
|
176
|
+
const C = await open('/premium-viewer.html?open=meeting¬e=n2', { init: () => { window.__premiumHolding = { id: 'n2' }; } });
|
|
173
177
|
const r = C.page;
|
|
174
178
|
const liveUp = await until(() => r.evaluate(() => !!document.querySelector('#czViewer .vz-mtg[data-mk="live"]') && !!document.querySelector('#czViewer .czs-sum')), 15000);
|
|
175
179
|
const s7 = await sum(r);
|
|
@@ -196,7 +200,7 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
196
200
|
await C.ctx.close();
|
|
197
201
|
|
|
198
202
|
/* S10 a note that is not there */
|
|
199
|
-
const D = await
|
|
203
|
+
const D = await open('/premium-viewer.html?open=meeting¬e=nope');
|
|
200
204
|
ok('S10 a note that is not there says so, and the card still opens', await until(() => D.page.evaluate(() => !!document.querySelector('#czViewer') && /Untitled note|could not|not/.test(document.querySelector('#czViewer').innerText))));
|
|
201
205
|
await D.ctx.close();
|
|
202
206
|
|
|
@@ -204,7 +208,7 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
204
208
|
// the meeting's own presses: the summary's (the shell's primary button is the shell's) and the folds; a press may keep its drawn size and take a 44 px hit area from ::before
|
|
205
209
|
const HITS = () => { window.hits = () => [...document.querySelectorAll('#czViewer .czs-sum button:not(.czs-pri), #czViewer [data-czs-fold]')].filter((b) => { if (!b.getClientRects().length) return false; const pb = getComputedStyle(b, '::before'), hit = pb.content !== 'none' && pb.position === 'absolute' ? parseFloat(pb.height) || 0 : 0; return Math.max(b.getBoundingClientRect().height, hit) < 44; }).map((b) => (b.dataset.a || b.dataset.czsFold || b.className) + ':' + Math.round(b.getBoundingClientRect().height)); };
|
|
206
210
|
for (const [u, o] of [['/premium-viewer.html?open=meeting¬e=n1', {}], ['/premium-viewer.html?open=meeting¬e=n2', { init: () => { window.__premiumHolding = { id: 'n2' }; } }]]) {
|
|
207
|
-
const E = await
|
|
211
|
+
const E = await open(u, { w: 390, h: 844, ...o });
|
|
208
212
|
await E.page.evaluate(HITS);
|
|
209
213
|
await until(() => E.page.evaluate(() => !!document.querySelector('#czViewer .vz-mtg') && !!document.querySelector('#czViewer .czs-sum')), 15000);
|
|
210
214
|
const fl = await until(async () => { const v = await E.page.evaluate(() => ({ over: document.documentElement.scrollWidth - innerWidth, full: (() => { const r = document.querySelector('#czViewer').getBoundingClientRect(); return r.left === 0 && Math.round(r.width) === innerWidth; })(),
|
|
@@ -75,7 +75,7 @@ body.cz { padding: 0; }
|
|
|
75
75
|
.ins-tbl { display: flex; flex-direction: column; border: 1px solid var(--cz-hair); border-radius: 18px; overflow: hidden; background: var(--cz-card); }
|
|
76
76
|
.ins-tr { display: grid; align-items: center; column-gap: 16px; padding: 0 16px; }
|
|
77
77
|
.ins-cols { grid-template-columns: minmax(210px, 270px) 190px 230px 170px minmax(0, 1fr); }
|
|
78
|
-
.ins-ocols { grid-template-columns: minmax(200px,
|
|
78
|
+
.ins-ocols { grid-template-columns: minmax(200px, 250px) minmax(0, 1.3fr) minmax(0, .7fr) minmax(0, 1fr) minmax(0, 1.2fr) 96px; } /* Promised · Check · Got · Verdict (IgA3Outcomes) */
|
|
79
79
|
.ins-dcols { grid-template-columns: minmax(200px, 270px) 110px 150px minmax(0, 1fr) 150px 112px; }
|
|
80
80
|
.ins-lcols { grid-template-columns: 40px minmax(0, 1fr) 170px 200px 190px 90px; }
|
|
81
81
|
.ins-th { background: var(--cz-well); font-size: var(--fs-label); font-weight: var(--fw-strong); color: var(--cz-ink2); min-height: 36px; line-height: var(--lh-label); }
|
|
@@ -665,6 +665,7 @@ body.ins-trv #goalTbl { border: 0; background: none; border-radius: 0; overflow:
|
|
|
665
665
|
/* Reports and dashboards (⋯): the old Library in a side panel */
|
|
666
666
|
.ins-lib { width: min(560px, 100%); }
|
|
667
667
|
.ins-lib .ins-sb { display: flex; flex-direction: column; gap: 12px; }
|
|
668
|
+
.ins-lib .ins-sb > * { flex-shrink: 0; } /* the panel scrolls; a table never shrinks under the block after it (Needs you) */
|
|
668
669
|
.ins-lib .ins-dgrid { grid-template-columns: minmax(0, 1fr); }
|
|
669
670
|
/* motion (design principle 12 as amended for Intelligence): a soft rise as each section arrives, once; nothing loops; reduced motion turns it off */
|
|
670
671
|
.ins > .ins-strip, .ins > .ins-ns, .ins > .ins-dr, .ins > .ins-pane { animation: ins-rise .45s cubic-bezier(.2, .8, .2, 1) both; }
|
|
@@ -930,3 +931,61 @@ body.ins-trv #pGoals .ins-trstrip { display: flex; } /* the narrow form hides
|
|
|
930
931
|
.ins-mrow b { margin-left: auto; }
|
|
931
932
|
.ins-mrow .cz-pill { margin-left: auto; }
|
|
932
933
|
.ins-trstrip .t { display: block; -webkit-line-clamp: unset; overflow: visible; }
|
|
934
|
+
|
|
935
|
+
/* ── Intelligence redesign A: the OKR file is the goal list (IgA1Open, IgA2Goal, IgA3Outcomes, IgA7Phone) ── */
|
|
936
|
+
.ins-krt { font-size: var(--fs-badge); font-weight: var(--fw-regular); color: var(--cz-ink3); margin-right: 6px; line-height: var(--lh-badge); }
|
|
937
|
+
.ins-need { display: inline-flex; align-items: center; gap: 6px; }
|
|
938
|
+
.ins-chk { display: inline-flex; align-items: center; gap: 6px; }
|
|
939
|
+
.ins-krcon { display: flex; align-items: center; gap: 10px; background: var(--cz-bluebg); border-radius: 12px; padding: 8px 10px 8px 12px; margin: 10px 0; font-size: var(--fs-meta); line-height: var(--lh-meta); color: var(--cz-ink2); }
|
|
940
|
+
.ins-krcon .sp { color: var(--cz-blueink); display: inline-flex; }
|
|
941
|
+
.ins-krcon .sp svg { width: 14px; height: 14px; }
|
|
942
|
+
.ins-krcon .cz-ib { margin-left: auto; }
|
|
943
|
+
.ins-cfold { display: block; border: 1px solid var(--cz-hair); border-radius: 18px; background: var(--cz-card); padding: 11px 14px; }
|
|
944
|
+
.ins-cfold > summary { cursor: pointer; font-size: var(--fs-row); line-height: var(--lh-row); }
|
|
945
|
+
.ins-cfold[open] > summary { margin-bottom: 8px; }
|
|
946
|
+
.ins-cfold .ins-card { margin-top: 8px; }
|
|
947
|
+
.ins-libfold { display: block; border: 1px solid var(--cz-hair); border-radius: 16px; padding: 10px 14px; margin: 8px 0; }
|
|
948
|
+
.ins-libfold > summary { cursor: pointer; font-size: var(--fs-row); line-height: var(--lh-row); }
|
|
949
|
+
.ins-libfold[open] > summary { margin-bottom: 8px; }
|
|
950
|
+
.ins-pcols { grid-template-columns: minmax(160px, 220px) minmax(0, 1.2fr) 130px 70px 110px minmax(0, 1.6fr); }
|
|
951
|
+
.ins-allp { margin-bottom: 14px; }
|
|
952
|
+
.ins-allp .ins-row { cursor: pointer; }
|
|
953
|
+
.ins-pav { width: 26px; height: 26px; border-radius: 50%; background: var(--cz-well2); color: var(--cz-ink2); font-size: var(--fs-badge); font-weight: var(--fw-strong); display: inline-flex; align-items: center; justify-content: center; flex: none; }
|
|
954
|
+
@media (max-width: 720px) {
|
|
955
|
+
.ins-row.ins-pcols { grid-template-columns: minmax(0, 1fr); row-gap: 6px; border: 1px solid var(--cz-hair); border-radius: 18px; background: var(--cz-card); padding: 12px 14px; }
|
|
956
|
+
.ins-allp .ins-th { display: none; }
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/* Reports and dashboards redesign R (RgR1Panel, RgR3Dash): Needs you rows with one ✓ each, the two ready-made folds, the drafted first dashboard */
|
|
960
|
+
.ins-nlist { flex: none; display: flex; flex-direction: column; border: 1px solid var(--cz-hair); border-radius: 16px; overflow: hidden; margin: 4px 0 8px; }
|
|
961
|
+
.ins-nrow { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-top: 1px solid var(--cz-hair); }
|
|
962
|
+
.ins-nrow:first-child { border-top: 0; }
|
|
963
|
+
.ins-nrow.sel { background: var(--cz-bluebg); }
|
|
964
|
+
.ins-nrow .nm { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; border: 0; background: none; padding: 0; font: inherit; color: inherit; text-align: left; cursor: pointer; }
|
|
965
|
+
.ins-nrow .nm b { font-size: var(--fs-body); font-weight: var(--fw-strong); line-height: var(--lh-body); }
|
|
966
|
+
.ins-nrow .nm small { font-size: var(--fs-meta); color: var(--cz-ink3); line-height: var(--lh-meta); }
|
|
967
|
+
.ins-nrow .hl { font-size: var(--fs-meta); color: var(--cz-ink2); line-height: var(--lh-meta); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
968
|
+
.ins-nrow .ac { flex: none; display: flex; align-items: center; gap: 8px; }
|
|
969
|
+
.ins-nrow .ac .note { white-space: nowrap; }
|
|
970
|
+
@media (hover: hover) { .ins-nrow:not(.sel):hover { background: rgba(244, 243, 240, .5); } }
|
|
971
|
+
.ins-mlist { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-meta); line-height: var(--lh-meta); }
|
|
972
|
+
.ins-libfold[open] .ins-mlist { display: none; }
|
|
973
|
+
.ins-libfold:not([open]) .ins-libin { display: none; }
|
|
974
|
+
.ins-ddraft { grid-column: 1 / -1; cursor: default; }
|
|
975
|
+
.ins-dtiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
976
|
+
.ins-dtile { position: relative; display: flex; flex-direction: column; gap: 2px; border: 1px solid var(--cz-hair); border-radius: 14px; padding: 10px 12px; min-width: 0; }
|
|
977
|
+
.ins-dtile.w { border-style: dashed; border-color: var(--cz-hair2, rgba(42,46,53,.14)); }
|
|
978
|
+
.ins-dtile .lb { font-size: var(--fs-meta); color: var(--cz-ink2); line-height: var(--lh-meta); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
979
|
+
.ins-dtile .kr { color: var(--cz-ink3); margin-right: 6px; }
|
|
980
|
+
.ins-dtile .big { font-size: var(--fs-panel); font-weight: var(--fw-strong); line-height: var(--lh-panel, 1.3); }
|
|
981
|
+
.ins-dtile .ft { display: flex; align-items: center; gap: 6px; justify-content: space-between; }
|
|
982
|
+
.ins-dtile .ft .cz-ib { flex: none; }
|
|
983
|
+
.ins-tdot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; background: var(--cz-dot-q); }
|
|
984
|
+
.ins-tdot.ok { background: var(--cz-dot-ok); } .ins-tdot.risk { background: var(--cz-dot-risk); } .ins-tdot.late { background: var(--cz-dot-late); }
|
|
985
|
+
.ins-dact { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
|
|
986
|
+
@media (max-width: 760px) {
|
|
987
|
+
.ins-nlist { border: 0; gap: 8px; overflow: visible; }
|
|
988
|
+
.ins-nrow { border: 1px solid var(--cz-hair); border-radius: 16px; background: var(--cz-card); }
|
|
989
|
+
.ins-nrow:first-child { border-top: 1px solid var(--cz-hair); }
|
|
990
|
+
.ins-nrow .ac .note { display: none; }
|
|
991
|
+
}
|
|
@@ -986,7 +986,9 @@ function productPageHtml(x, ws, vid, name, g) {
|
|
|
986
986
|
const tRow = (t) => row(`data-hp="ini" data-k="${esc(t.ini)}"`, '', t.name, t.of, t.late && t.due ? `<span class="pv-late">${esc(lateDays(t.due) + ' days late')}</span>` : '');
|
|
987
987
|
const U = own ? H.weeklyUpdate(S.D, S.D.now) : null, sent = U && (U.sent || []).some((y) => y.week === U.week.iso);
|
|
988
988
|
const R = d ? DB.reportsHtml(d, { open: x.open }) : '';
|
|
989
|
-
|
|
989
|
+
// redesign R (RgR4Make): "Ready to send" only in a week with news (something at risk or waiting for a decision); a quiet week says so
|
|
990
|
+
const news = U && U.sections.some(([k, , L]) => k !== 'ok' && L.length);
|
|
991
|
+
const extra = own ? (R ? '' : row('data-hp="update"', '', 'Weekly update', sent ? 'Sent this week' : news ? 'Ready to send' : 'Nothing new this week · skipped'))
|
|
990
992
|
: `<a class="pv-row" href="${esc(ownHome)}" target="_blank" rel="noopener"><span class="pv-rt"><b>Weekly update</b><small>In ${esc(name)}'s own Home, in a new tab</small></span></a>`;
|
|
991
993
|
const reps = ((d && d.reports) || []).filter((r) => r.kind !== 'heads-up');
|
|
992
994
|
const tabs = [
|