create-pmos 0.72.1 → 0.74.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 +1 -1
- package/workspace/guide/today.md +23 -2
- package/workspace/lib/brief.js +88 -2
- package/workspace/lib/day-marks.js +163 -0
- package/workspace/lib/day-run.js +38 -4
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +1 -1
- package/workspace/public/pm/app.js +2 -0
- package/workspace/public/pm/components.css +99 -8
- package/workspace/public/pm/note-dock.js +137 -0
- package/workspace/public/pm/notes-links.js +16 -37
- package/workspace/public/pm/today-copy.js +82 -4
- package/workspace/public/pm/today-day.js +349 -19
- package/workspace/public/pm/view-notes.js +39 -9
- package/workspace/server.js +81 -3
- package/workspace/skills/day-task.skill +1 -0
- package/workspace/skills/morning-brief.skill +13 -2
- package/workspace/test-lists/today-act-prepare.json +5 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* pm/note-dock.js — the meeting note being taken, kept small at the bottom right of the workspace (meeting-notes Run 6).
|
|
2
|
+
*
|
|
3
|
+
* Take notes on a calendar event makes the note and starts listening at once. The note stays minimized as a pill at the
|
|
4
|
+
* bottom right: a red dot while it listens, its title and its time. A press on the pill opens the note as a pop-up: the same
|
|
5
|
+
* note as the Notes tab, in one column (pm/view-notes.js renderInline). Minimize or Esc puts it back; Open in Notes shows it
|
|
6
|
+
* on the full screen. The pill stays on every destination.
|
|
7
|
+
*
|
|
8
|
+
* One note at a time. The note is kept in this browser (KEY), so it is still there after a reload; a note that no longer
|
|
9
|
+
* listens can be put away with the x. Starting another note while one is listening asks first.
|
|
10
|
+
*
|
|
11
|
+
* mountDock() → draws the dock on every render (pm/app.js boot)
|
|
12
|
+
* takeNotes(e, opts, btn) → makes a note linked to the event, starts listening, and keeps it minimized
|
|
13
|
+
* openDock(id) → shows a note in the pop-up
|
|
14
|
+
* minimize() · activeId()
|
|
15
|
+
*/
|
|
16
|
+
import { el } from './dom.js';
|
|
17
|
+
import { button, iconButton, layer, toast, ensureComponentsCss } from './primitives.js';
|
|
18
|
+
import { askConfirm } from './dialogs.js';
|
|
19
|
+
import { pmPost } from './http.js';
|
|
20
|
+
import { state, setState, subscribe, FIXTURE } from './state.js';
|
|
21
|
+
import { meetingOf, refreshNotes, noteById } from './notes-links.js';
|
|
22
|
+
|
|
23
|
+
const KEY = 'pmos.workspace.pm.note';
|
|
24
|
+
let active = read(); // { id, title }: the one note in the dock
|
|
25
|
+
let open = false; // the pop-up is open
|
|
26
|
+
let host = null;
|
|
27
|
+
let V = null; // pm/view-notes.js, read the first time the dock draws a note
|
|
28
|
+
let tick = null;
|
|
29
|
+
const viewCtx = { state, setState, FIXTURE };
|
|
30
|
+
|
|
31
|
+
function read() {
|
|
32
|
+
try { const j = JSON.parse(localStorage.getItem(KEY) || 'null'); return j && typeof j.id === 'string' ? { id: j.id, title: String(j.title || '') } : null; } catch { return null; }
|
|
33
|
+
}
|
|
34
|
+
function write() {
|
|
35
|
+
try { if (active) localStorage.setItem(KEY, JSON.stringify(active)); else localStorage.removeItem(KEY); } catch { /* private mode: the note stays for this page */ }
|
|
36
|
+
}
|
|
37
|
+
function view() {
|
|
38
|
+
if (!V) import('./view-notes.js').then((m) => { V = m; setState({}); });
|
|
39
|
+
return V;
|
|
40
|
+
}
|
|
41
|
+
const liveId = () => (V ? V.listeningId() : null);
|
|
42
|
+
const titleOf = () => { if (!active) return ''; const r = noteById(active.id); return (V && V.titleOf(active.id)) || (r && r.title) || active.title || 'Untitled note'; };
|
|
43
|
+
|
|
44
|
+
export const activeId = () => (active ? active.id : null);
|
|
45
|
+
|
|
46
|
+
export function mountDock() {
|
|
47
|
+
if (host || FIXTURE || typeof document === 'undefined') return;
|
|
48
|
+
ensureComponentsCss();
|
|
49
|
+
host = el('div', { class: 'nt-dock', 'data-nt-dock': '' });
|
|
50
|
+
// Esc in the pop-up minimizes it. It is read first (capture), so a field does not take it to undo the typing, and the shell
|
|
51
|
+
// does not also read it.
|
|
52
|
+
host.addEventListener('keydown', (e) => { if (e.key === 'Escape' && open) { e.preventDefault(); e.stopPropagation(); minimize(); } }, true);
|
|
53
|
+
const l = layer();
|
|
54
|
+
if (l) l.appendChild(host);
|
|
55
|
+
subscribe(draw);
|
|
56
|
+
if (active) view();
|
|
57
|
+
draw();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function minimize() {
|
|
61
|
+
if (!open) return;
|
|
62
|
+
open = false;
|
|
63
|
+
if (V) V.setPopped(null);
|
|
64
|
+
setState({});
|
|
65
|
+
}
|
|
66
|
+
/** Show this note in the pop-up. Another note that is listening is asked about first. */
|
|
67
|
+
export async function openDock(id) {
|
|
68
|
+
if (active && active.id !== id && liveId() === active.id && !await switchFrom()) { openDock(active.id); return; }
|
|
69
|
+
const r = noteById(id);
|
|
70
|
+
active = { id, title: (r && r.title) || '' }; write();
|
|
71
|
+
open = true;
|
|
72
|
+
const v = V || await import('./view-notes.js');
|
|
73
|
+
V = v; v.setPopped(id);
|
|
74
|
+
setState({});
|
|
75
|
+
requestAnimationFrame(() => { const t = host && host.querySelector('[data-nt-body]'); if (t) t.focus(); });
|
|
76
|
+
}
|
|
77
|
+
function close() {
|
|
78
|
+
if (liveId() === (active && active.id)) return;
|
|
79
|
+
active = null; open = false; write();
|
|
80
|
+
if (V) V.setPopped(null);
|
|
81
|
+
setState({});
|
|
82
|
+
}
|
|
83
|
+
/** Ask before a new note replaces the one that is listening. True when the PM stopped it. */
|
|
84
|
+
async function switchFrom() {
|
|
85
|
+
const sure = await askConfirm({ title: '"' + titleOf() + '" is still being taken.', body: 'Only one note is taken at a time.', confirmLabel: 'Stop it and start this one', cancelLabel: 'Keep it' });
|
|
86
|
+
if (!sure) return false;
|
|
87
|
+
if (V) await V.stopListening(viewCtx);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Take notes on a calendar event: make the note, start listening at once, and keep it minimized. */
|
|
92
|
+
export async function takeNotes(e, { ws, repo, initiative } = {}, btn) {
|
|
93
|
+
if (active && liveId() === active.id && !await switchFrom()) { openDock(active.id); return; }
|
|
94
|
+
if (btn) btn.disabled = true;
|
|
95
|
+
const ini = initiative && initiative.id ? { id: initiative.id, ws: ws || null, repo: repo || null, title: initiative.title || null } : undefined;
|
|
96
|
+
const r = await pmPost('/api/notes/new', { meeting: meetingOf(e), ...(ini ? { initiative: ini } : {}) });
|
|
97
|
+
if (btn) btn.disabled = false;
|
|
98
|
+
if (!r || !r.ok) { toast({ text: (r && r.error) || 'The note was not made.', tone: 'destructive' }); return; }
|
|
99
|
+
active = { id: r.note.id, title: r.note.title || '' }; write();
|
|
100
|
+
open = false;
|
|
101
|
+
const v = V || await import('./view-notes.js');
|
|
102
|
+
V = v; v.setPopped(null);
|
|
103
|
+
refreshNotes();
|
|
104
|
+
setState({});
|
|
105
|
+
if (await v.listenNow(viewCtx, r.note.id)) toast({ text: 'Notes are being taken. Let people know.' });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function clockText() {
|
|
109
|
+
const s = V && V.liveStarted && V.liveStarted();
|
|
110
|
+
return s ? V.fmtTime(Date.now() - s) : '';
|
|
111
|
+
}
|
|
112
|
+
function draw() {
|
|
113
|
+
if (!host) return;
|
|
114
|
+
const v = view();
|
|
115
|
+
const live = !!active && !!v && liveId() === active.id;
|
|
116
|
+
clearInterval(tick); tick = null;
|
|
117
|
+
if (!active || !v) { host.replaceChildren(); return; }
|
|
118
|
+
if (live) tick = setInterval(() => { const c = host.querySelector('[data-nt-dock-clock]'); if (c) c.textContent = clockText(); }, 1000);
|
|
119
|
+
if (!open) {
|
|
120
|
+
const pill = button({ label: el('span', { class: 'nt-pill__txt' },
|
|
121
|
+
live ? el('span', { class: 'nt-live-dot', 'aria-hidden': 'true' }) : null,
|
|
122
|
+
el('span', { class: 'nt-pill__title' }, titleOf()),
|
|
123
|
+
live ? el('span', { class: 'nt-pill__clock', 'data-nt-dock-clock': '' }, clockText()) : null),
|
|
124
|
+
variant: 'outline', attrs: { class: 'nt-pill', 'data-nt-pill': live ? 'live' : 'idle', 'aria-label': (live ? 'Notes are being taken: ' : 'Meeting note: ') + titleOf() + '. Open the note.' },
|
|
125
|
+
onClick: () => openDock(active.id) });
|
|
126
|
+
host.replaceChildren(el('div', { class: 'nt-pillrow' }, pill,
|
|
127
|
+
live ? null : iconButton({ icon: 'x', ariaLabel: 'Put the note away', onClick: close, attrs: { 'data-nt-dock-close': '' } })));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const body = el('div', { class: 'nt-pop__body' });
|
|
131
|
+
const pop = el('section', { class: 'nt-pop', role: 'dialog', 'aria-label': 'Meeting note', 'data-nt-pop': active.id },
|
|
132
|
+
el('div', { class: 'nt-pop__head' }, el('span', { class: 'nt-sec__h' }, live ? 'Taking notes' : 'Meeting note'),
|
|
133
|
+
button({ label: 'Minimize', variant: 'ghost', size: 'sm', onClick: minimize, attrs: { 'data-nt-minimize': '' } })),
|
|
134
|
+
body);
|
|
135
|
+
host.replaceChildren(pop);
|
|
136
|
+
if (!v.renderInline(body, viewCtx, active.id)) body.appendChild(el('p', { class: 'nt-hint' }, 'Another note is being taken.'));
|
|
137
|
+
}
|
|
@@ -2,27 +2,26 @@
|
|
|
2
2
|
*
|
|
3
3
|
* notesFor(e) → the note rows linked to a calendar event (same id, or same start minute and title)
|
|
4
4
|
* notesOfInitiative(id) → the note rows linked to an initiative, newest first
|
|
5
|
+
* noteById(id) → the note's row, or null
|
|
5
6
|
* notesBlock(e, { ws, repo, initiative }) → the "Meeting notes" block of an event's detail. Take notes makes a note linked
|
|
6
|
-
* to the event and
|
|
7
|
-
* an event that has a note shows its summary and Open notes,
|
|
7
|
+
* to the event, starts listening and keeps the note minimized at the bottom right
|
|
8
|
+
* (pm/note-dock.js, Run 6); an event that has a note shows its summary and Open notes,
|
|
9
|
+
* which opens it as a pop-up
|
|
8
10
|
* noteRows(rows, { onOpen }) → the rows of an initiative's Notes tab; onOpen runs before the note opens
|
|
9
11
|
* openNote(id) → opens the Notes tab on that note
|
|
10
12
|
* onNotes(fn) · refreshNotes() → redraw when the list is read
|
|
11
13
|
*
|
|
12
14
|
* One read feeds it: GET /api/notes (a row carries the meeting, the initiative and up to three summary lines, never the body;
|
|
13
|
-
* a private note carries its title only). Take notes writes POST /api/notes/new { meeting, initiative }.
|
|
15
|
+
* a private note carries its title only). Take notes (pm/note-dock.js) writes POST /api/notes/new { meeting, initiative }.
|
|
14
16
|
*/
|
|
15
17
|
import { el } from './dom.js';
|
|
16
18
|
import { button } from './primitives.js';
|
|
17
|
-
import { pmGet
|
|
18
|
-
import {
|
|
19
|
+
import { pmGet } from './http.js';
|
|
20
|
+
import { takeNotes, openDock, activeId } from './note-dock.js';
|
|
19
21
|
|
|
20
22
|
const READ_MS = 15000; // the list is read again at most this often while a calendar or an initiative is drawn
|
|
21
23
|
let rows = null; let readAt = 0; let reading = null; let failed = false;
|
|
22
24
|
const subs = new Set();
|
|
23
|
-
let shown = null; // the note open in an event's detail (Take notes or Open notes); the listening note is shown too
|
|
24
|
-
let V = null; // pm/view-notes.js, read the first time a note is shown in a detail
|
|
25
|
-
const viewCtx = { state, setState, FIXTURE };
|
|
26
25
|
|
|
27
26
|
export function onNotes(fn) { subs.add(fn); return () => subs.delete(fn); }
|
|
28
27
|
export function refreshNotes() {
|
|
@@ -51,6 +50,7 @@ export function sameEvent(meeting, e) {
|
|
|
51
50
|
export const notesFor = (e) => list().filter((n) => sameEvent(n.meeting, e));
|
|
52
51
|
export const notesOfInitiative = (id) => list().filter((n) => n.initiative && n.initiative.id === id);
|
|
53
52
|
export const notesKnown = () => rows !== null || failed;
|
|
53
|
+
export const noteById = (id) => (rows || []).find((n) => n.id === id) || null;
|
|
54
54
|
|
|
55
55
|
/** The meeting link a calendar event gives a note. */
|
|
56
56
|
export function meetingOf(e) {
|
|
@@ -63,22 +63,6 @@ export async function openNote(id) {
|
|
|
63
63
|
v.openNote(id);
|
|
64
64
|
c.openView('notes');
|
|
65
65
|
}
|
|
66
|
-
async function takeNotes(e, { ws, repo, initiative } = {}, btn) {
|
|
67
|
-
if (btn) btn.disabled = true;
|
|
68
|
-
const ini = initiative && initiative.id ? { id: initiative.id, ws: ws || null, repo: repo || null, title: initiative.title || null } : undefined;
|
|
69
|
-
const r = await pmPost('/api/notes/new', { meeting: meetingOf(e), ...(ini ? { initiative: ini } : {}) });
|
|
70
|
-
if (btn) btn.disabled = false;
|
|
71
|
-
if (!r || !r.ok) { const { toast } = await import('./primitives.js'); toast({ text: (r && r.error) || 'The note was not made.', tone: 'destructive' }); return; }
|
|
72
|
-
shown = r.note.id;
|
|
73
|
-
await refreshNotes();
|
|
74
|
-
setState({});
|
|
75
|
-
}
|
|
76
|
-
function show(id) { shown = id; setState({}); }
|
|
77
|
-
function view() {
|
|
78
|
-
if (!V) import('./view-notes.js').then((m) => { V = m; setState({}); });
|
|
79
|
-
return V;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
66
|
const dayOf = (iso) => { try { return new Date(iso).toLocaleDateString([], { weekday: 'short', day: 'numeric', month: 'short' }); } catch { return ''; } };
|
|
83
67
|
const clock = (iso) => { try { return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch { return ''; } };
|
|
84
68
|
|
|
@@ -88,30 +72,25 @@ export function notesBlock(e, opts = {}) {
|
|
|
88
72
|
const ended = !!e.ended;
|
|
89
73
|
if (mine.length) {
|
|
90
74
|
const n = mine[0];
|
|
91
|
-
const v = view();
|
|
92
|
-
const live = v && v.listeningId() === n.id;
|
|
93
|
-
if (v && (shown === n.id || live)) {
|
|
94
|
-
const host = el('div', { class: 'td-day__notes nt-inhost', 'data-notes-block': 'inline', 'data-notes-id': n.id });
|
|
95
|
-
const head = el('div', { class: 'nt-inhost__head' }, el('h4', null, 'Meeting notes'),
|
|
96
|
-
live ? null : button({ label: 'Close', variant: 'ghost', size: 'sm', attrs: { 'data-notes-close': n.id }, onClick: () => show(null) }));
|
|
97
|
-
host.appendChild(head);
|
|
98
|
-
if (v.renderInline(host, viewCtx, n.id)) return host;
|
|
99
|
-
}
|
|
100
75
|
const lines = n.summary_lines || [];
|
|
101
|
-
|
|
76
|
+
if (activeId() === n.id) {
|
|
77
|
+
return el('div', { class: 'td-day__notes', 'data-notes-block': 'docked', 'data-notes-id': n.id },
|
|
78
|
+
el('h4', null, 'Meeting notes'),
|
|
79
|
+
el('p', { class: 'td-day__note' }, 'This note is open at the bottom right.'),
|
|
80
|
+
el('div', { class: 'td-day__acts' }, button({ label: 'Open the note', size: 'sm', attrs: { 'data-notes-open': n.id }, onClick: () => openDock(n.id) })));
|
|
81
|
+
}
|
|
102
82
|
return el('div', { class: 'td-day__notes', 'data-notes-block': 'linked', 'data-notes-id': n.id },
|
|
103
83
|
el('h4', null, 'Meeting notes'),
|
|
104
84
|
n.private ? el('p', { class: 'td-day__note' }, 'This note is private.')
|
|
105
85
|
: lines.length ? el('ul', { class: 'nt-lsum' }, ...lines.map((l) => el('li', null, l)))
|
|
106
86
|
: el('p', { class: 'td-day__note' }, n.segments ? 'No summary yet. Open the note and press Summarize.' : 'No summary yet.'),
|
|
107
|
-
|
|
108
|
-
busy ? null : el('div', { class: 'td-day__acts' }, button({ label: 'Open notes', size: 'sm', attrs: { 'data-notes-open': n.id }, onClick: () => show(n.id) })));
|
|
87
|
+
el('div', { class: 'td-day__acts' }, button({ label: 'Open notes', size: 'sm', attrs: { 'data-notes-open': n.id }, onClick: () => openDock(n.id) })));
|
|
109
88
|
}
|
|
110
89
|
if (!notesKnown()) return null;
|
|
111
90
|
const take = button({ label: 'Take notes', variant: ended ? undefined : 'primary', size: 'sm', attrs: { 'data-notes-take': e.id || '' }, onClick: () => takeNotes(e, opts, take) });
|
|
112
91
|
return el('div', { class: 'td-day__notes', 'data-notes-block': 'none' },
|
|
113
92
|
el('h4', null, 'Meeting notes'),
|
|
114
|
-
el('p', { class: 'td-day__note' }, ended ? 'No notes were taken in PMOS for this meeting.' : 'Take notes
|
|
93
|
+
el('p', { class: 'td-day__note' }, ended ? 'No notes were taken in PMOS for this meeting.' : 'Take notes when the meeting starts. PMOS listens at once and keeps the note at the bottom right. It is linked to this event' + (opts.initiative ? ' and its initiative.' : '.')),
|
|
115
94
|
el('div', { class: 'td-day__acts' }, take));
|
|
116
95
|
}
|
|
117
96
|
|
|
@@ -159,7 +159,7 @@ export const COPY = {
|
|
|
159
159
|
all: 'All',
|
|
160
160
|
status2: {
|
|
161
161
|
waiting: 'waiting', working: 'agent working', review: 'approve', reviewAuto: 'check it', sending: 'sending', sent: 'sent',
|
|
162
|
-
done: 'done', failed: 'failed',
|
|
162
|
+
done: 'done', failed: 'failed', notStarted: 'not started', handled: 'handled', replyNeeded: 'reply needed',
|
|
163
163
|
},
|
|
164
164
|
newItem: 'new',
|
|
165
165
|
pill: { prepare: 'prepare', ready: 'ready', ended: 'ended', notes: 'notes', goal: (n) => `goal ${n}` },
|
|
@@ -189,8 +189,6 @@ export const COPY = {
|
|
|
189
189
|
prep: 'Prepare',
|
|
190
190
|
phases: { before: 'Before', during: 'During', after: 'After' },
|
|
191
191
|
who: { pm: 'you', agent: 'agent' },
|
|
192
|
-
needPrep: 'You need to prepare',
|
|
193
|
-
nothingToPrep: 'Nothing to prepare',
|
|
194
192
|
initiative: 'Initiative',
|
|
195
193
|
suggested: (why) => `Suggested. ${why}`,
|
|
196
194
|
openInitiative: 'Open the initiative',
|
|
@@ -235,7 +233,6 @@ export const COPY = {
|
|
|
235
233
|
waitingNote: 'The agent has not started this job yet. At most 5 sessions run at once.',
|
|
236
234
|
workingNote: 'An agent session is doing this job now.',
|
|
237
235
|
sendingNote: 'You said yes to this draft. An agent session is sending it now.',
|
|
238
|
-
youNote: 'Only you can do this one. The link opens the conversation.',
|
|
239
236
|
autoNote: 'The agent did this alone. Nothing went outside this machine.',
|
|
240
237
|
notTask: 'The agent starts this job when you press Prepare my day.',
|
|
241
238
|
related: 'Related',
|
|
@@ -351,6 +348,87 @@ export const COPY = {
|
|
|
351
348
|
today: 'Today',
|
|
352
349
|
},
|
|
353
350
|
},
|
|
351
|
+
/** The items the PM acts on and the meetings the PM prepares for (initiative today-act-prepare, PRD A1 to D4). A verb
|
|
352
|
+
* first, one sentence of what to do, then the steps, one button that fits the first open step, and the marks. */
|
|
353
|
+
act: {
|
|
354
|
+
verb: { reply: 'Reply', review: 'Review', decide: 'Decide', 'sign-off': 'Sign off', read: 'Read', do: 'Do', prepare: 'Prepare', approve: 'Approve' },
|
|
355
|
+
groups: { later: 'Snoozed', waiting: 'Waiting on others', done: 'Done today' },
|
|
356
|
+
by: (at) => `by ${at}`,
|
|
357
|
+
at: (at) => `at ${at}`,
|
|
358
|
+
left: (min) => (min <= 0 ? 'now' : min < 60 ? `in ${min} min` : `in ${Math.floor(min / 60)} h ${min % 60} min`),
|
|
359
|
+
back: (at) => `back ${at}`,
|
|
360
|
+
check: (at) => `ask ${at}`,
|
|
361
|
+
chip: { done: 'done', ready: 'ready', dismissed: 'not mine', skipped: 'no prep', today: 'today' },
|
|
362
|
+
from: (who, where) => (who && where ? `${who} in ${where}` : who || where || ''),
|
|
363
|
+
howTitle: (d, n) => `How to do it · ${d} of ${n}`,
|
|
364
|
+
beforeTitle: (at) => `Before ${at}`,
|
|
365
|
+
more: 'More context',
|
|
366
|
+
moreMeeting: (during, after) => `In the meeting (${during}) · After (${after})`,
|
|
367
|
+
readOnly: 'Read only. Nothing is sent before you press a button that says so.',
|
|
368
|
+
open: { slack: 'Reply in Slack ↗', jira: 'Open in Jira ↗', confluence: 'Open in Confluence ↗', github: 'Open on GitHub ↗', figma: 'Open in Figma ↗',
|
|
369
|
+
mail: 'Open the mail ↗', drive: 'Open in Google Drive ↗', calendar: 'Open the event ↗', wispr: 'Open the notes ↗', website: 'Open the page ↗' },
|
|
370
|
+
openLink: 'Open the link ↗',
|
|
371
|
+
startStep: (t) => `Start: ${t}`,
|
|
372
|
+
askAgentStep: 'Ask the agent',
|
|
373
|
+
draft: 'Have the agent draft it',
|
|
374
|
+
drafting: 'The agent is drafting. The draft shows here, and nothing is sent before you approve it.',
|
|
375
|
+
draftStarted: 'The agent is drafting it. Approve or revise the draft when it is ready.',
|
|
376
|
+
done: 'Mark done',
|
|
377
|
+
ready: 'Mark ready',
|
|
378
|
+
snooze: 'Snooze',
|
|
379
|
+
remind: 'Remind me',
|
|
380
|
+
hold: 'Waiting on…',
|
|
381
|
+
notMine: 'Not mine',
|
|
382
|
+
skip: 'Skip prep',
|
|
383
|
+
snoozeTo: { hour: 'In 1 hour', later: 'Later today, 16:00', due: 'Before it is due', end: 'End of the day, 17:30', m30: '30 min before it', m15: '15 min before it' },
|
|
384
|
+
holdWho: 'Who or what you wait on',
|
|
385
|
+
holdHint: 'For example: Raj, or the cost model',
|
|
386
|
+
holdWhen: 'Ask again',
|
|
387
|
+
holdWhenOpts: { h2: 'In 2 hours', end: 'At 17:30' },
|
|
388
|
+
holdGo: 'Put on hold',
|
|
389
|
+
holdNeed: 'Say who or what you wait on, in 1 to 80 characters.',
|
|
390
|
+
why: { 'no-action': 'No action needed', 'someone-else': 'Someone else handles it', 'done-elsewhere': 'It is already done', nothing: 'Nothing to prepare', elsewhere: 'I prepare elsewhere' },
|
|
391
|
+
did: {
|
|
392
|
+
done: (t) => `Done: ${t}.`, ready: (t) => `Ready: ${t}.`, snooze: (at) => `Snoozed. It comes back at ${at}.`,
|
|
393
|
+
hold: (who, at) => `Waiting on ${who}. Today asks you again at ${at}.`, dismiss: (why) => `${why}. Taken off Needs you.`, reopen: 'Back in Needs you.',
|
|
394
|
+
},
|
|
395
|
+
undo: 'Undo',
|
|
396
|
+
undone: 'Put back as it was.',
|
|
397
|
+
state: {
|
|
398
|
+
snoozed: (at) => `Snoozed. It comes back to Needs you at ${at}.`,
|
|
399
|
+
waiting: (who, at) => `On hold: waiting on ${who}. Today asks you again at ${at}.`,
|
|
400
|
+
done: (at) => `Done at ${at}.`, ready: (at) => `Ready at ${at}.`,
|
|
401
|
+
dismissed: (why) => `Not on your list: ${why}.`,
|
|
402
|
+
back: 'Back from snooze or hold: the time you set has come.',
|
|
403
|
+
},
|
|
404
|
+
bringBack: 'Bring it back now',
|
|
405
|
+
reopen: 'Reopen',
|
|
406
|
+
readiness: (d, n) => `${d} of ${n} ready`,
|
|
407
|
+
fit: { ok: 'Your prep is done.', time: (left, free) => `${left} ${left === 1 ? 'step' : 'steps'} left, ${free} min free before it.`,
|
|
408
|
+
short: (left, free) => `${left} ${left === 1 ? 'step' : 'steps'} left and ${free} min free. Give a step to the agent, or snooze.` },
|
|
409
|
+
noSteps: 'No steps were given. The sentence above says what to do.',
|
|
410
|
+
stepFailed: (why) => `The step was not saved: ${why}`,
|
|
411
|
+
// the context and the answer in the card (PRD X1, X2, F1 to F3)
|
|
412
|
+
threadTitle: 'The conversation',
|
|
413
|
+
refsTitle: 'What the sources say',
|
|
414
|
+
refRead: 'Read: ',
|
|
415
|
+
refOpen: 'Open the source ↗',
|
|
416
|
+
compose: { reply: 'Your reply', review: 'Your review', decide: 'Your answer', 'sign-off': 'Your sign-off' },
|
|
417
|
+
composeHint: 'Write it here, or start from a suggestion.',
|
|
418
|
+
startFrom: 'Start from:',
|
|
419
|
+
verdictLabel: 'Your verdict',
|
|
420
|
+
verdict: { approve: 'Approve', changes: 'Ask for changes' },
|
|
421
|
+
sendTo: (to) => `Send to ${to}`,
|
|
422
|
+
postReview: (to) => `Post the review to ${to}`,
|
|
423
|
+
sendNote: 'When you press, the agent posts exactly this text, as you, to that place. Nothing else is sent.',
|
|
424
|
+
sendNeed: 'Write the text to send first.',
|
|
425
|
+
verdictNeed: 'Choose Approve or Ask for changes first.',
|
|
426
|
+
sendStarted: (to) => `Sending to ${to}. The job shows Sent when it is posted.`,
|
|
427
|
+
noteTitle: 'Your note',
|
|
428
|
+
noteHint: 'Write it here. It is saved when you leave the box.',
|
|
429
|
+
noteStays: 'Saved with the meeting on this machine. Nothing is sent.',
|
|
430
|
+
noteSaved: 'Note saved.',
|
|
431
|
+
},
|
|
354
432
|
/** The Acceptance Gate card (D12): the PM's own act on a run. */
|
|
355
433
|
accept: {
|
|
356
434
|
review: 'Review',
|