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.
- 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/guide/notes.md +2 -0
- package/workspace/guide/today.md +21 -12
- package/workspace/lib/audio-resample.js +76 -0
- package/workspace/lib/brief.js +2 -2
- package/workspace/lib/day-marks.js +29 -9
- package/workspace/lib/goals.js +68 -10
- package/workspace/lib/notes-audio.js +62 -20
- package/workspace/lib/notes-echo.js +32 -14
- package/workspace/lib/notes-hires.js +94 -0
- package/workspace/lib/notes-polish.js +2 -3
- package/workspace/lib/routes-notes.js +9 -1
- package/workspace/notes-test-signals.js +65 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/audio-capture.js +20 -6
- package/workspace/public/pm/components.css +39 -11
- package/workspace/public/pm/today-agenda.js +27 -53
- package/workspace/public/pm/today-copy.js +34 -15
- package/workspace/public/pm/today-day.js +98 -76
- package/workspace/public/pm/today-goals.js +159 -15
- package/workspace/public/pm/today-jobs.js +84 -0
- package/workspace/public/pm/today-mentions.js +50 -0
- package/workspace/public/pm/view-notes.js +15 -4
- package/workspace/server.js +10 -8
- package/workspace/test-lists/meeting-notes.json +5 -3
- package/workspace/test-lists/today-goals-jobs-ui.json +5 -0
- package/workspace/test-lists/today-goals-jobs.json +5 -0
- 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
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
|
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),
|
|
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
|
-
|
|
164
|
-
function
|
|
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
|
|
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
|
-
|
|
262
|
-
|
|
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'
|
|
142
|
-
|
|
143
|
-
panels: {
|
|
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: '
|
|
166
|
-
done: '
|
|
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
|
-
|
|
307
|
-
|
|
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`,
|