create-pmos 0.73.0 → 0.75.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/scripts/workflow-state.js +45 -4
- package/payload/state/discoveries.md +1 -1
- package/workspace/guide/initiatives-screen.md +10 -0
- package/workspace/guide/today.md +22 -1
- 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/lib/engine/workflow-state.js +45 -4
- package/workspace/lib/jev-documents.js +224 -0
- package/workspace/lib/routes-initiatives.js +76 -3
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +1 -1
- package/workspace/public/pm/components.css +34 -0
- package/workspace/public/pm/initiatives-api.js +8 -2
- package/workspace/public/pm/initiatives-copy.js +19 -0
- package/workspace/public/pm/initiatives-ui.js +88 -21
- package/workspace/public/pm/today-copy.js +82 -4
- package/workspace/public/pm/today-day.js +349 -19
- 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
|
@@ -7,15 +7,18 @@
|
|
|
7
7
|
* checkBySheet({…}) a sheet with a date and Save or Clear
|
|
8
8
|
* say(out, ok) a toast for the answer of a write: the success text, or the server's refusal
|
|
9
9
|
* rescanRepo(ws) read the repository again now, with a toast at the start and at the end (scan-consistency R6);
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* in a document repository, Jev's check first when its gates are open (scan-documents S11),
|
|
11
|
+
* then the tidy sheet when there are kinds to change or duplicates (S10)
|
|
12
|
+
* jevPlanSheet(plan) what Jev would read and how many requests it sends, and Cancel, Scan without Jev or Check
|
|
13
|
+
* tidySheet(duplicates) the kinds Jev would change, the groups of duplicate documents, the one to keep in each,
|
|
14
|
+
* and Cancel, Skip or Archive
|
|
12
15
|
* No native dialog is used anywhere here. Every control is a primitive (primitives.js).
|
|
13
16
|
*/
|
|
14
17
|
|
|
15
18
|
import { el } from './dom.js';
|
|
16
19
|
import { button, field, signal, sheet, tabs, toast } from './primitives.js';
|
|
17
20
|
import { COPY } from './initiatives-copy.js';
|
|
18
|
-
import { rescan, duplicates, tidy } from './initiatives-api.js';
|
|
21
|
+
import { rescan, duplicates, tidy, jevCheck } from './initiatives-api.js';
|
|
19
22
|
|
|
20
23
|
export const horizonLabel = (h) => COPY.horizons[h] || h;
|
|
21
24
|
export const progressText = (row) => (row && row.progress && row.progress.label) || '';
|
|
@@ -52,22 +55,25 @@ export function scanMoves(before, after) {
|
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* The tidy sheet (scan-documents S10): each group of documents that share an initiative and a role, with the one to keep
|
|
55
|
-
* chosen, a switch to leave the group as it is, and three answers. Resolves { keep: [{ id, role, keep }] } on the
|
|
56
|
-
* 'skip' on Scan without tidying, and null on Cancel, Escape or the backdrop.
|
|
58
|
+
* chosen, a switch to leave the group as it is, and three answers. Resolves { keep: [{ id, role, keep }], kinds } on the
|
|
59
|
+
* confirm, 'skip' on Scan without tidying, and null on Cancel, Escape or the backdrop. After Jev's check (S11) it also lists
|
|
60
|
+
* the kinds Jev would change, each with a switch, and each group says which version Jev picks; `kinds` is the switched-on ones.
|
|
57
61
|
*/
|
|
58
62
|
export function tidySheet(d) {
|
|
59
63
|
return new Promise((resolve) => {
|
|
60
64
|
let settled = false;
|
|
61
65
|
const finish = (v) => { if (settled) return; settled = true; resolve(v); };
|
|
62
66
|
const state = d.groups.map((g) => ({ g, on: true, keep: g.keep }));
|
|
67
|
+
const fixes = (d.corrections || []).map((c) => ({ c, on: true }));
|
|
63
68
|
const count = () => state.reduce((n, x) => n + (x.on ? x.g.files.length - 1 : 0), 0);
|
|
69
|
+
const label = () => (fixes.length ? COPY.jevConfirm(fixes.filter((x) => x.on).length, count()) : COPY.tidyConfirm(count()));
|
|
64
70
|
const day = (t) => (t ? new Date(t * 1000).toISOString().slice(0, 10) : '');
|
|
65
71
|
const err = el('p', { class: 'in-error', role: 'alert', 'data-in-error': '' });
|
|
66
72
|
err.hidden = true;
|
|
67
73
|
const groups = state.map((x, gi) => {
|
|
68
74
|
const box = el('fieldset', { class: 'in-tidy__group', 'data-in-tidy-group': x.g.id + ':' + x.g.role });
|
|
69
75
|
const toggle = el('input', { type: 'checkbox', checked: true, 'aria-label': COPY.tidyGroupToggle, 'data-in-tidy-on': '' });
|
|
70
|
-
toggle.addEventListener('change', () => { x.on = toggle.checked; if (x.on) box.removeAttribute('data-off'); else box.setAttribute('data-off', ''); go.textContent =
|
|
76
|
+
toggle.addEventListener('change', () => { x.on = toggle.checked; if (x.on) box.removeAttribute('data-off'); else box.setAttribute('data-off', ''); go.textContent = label(); });
|
|
71
77
|
const head = el('legend', { class: 'in-tidy__head' }, toggle, `${x.g.title} · ${COPY.tidyRole[x.g.role] || x.g.role}`);
|
|
72
78
|
const files = x.g.files.map((f) => {
|
|
73
79
|
const radio = el('input', { type: 'radio', name: 'in-tidy-' + gi, value: f.path, checked: f.path === x.keep, 'data-in-tidy-keep': f.path });
|
|
@@ -76,21 +82,34 @@ export function tidySheet(d) {
|
|
|
76
82
|
return el('label', { class: 'in-tidy__file' }, radio, el('span', null, el('span', { class: 'in-tidy__path' }, f.path), meta ? el('span', { class: 'in-note' }, ' · ' + meta) : null));
|
|
77
83
|
});
|
|
78
84
|
const shared = (x.g.shared || []).map((p) => el('p', { class: 'in-note in-tidy__file' }, COPY.tidyShared(p)));
|
|
79
|
-
|
|
85
|
+
const jev = x.g.jev && x.g.jev.words ? [el('p', { class: 'in-note in-tidy__file', 'data-in-tidy-jev': '' }, x.g.jev.words)] : [];
|
|
86
|
+
box.replaceChildren(head, ...jev, ...files, ...shared);
|
|
80
87
|
return box;
|
|
81
88
|
});
|
|
89
|
+
const kindsBox = fixes.length ? (() => {
|
|
90
|
+
const box = el('fieldset', { class: 'in-tidy__group', 'data-in-tidy-kinds': '' });
|
|
91
|
+
const rows = fixes.map((x) => {
|
|
92
|
+
const t = el('input', { type: 'checkbox', checked: true, 'aria-label': COPY.jevKindToggle, 'data-in-tidy-kind': x.c.path });
|
|
93
|
+
t.addEventListener('change', () => { x.on = t.checked; go.textContent = label(); });
|
|
94
|
+
return el('label', { class: 'in-tidy__file' }, t, el('span', null, el('span', { class: 'in-tidy__path' }, COPY.jevKindLine(x.c)), x.c.words ? el('span', { class: 'in-note' }, ' · ' + x.c.words) : null));
|
|
95
|
+
});
|
|
96
|
+
box.replaceChildren(el('legend', { class: 'in-tidy__head' }, COPY.jevKindsHead(fixes.length)), el('p', { class: 'in-note in-tidy__file' }, COPY.jevKindsBody), ...rows);
|
|
97
|
+
return box;
|
|
98
|
+
})() : null;
|
|
82
99
|
const inits = new Set(d.groups.map((g) => g.id)).size;
|
|
83
100
|
const go = button({
|
|
84
|
-
label:
|
|
101
|
+
label: label(), variant: 'primary', attrs: { 'data-in-tidy-confirm': '' },
|
|
85
102
|
onClick: () => {
|
|
86
103
|
const keep = state.filter((x) => x.on).map((x) => ({ id: x.g.id, role: x.g.role, keep: x.keep }));
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
const kinds = fixes.filter((x) => x.on).map((x) => ({ path: x.c.path, kind: x.c.kind, was: x.c.was, p: x.c.p }));
|
|
105
|
+
if (!keep.length && !kinds.length) { err.textContent = COPY.tidyNone; err.hidden = false; return; }
|
|
106
|
+
finish(kinds.length ? { keep, kinds } : { keep }); s.close();
|
|
89
107
|
},
|
|
90
108
|
});
|
|
91
109
|
const s = sheet({
|
|
92
|
-
mode: 'auto', title: COPY.tidyTitle,
|
|
93
|
-
body: [el('p', { class: 'in-note' }, COPY.tidyBody(d.groups.length, inits, d.archive))
|
|
110
|
+
mode: 'auto', title: fixes.length ? COPY.jevTitle : COPY.tidyTitle,
|
|
111
|
+
body: [d.groups.length ? el('p', { class: 'in-note' }, COPY.tidyBody(d.groups.length, inits, d.archive)) : null,
|
|
112
|
+
el('div', { class: 'in-tidy' }, ...(kindsBox ? [kindsBox] : []), ...groups), err].filter(Boolean),
|
|
94
113
|
attrs: { 'data-in-sheet': 'tidy' },
|
|
95
114
|
onClose: () => finish(null),
|
|
96
115
|
actions: [
|
|
@@ -102,25 +121,73 @@ export function tidySheet(d) {
|
|
|
102
121
|
});
|
|
103
122
|
}
|
|
104
123
|
|
|
105
|
-
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
124
|
+
/**
|
|
125
|
+
* Jev's plan (scan-documents S11): how many documents Jev would read and how many requests that sends, before anything
|
|
126
|
+
* is sent. Resolves 'jev' on the check, 'skip' on Scan without Jev, and null on Cancel, Escape or the backdrop.
|
|
127
|
+
*/
|
|
128
|
+
export function jevPlanSheet(plan) {
|
|
129
|
+
return new Promise((resolve) => {
|
|
130
|
+
let settled = false;
|
|
131
|
+
const finish = (v) => { if (settled) return; settled = true; resolve(v); };
|
|
132
|
+
const s = sheet({
|
|
133
|
+
mode: 'auto', title: COPY.jevTitle,
|
|
134
|
+
body: [el('p', { class: 'in-note' }, COPY.jevPlan(plan)), plan.withheld_count || plan.excluded ? el('p', { class: 'in-note' }, COPY.jevWithheld((plan.withheld_count || 0) + (plan.excluded || 0))) : null].filter(Boolean),
|
|
135
|
+
attrs: { 'data-in-sheet': 'jev-plan' },
|
|
136
|
+
onClose: () => finish(null),
|
|
137
|
+
actions: [
|
|
138
|
+
button({ label: COPY.cancel, variant: 'outline', onClick: () => s.close(), attrs: { 'data-in-cancel': '' } }),
|
|
139
|
+
button({ label: COPY.jevSkip, variant: 'outline', onClick: () => { finish('skip'); s.close(); }, attrs: { 'data-in-jev-skip': '' } }),
|
|
140
|
+
button({ label: COPY.jevGo(plan.requests), variant: 'primary', onClick: () => { finish('jev'); s.close(); }, attrs: { 'data-in-jev-go': '' } }),
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Jev's part of a scan (S11): the plan, the PM's confirm, then the check. Answers { cancelled } on Cancel, { review } with
|
|
147
|
+
* the kinds to change and the groups when Jev answered, or { note } with what to say when Jev did not check. */
|
|
148
|
+
async function jevStep(ws) {
|
|
149
|
+
const j = await jevCheck(ws);
|
|
150
|
+
if (!j || !j.ok || j.profile !== 'pm-artifacts') return {};
|
|
151
|
+
if (!j.gates || !j.gates.open) return { note: COPY.jevShut(j.gates && j.gates.sentence ? j.gates.sentence : 'it is switched off.') };
|
|
152
|
+
if (!j.plan || !j.plan.documents) return {};
|
|
153
|
+
const go = await jevPlanSheet(j.plan);
|
|
154
|
+
if (!go) return { cancelled: true };
|
|
155
|
+
if (go === 'skip') return {};
|
|
156
|
+
toast({ text: COPY.jevReading, timeout: 6000 });
|
|
157
|
+
const r = await jevCheck(ws, j.plan);
|
|
158
|
+
if (!r || !r.ok) return { failed: COPY.jevFailed((r && r.error) || 'no answer') };
|
|
159
|
+
const pre = r.paused ? COPY.jevPaused(r.paused) + ' ' : '';
|
|
160
|
+
if (!(r.corrections || []).length && !(r.groups || []).length) return { note: pre + COPY.jevClean(r.checked || 0) };
|
|
161
|
+
return { review: { corrections: r.corrections || [], groups: r.groups || [], archive: r.archive }, note: pre || null };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Re-scan: read the repository again now. In a document repository, Jev's check comes first when its gates are open
|
|
165
|
+
* (S11), then the tidy sheet: the kinds Jev would change and the duplicates by Jev's kinds and picks. With Jev's gates
|
|
166
|
+
* shut, or on Scan without Jev, the tidy sheet lists the duplicates the file names show (S10). A toast says the scan
|
|
167
|
+
* started and another says how it ended. Answers the rescan answer; the caller reloads what it shows when `ok`. */
|
|
108
168
|
export async function rescanRepo(ws) {
|
|
109
169
|
let first = null;
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
170
|
+
const jv = await jevStep(ws);
|
|
171
|
+
if (jv.cancelled) return { ok: false, cancelled: true };
|
|
172
|
+
if (jv.failed) toast({ text: jv.failed, tone: 'error', timeout: 8000 });
|
|
173
|
+
let d = jv.review || null;
|
|
174
|
+
if (!d) { const dup = await duplicates(ws); if (dup && dup.ok && Array.isArray(dup.groups) && dup.groups.length) d = dup; }
|
|
175
|
+
if (d) {
|
|
112
176
|
const pick = await tidySheet(d);
|
|
113
177
|
if (!pick) return { ok: false, cancelled: true };
|
|
114
|
-
if (pick !== 'skip') first = () => tidy(ws, pick.keep);
|
|
178
|
+
if (pick !== 'skip') first = () => tidy(ws, pick.keep, pick.kinds);
|
|
115
179
|
}
|
|
116
180
|
toast({ text: COPY.rescanning, timeout: 4000 });
|
|
117
181
|
const a = await rescan(ws, first ? { first } : null);
|
|
118
182
|
const t = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
119
183
|
const moved = a && a.ok && a.after ? scanMoves(a.before, a.after) : null;
|
|
120
|
-
const
|
|
184
|
+
const td = a && a.tidied;
|
|
185
|
+
const archived = (td && td.kinds_written ? COPY.jevRecorded(td.kinds_written) + ' ' : '')
|
|
186
|
+
+ (td && Array.isArray(td.moved) && td.moved.length ? COPY.tidyArchived(td.moved.length) + ' ' : '');
|
|
187
|
+
const note = jv.note ? ' ' + jv.note : '';
|
|
121
188
|
if (!a || !a.ok) toast({ text: first && a && !a.after && !a.slow ? COPY.tidyFailed((a && a.error) || 'no answer') : COPY.rescanFailed((a && a.error) || 'no answer'), tone: 'error', timeout: 8000 });
|
|
122
|
-
else if (a.slow) toast({ text: archived + COPY.rescanSlow, timeout: 6000 });
|
|
123
|
-
else toast({ text: archived + (moved ? COPY.rescanResult(moved.checked, moved.moves, (a.after && a.after.scan) || null) : COPY.rescanDone(t)), timeout: 10000 });
|
|
189
|
+
else if (a.slow) toast({ text: archived + COPY.rescanSlow + note, timeout: 6000 });
|
|
190
|
+
else toast({ text: archived + (moved ? COPY.rescanResult(moved.checked, moved.moves, (a.after && a.after.scan) || null) : COPY.rescanDone(t)) + note, timeout: 10000 });
|
|
124
191
|
try { if (a && a.ok) window.dispatchEvent(new CustomEvent('pmos:initiatives-changed')); } catch { /* no window */ }
|
|
125
192
|
return a || { ok: false };
|
|
126
193
|
}
|
|
@@ -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',
|