create-pmos 0.130.0 → 0.132.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- 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/workspace/c3-one-home-test.js +4 -2
- package/workspace/c3-work-test.js +200 -0
- package/workspace/fixtures/one-home-baseline.json +0 -91
- package/workspace/guide/console-calendar.md +65 -0
- package/workspace/guide/data-freshness.md +40 -0
- package/workspace/guide/index.md +2 -0
- package/workspace/guide/my-work.md +76 -1
- package/workspace/lib/calendar-cli.js +84 -0
- package/workspace/lib/calendar-layers.js +372 -0
- package/workspace/lib/facts-async.js +144 -35
- package/workspace/lib/freshness.js +167 -0
- package/workspace/lib/offloop.js +94 -0
- package/workspace/lib/pm-items.js +71 -6
- package/workspace/lib/pmos-verbs.js +3 -0
- package/workspace/lib/report-needs.js +1 -1
- package/workspace/lib/route-modules.js +1 -0
- package/workspace/lib/routes-board.js +3 -1
- package/workspace/lib/routes-calendar-views.js +112 -3
- package/workspace/lib/routes-my-work.js +27 -9
- package/workspace/lib/routes-pm-items.js +123 -13
- package/workspace/lib/routes-sources.js +64 -0
- package/workspace/lib/work-focus.js +106 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-calendar.js +392 -0
- package/workspace/public/pm/c3-demo-cal4.js +114 -0
- package/workspace/public/pm/c3-demo-data.js +79 -77
- package/workspace/public/pm/c3-demo-misc2.js +4 -16
- package/workspace/public/pm/c3-demo-work-ix.js +19 -67
- package/workspace/public/pm/c3-demo.js +10 -3
- package/workspace/public/pm/c3-shell.js +5 -2
- package/workspace/public/pm/calendar-views.js +10 -3
- package/workspace/public/pm/components.css +34 -0
- package/workspace/public/pm/console3.css +155 -0
- package/workspace/public/pm/dest-settings.js +10 -1
- package/workspace/public/pm/freshness-line.js +123 -0
- package/workspace/public/pm/pm-work-view.js +364 -172
- package/workspace/public/pm/work-place.js +58 -0
- package/workspace/server.js +39 -2
- package/workspace/skills/calendar-sync.skill +30 -0
- package/workspace/test-files.json +2 -0
- package/workspace/test-lists/calendar-layers.json +5 -0
- package/workspace/test-lists/calendar-time-page.json +5 -0
- package/workspace/test-lists/console3-work-page.json +9 -0
- package/workspace/test-lists/workspace-data-freshness.json +6 -0
- package/workspace/public/pm/c3-demo-work2.js +0 -13
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Fields the real routes do not send and the views draw only when present (demo-only, additive): on a briefing card `sub`, `kpis`,
|
|
7
7
|
* `factsX`, `pov`, `hero.rich`, `move.suggested.conf`; on the briefing `needs`; on a work item `mock_row` (which switches on `kind`, `time`, `mins`, `src`, `init`,
|
|
8
|
-
* `agent_move`, `left`)
|
|
8
|
+
* `agent_move`, `left`) and `resolves_title`; on a calendar event `mock_routine`, `mock_hot`, `mock_decide_by`; on the forecast `mock_status_w`; on the calendar response `free_gaps`. */
|
|
9
|
+
import { buildWork, arrives } from './work-place.js';
|
|
9
10
|
|
|
10
11
|
export const NOW_ISO = '2026-10-08T08:12:00';
|
|
11
12
|
export const TODAY = '2026-10-08';
|
|
@@ -118,48 +119,36 @@ const CAL_DN = card('dn', 'Just in', 'Marta confirmed 3 slots and Priya replied'
|
|
|
118
119
|
{ kind: 'rows', rows: [['mail', 'Marta: 3 interview slots confirmed', 'ok', '08:05'], ['user', 'Priya: replied on the numbers', 'ok', '08:10']] },
|
|
119
120
|
{ kpis: [['2', 'new', C.acc], ['<2 min', 'each', C.ok], ['88%', 'confidence', C.ok]], factsX: [['bolt', '2 new'], ['clock', 'Both take under 2 minutes']] });
|
|
120
121
|
|
|
121
|
-
/* Work tab (the mockup's
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
{ kind: '
|
|
128
|
-
{
|
|
129
|
-
move: { suggested: { id: 'td', label: 'Put them in Today and block time', conf: 78 }, options: [{ id: 'td', label: 'Put them in Today and block time' }] } });
|
|
130
|
-
const WORK_KA = card('ka', 'Opportunity', 'The agent drafted 3 replies', 'Review them as a set. Nothing leaves until you send it.',
|
|
131
|
-
{ kind: 'rows', rows: [['reply', 'Confirm the 1 Oct numbers before th…', 'ok', '10:30'], ['reply', 'Say yes to Sam: ship the logging ch…', 'ok', 'today'], ['reply', 'Accept the 15 Oct interview invite', 'ok', 'today']] },
|
|
132
|
-
{ kpis: [['84%', 'confidence', C.acc], ['2', 'options', C.opp], ['3', 'evidence', C.ok]], factsX: [['spark', 'Drafted from your history'], ['out', 'Goes to Review first']],
|
|
133
|
-
move: { suggested: { id: 'ap', label: 'Approve all as drafts', conf: 84 }, options: [{ id: 'ap', label: 'Approve all as drafts' }, { id: 'ed', label: 'Open each first' }] } });
|
|
134
|
-
const WORK_KJ = card('kj', 'Just in', "Priya's request is the newest and has a clock", 'The 1 Oct numbers, due 10:30. Two sources disagree, so the case file has the answer.',
|
|
135
|
-
{ kind: 'ring', done: 14, total: 152, size: 96, cap: '2h 18m left', sub: 'until the 10:30 update' },
|
|
136
|
-
{ kpis: [['2h 18m', 'left', C.warn], ['2', 'sources disagree', C.bad], ['90%', 'confidence', C.ok]], factsX: [['user', 'Priya, 07:58'], ['sheet', 'Sheet says 3.1%']],
|
|
137
|
-
move: { suggested: { id: 'r', label: 'Reply 3.1% and cite the sheet', conf: 90 }, options: [{ id: 'r', label: 'Reply 3.1% and cite the sheet' }, { id: 't', label: 'Put it in Today' }] } });
|
|
122
|
+
/* Work tab (the deck per view: Inbox, Today, Upcoming, Waiting, Someday, Review). The decisions the mockup's Triage cards pointed at
|
|
123
|
+
* ("5 need a real decision", "The agent drafted 3 replies") live in Briefing · Needs you now, so no Work card names them. */
|
|
124
|
+
const WORK_KI = card('ki', 'Small win', 'Three items have no date yet', 'One has waited two weeks. Give each a day, a Not before date, or park it in Someday.',
|
|
125
|
+
{ kind: 'rows', rows: [['inbox', 'Read the competitor pricing teardown', '', 'yesterday'], ['inbox', 'Book five user interviews for returns discovery', '', 'Tue'], ['alert', 'Idea: weekly digest for partner admins', 'warn', '14 days']] },
|
|
126
|
+
{ kpis: [['3', 'to decide', C.acc], ['14 d', 'oldest', C.warn]], factsX: [['inbox', 'Capture now, decide later']] });
|
|
127
|
+
const WORK_KRV = card('krv', 'Review', 'Three items wait for your weekly review', 'Nobody touched them for a week or more. The oldest is 20 days.',
|
|
128
|
+
{ kind: 'bars', bars: [['Clean up the Checkout Jira labels', 1, C.warn, '20 d'], ['Idea: weekly digest for partner admins', 0.7, C.warn, '14 d'], ['Annual-plan label', 0.45, C.acc, '9 d']] },
|
|
129
|
+
{ eyebrow: { label: 'Review', icon: 'eye' }, kpis: [['3', 'to review', C.acc], ['20 d', 'oldest', C.warn]], factsX: [['eye', 'One at a time: keep, change or drop']] });
|
|
138
130
|
|
|
139
|
-
/* Work tab, the other
|
|
131
|
+
/* Work tab, the other views (the mockup's workCards: Today, Week, Waiting, Later) */
|
|
140
132
|
const mv = (id, label, conf, more) => ({ suggested: { id, label, conf }, options: [{ id, label }, ...(more || [])] });
|
|
141
|
-
const
|
|
142
|
-
{ kind: 'tiles', tiles: [['3', 'FYI items', C.mfg], ['9', 'real work', C.acc]] },
|
|
143
|
-
{ kpis: [['94%', 'confidence', C.acc], ['2', 'options', C.opp], ['3', 'evidence', C.ok]], factsX: [['filter', 'Kept out of your list']], move: mv('ar', 'Archive all 3', 94) });
|
|
144
|
-
const WK_KO = card('ko', 'Critical', 'Do these three first', 'Ranked by what each one unblocks and when it is due.',
|
|
133
|
+
const WK_KO = card('ko', 'Critical', 'Do these three first', 'Focus has three jobs for today, each with the reason.',
|
|
145
134
|
{ kind: 'axis', focus: [11.25, 13], now: 8.2, marks: [[10.5, '10:30', C.bad, 19], [13, '13:00', C.acc, 27], [14, '14:00', C.bad, 19]] },
|
|
146
|
-
{ kpis: [['
|
|
147
|
-
const WK_KR = card('kr', 'Critical', 'Two
|
|
148
|
-
{ kind: 'rows', rows: [['alert', '
|
|
149
|
-
{ kpis: [['
|
|
135
|
+
{ kpis: [['5', 'for today', C.acc], ['1.8 h', 'focus time', C.opp], ['78%', 'confidence', C.ok]], factsX: [['target', 'Hold the 24 Oct date'], ['clock', 'Best window 11:15']], move: mv('lk', 'Accept Focus and block 11:15 to 13:00', 78) });
|
|
136
|
+
const WK_KR = card('kr', 'Critical', 'Two promises are late', 'The timeline to Dara was due yesterday and the call with Priya is 2 days late.',
|
|
137
|
+
{ kind: 'rows', rows: [['alert', 'Send the revised Checkout v2 timeline…', 'bad', '1 d late'], ['alert', 'Call Priya about the Q4 budget variance', 'bad', '2 d late']] },
|
|
138
|
+
{ kpis: [['2', 'late', C.bad], ['1', 'outside PMOS', C.opp], ['80%', 'confidence', C.ok]], factsX: [['alert', 'Both are promises']], move: mv('es', 'Do the timeline first, then call Priya', 80) });
|
|
150
139
|
const WK_KW = card('kw', 'Just in', '2 things wait on others', 'Dara is 2 days silent on the keys. A nudge is drafted for each.',
|
|
151
140
|
{ kind: 'bars', bars: [['Dara: Payments sandbox keys', 1, C.bad, 'asked Tue'], ['Lee: Annual-plan label', 0.4, C.warn, 'asked Wed']] },
|
|
152
141
|
{ kpis: [['2', 'waiting', C.warn], ['2 d', 'oldest', C.bad], ['82%', 'confidence', C.ok]], factsX: [['user', 'Dara, Lee'], ['out', 'Nudges go to Review']], move: mv('nu', 'Nudge both (drafted)', 82) });
|
|
153
142
|
const WK_KL = card('kl', 'Opportunity', 'Today is 1.5 h of work against 1.8 h of focus', 'Move the two lowest-priority items to tomorrow and the day fits.',
|
|
154
143
|
{ kind: 'tiles', tiles: [['1.5 h', 'planned', C.acc], ['1.8 h', 'focus time', C.ok], ['0 h', 'over', C.bad]] },
|
|
155
144
|
{ kpis: [['2', 'to move', C.warn], ['0', 'promises hurt', C.ok], ['72%', 'confidence', C.acc]], factsX: [['clock', 'Capacity is the constraint']], move: mv('mv', 'Move the two lowest to tomorrow', 72) });
|
|
156
|
-
const WK_KDN = card('kdn', 'Big win', '0 done so far today', 'Each one closed without a context switch.', { kind: 'ring', done: 0, total:
|
|
145
|
+
const WK_KDN = card('kdn', 'Big win', '0 done so far today', 'Each one closed without a context switch.', { kind: 'ring', done: 0, total: 5, size: 96, cap: '0 closed', sub: '5 still open' },
|
|
157
146
|
{ kpis: [['1', 'facts', C.acc]], factsX: [['check', 'Momentum is good']] });
|
|
158
147
|
const DAYS7 = (dots) => ['Thu 8', 'Fri 9', 'Mon 12', 'Tue 13', 'Wed 14', 'Thu 15', 'Fri 16'].map((d, i) => [d, dots.includes(i)]);
|
|
159
|
-
const WK_KWK = card('kwk', '
|
|
160
|
-
{ eyebrow: { label: '
|
|
148
|
+
const WK_KWK = card('kwk', 'Upcoming', '5 items land in the next 7 days', 'Two happen outside PMOS. One waits for its Not before date on Monday.', { kind: 'days', days: DAYS7([1, 2, 3, 4]) },
|
|
149
|
+
{ eyebrow: { label: 'Upcoming', icon: 'cal' }, kpis: [['5', 'facts', C.acc]], factsX: [['cal', 'Next: Fri 9 Oct']] });
|
|
161
150
|
const WK_KJB = card('kjb', 'Jobs', '4 jobs to be done, in progress', 'Work grouped by the outcome it serves, not by where it came from.',
|
|
162
|
-
{ kind: 'bars', bars: [['Hold the 24 Oct date', 1 /
|
|
151
|
+
{ kind: 'bars', bars: [['Hold the 24 Oct date', 1 / 6, C.acc, '1 of 6'], ['Keep leadership informed', 0, C.acc, '0 of 1'], ['Learn why returns hurt', 0, C.acc, '0 of 3'], ['Keep customer promises', 0, C.acc, '0 of 1']] },
|
|
163
152
|
{ eyebrow: { label: 'Jobs', icon: 'target' }, kpis: [['4', 'facts', C.acc]], factsX: [['target', 'JTBD']] });
|
|
164
153
|
const WK_KPR = card('kpr', 'Opportunity', 'A customer promise falls on Monday', 'Confirm the SSO date to Marta after the freeze. It is yours and dated.', { kind: 'days', days: DAYS7([2]) },
|
|
165
154
|
{ kpis: [['86%', 'confidence', C.acc], ['2', 'options', C.opp], ['2', 'evidence', C.ok]], factsX: [['user', 'Marta'], ['flag', 'Keeps trust']], move: mv('td', 'Bring it into Today for Monday', 86) });
|
|
@@ -176,11 +165,13 @@ const LT_KLP = card('klp', 'Opportunity', 'The Q1 discovery plan is ready to pro
|
|
|
176
165
|
{ kpis: [['68%', 'confidence', C.acc], ['2', 'options', C.opp], ['1', 'evidence', C.ok]], factsX: [['spark', 'Has a window now']], move: mv('pr', 'Promote to this week', 68) });
|
|
177
166
|
const LT_KLS = card('kls', 'Small win', 'Nothing is stale', 'No someday item is older than 30 days.', { kind: 'ring', done: 1, total: 1, size: 96, cap: 'All fresh', sub: 'reviewed monthly' },
|
|
178
167
|
{ kpis: [['1', 'facts', C.acc]], factsX: [['check', 'Next review 1 Nov']] });
|
|
179
|
-
const WK_KEV = card('kev', 'Just in', '
|
|
180
|
-
{ kind: 'rows', rows: [['moon', '
|
|
181
|
-
{ eyebrow: { label: 'Evening review', icon: 'moon' }, kpis: [['
|
|
182
|
-
/* the deck the Work tab asks for, by
|
|
183
|
-
|
|
168
|
+
const WK_KEV = card('kev', 'Just in', '5 open at the end of the day', "Say Not yet, schedule or drop each one. Carry-overs lead tomorrow's briefing.",
|
|
169
|
+
{ kind: 'rows', rows: [['moon', 'Update the Checkout v2 risk log', '', '2026-10-08'], ['moon', 'Talk to Dara about QA capacity for t…', '', '2026-10-08'], ['moon', 'Prep the one-pager for the Search r…', '', 'flagged']] },
|
|
170
|
+
{ eyebrow: { label: 'Evening review', icon: 'moon' }, kpis: [['5', 'open', C.acc]], factsX: [['moon', 'Evening review']], move: mv('mg', 'Not yet: all to tomorrow', 80) });
|
|
171
|
+
/* the deck the Work tab asks for, by view (scope "work:<view>"); Close on Today adds the evening card first, as the mockup's S.tod === 'e' does.
|
|
172
|
+
* triage, week and later are the old lens names: the shell asks for "work:triage" before the view reports its own. */
|
|
173
|
+
const WORK_LENS = { inbox: [WORK_KI, WK_KJB], today: [WK_KO, WK_KR, WK_KW, WK_KL, WK_KDN], upcoming: [WK_KWK, WK_KPR, WK_KJB], waiting: [WT_KAG, WT_KCH, WT_KDP], someday: [LT_KLS, LT_KLP], review: [WORK_KRV, LT_KLS] };
|
|
174
|
+
WORK_LENS.triage = WORK_LENS.inbox; WORK_LENS.week = WORK_LENS.upcoming; WORK_LENS.later = WORK_LENS.someday;
|
|
184
175
|
|
|
185
176
|
/* Initiatives tab (the mockup's iniCards, Strategize) */
|
|
186
177
|
const INI_DIR = card('ydir', 'Year direction', 'North star is 4% behind and widening', 'It leaves tolerance in Dec \'26 and reaches the pivot line in Mar \'27.',
|
|
@@ -206,7 +197,7 @@ const PLAN_BRIEF = [DECISION, METRIC, DATE, INSIGHT, SSO].map(unfilled);
|
|
|
206
197
|
const BY_SCOPE = {
|
|
207
198
|
brief: PLAN_BRIEF,
|
|
208
199
|
cal: [CAL_DM, CAL_DD, CAL_DF, CAL_DL, CAL_DN],
|
|
209
|
-
work: [
|
|
200
|
+
work: [WK_KO, WK_KR, WORK_KI, WORK_KRV, WK_KDN],
|
|
210
201
|
init: [INI_DIR, INI_MAT, INI_SIG, { ...SSO, key: 'ysso' }, { ...BOARD, key: 'ybd' }],
|
|
211
202
|
rep: [REP_LEAD, REP_NEXT, { ...BOARD, key: 'rbd', pov: 'PRODUCT' }, { ...SEARCH, key: 'rsr' }, { ...SSO, key: 'rss' }],
|
|
212
203
|
};
|
|
@@ -367,57 +358,68 @@ export function calendarViews(url) {
|
|
|
367
358
|
selected: day ? { id: 'm4', title: 'Sprint planning', time: '14:00 to 15:00', text: 'Checkout v2: the sprint cannot start until scope is decided. The 24 Oct date depends on it.', status: 'Blocked on your decision', tone: 'bad', who: ['D', 'L', 'S'] } : null };
|
|
368
359
|
}
|
|
369
360
|
|
|
370
|
-
/* ── work items
|
|
361
|
+
/* ── work items: the PM's own work (initiative console3-work-page) and the four jobs ───────────────────────────────────────────────
|
|
362
|
+
* One home (PRD R9): the decisions and approvals the mockup listed in Work ("Approve pricing-page copy", "Say yes to Sam", the Review
|
|
363
|
+
* drafts, the scope decision) live in Briefing · Needs you and are not Work items. What is left is the PM's own to-do, with the console3
|
|
364
|
+
* feedback mockup's flavour: things done outside PMOS (talk to, call, set up a meeting), a "not before" date, a flag, a link to the
|
|
365
|
+
* team task an item advances, and one offline item with proof that it looks done. The answer is placed by work-place.js, the same rule
|
|
366
|
+
* the server applies, so the demo shows `work`, `review`, `focus` and `counts` in the real route's shape. */
|
|
371
367
|
|
|
372
368
|
const JOBS = [
|
|
373
|
-
{ id: 'j1', when: 'the scope is too big for the date', want: 'a decision fast and a clear plan', so: 'we ship on 24 Oct', mock_name: 'Hold the 24 Oct date'
|
|
374
|
-
{ id: 'j2', when: 'leadership asks for numbers', want: 'one answer that is true', so: 'their picture stays accurate', mock_name: 'Keep leadership informed'
|
|
375
|
-
{ id: 'j3', when: 'customers return items', want: 'to see where it hurts', so: 'we fix the right thing', mock_name: 'Learn why returns hurt'
|
|
376
|
-
{ id: 'j4', when: 'I promise a customer a date', want: 'to keep it or tell them early', so: 'trust holds', mock_name: 'Keep customer promises'
|
|
369
|
+
{ id: 'j1', when: 'the scope is too big for the date', want: 'a decision fast and a clear plan', so: 'we ship on 24 Oct', mock_name: 'Hold the 24 Oct date' },
|
|
370
|
+
{ id: 'j2', when: 'leadership asks for numbers', want: 'one answer that is true', so: 'their picture stays accurate', mock_name: 'Keep leadership informed' },
|
|
371
|
+
{ id: 'j3', when: 'customers return items', want: 'to see where it hurts', so: 'we fix the right thing', mock_name: 'Learn why returns hurt' },
|
|
372
|
+
{ id: 'j4', when: 'I promise a customer a date', want: 'to keep it or tell them early', so: 'trust holds', mock_name: 'Keep customer promises' },
|
|
377
373
|
];
|
|
378
374
|
let seq = 0;
|
|
379
|
-
const it = (title, o) => ({ title, due: null, priority: null, waiting_on: null, job: null, id: 'item-demo' + String(++seq).padStart(3, '0'), source: 'local', created_at: '2026-10-08T05:00:00.000Z',
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
];
|
|
407
|
-
|
|
375
|
+
const it = (title, o) => ({ title, due: null, priority: null, waiting_on: null, job: null, id: 'item-demo' + String(++seq).padStart(3, '0'), source: 'local', created_at: '2026-10-08T05:00:00.000Z', touched_at: '2026-10-07T16:00:00.000Z', steps: [], ...(o || {}) });
|
|
376
|
+
const at = (d) => d + 'T09:00:00.000Z';
|
|
377
|
+
/* Today: overdue, due today and flagged */
|
|
378
|
+
const W_TIMELINE = it('Send the revised Checkout v2 timeline to Dara', { due: '2026-10-07', priority: 'P1', job: 'j1', mock_row: true, kind: 'do', mins: 10, src: 'Jira', init: 'Checkout v2', agent_move: true });
|
|
379
|
+
const W_QA = it('Talk to Dara about QA capacity for the beta', { kind: 'offline', person: 'Dara', due: TODAY, flagged: true, job: 'j1',
|
|
380
|
+
evidence: 'Dara wrote in #checkout at 07:40: "QA plan agreed, 2 testers from Monday".' });
|
|
381
|
+
const W_CALL = it('Call Priya about the Q4 budget variance', { kind: 'offline', person: 'Priya', due: '2026-10-06', not_yet_count: 1, job: 'j2', touched_at: at('2026-10-06') });
|
|
382
|
+
const W_RISK = it('Update the Checkout v2 risk log', { due: TODAY, priority: 'P2', job: 'j1' });
|
|
383
|
+
const W_PAGER = it('Prep the one-pager for the Search relevance review', { flagged: true, resolves: 'search-beta-pack', resolves_title: 'Search relevance beta: review pack' });
|
|
384
|
+
/* Upcoming: the next 7 days, then Later */
|
|
385
|
+
const W_SAM = it('Set up a meeting with Sam about the logging rollout', { kind: 'offline', person: 'Sam', due: '2026-10-09' });
|
|
386
|
+
const W_SSO = it('Confirm the SSO date to Marta', { due: '2026-10-12', priority: 'P2', job: 'j4' });
|
|
387
|
+
const W_NOTES = it('Write release notes for SSO 1.0', { defer: '2026-10-12', due: '2026-10-14', resolves: 'sso-ga-checklist', resolves_title: 'SSO general release checklist' });
|
|
388
|
+
const W_SALES = it('Share returns insights with sales', { due: '2026-10-13', job: 'j3' });
|
|
389
|
+
const W_TAGS = it('Ask Marta for the top ten support tags', { kind: 'offline', person: 'Marta', due: '2026-10-14', job: 'j3', resolves: 'returns-interview-5' });
|
|
390
|
+
const W_Q1 = it('Write the Q1 discovery plan', { due: '2026-11-16' });
|
|
391
|
+
/* Inbox: captured, no date yet */
|
|
392
|
+
const W_TEAR = it('Read the competitor pricing teardown', { created_at: '2026-10-07T15:20:00.000Z' });
|
|
393
|
+
const W_BOOK = it('Book five user interviews for returns discovery', { job: 'j3', created_at: '2026-10-06T10:00:00.000Z', touched_at: at('2026-10-06') });
|
|
394
|
+
const W_IDEA = it('Idea: weekly digest for partner admins', { created_at: '2026-09-24T11:00:00.000Z', touched_at: at('2026-09-24') });
|
|
395
|
+
/* Waiting and Someday */
|
|
396
|
+
const W_KEYS = it('Payments sandbox keys', { waiting_on: 'Dara', due: '2026-10-06', job: 'j1', priority: 'P2', touched_at: at('2026-10-06') });
|
|
397
|
+
const W_LABEL = it('Annual-plan label', { waiting_on: 'Lee', due: '2026-10-07', job: 'j1', priority: 'P3', touched_at: at('2026-09-29') });
|
|
398
|
+
const W_JIRA = it('Clean up the Checkout Jira labels', { parked: true, created_at: '2026-09-10T09:00:00.000Z', touched_at: at('2026-09-18') });
|
|
399
|
+
const W_NOMEET = it('Try a no-meeting Wednesday', { parked: true, touched_at: at('2026-10-02') });
|
|
400
|
+
/* done yesterday: counts in its job, never drawn */
|
|
401
|
+
const W_PRD = it('Send revised PRD to Dara', { due: '2026-10-07', job: 'j1', done: true, done_at: '2026-10-07T17:10:00.000Z' });
|
|
402
|
+
const ITEMS = [W_TIMELINE, W_QA, W_CALL, W_RISK, W_PAGER, W_SAM, W_SSO, W_NOTES, W_SALES, W_TAGS, W_Q1, W_TEAR, W_BOOK, W_IDEA, W_KEYS, W_LABEL, W_JIRA, W_NOMEET, W_PRD];
|
|
403
|
+
/* Focus: up to three jobs for today, each with the reason (rules first: an overdue promise, an offline item due, a flag) */
|
|
404
|
+
const DAY_FOCUS = { suggestions: [
|
|
405
|
+
{ key: 'job:j1', title: 'Hold the 24 Oct date', why: 'An overdue promise: the revised timeline to Dara was due yesterday.', item_ids: [W_TIMELINE.id, W_QA.id, W_RISK.id], job: 'j1' },
|
|
406
|
+
{ key: 'item:' + W_CALL.id, title: 'Call Priya about the Q4 budget variance', why: 'Outside PMOS and 2 days overdue. You moved it once.', item_ids: [W_CALL.id], job: 'j2' },
|
|
407
|
+
{ key: 'item:' + W_PAGER.id, title: 'Prep the one-pager for the Search relevance review', why: 'You flagged it for today.', item_ids: [W_PAGER.id] },
|
|
408
|
+
], accepted: [] };
|
|
408
409
|
export function pmItems() {
|
|
409
|
-
const
|
|
410
|
+
const { work, review, counts } = buildWork(ITEMS, TODAY, 7);
|
|
411
|
+
const jobs = JOBS.map((j) => { const its = ITEMS.filter((i) => i.job === j.id); return { ...j, done: its.filter((i) => i.done).length, total: its.length }; });
|
|
412
|
+
/* the old five lists, kept for the readers that still use them (the shell's Work badge, the Calendar's dated tasks) */
|
|
413
|
+
const week = addDays(TODAY, 7);
|
|
414
|
+
const lists = { triage: work.inbox, today: work.today, week: work.upcoming.filter((i) => arrives(i, TODAY) <= week), waiting: work.waiting,
|
|
415
|
+
later: [...work.upcoming.filter((i) => arrives(i, TODAY) > week), ...work.someday] };
|
|
410
416
|
const due_today = [
|
|
411
417
|
{ id: 'i9', title: 'Payments sandbox keys', sub: 'asked Tue · Checkout v2', right: '2 d late', tone: 'bad' },
|
|
412
418
|
{ id: 'i2', title: 'Confirm the 1 Oct numbers before the 10:30 update', sub: '10:30 · Checkout v2', right: '2h 18m left', tone: 'warn', left: { text: '2h 18m left', pct: 0.3, tone: 'warn' } },
|
|
413
419
|
{ id: 'i1', title: 'Cut Checkout v2 to 3 core flows to hold 24 Oct?', sub: '14:00 · Checkout v2', right: 'At risk', tone: 'warn', left: { text: '5h 48m left', pct: 0.36, tone: 'acc' } },
|
|
414
420
|
{ id: 'i6', title: 'Review the 13:00 interview guide', sub: '13:00 · Returns experience', right: '4h 48m left', tone: 'warn', left: { text: '4h 48m left', pct: 0.55, tone: 'acc' } },
|
|
415
421
|
];
|
|
416
|
-
return { ok: true, today: TODAY, lists,
|
|
417
|
-
flow_note: 'The agent clarified every item before you saw it. Your part is the commit.',
|
|
418
|
-
noise: [{ title: 'Weekly product newsletter', src: 'Gmail' }, { title: 'Jira digest: 14 updates', src: 'Jira' }, { title: 'Figma: 3 files updated', src: 'Figma' }],
|
|
419
|
-
flow: [['Captured', 12, 'var(--muted-foreground)'], ['Clarified', 9, 'var(--acc)'], ['Committed', 1, 'var(--warning)'], ['Done', 0, 'var(--success)']],
|
|
420
|
-
lens_text: { triage: ['9 to triage, 3 take under 2 minutes', 'Nothing stays past 17:30. The agent has read all of it and drafted 3 replies.'] } };
|
|
422
|
+
return { ok: true, today: TODAY, lists, work, review, review_days: 7, focus: DAY_FOCUS, counts, jobs, items: ITEMS, due_today };
|
|
421
423
|
}
|
|
422
424
|
|
|
423
425
|
/* ── initiatives (the roadmap rows the Initiatives tab reads), forecast, releases ──────────────────────────────────────────────── */
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
/* pm/c3-demo-misc2.js — DEMO ONLY (/c3.html?demo=1): the sample content for
|
|
1
|
+
/* pm/c3-demo-misc2.js — DEMO ONLY (/c3.html?demo=1): the sample content for panels the content audit found missing (planning/research/console3-content-parity.md), in the REAL
|
|
2
2
|
* response shapes. Real mode never loads this file. installMisc2() is called from installDemo() in c3-demo.js, after the briefing fixtures, and wraps window.fetch once:
|
|
3
3
|
* GET /api/briefing (the Briefing's own scope) → adds `stop`: [{ icon, text }], the agent's proposals of what to stop (c3-misc-panels.js stopCard shows them in the Reflect lens only).
|
|
4
4
|
* The real route sends no `stop`, so a real Reflect lens draws no such panel.
|
|
5
|
-
* GET /api/pm-items → lists.today is the mockup's Today (console3.html wItems, line 3215): the work not yet done that is due today, and the inbox items
|
|
6
|
-
* due today. Those are the same item objects as in lists.triage, which is how the mockup's one item list shows them in both lenses.
|
|
7
|
-
* This puts "Say yes to Sam", which has no job, in Today, so the grouped list ends with "No job yet".
|
|
8
5
|
* window.__c3repix.INM → the initiative names the Project report's forecast card uses (mockup INM, line 3438).
|
|
9
|
-
* The Project forecast chart itself needs no fixture here: it reads /api/c3-timeline, which c3-demo-time.js answers.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/* The fixture's work set for today also holds five Review drafts the mockup does not list in Today. The Work view reads /api/pm-items before the fetch wrapper below exists,
|
|
14
|
-
* so the array (the very one pmItems() returns) is cut in place, once, when this file loads. The inbox items due today join Today in pm-work-view.js. */
|
|
15
|
-
{ const T = pmItems().lists.today; const keep = T.filter((x) => /^Cut Checkout v2/.test(x.title)); T.splice(0, T.length, ...keep); }
|
|
6
|
+
* The Project forecast chart itself needs no fixture here: it reads /api/c3-timeline, which c3-demo-time.js answers.
|
|
7
|
+
* The Work page's Today no longer comes from here: the server places every item (initiative console3-work-page), and the demo places its fixture with the same rule
|
|
8
|
+
* (work-place.js, called from c3-demo-data.js pmItems). */
|
|
16
9
|
|
|
17
10
|
const STOP = [
|
|
18
11
|
{ icon: 'cal', text: 'Skip Sprint planning, send the decision in writing' },
|
|
@@ -34,11 +27,6 @@ export function installMisc2() {
|
|
|
34
27
|
if (u.pathname === '/api/briefing' && (!u.searchParams.get('scope') || u.searchParams.get('scope') === 'brief')) {
|
|
35
28
|
const j = await res.clone().json();
|
|
36
29
|
if (j && j.ok !== false) return new Response(JSON.stringify({ ...j, stop: STOP }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
37
|
-
} else if (u.pathname === '/api/pm-items') {
|
|
38
|
-
const j = await res.clone().json();
|
|
39
|
-
if (j && j.lists) {
|
|
40
|
-
return new Response(JSON.stringify({ ...j, lens_text: { ...(j.lens_text || {}), today: ['6 committed today, 1 overdue', 'Best focus window is 11:15. 2 items wait on others.'] } }), { status: 200, headers: { 'content-type': 'application/json' } });
|
|
41
|
-
}
|
|
42
30
|
}
|
|
43
31
|
} catch { /* an unreadable answer goes through as it is */ }
|
|
44
32
|
return res;
|
|
@@ -1,76 +1,28 @@
|
|
|
1
|
-
/* pm/c3-demo-work-ix.js — the case-file content of the Work rows in DEMO mode (/c3.html?demo=1):
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* items never carry one, draws a case file from the item's own fields and never loads this file.
|
|
1
|
+
/* pm/c3-demo-work-ix.js — the case-file content of the Work rows in DEMO mode (/c3.html?demo=1): sample evidence, options and an agent
|
|
2
|
+
* run, in the console3 mockup's style (console3.html genTask 3216, the agent moves AM at line 4958). Importing this file attaches an `ix`
|
|
3
|
+
* object to demo work items of c3-demo-data.js, matched by TITLE (the item ids come from a counter in that file). pm-work-view.js reads
|
|
4
|
+
* `item.ix` only when the item has it, so real mode, whose items never carry one, draws a case file from the item's own fields.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
6
|
+
* The decisions and approvals that used to have case files here ("Approve pricing-page copy", "Say yes to Sam", the scope decision)
|
|
7
|
+
* are Briefing · Needs you items now (initiative console3-work-page, PRD R9); their case files live in c3-demo-brief-ix.js.
|
|
8
|
+
*
|
|
9
|
+
* Shape of ix: { move, conf, why: [text, source] (the first evidence row), chg / unsure / ign (a case of its own), steps: [agent run steps],
|
|
8
10
|
* res: { h, draft?, out?, claims: [[label, text]] }, ev: { h, claims } }
|
|
9
11
|
* Nothing here is sent anywhere: a draft stays a draft (the case file's slide for a send is disabled: no channel connected). */
|
|
10
12
|
import { pmItems } from './c3-demo-data.js';
|
|
11
13
|
|
|
12
14
|
const FIX = {
|
|
13
|
-
'
|
|
14
|
-
move: '
|
|
15
|
-
why: ['
|
|
16
|
-
steps: ['Reading the
|
|
17
|
-
res: { h: '
|
|
18
|
-
claims: [['fact', '
|
|
19
|
-
ev: { h: 'Where
|
|
20
|
-
},
|
|
21
|
-
'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
steps: ['Reading the whiteboard photo', 'Matching actions to owners', 'Drawing the owner map'],
|
|
25
|
-
res: { h: 'Owners for the two workshop actions', claims: [['est', 'Action 2 was read at medium confidence (handwriting)'], ['fact', 'Dara owns the sandbox work in Jira (CHK-141)'], ['fact', 'Lee owns the pricing label in Figma']] },
|
|
26
|
-
ev: { h: 'What was read from the photo', claims: [['est', 'Action 1: sandbox follow-up, read at high confidence'], ['est', 'Action 2: label review, read at medium confidence'], ['rep', 'Both due next week, from the whiteboard']] },
|
|
27
|
-
},
|
|
28
|
-
'Approve pricing-page copy before handoff': {
|
|
29
|
-
move: '"Save 20% billed yearly"', conf: 70,
|
|
30
|
-
why: ['One open Figma comment on the annual-plan label.', 'Figma'],
|
|
31
|
-
steps: ['Reading the Figma comment', 'Reading the PRD pricing section', 'Writing the options'],
|
|
32
|
-
res: { h: 'Three slides for the pricing-copy call', claims: [['fact', '"Save 20% billed yearly" shows the discount'], ['est', 'Longer on mobile, one line wraps'], ['ass', 'Customers read "Annual" as a price rise']] },
|
|
33
|
-
ev: { h: 'What Figma says', claims: [['fact', 'One open comment, from Lee on Tue'], ['est', 'The file was last read Tue 17:10, so newer comments may exist']] },
|
|
34
|
-
},
|
|
35
|
-
'Review the 13:00 interview guide': {
|
|
36
|
-
move: 'Ask openly: "What did you do when printing failed?"', conf: 76,
|
|
37
|
-
why: ['Question 6 may lead the answer.', 'Agent'],
|
|
38
|
-
steps: ['Reading the last two calls', 'Reading the guide'],
|
|
39
|
-
res: { h: 'Question 6 is set and the guide is ready', claims: [['fact', 'Question 6 now reads "What did you do when printing failed?"'], ['est', 'The open wording does not lead the answer'], ['fact', 'Written to discovery/returns-guide, pushed to its own branch']] },
|
|
40
|
-
ev: { h: 'Why question 6 needs your wording', claims: [['fact', '2 of 3 calls mentioned the support route'], ['rep', '"we just call support" (call 2, imported)'], ['ass', 'A direct question about support may lead the answer']] },
|
|
41
|
-
},
|
|
42
|
-
'Confirm the growth assumption in the Q4 forecast': {
|
|
43
|
-
move: 'Set 4% and tell Marta', conf: 72,
|
|
44
|
-
why: ['Forecast row 14 says 6% growth.', 'Sheets'],
|
|
45
|
-
steps: ['Reading forecast row 14', 'Reading the PRD section', 'Comparing with 3 months of actuals'],
|
|
46
|
-
res: { h: 'The growth assumption has three sources and two disagree', claims: [['fact', 'Actual growth over 3 months is 3.8%'], ['est', 'Setting 4% leaves a gap of 0.2 points, 2 either way'], ['ass', 'Finance locks the forecast tomorrow as planned']] },
|
|
47
|
-
ev: { h: 'Where 6% came from', claims: [['fact', 'Row 14 was last edited 3 days ago by Finance'], ['rep', 'The edit note says "growth target" (imported text, untrusted)'], ['est', 'The 6% matches the target, not the trend']] },
|
|
48
|
-
},
|
|
49
|
-
'Say yes to Sam: ship the logging change': {
|
|
50
|
-
move: 'Open the logging change as a pull request', conf: 84,
|
|
51
|
-
why: ['Sam asked for a yes in Slack.', 'Slack'],
|
|
52
|
-
steps: ['Reading the change', 'Running lint locally'],
|
|
53
|
-
res: { h: 'Logging change is up for review', claims: [['fact', 'Local lint passes'], ['fact', 'Pushed 5c1d7aa to initiative/search-relevance (observed)'], ['fact', 'Pull request opened; it merges when required CI is green']] },
|
|
54
|
-
ev: { h: 'What the change touches', claims: [['fact', 'Adds one log line in src/rank.js'], ['est', 'No behaviour change, low risk']] },
|
|
55
|
-
},
|
|
56
|
-
'Pick interview slots with 3 customers': {
|
|
57
|
-
move: 'Accept the 3 held slots', conf: 80,
|
|
58
|
-
why: ['3 slots are held, none overlaps a standing meeting.', 'Calendar'],
|
|
59
|
-
steps: ['Reading the calendar', 'Writing the slot brief'],
|
|
60
|
-
res: { h: 'A one-page brief on the three held slots', claims: [['fact', '3 slots held, none overlapping (calendar, now)'], ['rep', 'Marta sent five times (Gmail, imported)']] },
|
|
61
|
-
ev: { h: 'Why these three slots', claims: [['fact', '3 slots held, none overlapping (calendar, now)'], ['rep', 'Marta sent five times (Gmail, imported)']] },
|
|
62
|
-
},
|
|
63
|
-
'Self-serve onboarding: continue or park?': {
|
|
64
|
-
move: 'Park and revisit in Q1', conf: 66,
|
|
65
|
-
why: ['9 days without movement', 'Jira'],
|
|
66
|
-
steps: ['Reading the stop rule'],
|
|
67
|
-
res: { h: 'Parked, with a line in the Q1 plan', claims: [['fact', 'Stop rule asked for 2 signals by 30 Sep, found 0'], ['fact', 'One line added to the Q1 planning list'], ['est', 'Frees one discovery slot']] },
|
|
68
|
-
ev: { h: 'Why it was kept this long', claims: [['fact', '9 days without movement (Jira)'], ['rep', 'The brief said "2 signals by 30 Sep" (imported)']] },
|
|
69
|
-
},
|
|
70
|
-
'Cut Checkout v2 to 3 core flows to hold 24 Oct?': {
|
|
71
|
-
sit: 'When the scope is too big for the date, I want a decision fast and a clear plan, so we ship on 24 Oct.',
|
|
72
|
-
chg: [['scale', 'Four flows fit 31 Oct, three fit 24 Oct.', 'Plan run 09:12', 'warn'], ['doc', 'The PRD lists 4 flows (v5)', 'Confluence', 'warn'], ['clock', 'The decision meeting is at 14:00', 'Calendar']],
|
|
73
|
-
unsure: 'The 24 Oct promise was made to leadership.', ign: 'The forecast stays at 31 Oct.',
|
|
15
|
+
'Send the revised Checkout v2 timeline to Dara': {
|
|
16
|
+
move: 'Draft the email with the 24 Oct plan and the two risks', conf: 80,
|
|
17
|
+
why: ['You told Dara on Tuesday the timeline would follow by Wednesday.', 'Gmail'],
|
|
18
|
+
steps: ['Reading the Jira board', 'Reading the risk log', 'Drafting the email'],
|
|
19
|
+
res: { h: 'Email to Dara drafted: the 24 Oct plan and two risks', draft: 'Hi Dara, here is the revised Checkout v2 timeline. Code freeze 20 Oct, release 24 Oct. Two risks: the sandbox keys and QA capacity. I will confirm QA with you today.', out: 'Dara, email',
|
|
20
|
+
claims: [['fact', 'Code freeze 20 Oct, from the Jira board'], ['fact', 'Two open risks in the risk log'], ['est', 'QA capacity is not confirmed yet']] },
|
|
21
|
+
ev: { h: 'Where the dates come from', claims: [['fact', 'Jira board "Checkout v2", fix version 24 Oct'], ['rep', 'Dara asked on Tuesday (Gmail, imported)']] },
|
|
22
|
+
},
|
|
23
|
+
'Update the Checkout v2 risk log': {
|
|
24
|
+
sit: 'Two risks changed this week: the sandbox keys are late and QA capacity is open. The log still shows both as green.',
|
|
25
|
+
chg: [['alert', 'Payments sandbox keys: 2 days late', 'Jira', 'warn'], ['user', 'QA capacity: talk to Dara today', 'Work'], ['clock', 'Last edited 6 days ago', 'Confluence']],
|
|
74
26
|
},
|
|
75
27
|
};
|
|
76
28
|
|
|
@@ -15,12 +15,12 @@ import './c3-demo-cal-ix.js'; // Calendar and the ribbon: sets window.__c3cali
|
|
|
15
15
|
import './c3-demo-cal3.js'; // Calendar Quarter and Year: answers GET /api/c3-strategy (chained under this file's fetch wrapper)
|
|
16
16
|
import './c3-demo-rep-ix.js'; // Reports fixtures: sets window.__c3repix (report-families-view.js reads it in demo only)
|
|
17
17
|
import './c3-demo-cal2.js'; // Calendar After notes, Week grid, Progress and timing: sets window.__c3cal2 (calendar-views.js reads it in demo only)
|
|
18
|
-
import './c3-demo-work2.js'; // Work Jobs card: sets window.__c3work2 (pm-work-view.js reads it in demo only)
|
|
19
18
|
import './c3-demo-work-ix.js'; // Work case files: attaches `ix` (evidence, options, agent steps) to the demo work items (pm-work-view.js reads it in demo only)
|
|
20
19
|
import './c3-demo-term.js'; // Agent surfaces: registers the run fixtures (window.__c3termfx_make); c3-terminal-data.js reads them in demo only
|
|
21
20
|
import './c3-demo-life.js'; // Lifecycle panels: ideas, hypotheses, lessons, gates, evaluations, corrections (wraps window.fetch for those GETs; innermost, so it loads before realFetch is taken)
|
|
22
|
-
import './c3-demo-misc2.js'; //
|
|
21
|
+
import './c3-demo-misc2.js'; // the Reflect lens's "What I could stop" (installMisc2 wraps the fetch after the briefing fixtures)
|
|
23
22
|
import { timeline } from './c3-demo-time.js'; // Initiatives Timeline: every check as a frame, in the real /api/c3-timeline shape
|
|
23
|
+
import { withCalendarPage } from './c3-demo-cal4.js'; // Calendar page: the layers, the free blocks and the meeting context on the calendar-views answer
|
|
24
24
|
import './c3-demo-plan2.js'; // Commitments, Why it slips, Dependencies and Releases of Deliver: sets window.__c3planfx (c3-plan-panels.js, timeline-forecast-view.js, releases-view.js read it in demo only)
|
|
25
25
|
import { installRep3 } from './c3-demo-rep3.js'; // Reports dashboards: GET /api/report-dashboards and /data, POST layout (installed OVER this file's wrapper, see its header)
|
|
26
26
|
|
|
@@ -49,7 +49,7 @@ function getRoute(path, url) {
|
|
|
49
49
|
const q = new URL(url, location.origin).searchParams;
|
|
50
50
|
switch (path) {
|
|
51
51
|
case '/api/briefing': return D.briefing(url);
|
|
52
|
-
case '/api/calendar-views': return D.calendarViews(url);
|
|
52
|
+
case '/api/calendar-views': return withCalendarPage(D.calendarViews(url));
|
|
53
53
|
case '/api/pm-items': return D.pmItems();
|
|
54
54
|
case '/api/runs': return D.runs();
|
|
55
55
|
case '/api/releases': return D.releases();
|
|
@@ -75,6 +75,13 @@ function postRoute(path, body) {
|
|
|
75
75
|
try { b = body ? JSON.parse(body) : {}; } catch { b = {}; }
|
|
76
76
|
if (path === '/api/pm-items/add') return { ok: true, item: { id: 'item-demo-new' + (++added), title: String(b.line || '').trim() || 'New item', due: null, priority: null, waiting_on: null, job: null, steps: [], created_at: D.NOW_ISO + 'Z' } };
|
|
77
77
|
if (path === '/api/pm-items/remove') return { ok: true, undo_ms: 8000 };
|
|
78
|
+
/* the Work page's routes (initiative console3-work-page): each answers as the real route does; pm-work-view.js lays the press over the fixtures */
|
|
79
|
+
if (path === '/api/pm-items/notyet') {
|
|
80
|
+
const when = String(b.when || ''); const n = when === 'tomorrow' ? 1 : when === 'next-week' ? 7 : 0;
|
|
81
|
+
return { ok: true, defer: n ? new Date(Date.parse(D.TODAY + 'T00:00:00Z') + n * 86400000).toISOString().slice(0, 10) : null };
|
|
82
|
+
}
|
|
83
|
+
if (['/api/pm-items/flag', '/api/pm-items/defer', '/api/pm-items/someday', '/api/pm-items/waiting', '/api/pm-items/keep', '/api/pm-items/done'].includes(path)) return { ok: true, item: { id: b.id } };
|
|
84
|
+
if (path.startsWith('/api/pm-items/focus/')) return { ok: true, key: b.key };
|
|
78
85
|
if (path === '/api/pm-items/undo') return { ok: true, item: { id: b.id } };
|
|
79
86
|
if (path === '/api/pm-items/steps/add' || path === '/api/pm-items/steps/plan') return { ok: true, step: { id: 'step-demo' + (++added), text: b.text || 'Review the plan', who: b.who || 'you', done: false } };
|
|
80
87
|
if (path === '/api/pm-items/steps/who') return { ok: true, who: b.who };
|
|
@@ -44,6 +44,7 @@ import { mountTicker } from './shell-ticker.js';
|
|
|
44
44
|
import { mountBell, openBellList } from './shell-bell.js';
|
|
45
45
|
import { onBriefing, loadBriefing } from './shell-ticker.js';
|
|
46
46
|
import { registry, filterPalette, rowStep } from './briefing-model.js';
|
|
47
|
+
import { freshnessLine } from './freshness-line.js';
|
|
47
48
|
|
|
48
49
|
/* In demo mode each tab's deck asks for its own cards (the mockup draws a different deck per tab); real mode sends no scope. */
|
|
49
50
|
const sc = (name) => (DEMO ? name : undefined);
|
|
@@ -105,7 +106,9 @@ function header() {
|
|
|
105
106
|
mountCalPopupTrigger(S.tools); // the calendar button (before Search) and the key C (c3-calpop.js)
|
|
106
107
|
S.moreBtn = ibtn('More', 'more', { 'aria-haspopup': 'menu', 'aria-expanded': 'false', 'data-c3pg': 'more', onclick: toggleMore });
|
|
107
108
|
S.tools.append(el('span', { class: 'c3pg-menuwrap' }, S.moreBtn));
|
|
108
|
-
|
|
109
|
+
/* workspace-data-freshness R9: how old the sources are, quiet when they are up to date (freshness-line.js) */
|
|
110
|
+
S.fresh = S.demo ? null : freshnessLine(S.ws);
|
|
111
|
+
return el('div', { class: 'c3top' }, el('div', null, S.ttl, S.sub, S.fresh), S.tools);
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
/* the More menu (console3.html menuPop, line 1940): why each element is here, incident day, agent playbooks, phone preview; and ours: the theme and a way to
|
|
@@ -484,7 +487,7 @@ function draftsTab(pane) {
|
|
|
484
487
|
});
|
|
485
488
|
}
|
|
486
489
|
|
|
487
|
-
const BUILD = { brief: briefTab, cal: (p) => mountCalendarViews(p, { view: '
|
|
490
|
+
const BUILD = { brief: briefTab, cal: (p) => mountCalendarViews(p, { view: 'week', page: true, scope: sc('cal') }), work: (p) => { const plan = planNode(sc('work') && 'work:triage'); mountPmWork(p, { plan, onLens: (l) => { if (DEMO) setPlanScope(plan, 'work:' + l); } }); }, init: initTab, rep: repTab };
|
|
488
491
|
|
|
489
492
|
function showTab(id, push) {
|
|
490
493
|
if (!BUILD[id]) id = 'brief';
|
|
@@ -18,6 +18,7 @@ import { renderCase } from './c3-case.js';
|
|
|
18
18
|
import { makeXp, attachExpand } from './c3-expand.js';
|
|
19
19
|
import { mountQuarter, mountYear, dispose as disposeStrategy } from './c3-cal-strategy.js'; // the Quarter and Year panes
|
|
20
20
|
import { simDock, simStart, simState, isDemo } from './shell-recording.js';
|
|
21
|
+
import { drawCalendarPage } from './c3-calendar.js'; // the Calendar tab as a page about time (ctx.page)
|
|
21
22
|
|
|
22
23
|
/* ── shared state of the /c3.html page (console3 wave C3) ──────────────────────────────────────────────────────────
|
|
23
24
|
* `live` holds every mounted instance, so a press in the Briefing's ribbon can select a meeting on the Calendar tab's instance.
|
|
@@ -28,7 +29,8 @@ let SHARED_SEL = null; // a meeting chosen on the Briefing, read when the Cale
|
|
|
28
29
|
const IX = () => (typeof window !== 'undefined' && window.__c3calix) || null;
|
|
29
30
|
const baseId = (e) => String((e && e.id) || '').split('@')[0];
|
|
30
31
|
const gotoTab = (id) => { const b = document.querySelector('[data-c3-tab="' + id + '"]'); if (b) b.click(); };
|
|
31
|
-
const isC3 = (r) => !!(r && r.ctx && (r.ctx.ribbon || r.ctx.plan));
|
|
32
|
+
const isC3 = (r) => !!(r && r.ctx && (r.ctx.ribbon || r.ctx.plan || r.ctx.page));
|
|
33
|
+
const isCalTab = (x) => !!(x && x.ctx && (x.ctx.plan || x.ctx.page)); // the Calendar tab's own instance (the older mount passes plan, the page passes page)
|
|
32
34
|
|
|
33
35
|
const NS = 'http://www.w3.org/2000/svg';
|
|
34
36
|
const VIEWS = [['day', 'Day'], ['week', 'Week'], ['month', 'Month'], ['quarter', 'Quarter'], ['year', 'Year']];
|
|
@@ -564,7 +566,7 @@ const todayOf = (r) => (r.data && r.data.today) || todayStr();
|
|
|
564
566
|
/* a day (and a meeting) on the Calendar tab's own instance; if that tab has not mounted yet its instance reads PENDING when it does */
|
|
565
567
|
let PENDING = null;
|
|
566
568
|
function openDay(date, sel) {
|
|
567
|
-
const cal = [...live].find(
|
|
569
|
+
const cal = [...live].find(isCalTab);
|
|
568
570
|
if (cal) { cal.sel = sel || null; cal.phase = null; go(cal.host, cal, { view: 'day', date, anim: 'zi' }); }
|
|
569
571
|
else PENDING = { date, sel: sel || null };
|
|
570
572
|
gotoTab('cal');
|
|
@@ -878,6 +880,7 @@ function weekHead(data) {
|
|
|
878
880
|
|
|
879
881
|
function draw(host, r) {
|
|
880
882
|
if (r.ctx && r.ctx.ribbon) { r.root.replaceChildren(ribbonCard(host, r)); if (!r.loading) r.anim = null; return; }
|
|
883
|
+
if (r.ctx && r.ctx.page) { disposeStrategy(r.root); r.root.replaceChildren(drawCalendarPage(host, r, PAGE)); if (!r.loading) r.anim = null; return; } // the Calendar tab (c3-calendar.js): no deck, no due list, no strategy lens
|
|
881
884
|
const phone = r.root.classList.contains('ph');
|
|
882
885
|
const head = bar(host, r, phone);
|
|
883
886
|
let body;
|
|
@@ -904,6 +907,10 @@ function draw(host, r) {
|
|
|
904
907
|
r.root.replaceChildren(head, body);
|
|
905
908
|
}
|
|
906
909
|
|
|
910
|
+
/* What the Calendar page (c3-calendar.js) reuses from this file: the zoom bar with the Today chip, the reads and the date helpers. */
|
|
911
|
+
const PAGE = { bar, go: (host, r, change) => go(host, r, change), redraw: (host, r) => draw(host, r), shift, dayLabel, timeOf, endH, hhmm, baseId, dowOf, MN, DN,
|
|
912
|
+
gotoTab, goInitiative, openInit };
|
|
913
|
+
|
|
907
914
|
/* Change the view or the date and read again. anim names the mockup's transition (.zi, .zo, .sn, .sp, console3.css). */
|
|
908
915
|
function go(host, r, change) {
|
|
909
916
|
if (change.view) r.view = change.view;
|
|
@@ -982,7 +989,7 @@ function wire(host, r) {
|
|
|
982
989
|
let simOnWin = false;
|
|
983
990
|
function installSim() {
|
|
984
991
|
if (simOnWin || typeof window === 'undefined') return; simOnWin = true;
|
|
985
|
-
const calInst = () => [...live].filter((x) => x
|
|
992
|
+
const calInst = () => [...live].filter((x) => isCalTab(x) && x.root.isConnected);
|
|
986
993
|
window.addEventListener('c3-sim-rec', () => calInst().forEach((x) => { if (selEvent(x)) refreshSel(x.host, x); }));
|
|
987
994
|
window.addEventListener('c3-sim-open', () => { gotoTab('cal'); calInst().forEach((x) => { if (x.view !== 'day') x.view = 'day'; x.phase = 'during'; if (x.data && x.data.unit === 'day' && x.data.range.start === x.data.range.end) refreshSel(x.host, x); else go(x.host, x, { view: 'day', date: todayOf(x) }); }); });
|
|
988
995
|
}
|
|
@@ -3499,6 +3499,39 @@
|
|
|
3499
3499
|
.fr-set__envrows { display: flex; flex-direction: column; gap: 6px; }
|
|
3500
3500
|
.fr-set__flags { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; min-width: 0; width: 100%; }
|
|
3501
3501
|
|
|
3502
|
+
/* workspace-data-freshness: the console header's freshness line and Settings › Data freshness (pm/freshness-line.js) */
|
|
3503
|
+
.fr-fresh {
|
|
3504
|
+
display: flex;
|
|
3505
|
+
flex-wrap: wrap;
|
|
3506
|
+
align-items: center;
|
|
3507
|
+
gap: 8px;
|
|
3508
|
+
font-size: 12px;
|
|
3509
|
+
color: var(--muted-foreground);
|
|
3510
|
+
}
|
|
3511
|
+
.fr-fresh__state--fresh { color: var(--foreground); }
|
|
3512
|
+
.fr-fresh__state--stale { color: var(--warning); }
|
|
3513
|
+
.fr-fresh__state--failed { color: var(--destructive); }
|
|
3514
|
+
.fr-fresh__state--never,
|
|
3515
|
+
.fr-fresh__state--building,
|
|
3516
|
+
.fr-fresh__state--off { color: var(--muted-foreground); }
|
|
3517
|
+
.fr-fresh-table__grid {
|
|
3518
|
+
width: 100%;
|
|
3519
|
+
border-collapse: collapse;
|
|
3520
|
+
font-size: 12px;
|
|
3521
|
+
}
|
|
3522
|
+
.fr-fresh-table__grid th {
|
|
3523
|
+
padding: 4px 8px;
|
|
3524
|
+
text-align: left;
|
|
3525
|
+
font-weight: 600;
|
|
3526
|
+
}
|
|
3527
|
+
.fr-fresh-table__grid td {
|
|
3528
|
+
padding: 4px 8px;
|
|
3529
|
+
vertical-align: top;
|
|
3530
|
+
}
|
|
3531
|
+
.fr-fresh-table__watch {
|
|
3532
|
+
margin-top: 8px;
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3502
3535
|
/* -- layouts · .td- Today (Run 3, slice 3B) -- */
|
|
3503
3536
|
.td-board {
|
|
3504
3537
|
display: flex;
|
|
@@ -7932,4 +7965,5 @@
|
|
|
7932
7965
|
containers). The tab strip and the scratchpad are drawn with inline radii (workspace-cockpit-nav); the inline radii
|
|
7933
7966
|
(8px tab strip, 6px controls) stay. A tab's colour dot is a dot and keeps its circle. */
|
|
7934
7967
|
|
|
7968
|
+
/* -- layouts · .cal3- the console3 Calendar page (calendar-time-page): its rules sit in console3.css beside the .vday rules they extend -- */
|
|
7935
7969
|
/* -- end of the layout sections: add nothing below this line -- */
|