create-pmos 0.179.0 → 0.180.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.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +1 -1
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/state/discoveries.md +1 -1
  6. package/workspace/c3-report-setup-test.js +190 -0
  7. package/workspace/c3-reports-test.js +30 -97
  8. package/workspace/guide/document-sheet.md +14 -0
  9. package/workspace/guide/index.md +3 -2
  10. package/workspace/guide/reports-dashboards.md +17 -28
  11. package/workspace/guide/reports-page.md +10 -5
  12. package/workspace/guide/reports-set-up.md +56 -0
  13. package/workspace/guide/reports.md +13 -1
  14. package/workspace/guide/settings.md +11 -2
  15. package/workspace/lib/doc-sync.js +28 -6
  16. package/workspace/lib/docs-cli.js +7 -11
  17. package/workspace/lib/four-panel.js +1 -1
  18. package/workspace/lib/headless.js +21 -2
  19. package/workspace/lib/report-checks.js +103 -0
  20. package/workspace/lib/report-cli.js +89 -4
  21. package/workspace/lib/report-export.js +347 -0
  22. package/workspace/lib/report-finder.js +156 -0
  23. package/workspace/lib/report-needs.js +1 -1
  24. package/workspace/lib/report-setups.js +743 -0
  25. package/workspace/lib/route-modules.js +1 -0
  26. package/workspace/lib/routes-c3-reports.js +35 -17
  27. package/workspace/lib/routes-doc-health.js +15 -1
  28. package/workspace/lib/routes-report-setups.js +368 -0
  29. package/workspace/lib/schedule-logic.js +5 -2
  30. package/workspace/package-lock.json +2 -2
  31. package/workspace/package.json +1 -1
  32. package/workspace/public/pm/c3-demo-rep2.js +15 -0
  33. package/workspace/public/pm/c3-demo-rs.js +281 -0
  34. package/workspace/public/pm/c3-demo.js +1 -1
  35. package/workspace/public/pm/c3-doc-formats.js +152 -0
  36. package/workspace/public/pm/c3-doc-sheet.js +115 -25
  37. package/workspace/public/pm/c3-quiet.js +2 -2
  38. package/workspace/public/pm/c3-report-print.css +20 -0
  39. package/workspace/public/pm/c3-report-setup.css +62 -0
  40. package/workspace/public/pm/c3-report-setup.js +260 -0
  41. package/workspace/public/pm/c3-report-store.js +208 -0
  42. package/workspace/public/pm/c3-reports.css +4 -0
  43. package/workspace/public/pm/c3-reports.js +47 -181
  44. package/workspace/public/pm/c3-settings.js +4 -3
  45. package/workspace/public/pm/c3-shell.js +15 -31
  46. package/workspace/server.js +66 -5
  47. package/workspace/skills/doc-sync.skill +17 -8
  48. package/workspace/skills/report-find.skill +75 -0
  49. package/workspace/skills/report-setup.skill +67 -0
  50. package/workspace/test-files.json +10 -0
  51. package/workspace/test-lists/report-workspace-ui.json +5 -0
@@ -2,7 +2,7 @@
2
2
  * Flow reference: the restructure mockup's 33-reports.js (dashboards with scope buttons, edit mode, library, share sheet). Not a pixel spec.
3
3
  *
4
4
  * mountReports(host, ctx) -> { dispose() }
5
- * ctx = { ws, pmGet?, deck?(hostEl), more?(hostEl), drafts?(hostEl), goInit?(id, title) }
5
+ * ctx = { ws, pmGet?, deck?(hostEl), goInit?(id, title) }
6
6
  * ws the workspace id; every read names it
7
7
  * pmGet the shell's GET helper (default: http.js pmGet)
8
8
  * deck mounts the page's headline deck into hostEl, above the tab bar (the shell mounts its Plan deck for the Reports scope).
@@ -11,21 +11,12 @@
11
11
  * first time the Drafts tab opens. Without it the Drafts tab says the drafts are not available on this page.
12
12
  * goInit opens an initiative from a row inside a report (a bet, a commitment). Default: the Initiative dashboard of it.
13
13
  *
14
- * Three tabs: Dashboards | Library | Drafts.
15
- * Dashboards scope buttons Portfolio | Board | Initiative, a chooser for the board or the initiative, then one widget per report of the
16
- * layout (or the scope's default). A widget is the Calendar's own builder (c3-cal-strategy.js PANEL_BUILDERS), drawn from
17
- * GET /api/report-dashboards/data?scope=&id=&ws= (kept a minute per scope). A widget is a reference (data-ref-row); its
18
- * "About this report" line opens the report's case in place and links to its home in the library.
19
- * Edit: up and down arrows, Remove (with Undo), Add from the report library, Reset to default. Every change POSTs
20
- * /api/report-dashboards/layout { key, reports } (reports: null resets). A failed POST is toasted; the page keeps the change.
21
- * Share: a sheet (c3-sheet.js) with the report frame (Headline, Sections, Needs your call, Sources) for Leadership or Team.
22
- * "Make a draft now" queues ONE draft through POST /api/outbox/draft. Nothing is sent. Schedule: Not scheduled, weekly or monthly,
23
- * for the sheet's audience, saved by POST /api/report-dashboards/schedule { key, plan, audience, ws }. A run makes a ready draft
24
- * and a notice on the bell (lib/report-schedules.js); nothing is sent.
25
- * Library one row per report (data-home): the title, the question, the source and where it is used. The row opens the card in place.
26
- * Drafts the dashboard drafts the Share sheet made (GET /api/outbox, subject "<dashboard> for <audience>"; Preview, Discard with Undo through
27
- * POST /api/outbox/discard; "Made by schedule" or "Made by you"), Scheduled (each schedule and its next draft), ctx.more(host), then "Reports to send": ctx.drafts(host), the old
28
- * report families, their claims check and the outbox queue.
14
+ * Three tabs, one rule each: Dashboards (live) | Reports (sent) | Charts (building blocks). Old ?tab=lib and ?tab=drafts land on Charts and Reports.
15
+ * Dashboards scope buttons Portfolio | Board | Initiative, a chooser, then one widget per chart of the layout (or the scope's default). A widget is the
16
+ * Calendar's own builder, drawn from GET /api/report-dashboards/data. Edit: arrows, Remove (with Undo), Add from Charts, Reset.
17
+ * "Send this as a report..." opens the set-up sheet (c3-report-setup.js openSendSheet). Nothing is sent.
18
+ * Reports c3-report-setup.js: Ask PMOS, Found in your work, Set up, Documents.
19
+ * Charts one row per chart (data-home): title, question, source, and the dashboards that use it. The row opens the card in place.
29
20
  * DATA: GET /api/report-dashboards?ws= -> { library, layouts, defaults, boards, initiatives, schedules }. In demo mode c3-demo-rep3.js answers both reads
30
21
  * and the layout and schedule POSTs. Nothing here invents a number: a report with no rows in the scope draws its builder's own empty card. */
31
22
  import { el } from './dom.js';
@@ -36,10 +27,13 @@ import { PANEL_BUILDERS, todayStr } from './c3-cal-strategy.js';
36
27
  import { makeXp, attachExpand } from './c3-expand.js';
37
28
  import { openSheet } from './c3-sheet.js';
38
29
  import { toast } from './c3-toast.js';
30
+ import { mountReportSetup, openSendSheet } from './c3-report-setup.js';
31
+ import { RS, loadReports, onReports, draftsWaiting } from './c3-report-store.js';
39
32
  import '/lib/report-frame.js'; // sets self.ReportFrame (lib/report-frame.js, shared with the scheduled run)
40
33
 
41
34
  const SCOPES = [['portfolio', 'Portfolio'], ['board', 'Board'], ['initiative', 'Initiative']];
42
- const TABS = [['dash', 'Dashboards', 'report'], ['lib', 'Library', 'doc'], ['drafts', 'Drafts', 'mail']];
35
+ const TABS = [['dash', 'Dashboards', 'report'], ['reports', 'Reports', 'doc'], ['charts', 'Charts', 'cols']];
36
+ const TAB_OF = { lib: 'charts', library: 'charts', drafts: 'reports', rs: 'reports' }; // the old tab names keep working
43
37
  const ICON = { north: 'target', outcomes: 'target', signals: 'alert', delivery: 'clock', maturity: 'flag', bets: 'flag', synergy: 'init' };
44
38
  const TTL = 60000;
45
39
 
@@ -115,39 +109,24 @@ function trimHead(card) {
115
109
  if (line) h.replaceChildren(line); else h.remove();
116
110
  return card;
117
111
  }
118
- const DAY = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
119
- const dateOf = (iso) => { const d = new Date(iso); return isNaN(d) ? '' : DAY[d.getUTCDay()] + ' ' + d.getUTCDate() + ' ' + MON[d.getUTCMonth()]; };
120
- /* A dashboard draft is an outbox entry the Share sheet made: its subject is "<dashboard> for <audience>". */
121
- const DRAFT_RE = /^(.*) for (Leadership|Team)$/;
122
- /* A schedule per dashboard (lib/report-schedules.js PLANS): it makes a ready draft and a notice on the bell. Nothing is sent. */
123
- const SCHEDULES = [['none', 'Not scheduled'], ['weekly', 'Weekly, Mondays 08:00'], ['monthly', 'Monthly, 1st at 08:00']];
124
- const SCHED_TXT = Object.fromEntries(SCHEDULES);
125
- /* The next slot, host-local, as lib/schedule-logic.js nextSlot reads PLANS: Mondays 08:00, or the 1st at 08:00. */
126
- export function nextDraftOf(plan, now = new Date()) {
127
- const d = new Date(now); d.setHours(8, 0, 0, 0);
128
- if (plan === 'weekly') { for (let i = 0; i < 8; i++) { if (d.getDay() === 1 && d > now) return d; d.setDate(d.getDate() + 1); } return null; }
129
- if (plan === 'monthly') { d.setDate(1); if (d <= now) d.setMonth(d.getMonth() + 1); return d; }
130
- return null;
131
- }
132
- const localDate = (d) => (d ? DAY[d.getDay()] + ' ' + d.getDate() + ' ' + MON[d.getMonth()] + ', 08:00' : '');
133
-
134
- /* The live page, so the shell can open one of its tabs (a bell notice opens Drafts). A request before the page mounts waits for it. */
112
+ /* The live page, so the shell can open one of its tabs. A request before the page mounts waits for it. Old names (lib, drafts) map to the new tabs. */
135
113
  let livePage = null; let wantView = null;
136
- export function showReportsView(view) { if (livePage) livePage.showTab(view); else wantView = view; }
114
+ export function showReportsView(view) { const v = TAB_OF[view] || view; if (livePage) livePage.showTab(v); else wantView = v; }
137
115
 
138
116
  export function mountReports(host, ctx) {
139
117
  ensureC3Css(); ensureComponentsCss(); ensureReportsCss();
140
118
  const C = ctx || {};
141
119
  const get = C.pmGet || httpGet;
142
- const S = { schedules: {}, tab: 'dash', scope: 'portfolio', pick: { board: '', initiative: '' }, meta: null, metaErr: null, layouts: {}, edit: false, drafts: false, alive: true, libOpen: new Set(), outbox: null, outboxErr: null };
120
+ const S = { tab: 'dash', scope: 'portfolio', pick: { board: '', initiative: '' }, meta: null, metaErr: null, layouts: {}, edit: false, alive: true, libOpen: new Set(), setup: null };
143
121
  const root = el('div', { class: 'rp3 calwrap', 'data-rp3': '' });
144
122
  const stopPhone = host.closest && host.closest('.c3') ? () => {} : c3Phone(root);
145
123
  if (!(host.closest && host.closest('.c3'))) root.classList.add('c3');
146
124
  const tabBar = el('div', { class: 'seg rp3-tabs', role: 'tablist', 'aria-label': 'Reports' });
147
125
  const scopeHost = el('div', { class: 'chips2 rp3-scope', role: 'group', 'aria-label': 'Scope' });
148
- const panes = { dash: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'dash' }), lib: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'lib', hidden: true }), drafts: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'drafts', hidden: true }) };
126
+ const panes = { dash: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'dash' }), reports: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'reports', hidden: true }), charts: el('div', { class: 'rp3-pane', role: 'tabpanel', 'data-rp3-pane': 'charts', hidden: true }) };
127
+ const ruleHost = el('div', { class: 'chips2 rp3-rule', role: 'note', 'aria-label': 'How Reports is organised', 'data-rp3-rule': '' });
149
128
  const deckHost = el('div', { 'data-rp3-deck': '' });
150
- root.append(deckHost, el('div', { class: 'calbar' }, tabBar, scopeHost), panes.dash, panes.lib, panes.drafts);
129
+ root.append(deckHost, el('div', { class: 'calbar' }, tabBar, scopeHost), ruleHost, panes.dash, panes.reports, panes.charts);
151
130
  host.replaceChildren(root);
152
131
  if (typeof C.deck === 'function') C.deck(deckHost); else deckHost.hidden = true;
153
132
 
@@ -163,8 +142,8 @@ export function mountReports(host, ctx) {
163
142
  const defaultOf = (scope) => known(S.meta && S.meta.defaults && S.meta.defaults[scope]);
164
143
  const hasOwn = (scope, id) => Object.prototype.hasOwnProperty.call(S.layouts, keyOf(scope, id));
165
144
  const layoutOf = (scope, id) => (hasOwn(scope, id) ? known(S.layouts[keyOf(scope, id)]) : defaultOf(scope));
166
- const schedOf = (scope, id) => S.schedules[keyOf(scope, id)] || null;
167
- const titleOfKey = (key) => { if (key.indexOf(':') < 0) return 'Portfolio'; const i = key.indexOf(':'); const sc = key.slice(0, i); return (sc === 'board' ? 'Board: ' : 'Initiative: ') + nameOf(sc, key.slice(i + 1)); };
145
+ /* the set-ups name a dashboard by its key (portfolio, board:shop); the pages of Reports say its title */
146
+ RS.names = (k) => (({ portfolio: 'Portfolio' })[k] || nameOf(k.split(':')[0], k.split(':').slice(1).join(':')));
168
147
  const goInit = (id, title) => {
169
148
  if (C.goInit) { C.goInit(id, title); return; }
170
149
  if (!listOf('initiative').some((x) => x.id === id)) { toast('No dashboard is kept for ' + (title || id) + '.'); return; }
@@ -172,17 +151,23 @@ export function mountReports(host, ctx) {
172
151
  };
173
152
 
174
153
  /* tabs */
154
+ const badgeOf = (id) => (id === 'dash' ? SCOPES.length : id === 'charts' ? (S.meta ? lib().length : null) : (RS.loaded ? draftsWaiting(RS.docs) : null));
175
155
  function drawTabs() {
176
- tabBar.replaceChildren(...TABS.map(([id, label, icon]) => pill({ type: 'button', role: 'tab', 'aria-selected': String(S.tab === id), 'aria-pressed': String(S.tab === id), tabindex: S.tab === id ? '0' : '-1', 'data-rp3-tab': id, onclick: () => showTab(id) }, c3ic(icon, 'sm'), label,
177
- id === 'lib' && S.meta ? el('span', { class: 'bdg g' }, String(lib().length)) : id === 'drafts' && S.outbox && dashDrafts().length ? el('span', { class: 'bdg g', 'data-rp3-draftcount': '' }, String(dashDrafts().length)) : null)));
156
+ tabBar.replaceChildren(...TABS.map(([id, label, icon]) => { const n = badgeOf(id); return pill({ type: 'button', role: 'tab', 'aria-selected': String(S.tab === id), 'aria-pressed': String(S.tab === id), tabindex: S.tab === id ? '0' : '-1', 'data-rp3-tab': id, onclick: () => showTab(id) }, c3ic(icon, 'sm'), label,
157
+ n ? el('span', { class: 'bdg g', 'data-rp3-badge': id, title: id === 'reports' ? 'Drafts waiting for you' : '' }, String(n)) : null); }));
178
158
  scopeHost.hidden = S.tab !== 'dash';
159
+ ruleHost.replaceChildren(el('span', { class: 'dim mono rp3-rulelbl' }, 'HOW REPORTS IS ORGANISED'),
160
+ ...[['dash', 'report', 'Live \u2192 Dashboards'], ['reports', 'doc', 'Sent \u2192 Reports'], ['charts', 'cols', 'Building blocks \u2192 Charts']].map(([id, ic, t]) => pill({ type: 'button', class: 'chip2 lk', 'data-rp3-rulego': id, onclick: () => showTab(id) }, c3ic(ic, 'sm'), t)));
179
161
  }
180
162
  function showTab(id) {
181
- S.tab = id; drawTabs();
163
+ id = TAB_OF[id] || id; S.tab = id; drawTabs();
182
164
  for (const k of Object.keys(panes)) panes[k].hidden = k !== id;
183
- if (id === 'lib') drawLib();
184
- if (id === 'drafts') { drawDrafts(); loadOutbox(); }
165
+ if (id === 'charts') drawLib();
166
+ if (id === 'reports') drawReportsTab();
185
167
  }
168
+ function drawReportsTab() { if (!S.setup) S.setup = mountReportSetup(panes.reports, { ws: C.ws }); }
169
+ const offReports = onReports(() => { if (S.alive) drawTabs(); });
170
+ loadReports(C.ws);
186
171
 
187
172
  /* ── Dashboards ── */
188
173
  /* The scope pills sit on the tab row (the mockup's calbar); the pill names the picked board or initiative. */
@@ -240,14 +225,14 @@ export function mountReports(host, ctx) {
240
225
  const own = !noScope && hasOwn(S.scope, id);
241
226
  root.classList.toggle('editing', S.edit);
242
227
  drawScope(); // again: the pick above may have changed the pill's name
243
- /* the mockup's .lh: report icon, bold title, one dim line (scope, count, schedule), then Share and Edit dashboard */
228
+ /* the mockup's .lh: report icon, bold title, one dim line (scope, count), then Send this as a report and Edit dashboard */
244
229
  const head = el('div', { class: 'lh rp3-head' }, el('span', { class: 'ki' }, c3ic('report')),
245
230
  el('div', { class: 'rp3-min0' }, el('b', { 'data-rp3-title': '' }, noScope ? (S.scope === 'board' ? 'Board' : 'Initiative') : dashTitle()),
246
231
  el('div', { class: 'dim' }, noScope ? '' : [el('span', { 'data-rp3-count': String(ids.length) }, plural(ids.length, 'report', 'reports') + (own ? ', your layout' : ', the default layout')),
247
- el('span', { 'data-rp3-schedline': '' }, ' · ' + (schedOf(S.scope, id) ? 'Scheduled: ' + SCHED_TXT[schedOf(S.scope, id).plan] + ', draft only' : 'Not scheduled'))]),
232
+ el('span', null, ' · Live. Changes as your work changes.')]),
248
233
  S.scope !== 'portfolio' ? chooser() : null),
249
234
  noScope ? null : el('span', { class: 'chips2' },
250
- btnV('outline', { 'data-rp3-share': '', onclick: () => share(ids) }, c3ic('out', 'sm'), 'Share'),
235
+ btnV('outline', { 'data-rp3-send': '', onclick: () => openSendSheet({ ws: C.ws, key: keyOf(S.scope, id), title: dashTitle(), charts: ids.length, onDone: () => { loadReports(C.ws, true); showTab('reports'); } }) }, c3ic('mail', 'sm'), 'Send this as a report\u2026'),
251
236
  S.edit ? btnV('ghost', { 'data-rp3-reset': '', disabled: !own, title: own ? 'Return this scope to its default layout' : 'This scope shows its default layout', onclick: () => resetLayout() }, c3ic('undo', 'sm'), 'Reset to default') : null,
252
237
  btnV(S.edit ? 'primary' : 'outline', { 'data-rp3-edit': '', 'aria-pressed': String(S.edit), onclick: () => { S.edit = !S.edit; drawDash(); } }, c3ic(S.edit ? 'check' : 'edit', 'sm'), S.edit ? 'Done editing' : 'Edit dashboard')));
253
238
  const kids = [head];
@@ -286,7 +271,7 @@ export function mountReports(host, ctx) {
286
271
  async function save(scope, id, reports) {
287
272
  const key = keyOf(scope, id);
288
273
  if (reports === null) delete S.layouts[key]; else S.layouts[key] = reports.slice();
289
- drawDash(); if (S.tab === 'lib') drawLib();
274
+ drawDash(); if (S.tab === 'charts') drawLib();
290
275
  const r = await pmPost('/api/report-dashboards/layout', { key, reports });
291
276
  if (!r || !r.ok) toast({ text: 'The layout was not saved: ' + ((r && (r.error || r.reason)) || 'no answer') + '. This page keeps it until you reload.', tone: 'error', timeout: 6000 });
292
277
  return r;
@@ -311,70 +296,7 @@ export function mountReports(host, ctx) {
311
296
  }
312
297
  function resetLayout() { save(S.scope, curId(), null); toast('This dashboard is back to its default layout.'); }
313
298
 
314
- /* ── Share: the report frame for an audience, one draft in the outbox, a schedule that is not available ── */
315
- function share(ids) {
316
- const scope = S.scope; const id = curId(); const title = dashTitle(); const reps = ids.map(repOf).filter(Boolean);
317
- const cur = schedOf(scope, id);
318
- const st = { aud: cur ? cur.audience : 'Leadership', saved: {}, busy: false, msg: '', sched: cur ? cur.plan : 'none', saving: false };
319
- const body = el('div', { class: 'rp3-share', 'data-rp3-sheet': '' });
320
- const paint = (raw) => {
321
- const today = (raw && raw.today) || todayStr(Date.now());
322
- const F = frameOf({ title, reports: reps, raw: raw && raw.ok ? raw : {}, audience: st.aud, today });
323
- /* the frame in the mockup's cards: a .lh head, then Headline, Sections, Needs your call and Sources as section cards */
324
- const sec = (name, icon, count, ...kids) => sect(name, icon, count, kids, null, { 'data-rp3-frame': name });
325
- const frame = el('div', { class: 'rp3-frame', 'data-rp3-aud': st.aud },
326
- el('div', { class: 'lh' }, el('span', { class: 'ki' }, c3ic('mail')), el('div', { class: 'rp3-min0' }, el('b', null, title), el('div', { class: 'dim' }, 'For ' + st.aud + ' · ' + today)), el('span', { class: 'chip2' }, 'Draft')),
327
- sec('Headline', null, null, el('div', { class: 'hero' }, el('b', { class: 'rp3-hl' }, F.headline))),
328
- sec('Sections', null, F.sections.length, ...(F.sections.length ? [el('ul', { class: 'chgl' }, F.sections.map((s) => el('li', null, c3ic('report', 'sm'),
329
- el('span', null, el('b', null, s.title), el('br'), s.line, s.nums.length ? el('ul', { class: 'rp3-nums', 'data-rp3-nums': s.id }, s.nums.map((t) => el('li', null, t))) : null), el('span', null))))] : [el('div', { class: 'fold' }, 'Nothing to show.')])),
330
- sec('Needs your call', null, F.needs.length, ...(F.needs.length ? [el('ul', { class: 'chgl' }, F.needs.map((t) => el('li', { class: 'warn' }, c3ic('flag', 'sm'), el('span', null, t), el('span', null))))] : [el('div', { class: 'fold' }, 'Nothing needs your call in this scope.')])),
331
- sec('Sources', null, null, el('div', { class: 'chips' }, F.sources.map((x) => el('span', { class: 'chip2' }, x))), el('div', { class: 'dim rp3-sub' }, 'Read from the repository on ' + today + '.')));
332
- const saved = st.saved[st.aud];
333
- const saveBtn = btnV('outline', { 'data-rp3-draft': '', disabled: st.busy || !!saved || !raw || !raw.ok,
334
- onclick: async () => {
335
- st.busy = true; paint(raw);
336
- const r = await pmPost('/api/outbox/draft', { channel: 'customer-note', recipient: st.aud, subject: title + ' for ' + st.aud, body: draftBody(F) });
337
- st.busy = false;
338
- if (r && r.ok) {
339
- st.saved[st.aud] = true;
340
- st.msg = r.entry && r.entry.state === 'blocked' ? 'Saved in the outbox, but the claims check blocks it: ' + ((r.entry.claims && r.entry.claims.refused || []).join(' ') || 'a claim has no label') + '. Nothing was sent.'
341
- : 'Saved as one draft in the outbox. Nothing was sent: no channel is connected.';
342
- } else st.msg = 'The draft was not saved: ' + ((r && (r.error || r.reason)) || 'no answer') + '. Nothing was sent.';
343
- toast(st.msg); paint(raw); loadOutbox();
344
- } }, c3ic('mail', 'sm'), saved ? 'Saved as draft' : st.busy ? 'Saving' : 'Make a draft now');
345
- /* Schedule: one per dashboard. A run makes a ready draft in the outbox and a notice on the bell; nothing is sent (lib/report-schedules.js). */
346
- const next = nextDraftOf(st.sched);
347
- const saveSched = async () => {
348
- st.saving = true; paint(raw);
349
- const r = await pmPost('/api/report-dashboards/schedule', { key: keyOf(scope, id), plan: st.sched, audience: st.aud, ws: C.ws || null });
350
- st.saving = false;
351
- if (r && r.ok) {
352
- S.schedules = r.schedules || {};
353
- st.msg = st.sched === 'none' ? 'Schedule removed' : 'Scheduled: ' + SCHED_TXT[st.sched] + '. You get a draft and a notification.';
354
- toast(st.msg); drawDash(); if (S.tab === 'drafts') drawDrafts();
355
- } else { st.msg = 'The schedule was not saved: ' + ((r && (r.error || r.reason)) || 'no answer') + '.'; toast({ text: st.msg, tone: 'error', timeout: 6000 }); }
356
- paint(raw);
357
- };
358
- const schedule = sect('Schedule', 'cal', null, [
359
- el('div', { class: 'seg', role: 'group', 'aria-label': 'Send', 'data-rp3-sched': '' }, SCHEDULES.map(([k, label]) => pill({ type: 'button', 'aria-pressed': String(k === st.sched), 'data-sched': k, onclick: () => { st.sched = k; st.msg = ''; paint(raw); } }, label))),
360
- next ? el('div', { class: 'dim rp3-sub', 'data-rp3-next': '' }, 'Next draft: ' + localDate(next)) : null,
361
- el('div', { class: 'fold w rp3-why', id: 'rp3-sched-why' }, c3ic('alert', 'sm'), 'No sender channel is connected yet. A scheduled send makes a ready draft and notifies you. Nothing is sent to ' + (st.aud === 'Leadership' ? 'leadership' : 'the team') + ' until you connect a channel.'),
362
- el('div', { class: 'chips2' }, btnV('primary', { 'data-rp3-sched-save': '', disabled: st.saving, onclick: saveSched }, st.saving ? 'Saving' : 'Save schedule'), saveBtn),
363
- el('p', { class: 'dim rp3-sub rp3-flat', role: 'status', 'data-rp3-share-msg': '' }, st.msg || 'Make a draft now puts one draft in the outbox. Nothing is sent from this page.')],
364
- null, { 'data-rp3-schedule': '', class: 'rp3-stack' });
365
- body.replaceChildren(el('div', { class: 'calbody brief' },
366
- el('div', { class: 'calbar' }, el('div', { class: 'seg', role: 'group', 'aria-label': 'Audience' }, ['Leadership', 'Team'].map((a) => pill({ type: 'button', 'aria-pressed': String(st.aud === a), 'data-rp3-audbtn': a, onclick: () => { st.aud = a; st.msg = ''; paint(raw); } }, a))),
367
- el('span', { class: 'dim rp3-sub' }, st.aud === 'Leadership' ? 'Short: one sentence per report, and what needs your call.' : 'Fuller: adds the numbers behind each sentence.')),
368
- raw ? (raw.ok ? frame : el('div', { class: 'fold', role: 'status' }, c3ic('alert', 'sm'), 'The reports could not be read: ' + raw.error)) : el('p', { class: 'dim mono', role: 'status' }, 'Reading the repository'),
369
- schedule));
370
- };
371
- const cached = cachedData(scope, id);
372
- openSheet({ title: 'Share: ' + title, icon: 'out', wide: true, body });
373
- paint(cached);
374
- if (!cached) readData(get, C.ws, scope, id).then((m) => { if (body.isConnected) paint(m); });
375
- }
376
-
377
- /* ── Library: the home of every report ── */
299
+ /* ── Charts: the home of every chart ── */
378
300
  function usedOn(rid, short) {
379
301
  if (!S.meta) return 'nowhere yet';
380
302
  if (short) { // the mockup's chip: the scopes it is on, by short name
@@ -389,8 +311,10 @@ export function mountReports(host, ctx) {
389
311
  }
390
312
  return on.length ? on.join(', ') : 'no dashboard';
391
313
  }
314
+ /* a set-up built from a dashboard uses the charts of that dashboard; the contract names the dashboard key, not its charts, so the match is by key */
315
+ const reportsUsing = (r) => { const names = RS.setups.filter((x) => String(x.source || '').startsWith('dashboard:') && layoutOf(String(x.source).slice(10).split(':')[0], String(x.source).slice(10).split(':').slice(1).join(':')).includes(r.id)).map((x) => x.name); return names.length ? names.join(', ') : 'none'; };
392
316
  function drawLib() {
393
- const P = panes.lib;
317
+ const P = panes.charts;
394
318
  if (S.metaErr) { P.replaceChildren(el('div', { class: 'fold', role: 'status' }, c3ic('alert', 'sm'), 'The library could not be read: ' + S.metaErr)); return; }
395
319
  if (!S.meta) { P.replaceChildren(el('p', { class: 'dim mono rp3-status', role: 'status' }, 'Reading the repository')); return; }
396
320
  const rows = lib().map((r) => {
@@ -399,7 +323,7 @@ export function mountReports(host, ctx) {
399
323
  const row = el('li', { class: 'nrow rp3-librow', 'data-home': r.id },
400
324
  el('span', { class: 'ki hot' }, c3ic(ICON[builderOf(r)] || 'report')),
401
325
  el('div', { class: 'nb' }, el('span', { class: 'tt' }, r.title), el('div', { class: 'dim' }, r.question || ''),
402
- el('div', { class: 'chips' }, el('span', { class: 'chip2' }, r.source || 'no source named'), el('span', { class: 'chip2', 'data-rp3-used': r.id }, 'On: ' + usedOn(r.id, true))), xp));
326
+ el('div', { class: 'chips' }, el('span', { class: 'chip2' }, r.source || 'no source named'), el('span', { class: 'chip2', 'data-rp3-used': r.id }, c3ic('report', 'sm'), 'Dashboards: ' + usedOn(r.id, true).replace('no dashboard', 'none')), el('span', { class: 'chip2', 'data-rp3-used-reports': r.id }, c3ic('doc', 'sm'), 'Reports: ' + reportsUsing(r))), xp));
403
327
  const fill = () => {
404
328
  const m = cachedData('portfolio', '');
405
329
  if (m) { card.replaceChildren(m.ok ? drawReport(r, m, { goInit }) : el('p', { class: 'dim mono' }, 'This report could not be read: ' + m.error)); return; }
@@ -410,82 +334,24 @@ export function mountReports(host, ctx) {
410
334
  if (S.libOpen.has(r.id)) api.open();
411
335
  return row;
412
336
  });
413
- P.replaceChildren(el('div', { class: 'calbody brief' }, el('div', { class: 'fold rp3-note' }, c3ic('report', 'sm'), 'Every report PMOS can draw lives here. Dashboards point at these; changing a report changes it everywhere it is used. Open a row to see the report across the portfolio.'),
414
- sect('Reports', 'report', rows.length, el('ul', { class: 'list', role: 'list', 'data-rp3-lib': '' }, rows))));
337
+ P.replaceChildren(el('div', { class: 'calbody brief' }, el('div', { class: 'fold rp3-note' }, c3ic('cols', 'sm'), 'Each chart lives here once. Dashboards and reports point at it.'),
338
+ sect('Charts', 'cols', rows.length, el('ul', { class: 'list', role: 'list', 'data-rp3-lib': '' }, rows))));
415
339
  }
416
340
  function openInLibrary(rid) {
417
- S.libOpen.add(rid); showTab('lib');
418
- const row = panes.lib.querySelector('[data-home="' + rid + '"]');
341
+ S.libOpen.add(rid); showTab('charts');
342
+ const row = panes.charts.querySelector('[data-home="' + rid + '"]');
419
343
  if (row) { try { row.focus({ preventScroll: true }); row.scrollIntoView({ block: 'nearest' }); } catch { /* not focusable */ } }
420
344
  }
421
345
 
422
- /* ── Drafts: the dashboard drafts, what is scheduled (nothing), what the Briefing used to carry, and the old report families ── */
423
- const dashDrafts = () => ((S.outbox || []).filter((e) => e.channel === 'customer-note' && DRAFT_RE.test(e.subject || '')));
424
- function loadOutbox() {
425
- return Promise.resolve(get('/api/outbox')).then((r) => {
426
- if (!S.alive) return;
427
- if (r && r.ok) { S.outbox = r.entries || []; S.outboxErr = null; } else S.outboxErr = (r && (r.error || r.reason)) || 'not available';
428
- drawTabs(); if (S.tab === 'drafts') drawDrafts();
429
- }, (e) => { S.outboxErr = String((e && e.message) || e); if (S.tab === 'drafts') drawDrafts(); });
430
- }
431
- const draftRows = () => dashDrafts().map((x) => {
432
- const m = DRAFT_RE.exec(x.subject);
433
- return el('li', { class: 'nrow', 'data-draft-row': x.id }, el('span', { class: 'ki' }, c3ic('mail')),
434
- el('div', { class: 'nb' }, el('span', { class: 'tt' }, m[1]),
435
- el('div', { class: 'chips' }, el('span', { class: 'chip2' }, 'Draft'), el('span', { class: 'chip2' }, c3ic('user', 'sm'), 'For ' + m[2]),
436
- x.created_at ? el('span', { class: 'chip2' }, c3ic('cal', 'sm'), dateOf(x.created_at)) : null, el('span', { class: 'tag', 'data-rp3-madeby': x.made_by === 'schedule' ? 'schedule' : 'you' }, x.made_by === 'schedule' ? 'Made by schedule' : 'Made by you'), el('span', { class: 'tag' }, x.state === 'blocked' ? 'Blocked by the claims check' : 'Not sent'))),
437
- el('div', { class: 'acts' }, btnV('outline', { 'data-draft': 'preview', 'data-id': x.id, onclick: () => previewDraft(x, m) }, c3ic('eye', 'sm'), 'Preview'),
438
- btnV('ghost', { 'data-draft': 'discard', 'data-id': x.id, onclick: () => discardDraft(x) }, c3ic('x', 'sm'), 'Discard')));
439
- });
440
- function previewDraft(x, m) {
441
- openSheet({ title: 'Draft: ' + m[1], icon: 'mail', wide: true, body: el('div', { class: 'calbody brief' },
442
- el('div', { class: 'lh' }, el('span', { class: 'ki' }, c3ic('mail')), el('div', { class: 'rp3-min0' }, el('b', null, m[1]), el('div', { class: 'dim' }, 'For ' + m[2] + (x.created_at ? ' · ' + dateOf(x.created_at) : ''))), el('span', { class: 'chip2' }, 'Draft')),
443
- sect('Text', 'doc', null, el('pre', { class: 'rp3-draft-text', 'data-rp3-draft-text': '' }, x.body || ''))) });
444
- }
445
- async function discardDraft(x) {
446
- const r = await pmPost('/api/outbox/discard', { id: x.id });
447
- if (!r || !r.ok) { toast({ text: 'The draft was not discarded: ' + ((r && (r.error || r.reason)) || 'no answer'), tone: 'error', timeout: 6000 }); return; }
448
- S.outbox = (S.outbox || []).filter((e) => e.id !== x.id); drawTabs(); drawDrafts();
449
- toast({ text: 'Draft discarded', actionLabel: 'Undo', timeout: 6000, onAction: async () => { await pmPost('/api/outbox/discard', { id: x.id, undo: true }); loadOutbox(); } });
450
- }
451
- function scheduledList() {
452
- const L = Object.values(S.schedules || {});
453
- if (!L.length) return el('div', { class: 'fold' }, c3ic('clock', 'sm'), 'No dashboard is scheduled. Share a dashboard to set one.');
454
- return el('ul', { class: 'list', role: 'list' }, L.map((x) => el('li', { class: 'nrow', 'data-rp3-sched-row': x.key }, el('span', { class: 'ki' }, c3ic('cal')),
455
- el('div', { class: 'nb' }, el('span', { class: 'tt' }, titleOfKey(x.key)),
456
- el('div', { class: 'chips' }, el('span', { class: 'chip2' }, SCHED_TXT[x.plan] || x.plan), el('span', { class: 'chip2' }, c3ic('user', 'sm'), 'For ' + x.audience),
457
- el('span', { class: 'chip2' }, 'Next draft ' + localDate(x.next_run ? new Date(x.next_run) : nextDraftOf(x.plan))),
458
- x.last_status === 'failed' ? el('span', { class: 'tag', 'data-rp3-sched-failed': '' }, 'The last run made no draft: ' + (x.last_error || 'no reason recorded')) : null)),
459
- el('span', null))));
460
- }
461
- function drawDrafts() {
462
- const P = panes.drafts;
463
- if (!S.draftsHost) {
464
- S.draftsHost = { list: el('div', { 'data-rp3-draftlist': '', class: 'rp3-stack4' }), more: el('div', { 'data-rp3-more': '', class: 'rp3-stack4' }), send: el('div', { class: 'rp3-drafts', 'data-rp3-drafts': '' }) };
465
- let sendBody;
466
- if (typeof C.drafts === 'function') { sendBody = S.draftsHost.send; C.drafts(sendBody); } else sendBody = el('div', { class: 'fold' }, c3ic('info', 'sm'), 'The report drafts are not available on this page.');
467
- if (typeof C.more === 'function') C.more(S.draftsHost.more); else S.draftsHost.more.hidden = true;
468
- S.draftsHost.sendSect = sect('Reports to send', 'out', null, [el('div', { class: 'dim rp3-sub' }, 'The earlier report families, each with its claims check. Nothing is sent without your press.'), sendBody]);
469
- P.replaceChildren(el('div', { class: 'calbody brief' }, S.draftsHost.list, S.draftsHost.more, S.draftsHost.sendSect));
470
- }
471
- const rows = draftRows();
472
- S.draftsHost.list.replaceChildren(
473
- el('div', { class: 'fold w' }, c3ic('alert', 'sm'), 'Nothing here is sent. No sender channel is connected yet, so a schedule only makes a ready draft and notifies you.'),
474
- sect('Drafts', 'mail', S.outbox ? rows.length : null, S.outboxErr ? el('div', { class: 'fold', role: 'status' }, c3ic('alert', 'sm'), 'The drafts could not be read: ' + S.outboxErr)
475
- : !S.outbox ? el('p', { class: 'dim mono rp3-status', role: 'status' }, 'Reading the repository')
476
- : rows.length ? el('ul', { class: 'list', role: 'list', 'data-rp3-drafts-list': '' }, rows) : el('div', { class: 'fold' }, c3ic('check', 'sm'), 'No drafts. Share a dashboard to make one.')),
477
- sect('Scheduled', 'cal', null, scheduledList(), null, { 'data-rp3-scheduled': '' }));
478
- }
479
-
480
346
  /* boot */
481
- drawTabs(); drawDash(); loadOutbox();
347
+ drawTabs(); drawDash();
482
348
  Promise.resolve(get('/api/report-dashboards' + (C.ws ? '?ws=' + encodeURIComponent(C.ws) : ''))).then((r) => {
483
349
  if (!S.alive) return;
484
- if (r && r.ok) { S.meta = r; S.layouts = { ...(r.layouts || {}) }; S.schedules = { ...(r.schedules || {}) }; } else S.metaErr = (r && (r.error || r.reason)) || 'not available';
485
- drawTabs(); drawDash(); if (S.tab === 'lib') drawLib(); if (S.tab === 'drafts') drawDrafts();
350
+ if (r && r.ok) { S.meta = r; S.layouts = { ...(r.layouts || {}) }; } else S.metaErr = (r && (r.error || r.reason)) || 'not available';
351
+ drawTabs(); drawDash(); if (S.tab === 'charts') drawLib();
486
352
  }, (e) => { S.metaErr = String((e && e.message) || e); drawDash(); });
487
353
 
488
- const api = { dispose() { S.alive = false; if (livePage === api) livePage = null; stopPhone(); root.remove(); }, root, showTab: (v) => { if (panes[v]) showTab(v); } };
354
+ const api = { dispose() { S.alive = false; offReports(); if (livePage === api) livePage = null; stopPhone(); root.remove(); }, root, showTab: (v) => { v = TAB_OF[v] || v; if (panes[v]) showTab(v); } };
489
355
  livePage = api;
490
356
  if (wantView) { const v = wantView; wantView = null; api.showTab(v); }
491
357
  return api;
@@ -26,6 +26,7 @@ import { c3ic } from './pm-work-view.js';
26
26
  import { sessionDefaults, setSessionDefaults } from './dock.js';
27
27
  import { toast } from './c3-toast.js';
28
28
  import { dayPartOf } from './c3-daypart.js';
29
+ import { finderBlock } from './c3-report-store.js';
29
30
 
30
31
  const cx = (...c) => c.filter(Boolean).join(' ');
31
32
 
@@ -36,7 +37,7 @@ export const SECTIONS = Object.freeze([
36
37
  ['connectors', 'Connectors', 'org', 'PMOS keeps no passwords or keys for these. Your Claude session reaches each tool through the connectors of its profile.'],
37
38
  ['recording', 'Recording', 'mic', 'How meetings are transcribed. This is the one place to check when a transcript looks wrong.'],
38
39
  ['time', 'Day and time', 'sun', 'The Briefing follows your clock. These settings say what your day looks like.'],
39
- ['sharing', 'Sharing', 'mail', 'Who dashboards go to, and how. Each dashboard\'s schedule is set on the dashboard, in Reports.'],
40
+ ['sharing', 'Sharing', 'mail', 'Who reports go to, and how. Each report\'s schedule is set in Reports, under Set up.'],
40
41
  ['access', 'Access and devices', 'phone', 'Open PMOS from your phone or another computer.'],
41
42
  ['sessions', 'Claude sessions', 'term', 'The Claude Code sessions PMOS starts, and what they start with.'],
42
43
  ['privacy', 'Privacy and data', 'shield', 'Nothing leaves this computer unless a line below says so.'],
@@ -529,7 +530,7 @@ export function mountSettings(host, ctx = {}) {
529
530
  group('Sending', [storeRow('share.mode', { disabled: sender === 'none', lock: sender === 'none' ? 'Connect a sender in Connectors first. Until then every share is a draft.' : null })], 'mail'),
530
531
  group('Detail level', [notYet('share.detail', 'Detail for each audience', 'Short is one sentence per report and what needs a decision. Full adds the numbers behind each sentence.', 'The Share sheet sets the detail for each send. A default per audience comes later.', 'ws')], 'mail'),
531
532
  sect('Audiences', [list(rows), M.err['share.audiences'] ? fold('alert', M.err['share.audiences'], 'w') : null], { ic: 'user', count: rows.length }),
532
- sect('Scheduled shares', [fold(null, 'Each schedule is set on its dashboard in Reports. When dashboards can be scheduled, they are listed here so you can see everything that goes out.', null, btn('Open Reports', { variant: 'ghost', ic: 'next', onclick: () => ctx.goTab && ctx.goTab('rep') }))], { ic: 'clock' })];
533
+ sect('Scheduled reports', [fold(null, 'Each report has its own schedule. See and change them in Reports, under Set up.', null, btn('Open Reports', { variant: 'ghost', ic: 'next', onclick: () => ctx.goTab && ctx.goTab('rep', 'reports') }))], { ic: 'clock', attrs: { 'data-c3set-sched-link': '' } })];
533
534
  }
534
535
 
535
536
  function accessSec() {
@@ -705,7 +706,7 @@ export function mountSettings(host, ctx = {}) {
705
706
  return [sect('PMOS app', [list([appRow])], { ic: 'cloud', attrs: { 'data-c3set-app': '' } }), sect('Kit in each workspace', [list(rows)], { ic: 'repo', count: rows.length }), groupOf('updates', 'When', 'cloud')];
706
707
  }
707
708
 
708
- const BUILD = { about: aboutSec, workspaces: workspacesSec, boards: boardsSec, connectors: connectorsSec, recording: recordingSec, time: timeSec, sharing: sharingSec, access: accessSec, sessions: sessionsSec, privacy: privacySec, appearance: appearanceSec, updates: updatesSec };
709
+ const BUILD = { about: aboutSec, workspaces: workspacesSec, boards: boardsSec, connectors: () => [...connectorsSec(), finderBlock(M.ws)], recording: recordingSec, time: timeSec, sharing: sharingSec, access: accessSec, sessions: sessionsSec, privacy: privacySec, appearance: appearanceSec, updates: updatesSec };
709
710
 
710
711
  function searchView() {
711
712
  const hits = rowsMatching(M.q);
@@ -19,20 +19,18 @@ import { mountCalendarViews } from './calendar-views.js';
19
19
  import { todaySection } from './c3-brief-today.js';
20
20
  import { needsSection, needsCount } from './c3-brief-needs.js';
21
21
  import { scanSection } from './c3-scan.js';
22
+ import { reportFoundSection } from './c3-report-store.js';
22
23
  import { pulse } from './c3-brief-pulse.js';
23
24
  import { loadBriefPage, onBriefPage } from './c3-brief-data.js';
24
25
  import { mountLifecycle } from './lifecycle-lens.js';
25
26
  import { mountForecast } from './timeline-forecast-view.js';
26
27
  import { mountReleases } from './releases-view.js';
27
- import { mountReportFamilies } from './report-families-view.js';
28
28
  import { mountReports, showReportsView } from './c3-reports.js';
29
- import { mountOutbox } from './outbox-panel.js';
30
29
  import { createDock } from './c3-dock.js';
31
- import { reviewSection, handledRow, trustCard, footLine, rowApi, BX, toast, setNeedsTitle, hooks as briefHooks } from './c3-brief.js';
32
- import { stopCard } from './c3-misc-panels.js';
30
+ import { reviewSection, footLine, rowApi, BX, toast, setNeedsTitle, hooks as briefHooks } from './c3-brief.js';
33
31
  import { openSheet } from './c3-sheet.js';
34
32
  import { mountCalPopupTrigger } from './c3-calpop.js';
35
- import { planNode, dueCard, crossLinks, newInitLine, portfolioCard } from './c3-plan.js';
33
+ import { planNode, dueCard, crossLinks, newInitLine } from './c3-plan.js';
36
34
  import { initiativesViewCtx } from './registry.js';
37
35
  import { mount as mountRoadmap } from './c3-roadmap.js';
38
36
  import { mount as mountBreakdown } from './c3-breakdown.js';
@@ -303,7 +301,7 @@ function briefTab(pane) {
303
301
  if (S.why) whyMark();
304
302
  });
305
303
  S.needsBox = needsSection(S);
306
- left.append(deckHost, scanSection(S), S.needsBox); // the Horizon scan card sits above Needs you, and shows only on a day a scan ran
304
+ left.append(deckHost, scanSection(S), reportFoundSection(S), S.needsBox); // the Horizon scan card sits above Needs you, and shows only on a day a scan ran
307
305
  right.append(todaySection(), pulse(S));
308
306
  pane.append(el('div', { class: 'brief' }, el('div', { class: 'bcols' }, left, right), ftHost));
309
307
  mountBriefing(deckHost, { noTicker: true, onLens: lensChanged });
@@ -317,6 +315,13 @@ function lensChanged(l) {
317
315
 
318
316
  /* ── global: where a notification goes, the person sheet, the palette and the keys ── */
319
317
  /* A bell entry or the banner goes to its target (console3.html nopen, line 2530): the card to turn over, the row to open, or the sheet to show. */
318
+ /* The report pages ask the shell to go somewhere (c3-report-store.js goTo): { tab, view?, section?, finding? }. Settings opens on a section. */
319
+ if (typeof window !== 'undefined') window.addEventListener('pmos:go-tab', (e) => {
320
+ const d = (e && e.detail) || {}; if (!d.tab) return;
321
+ showTab(d.tab, true);
322
+ if (d.tab === 'settings' && d.section && S.settings) S.settings.show(d.section);
323
+ if (d.tab === 'rep' && d.view) showReportsView(d.view);
324
+ });
320
325
  function goTarget(t) {
321
326
  /* a dashboard schedule's notice (routes-briefing.js notices): Reports, then its Drafts tab */
322
327
  if (t && t.tab && BUILD[t.tab]) { showTab(t.tab, true); if (t.tab === 'rep' && t.view) showReportsView(t.view); return; }
@@ -519,30 +524,9 @@ function showInit(id) {
519
524
  }
520
525
  }
521
526
  }
522
- /* Reports: the headline deck on top (the Plan deck of the Reports scope), the tabs, and on Drafts what the Briefing no longer carries
523
- * (Handled, What I could stop, Data you can lean on) above "Reports to send" (the old families, their claims check and the outbox). */
524
- /* The Reports deck always names its scope, so real mode gets the engine's report signals and never the Briefing's cards (console3-deck-flow-fixes). */
525
- function repTab(pane) { mountReports(pane, { ws: S.ws, pmGet, deck: (h) => h.append(planNode('rep')), more: draftsMore, drafts: draftsTab }); }
526
- function draftsMore(host) {
527
- let sig = '';
528
- onBriefing((b) => { // the same cards and the same condition as the Briefing had: only what the briefing carries (the demo's); redrawn only when it changed, so an open row stays open
529
- const g = JSON.stringify([b && b.handled, b && b.stop, b && b.trust]);
530
- if (g === sig) return; sig = g;
531
- host.replaceChildren(...[b && b.handled ? handledRow(b.handled) : null, stopCard(b && b.stop), b && b.trust ? trustCard(b.trust) : null].filter(Boolean));
532
- });
533
- loadBriefing();
534
- }
535
- function draftsTab(pane) {
536
- const fam = el('div', { 'data-c3pg': 'families-host' }); const out = el('div', { class: 'brief', 'data-c3pg': 'outbox-host', style: 'margin-top:var(--sp-4)' });
537
- pane.append(fam, out);
538
- pmGet('/api/report-families/facts?ws=' + encodeURIComponent(S.ws)).then((j) => {
539
- mountReportFamilies(fam, { reportFacts: j && j.ok ? j.facts : {}, plan: null, portfolio: portfolioCard() });
540
- mountOutbox(out, {});
541
- /* The mockup's Reports draw the outbox's drafts under Communications only (the Messages queue); the other families have no outbox. */
542
- const sync = () => { out.hidden = !fam.querySelector('[data-family="communications"][aria-pressed="true"]'); };
543
- sync(); new MutationObserver(sync).observe(fam, { subtree: true, attributes: true, attributeFilter: ['aria-pressed'] });
544
- });
545
- }
527
+ /* Reports: the headline deck on top (the Plan deck of the Reports scope), then the three tabs (Dashboards, Reports, Charts; c3-reports.js).
528
+ * The Reports deck always names its scope, so real mode gets the engine's report signals and never the Briefing's cards (console3-deck-flow-fixes). */
529
+ function repTab(pane) { mountReports(pane, { ws: S.ws, pmGet, deck: (h) => h.append(planNode('rep')) }); }
546
530
 
547
531
  const BUILD = { brief: briefTab, cal: (p) => mountCalendarViews(p, { view: 'week', page: true, scope: sc('cal'), deck: planNode('cal', { bare: true }) }), work: (p) => { mountPmWork(p, { go: (t) => showTab(t, true), ws: S.ws }); }, init: (p) => mountInitiatives(p, { ws: S.ws, workspaces: S.workspaces, demo: DEMO, showTab }), rep: repTab };
548
532
 
@@ -559,7 +543,7 @@ function showTab(id, push) {
559
543
  if (S.gearBtn) S.gearBtn.setAttribute('aria-pressed', String(id === 'settings'));
560
544
  if (id === 'settings') {
561
545
  S.ttl.replaceChildren(gearIcon('lg'), 'Settings'); document.title = 'Settings · PMOS console';
562
- if (!S.settings) { S.settingsCtx = { ws: S.ws, workspaces: S.workspaces, theme: S.theme, themePref: S.themePref, setTheme, goTab: (t) => showTab(t, true), section: q.get('section') || '', root: S.root }; S.settings = mountSettings(S.panes.settings, S.settingsCtx); }
546
+ if (!S.settings) { S.settingsCtx = { ws: S.ws, workspaces: S.workspaces, theme: S.theme, themePref: S.themePref, setTheme, goTab: (t, v) => { showTab(t, true); if (t === 'rep' && v) showReportsView(v); }, section: q.get('section') || '', root: S.root }; S.settings = mountSettings(S.panes.settings, S.settingsCtx); }
563
547
  else S.settings.refresh();
564
548
  } else if (!S.mounted[id]) { S.mounted[id] = true; BUILD[id](S.panes[id]); }
565
549
  S.root.dataset.tab = id;