create-pmos 0.115.0 → 0.117.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 (33) 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/guide/notes.md +2 -0
  7. package/workspace/guide/today.md +21 -12
  8. package/workspace/lib/audio-resample.js +76 -0
  9. package/workspace/lib/brief.js +2 -2
  10. package/workspace/lib/day-marks.js +29 -9
  11. package/workspace/lib/goals.js +68 -10
  12. package/workspace/lib/notes-audio.js +62 -20
  13. package/workspace/lib/notes-echo.js +32 -14
  14. package/workspace/lib/notes-hires.js +94 -0
  15. package/workspace/lib/notes-polish.js +2 -3
  16. package/workspace/lib/routes-notes.js +9 -1
  17. package/workspace/notes-test-signals.js +65 -0
  18. package/workspace/package-lock.json +2 -2
  19. package/workspace/package.json +1 -1
  20. package/workspace/public/pm/audio-capture.js +20 -6
  21. package/workspace/public/pm/components.css +39 -11
  22. package/workspace/public/pm/today-agenda.js +27 -53
  23. package/workspace/public/pm/today-copy.js +34 -15
  24. package/workspace/public/pm/today-day.js +98 -76
  25. package/workspace/public/pm/today-goals.js +159 -15
  26. package/workspace/public/pm/today-jobs.js +84 -0
  27. package/workspace/public/pm/today-mentions.js +50 -0
  28. package/workspace/public/pm/view-notes.js +15 -4
  29. package/workspace/server.js +10 -8
  30. package/workspace/test-lists/meeting-notes.json +5 -3
  31. package/workspace/test-lists/today-goals-jobs-ui.json +5 -0
  32. package/workspace/test-lists/today-goals-jobs.json +5 -0
  33. package/workspace/test-lists/today-mentions-cards.json +5 -0
@@ -1,6 +1,6 @@
1
1
  /* pm/today-agenda.js — the Agenda month in the left column of Today (initiative today-goals, PRD G25 to G27).
2
2
  *
3
- * agendaPanel(ctx, h) → the panel: the month on top, the agenda of the picked day or week under it; null when the
3
+ * agendaPanel(ctx, h) → the panel: the agenda of the picked day or week (today at first); the month shows above it on the Calendar press; null when the
4
4
  * server has no agenda route (an older workspace), so the day's calendar list shows instead
5
5
  * meetingDetail(ctx, id) → the right column for a remembered meeting: what it means, before it, the last time
6
6
  * memoryBlock(id) → the series part of that detail, for a meeting of today drawn from the brief
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * One read feeds it: GET /api/proactive/agenda?month=YYYY-MM. The server derives every answer (how full, the key
11
11
  * meeting, readiness, no notes); this file only draws them. A day cell is a button: it picks that day. A week number
12
- * picks the week. The lens switch says which answer the cells show; the agenda under the month always shows them all.
12
+ * picks the week.
13
13
  * The calendar memory changes only through the morning-brief session's brief. The one write here is the PM's key-meeting
14
14
  * mark (POST /api/proactive/agenda/mark): this time or every time, key or not key, over what the calendar says.
15
15
  * A meeting's detail shows its meeting notes, and an event row a notes chip, from pm/notes-links.js (meeting-notes Run 4);
@@ -18,17 +18,16 @@
18
18
 
19
19
  import { peopleLinks } from './people-link.js';
20
20
  import { el } from './dom.js';
21
- import { button, iconButton, badge, tabs, toast } from './primitives.js';
21
+ import { button, iconButton, badge, toast } from './primitives.js';
22
22
  import { pmGet, pmPost } from './http.js';
23
23
  import { COPY } from './today-copy.js';
24
24
  import { notesBlock, notesFor } from './notes-links.js';
25
25
 
26
26
  const A = COPY.day.ag;
27
- const DAY0 = 9 * 60;
28
- const SPAN = 9 * 60;
29
27
 
30
28
  const cache = new Map(); // month -> { data, error, old }
31
- const ag = { month: null, pick: null, lens: 'overview', showRoutine: false };
29
+ // pick and calOpen are the PM's view of this page only: neither is stored, so a reload opens on today's meetings, month hidden
30
+ const ag = { month: null, pick: null, calOpen: false, showRoutine: false };
32
31
  let redraw = () => {};
33
32
  let inflight = new Set();
34
33
 
@@ -108,60 +107,35 @@ export function litSeries(sel) {
108
107
 
109
108
  /* ── the month ────────────────────────────────────────────────────────────────────────────────────────────────── */
110
109
 
111
- function strip(day, lens) {
112
- const s = el('div', { class: 'td-ag__strip', 'aria-hidden': 'true' });
113
- const put = (from, mins, cls) => {
114
- const a = Math.max(0, from - DAY0); const b = Math.min(SPAN, from - DAY0 + mins);
115
- if (b <= a) return;
116
- const n = el('span', { class: cls });
117
- n.style.setProperty('--td-ag-l', (a / SPAN * 100).toFixed(2) + '%');
118
- n.style.setProperty('--td-ag-w', ((b - a) / SPAN * 100).toFixed(2) + '%');
119
- s.append(n);
120
- };
121
- if (lens === 'free') for (const [a, b] of day.gaps) put(a, b - a, 'td-ag__blk td-ag__blk--free');
122
- else for (const e of day.events) { const [h, m] = e.time.split(':').map(Number); put(h * 60 + m, e.minutes, 'td-ag__blk' + (e.imp ? ' td-ag__blk--imp' : '')); }
123
- return s;
124
- }
125
-
126
110
  function readyBadge(day, withWord) {
127
111
  if (!day.need) return null;
128
112
  return el('span', { class: 'td-ag__ready', 'data-ag-ready': day.ready || 'ok', title: A.readyWord[day.ready || 'ok'] },
129
113
  A.readyBadge(day.done, day.need), withWord ? ' ' + A.readyWord[day.ready || 'ok'] : null);
130
114
  }
131
115
 
132
- function keyLine(day) {
133
- if (!day.key) return null;
134
- const e = day.events.find((x) => x.id === day.key);
135
- return e ? el('span', { class: 'td-ag__key' }, A.star + ' ' + e.title) : null;
136
- }
116
+ /** How full a day is, as a step from 0 to 3: the meetings that are not repeating ones. The cell tints by it. */
117
+ const loadOf = (day) => { const n = day.events.filter((e) => !e.routine).length; return n === 0 ? 0 : n === 1 ? 1 : n <= 3 ? 2 : 3; };
137
118
 
119
+ /* A2: a cell is the day number, a tint for how many meetings the day has, and a star when it has a key meeting. */
138
120
  function cell(data, d, lit) {
139
121
  const day = data.days[d];
140
- const lens = ag.lens;
141
- const kids = [el('span', { class: 'td-ag__num' }, String(Number(d.slice(8))))];
142
- if (day.events.length && (lens === 'overview' || lens === 'full' || lens === 'free')) kids.push(strip(day, lens));
143
- if (lens === 'overview') {
144
- kids.push(keyLine(day));
145
- if (day.ready === 'risk' || day.ready === 'warn' || (day.ready === 'ok' && !day.past)) kids.push(readyBadge(day));
146
- if (day.past && day.no_notes) kids.push(el('span', { class: 'td-ag__nonotes' }, A.noNotes(day.no_notes)));
147
- } else if (lens === 'full') {
148
- if (day.busy) kids.push(el('span', { class: 'td-ag__meta' }, A.busy(day.busy)));
149
- } else if (lens === 'key') {
150
- kids.push(keyLine(day));
151
- } else if (lens === 'ready') {
152
- kids.push(readyBadge(day, false));
153
- if (day.past && day.no_notes) kids.push(el('span', { class: 'td-ag__nonotes' }, A.noNotes(day.no_notes)));
154
- } else if (lens === 'free' && !day.weekend) {
155
- kids.push(el('span', { class: 'td-ag__meta' }, day.gaps.length ? A.freeBlocks(day.gaps.length) : A.noFree));
156
- }
122
+ const key = day.key ? day.events.find((x) => x.id === day.key) : null;
123
+ const kids = [el('span', { class: 'td-ag__num' }, String(Number(d.slice(8)))), key ? el('span', { class: 'td-ag__star', 'aria-hidden': 'true' }, A.star) : null];
157
124
  const picked = ag.pick && ag.pick.type === 'day' && ag.pick.date === d;
158
125
  return button({ label: el('span', { class: 'td-ag__in' }, ...kids.filter(Boolean)), variant: 'ghost', onClick: () => pickDay(d),
159
- attrs: { class: 'td-ag__cell', 'data-ag-day': d, 'data-out': day.in_month ? null : 'true', 'data-past': day.past ? 'true' : null, 'data-today': day.today ? 'true' : null,
160
- 'data-weekend': day.weekend ? 'true' : null, 'data-lit': lit && lit.has(d) ? 'true' : null, 'aria-pressed': picked ? 'true' : 'false', title: A.dayHint(dayLabel(d), day.key ? (day.events.find((e) => e.id === day.key) || {}).title : null) } });
126
+ attrs: { class: 'td-ag__cell', 'data-ag-day': d, 'data-load': String(loadOf(day)), 'data-key': key ? 'true' : null, 'data-out': day.in_month ? null : 'true', 'data-past': day.past ? 'true' : null, 'data-today': day.today ? 'true' : null,
127
+ 'data-weekend': day.weekend ? 'true' : null, 'data-lit': lit && lit.has(d) ? 'true' : null, 'aria-pressed': picked ? 'true' : 'false', title: A.dayHint(dayLabel(d), key ? key.title : null) } });
161
128
  }
162
129
 
163
- function pickDay(d) { ag.pick = { type: 'day', date: d }; ag.showRoutine = false; redraw(); }
164
- function pickWeek(i) { ag.pick = { type: 'week', i }; ag.showRoutine = false; redraw(); }
130
+ // A3: picking a day (or a week) closes the month and shows its meetings
131
+ function pickDay(d) { ag.pick = { type: 'day', date: d }; ag.showRoutine = false; ag.calOpen = false; redraw(); }
132
+ function pickWeek(i) { ag.pick = { type: 'week', i }; ag.showRoutine = false; ag.calOpen = false; redraw(); }
133
+ function toggleCalendar(data) {
134
+ ag.calOpen = !ag.calOpen;
135
+ // closed with no day picked, the meetings are today's: the month goes back to today's month too
136
+ if (!ag.calOpen && !ag.pick && ag.month && ag.month !== ymOf(data.today)) { ag.month = ymOf(data.today); if (!cache.has(ag.month)) loadMonth(ag.month); }
137
+ redraw();
138
+ }
165
139
  function goMonth(n, data) {
166
140
  ag.month = n === 0 ? ymOf(data.today) : shiftMonth(ag.month || data.month, n);
167
141
  ag.pick = null;
@@ -175,8 +149,6 @@ function month(data, lit) {
175
149
  el('b', { class: 'td-ag__mlabel', 'data-ag': 'month' }, label(data.month + '-01', { month: 'long', year: 'numeric' })),
176
150
  iconButton({ icon: 'chevron-right', ariaLabel: A.later, size: 'sm', attrs: { 'data-ag': 'later' }, onClick: () => goMonth(1, data) }),
177
151
  ymOf(data.today) !== data.month ? button({ label: A.thisMonth, size: 'sm', variant: 'ghost', attrs: { 'data-ag': 'this-month' }, onClick: () => goMonth(0, data) }) : null);
178
- const lens = tabs({ variant: 'segmented', role: 'radiogroup', ariaLabel: A.title, value: ag.lens, attrs: { 'data-ag': 'lens', class: 'td-ag__lens' },
179
- items: Object.keys(A.lens).map((k) => ({ id: k, label: A.lens[k] })), onChange: (v) => { ag.lens = v; redraw(); } });
180
152
  const wd = el('div', { class: 'td-ag__wk td-ag__wk--head', 'aria-hidden': 'true' }, el('span', null, ''),
181
153
  ...data.weeks[0].map((d) => el('span', { class: 'td-ag__wd' }, label(d, { weekday: 'short' }))));
182
154
  const rows = data.weeks.map((w, i) => {
@@ -186,8 +158,7 @@ function month(data, lit) {
186
158
  button({ label: A.weekNo(n), variant: 'ghost', size: 'sm', onClick: () => pickWeek(i), attrs: { class: 'td-ag__wno', 'data-ag-week': String(n), 'aria-pressed': picked ? 'true' : 'false', title: A.weekHint(n) } }),
187
159
  ...w.map((d) => cell(data, d, lit)));
188
160
  });
189
- return [head, lens, el('p', { class: 'td-day__note', 'data-ag': 'lens-help' }, A.lensHelp[ag.lens]),
190
- el('div', { class: 'td-ag__grid', 'data-ag': 'grid', 'data-lens': ag.lens }, wd, ...rows)];
161
+ return el('div', { class: 'td-ag__month', 'data-ag': 'month-box' }, head, el('div', { class: 'td-ag__grid', 'data-ag': 'grid' }, wd, ...rows));
191
162
  }
192
163
 
193
164
  /* ── the agenda under the month ───────────────────────────────────────────────────────────────────────────────── */
@@ -257,9 +228,12 @@ export function agendaPanel(ctx, h) {
257
228
  const lit = litSeries(h.sel);
258
229
  const litDays = lit && data.series[lit.key] ? new Set(data.series[lit.key].dates) : null;
259
230
  const empty = !data.memory || !data.memory.events;
231
+ const calendar = button({ label: ag.calOpen ? A.hideCalendar : A.calendar, icon: 'calendar', variant: 'outline', size: 'sm',
232
+ attrs: { 'data-ag': 'calendar', 'aria-expanded': ag.calOpen ? 'true' : 'false' }, onClick: () => toggleCalendar(data) });
260
233
  return h.panel('agenda', null,
261
- ...month(data, litDays),
262
- litDays ? el('p', { class: 'td-day__note', 'data-ag': 'lit' }, A.lit(lit.title, [...litDays].filter((d) => data.days[d]).length)) : null,
234
+ el('div', { class: 'td-ag__bar' }, calendar),
235
+ ag.calOpen ? month(data, litDays) : null,
236
+ ag.calOpen && litDays ? el('p', { class: 'td-day__note', 'data-ag': 'lit' }, A.lit(lit.title, [...litDays].filter((d) => data.days[d]).length)) : null,
263
237
  empty ? el('p', { class: 'td-day__note', 'data-ag': 'empty' }, A.empty) : null,
264
238
  agendaOf(data, h));
265
239
  }
@@ -83,6 +83,22 @@ export const COPY = {
83
83
  done: 'Done', drop: 'Drop', reopen: 'Reopen', remove: 'Remove',
84
84
  you: 'You', agent: 'Agent', mention: 'mention',
85
85
  noStep: 'No step found yet. Press Prepare my day, or open the initiative.',
86
+ // goals own jobs (initiative today-goals-jobs, PRD G1 to G6)
87
+ doneWhen: 'Done when', doneWhenHint: 'What does finished look like?', addDoneWhen: 'Add done when',
88
+ anchor: 'Anchor', anchorHint: 'A KR, or health:<id>', due: 'Due', dueHint: 'For example: Friday',
89
+ addContext: 'Add context',
90
+ dueMeta: (d) => `due ${d}`, anchorMeta: (a) => `for ${a}`,
91
+ closeNote: 'Only your press on Done or Drop closes a goal. The jobs never close it.',
92
+ jobsTitle: 'Jobs',
93
+ noJobs: 'No job yet. Type one below, or link a job of the day.',
94
+ addJob: 'Add a job', addJobHint: 'Type a job and press Enter.',
95
+ removeJob: 'Remove', jobFull: (max) => `A goal has at most ${max} jobs you type.`,
96
+ linkJob: 'Link a job of the day', linkJobPick: 'Link a job…', linkNone: 'Every job of the day has a goal.',
97
+ noGoalTitle: 'Jobs without a goal',
98
+ noGoalNone: 'Every job has a goal.',
99
+ linkTo: 'Link to a goal…',
100
+ linkLabel: (t) => `Goal for: ${t}`,
101
+ forGoal: (n, text) => `Goal ${n}: ${text}`,
86
102
  prepare: 'Prepare',
87
103
  queue: 'Queue it',
88
104
  prepareShows: (key, branch) => `${key} on ${branch || 'a local branch'}. Queue it to approve this exact prompt.`,
@@ -138,9 +154,9 @@ export const COPY = {
138
154
  unreached: 'The workspace did not answer.',
139
155
  refused: (why) => `Nothing changed. ${why}`,
140
156
  oldServer: 'This workspace server has no day run yet. Only your goals show.',
141
- sections: { label: 'Agenda sections', agenda: 'Agenda', goals: 'Goals', jobs: 'Jobs', mentions: 'Mentions', summary: 'Result' },
142
- noResult: 'No agent results yet.',
143
- panels: { summary: 'Agent results', calendar: 'Calendar', agenda: 'Agenda', jobs: 'Jobs', goals: 'Goals for today', mentions: 'Mentions since yesterday', detail: 'Detail' },
157
+ sections: { label: 'Agenda sections', agenda: 'Agenda', goals: 'Goals', jobs: 'Jobs', mentions: 'Mentions' },
158
+ menu: { label: 'More', showAll: 'Show items with no repository' },
159
+ panels: { calendar: 'Calendar', agenda: 'Agenda', jobs: 'Jobs', goals: 'Goals for today', mentions: 'Mentions since yesterday', detail: 'Detail' },
144
160
  summary: (c) => [c.review ? `${c.review} ${c.review === 1 ? 'draft waits' : 'drafts wait'} for you` : null, c.done ? `${c.done} done` : null,
145
161
  c.working ? `${c.working} working` : null, c.failed ? `${c.failed} failed` : null].filter(Boolean).join(' · ') || 'No agent job yet.',
146
162
  summaryAll: (n) => `All ${n} agent ${n === 1 ? 'job has' : 'jobs have'} a result.`,
@@ -162,10 +178,19 @@ export const COPY = {
162
178
  mentionsCount: (n, need) => `${n} · ${need} ${need === 1 ? 'needs' : 'need'} you`,
163
179
  all: 'All',
164
180
  status2: {
165
- waiting: 'waiting', working: 'agent working', review: 'approve', reviewAuto: 'check it', sending: 'sending', sent: 'sent',
166
- done: 'done', failed: 'failed', notStarted: 'not started', handled: 'handled', replyNeeded: 'reply needed',
181
+ waiting: 'To do', working: 'Running', review: 'Needs you', reviewAuto: 'Needs you', sending: 'Running', sent: 'Done',
182
+ done: 'Done', failed: 'Blocked', notStarted: 'To do', you: 'Needs you', handled: 'handled', replyNeeded: 'reply needed',
167
183
  },
168
184
  newItem: 'new',
185
+ // who acts on a job, and what a press does (initiative today-goals-jobs, PRD J1 to J5)
186
+ who2: { you: 'Waiting on you', youDone: 'You finished it', working: 'Agent is working', queued: 'Waiting for the agent', finished: 'Agent finished' },
187
+ tag: { you: 'YOU', agent: 'AGENT' },
188
+ origin: { label: 'Where it came from', goal: 'From a goal step', found: 'Found while preparing', mention: 'From a mention', you: 'Added by you' },
189
+ goalCtl: { label: 'Goal', none: 'No goal' },
190
+ linked: (text) => `Linked to the goal: ${text}`,
191
+ unlinked: 'The job has no goal now.',
192
+ openSessionOf: (name) => `Open session ↗ ${name}`,
193
+ result: 'Result',
169
194
  pill: { prepare: 'prepare', ready: 'ready', ended: 'ended', notes: 'notes', goal: (n) => `goal ${n}` },
170
195
  watched: 'Watched channels, projects and spaces',
171
196
  showIgnored: (n) => `Show ${n} ignored`,
@@ -215,6 +240,8 @@ export const COPY = {
215
240
  where: 'From',
216
241
  yes: 'Yes',
217
242
  no: 'No',
243
+ // a mention drawn as a card of its source (initiative today-goals-jobs, PRD M1): the source line above the message
244
+ mc: { via: (svc, where) => (where ? `${svc} · ${where}` : svc) },
218
245
  fromWho: (who, where) => (who && where ? `${who} in ${where}` : who || where || ''),
219
246
  draft: 'The draft',
220
247
  approve: 'Approve and let the agent send it',
@@ -303,17 +330,9 @@ export const COPY = {
303
330
  weekOf: (n) => `Week ${n}`,
304
331
  weekHint: (n) => `Show the agenda of week ${n}`,
305
332
  dayHint: (label, key) => `Show the agenda of ${label}${key ? '. Matters most: ' + key : ''}`,
306
- lens: { overview: 'Overview', full: 'Full', key: 'Key', ready: 'Prep', free: 'Free' },
307
- lensHelp: {
308
- overview: 'Each day: how full it is, the meeting that matters and your prep.',
309
- full: 'The bar is 09:00 to 18:00. Each block is a meeting. Dark blocks are important.',
310
- key: 'The one meeting of the day that matters, when there is one.',
311
- ready: 'Prep done out of prep needed. Red: too little free time before the meeting.',
312
- free: 'Free blocks of 90 minutes or more.',
313
- },
333
+ calendar: 'Calendar',
334
+ hideCalendar: 'Hide calendar',
314
335
  busy: (min) => (min >= 60 ? `${Math.floor(min / 60)} h${min % 60 ? ' ' + (min % 60) + ' min' : ''}` : `${min} min`),
315
- freeBlocks: (n) => `${n} free ${n === 1 ? 'block' : 'blocks'}`,
316
- noFree: 'No long free block',
317
336
  readyBadge: (done, need) => `${done}/${need}`,
318
337
  readyWord: { ok: 'prep done', warn: 'prep left', risk: 'prep at risk' },
319
338
  noNotes: (n) => `${n} no notes`,