create-pmos 0.496.0 → 0.498.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 +51 -33
- package/payload/AGENTS.md +1 -0
- 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 +35 -19
- package/payload/state/discoveries.md +1 -1
- package/workspace/briefing-premium-test.js +2 -2
- package/workspace/changelog.json +50 -0
- package/workspace/guide/briefing-premium.md +4 -2
- package/workspace/guide/data-freshness.md +5 -1
- package/workspace/guide/home.md +3 -3
- package/workspace/guide/launcher.md +7 -1
- package/workspace/lib/briefing-premium.js +38 -12
- package/workspace/lib/engine/workflow-state.js +35 -19
- package/workspace/lib/git-identity-cli.js +67 -0
- package/workspace/lib/git-identity.js +93 -0
- package/workspace/lib/pmos-verbs.js +3 -0
- package/workspace/lib/routes-boards.js +8 -3
- package/workspace/lib/vendor/scripts/workflow-state.js +35 -19
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/bd-ini-panel.js +1 -1
- package/workspace/public/pm/chat-turn-end.js +19 -0
- package/workspace/public/pm/home-app.js +13 -3
- package/workspace/public/pm/home-chat.js +7 -2
- package/workspace/public/pm/home-ini.js +8 -1
- package/workspace/public/pm/home-need.js +1 -1
- package/workspace/public/pm/ini-edit.js +82 -19
- package/workspace/public/pm/ini-tasks.js +6 -1
- package/workspace/public/pm/ini-view-model.js +3 -1
- package/workspace/public/pm/ini-view.css +31 -7
- package/workspace/public/pm/premium-board.js +2 -2
- package/workspace/stage-card-tasks-test.js +4 -3
- package/workspace/test-lists/chat-turn-end.json +9 -0
- package/workspace/test-lists/git-identity.json +5 -0
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
* dashed Dates chip) and ⋯ right after the dates (StA8Desk)
|
|
10
10
|
* ticketHtml(k, o) an unlinked Jira ticket's open row: the same, with Open the ticket in place of ⋯ (its place is Jira's)
|
|
11
11
|
* plusHtml(sec, id, o) the + on an open stage's header: "New task in <stage>" (StA5Add)
|
|
12
|
-
* addHtml(sec, id) the field at the top of that stage's list,
|
|
13
|
-
*
|
|
12
|
+
* addHtml(sec, id) the field at the top of that stage's list, and one chip row under it (batch 25 item 5, canvas
|
|
13
|
+
* TaskAddChips): Stage (the stage the + opened in; a picker of the initiative's workflow stages), Type (Task
|
|
14
|
+
* or Milestone: a milestone's box is ◇ and its Dates chip takes one date), Dates, Owner. Only what the task
|
|
15
|
+
* model stores: stage, milestone, start, due, owner
|
|
16
|
+
* press(b, d, root, H, e) a press this file owns → true: a title (opens the row), ⋯ and its menu, the +, the field's chips
|
|
14
17
|
* wire(body) once per body: the name field (Enter or blur saves, Esc closes), the add field (Enter adds and stays open),
|
|
15
18
|
* a press outside the open row closes it, and the drag (StA10Drag): a grip on hover on a desktop, a long press
|
|
16
19
|
* on the title on a phone; the target stage lights up and a blue line shows where the task lands
|
|
@@ -29,14 +32,15 @@ const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&':
|
|
|
29
32
|
const SVG = (d, w = 16, sw = 1.8) => `<svg aria-hidden="true" width="${w}" height="${w}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round"><path d="${d}"/></svg>`;
|
|
30
33
|
const P = {
|
|
31
34
|
more: 'M5 12h.01M12 12h.01M19 12h.01', plus: 'M12 5v14M5 12h14', check: 'M5 12l5 5 9-10', down: 'M7 10l5 5 5-5', right: 'M10 7l5 5-5 5', left: 'M14 7l-5 5 5 5',
|
|
32
|
-
dia: 'M12 4l8 8-8 8-8-8z',
|
|
35
|
+
dia: 'M12 4l8 8-8 8-8-8z', stage: 'M12 3l9 5-9 5-9-5zM3 13l9 5 9-5', box: 'M6 5h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z',
|
|
36
|
+
who: 'M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM4 21c1-4 4-6 8-6s7 2 8 6', cal: 'M4 5h16v15H4zM8 3v4M16 3v4M4 10h16', grip: 'M9 6h.01M15 6h.01M9 12h.01M15 12h.01M9 18h.01M15 18h.01',
|
|
33
37
|
open: 'M14 4h6v6M20 4l-9 9M18 14v6H4V6h6', bin: 'M5 7h14M10 7V4h4v3M7 7l1 13h8l1-13',
|
|
34
38
|
};
|
|
35
39
|
|
|
36
40
|
/* ── what is open, per initiative ── */
|
|
37
41
|
const OPEN = new Map(); // initiative id → the uid of the row open in place
|
|
38
42
|
const ADD = new Map(); // initiative id → the stage key whose + field is open
|
|
39
|
-
const ADD_D = new Map(); // initiative id → { start, due
|
|
43
|
+
const ADD_D = new Map(); // initiative id → what the add field's chips picked: { start, due, section, ms, owner } (batch 25 item 5)
|
|
40
44
|
const STAGES = new Map(); // initiative id → [{ key, name }] the stages a task can move to
|
|
41
45
|
const VIEW = new Map(); // initiative id → the view the List drew last (the drag reads its stages' tasks)
|
|
42
46
|
export const openOf = (id) => OPEN.get(id) || '';
|
|
@@ -94,14 +98,41 @@ export function plusHtml(sec, id, o) {
|
|
|
94
98
|
return `<button type="button" class="iv-ib iv-plus${on ? ' on' : ''}" data-iv-plus="${esc(sec.key || '')}" data-nm="${esc(nm)}" aria-pressed="${on}" aria-label="${esc('New task in ' + nm)}" title="${esc('New task in ' + nm)}">${SVG(P.plus, 16, 2.2)}</button>`;
|
|
95
99
|
}
|
|
96
100
|
const mdIso = (iso) => { const n = TV.dn(iso); return n == null ? '' : TV.md(n); };
|
|
101
|
+
const initials = (n) => String(n || '').split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join('') || '?';
|
|
102
|
+
/** The add field's picks: the stage it adds into (the + it opened from, unless the Stage chip changed it) and that stage's name. */
|
|
103
|
+
function addTo(id, own, nm) {
|
|
104
|
+
const D = ADD_D.get(id) || {};
|
|
105
|
+
return D.section != null && D.section !== own ? { key: D.section, name: stageName(id, D.section) } : { key: own, name: nm };
|
|
106
|
+
}
|
|
107
|
+
/** The chip row under the add field (TaskAddChips): Stage, Type, Dates, Owner. A chip with nothing picked is dashed. */
|
|
108
|
+
function chipsHtml(id, D, to) {
|
|
109
|
+
const ms = !!D.ms;
|
|
110
|
+
const dt = ms ? mdIso(D.due) : D.start || D.due ? [mdIso(D.start), mdIso(D.due)].filter(Boolean).join(' – ') : '';
|
|
111
|
+
const car = SVG(P.down, 12, 2.2);
|
|
112
|
+
const stage = `<button type="button" class="iv-edc iv-adc on" data-iv-addst aria-haspopup="menu" aria-expanded="false" aria-label="${esc('Stage: ' + to.name + '. Change')}" title="Stage">${SVG(P.stage, 14, 2)}<b>${esc(to.name)}</b>${car}</button>`;
|
|
113
|
+
const type = `<button type="button" class="iv-edc iv-adc${ms ? ' ms' : ''}" data-iv-addty aria-haspopup="menu" aria-expanded="false" aria-label="${esc('Type: ' + (ms ? 'Milestone' : 'Task') + '. Change')}" title="Task or milestone">${SVG(ms ? P.dia : P.box, 14, 2)}${ms ? 'Milestone' : 'Task'}${car}</button>`;
|
|
114
|
+
const dates = `<button type="button" class="iv-edc iv-edd${dt ? '' : ' dash'}" data-iv-adddt data-s="${esc(ms ? '' : D.start || '')}" data-e="${esc(D.due || '')}" data-f="${ms ? 'due' : 'start'}"${ms ? ' data-one="1"' : ''} aria-haspopup="dialog" aria-label="${esc(dt ? (ms ? 'Date: ' : 'Dates: ') + dt + '. Change' : ms ? 'Date, optional' : 'Dates, optional')}" title="${ms ? 'Date, optional' : 'Dates, optional'}">${SVG(P.cal, 14, 2)}${esc(dt || (ms ? 'Date' : 'Dates'))}</button>`;
|
|
115
|
+
const owner = D.owner
|
|
116
|
+
? `<button type="button" class="iv-edc iv-adc" data-iv-addow aria-haspopup="menu" aria-expanded="false" aria-label="${esc('Owner: ' + D.owner + '. Change')}" title="Owner"><span class="iv-av" aria-hidden="true">${esc(initials(D.owner))}</span>${esc(D.owner)}</button>`
|
|
117
|
+
: `<button type="button" class="iv-edc iv-adc dash" data-iv-addow aria-haspopup="menu" aria-expanded="false" aria-label="Owner, optional: you when none is picked" title="Owner, optional">${SVG(P.who, 14, 2)}Owner</button>`;
|
|
118
|
+
return stage + type + dates + owner;
|
|
119
|
+
}
|
|
120
|
+
const addBox = (ms) => (ms ? `<span class="iv-adms" aria-hidden="true">${SVG(P.dia, 20, 2)}</span>` : '<span class="iv-cb" aria-hidden="true"></span>');
|
|
97
121
|
export function addHtml(sec, id) {
|
|
98
122
|
if (addOf(id) !== (sec.key || '')) return '';
|
|
99
|
-
const nm = sec.name || 'Not in a stage', D = ADD_D.get(id) || {};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
+ `<input type="text" data-iv-addinp maxlength="200" autocomplete="off" placeholder="${esc('New task in ' + nm)}" aria-label="${esc('New task in ' + nm)}">`
|
|
123
|
+
const own = sec.key || '', nm = sec.name || 'Not in a stage', D = ADD_D.get(id) || {}, to = addTo(id, own, nm);
|
|
124
|
+
return `<form class="iv-addf iv-addin" data-esc-layer data-iv-addin="${esc(own)}" data-nm="${esc(nm)}">${addBox(D.ms)}`
|
|
125
|
+
+ `<input type="text" data-iv-addinp maxlength="200" autocomplete="off" placeholder="${esc('New task in ' + to.name)}" aria-label="${esc('New task in ' + to.name)}">`
|
|
103
126
|
+ `<button type="submit" class="iv-go" aria-label="Add the task" title="Add (Enter). Esc closes.">${SVG(P.check, 16, 2.2)}</button></form>`
|
|
104
|
-
+ `<div class="iv-addd"
|
|
127
|
+
+ `<div class="iv-addd">${chipsHtml(id, D, to)}</div>`;
|
|
128
|
+
}
|
|
129
|
+
/** The names an owner can be: You first, then everyone who owns a task of this initiative. */
|
|
130
|
+
function ownersOf(id) {
|
|
131
|
+
const v = VIEW.get(id), out = new Set(['You']);
|
|
132
|
+
for (const s of (v && v.tasks && v.tasks.sections) || []) for (const c of s.stages || [s]) {
|
|
133
|
+
for (const x of [...(c.top || []), ...(c.groups || []).flatMap((g) => g.rows), ...(c.done || [])]) if (x.owner && !x.jkey) out.add(x.owner);
|
|
134
|
+
}
|
|
135
|
+
return [...out];
|
|
105
136
|
}
|
|
106
137
|
|
|
107
138
|
/* ── the writes (Undo in the host's toast) ── */
|
|
@@ -173,9 +204,11 @@ export function press(b, d, root, H, e) {
|
|
|
173
204
|
}
|
|
174
205
|
if ('ivAdddt' in d) {
|
|
175
206
|
e.preventDefault(); TOUCHED = Date.now();
|
|
176
|
-
|
|
207
|
+
const list = b.closest('.iv-wl');
|
|
208
|
+
TV.datePop(b, '', { dates: (u, p) => { ADD_D.set(id, { ...(ADD_D.get(id) || {}), ...p }); redrawAdd(list, id); } });
|
|
177
209
|
return true;
|
|
178
210
|
}
|
|
211
|
+
if ('ivAddst' in d || 'ivAddty' in d || 'ivAddow' in d) { e.preventDefault(); TOUCHED = Date.now(); addMenu(b, d, id); return true; }
|
|
179
212
|
if (d.ivMore) { e.preventDefault(); TOUCHED = Date.now(); moreMenu(b, d.ivMore, id, v, H); return true; }
|
|
180
213
|
if (b.closest('.iv-ed')) TOUCHED = Date.now();
|
|
181
214
|
return false;
|
|
@@ -192,14 +225,43 @@ function focusLater(root, sel, select) {
|
|
|
192
225
|
};
|
|
193
226
|
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(go); else go();
|
|
194
227
|
}
|
|
195
|
-
/** The add field's
|
|
228
|
+
/** The add field's chips, box and placeholder again after a pick, without a redraw (the typed words stay). */
|
|
196
229
|
function redrawAdd(list, id) {
|
|
197
|
-
const f = list && list.querySelector('.iv-addd'); if (!f) return;
|
|
198
|
-
const D = ADD_D.get(id) || {},
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
230
|
+
const f = list && list.querySelector('[data-iv-addin]'), row = list && list.querySelector('.iv-addd'); if (!f || !row) return;
|
|
231
|
+
const D = ADD_D.get(id) || {}, to = addTo(id, f.dataset.ivAddin, f.dataset.nm);
|
|
232
|
+
row.innerHTML = chipsHtml(id, D, to);
|
|
233
|
+
const box = f.firstElementChild; if (box && box.tagName === 'SPAN') box.outerHTML = addBox(D.ms);
|
|
234
|
+
const inp = f.querySelector('[data-iv-addinp]');
|
|
235
|
+
if (inp) { inp.placeholder = 'New task in ' + to.name; inp.setAttribute('aria-label', 'New task in ' + to.name); }
|
|
236
|
+
}
|
|
237
|
+
/** The Stage, Type and Owner chips' pickers, right under the chip. A pick changes only the add field; nothing is saved until ✓. */
|
|
238
|
+
function addMenu(b, d, id) {
|
|
239
|
+
const list = b.closest('.iv-wl'), f = list && list.querySelector('[data-iv-addin]'); if (!f) return;
|
|
240
|
+
const D = ADD_D.get(id) || {}, set = (p) => { ADD_D.set(id, { ...(ADD_D.get(id) || {}), ...p }); redrawAdd(list, id); const n = list.querySelector('[data-iv-addinp]'); if (n) n.focus({ preventScroll: true }); };
|
|
241
|
+
const item = (attr, on, inner) => `<button type="button" role="menuitemradio" class="o${on ? ' hl' : ''}" aria-checked="${on}" ${attr}><span class="ck" aria-hidden="true">${on ? SVG(P.check, 14, 2.4) : ''}</span>${inner}</button>`;
|
|
242
|
+
if ('ivAddst' in d) {
|
|
243
|
+
const to = addTo(id, f.dataset.ivAddin, f.dataset.nm).key;
|
|
244
|
+
let n = 0;
|
|
245
|
+
const html = `<div class="tkc-pop ivq-m ive-m" role="menu" aria-label="Stage">` + (STAGES.get(id) || []).map((s) => item(`data-ive-to="${esc(s.key)}"`, s.key === to, `${s.key ? `<span class="nn" aria-hidden="true">${++n}</span>` : ''}<span>${esc(s.name)}</span>`)).join('') + '</div>';
|
|
246
|
+
TV.openPop(b, html, (o) => { if (o.dataset.iveTo == null) return; TV.closePop(); set({ section: o.dataset.iveTo }); });
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if ('ivAddty' in d) {
|
|
250
|
+
const html = `<div class="tkc-pop ivq-m ive-m" role="menu" aria-label="Task or milestone">` + item('data-ive-ty="task"', !D.ms, `<span class="nn ic" aria-hidden="true">${SVG(P.box, 14, 2)}</span><span>Task</span>`)
|
|
251
|
+
+ item('data-ive-ty="ms"', !!D.ms, `<span class="nn ic" aria-hidden="true">${SVG(P.dia, 14, 2)}</span><span>Milestone</span><span class="k">one date</span>`) + '</div>';
|
|
252
|
+
TV.openPop(b, html, (o) => {
|
|
253
|
+
const t = o.dataset.iveTy; if (!t) return; TV.closePop();
|
|
254
|
+
// a milestone has one date: the due day (or the start, when only a start was picked)
|
|
255
|
+
if (t === 'ms') set({ ms: true, due: D.due || D.start || '', start: '' }); else set({ ms: false });
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const names = ownersOf(id), cur = D.owner || '';
|
|
260
|
+
const html = `<div class="tkc-pop ivq-m ive-m" role="menu" aria-label="Owner">` + names.map((nm) => item(`data-ive-ow="${esc(nm === 'You' ? '' : nm)}"`, nm === 'You' ? !cur : nm === cur, `<span class="iv-av" aria-hidden="true">${esc(nm === 'You' ? 'Y' : initials(nm))}</span><span>${esc(nm)}</span>`)).join('')
|
|
261
|
+
+ `<hr><form class="tkc-new ive-own"><input type="text" maxlength="80" placeholder="Someone else" aria-label="Owner's name" autocomplete="off"></form></div>`;
|
|
262
|
+
const el = TV.openPop(b, html, (o) => { if (o.dataset.iveOw == null) return; TV.closePop(); set({ owner: o.dataset.iveOw }); });
|
|
263
|
+
const form = el.querySelector('.ive-own');
|
|
264
|
+
if (form) form.addEventListener('submit', (e) => { e.preventDefault(); const v = form.querySelector('input').value.trim(); if (!v) return; TV.closePop(); set({ owner: v }); });
|
|
203
265
|
}
|
|
204
266
|
/** ⋯ of the open row (StA2bMore): Milestone (or Make it a plain task), Move to a stage, Open the task card (Open the ticket on a Jira row),
|
|
205
267
|
* Delete (only where the task can be deleted; never a Jira row). Move to a stage turns the menu into the workflow's stages, ← goes back. */
|
|
@@ -256,9 +318,10 @@ export function wire(body) {
|
|
|
256
318
|
if (f.dataset.ivRenf != null) { const inp = f.querySelector('[data-iv-ren]'), id = idOf(); OPEN.delete(id); if (!save(inp) && H().redraw) H().redraw(); return; }
|
|
257
319
|
const inp = f.querySelector('[data-iv-addinp]'), t = inp ? inp.value.trim() : '', id = idOf();
|
|
258
320
|
if (!t || !H().addTask) return;
|
|
259
|
-
|
|
321
|
+
// the chips' picks (batch 25 item 5): the stage, a milestone with its one date, the owner (none: you)
|
|
322
|
+
const D = ADD_D.get(id) || {}, to = addTo(id, f.dataset.ivAddin, f.dataset.nm);
|
|
260
323
|
inp.disabled = true;
|
|
261
|
-
Promise.resolve(H().addTask(t, inp, { section:
|
|
324
|
+
Promise.resolve(H().addTask(t, inp, { section: to.key, name: to.name, ...(D.start && !D.ms ? { start: D.start } : {}), ...(D.due ? { due: D.due } : {}), ...(D.ms ? { milestone: true } : {}), ...(D.owner ? { owner: D.owner } : {}) })).then(() => {
|
|
262
325
|
// Enter keeps the field open for the next one (StA5Add)
|
|
263
326
|
ADD_D.delete(id);
|
|
264
327
|
const n = body.querySelector('[data-iv-addinp]'); if (n) { if (n === inp) n.value = ''; n.disabled = false; n.focus({ preventScroll: true }); }
|
|
@@ -157,10 +157,15 @@ const tkTitle = (t) => `<button type="button" class="iv-tt" data-ig-tk="${esc(t.
|
|
|
157
157
|
/** A task linked to a Jira ticket it names (ini-view-model.js linkOf): the key once, in blue, then the rest of its title, the same in the List,
|
|
158
158
|
* the Timeline and the Kanban (class lk). A key PMOS has not read stays plain text in the title (0.442.0 hotfix). */
|
|
159
159
|
export function keyName(x) {
|
|
160
|
-
if (!x.jkey) return
|
|
160
|
+
if (!x.jkey) return prefixName(x.title);
|
|
161
161
|
const rest = String(x.title || '').replace(new RegExp('[\\[(]?\\b' + x.jkey + '\\b[\\])]?\\s*[:\u2013\u2014-]?\\s*'), '').trim();
|
|
162
162
|
return `<em class="lk">${esc(x.jkey)}</em>${rest ? ' ' + esc(rest) : ''}`;
|
|
163
163
|
}
|
|
164
|
+
/** A title that starts with a short prefix ("Bus shelter: Confirm QR placement") keeps it, quieter (batch 25 item 4, canvas TaskNamesWrap). */
|
|
165
|
+
export function prefixName(t) {
|
|
166
|
+
const m = /^([^:]{1,32}):\s+(\S[\s\S]*)$/.exec(String(t == null ? '' : t));
|
|
167
|
+
return m && m[1].trim().split(/\s+/).length <= 4 && !/\/\/|\d{1,2}$/.test(m[1]) ? `<span class="iv-pf">${esc(m[1])}: </span>${esc(m[2])}` : esc(t);
|
|
168
|
+
}
|
|
164
169
|
/** The Jira mark of a linked task, in place of its box: the status square the ticket rows draw (To do, In progress, Done). Jira owns the
|
|
165
170
|
* status, so it marks nothing here. */
|
|
166
171
|
export const jqMark = (x) => `<span class="iv-jq ig-sq ${x.closed ? 'done' : x.prog ? 'doing' : 'todo'}${mvCls(x.jmove)}" role="img" aria-label="${esc('Jira ' + x.jkey + ': ' + (movingOf(x.jmove) ? 'moving to ' + x.status : x.jst || x.status))}" title="${esc('Jira · ' + (movingOf(x.jmove) ? 'moving to ' + x.status : x.jst || x.status))}"></span>`;
|
|
@@ -302,7 +302,9 @@ export function iniView(r, ctx = {}) {
|
|
|
302
302
|
// what waits on the PM: the track's, else the trail's (the Board merges the trail into the track; Home reads the row as sent)
|
|
303
303
|
const you = t.you || (it.you && it.you.text) || null, youKind = t.youKind || (it.you && it.you.kind) || null, youWhy = t.youWhy || (it.you && it.you.why) || '';
|
|
304
304
|
let wait = null;
|
|
305
|
-
|
|
305
|
+
// Claude's ✦ proposal for a blocked step (batch 25 item 6): what it will do, for the PM to approve; never a bare question
|
|
306
|
+
if (ctx.ask && ctx.ask.propose && ctx.ask.propose.say) wait = { text: '✦ ' + ctx.ask.propose.say, say: '✦ ' + ctx.ask.propose.say + ' Shall I go ahead?', primary: { act: 'ask', label: '✦ ' + ctx.ask.propose.label } };
|
|
307
|
+
else if (ctx.ask && ctx.ask.title) wait = { text: '“' + ctx.ask.title + '” needs your answer.', say: 'Can you answer “' + ctx.ask.title + '”? I’ll carry on once you do.', primary: { act: 'ask', label: 'Answer: ' + ctx.ask.title } };
|
|
306
308
|
else if (you && youKind) wait = { text: youWhy, primary: youKind === 'drift' && it.links && it.links.prd ? { act: 'doc:' + it.links.prd, label: 'Open the PRD' } : youKind === 'commit' ? { act: 'commit', label: 'Send to Jira' } : { act: 'page', label: you } };
|
|
307
309
|
else if (you) { const opn = tk.filter((k) => k.status !== 'Done').length; wait = { text: opn ? plural(opn, 'ticket is', 'tickets are') + ' still open, so the sign-off can wait until they close.' : 'Every release ticket is done.', primary: { act: 'page', label: you } }; }
|
|
308
310
|
else if (ctx.grill && ctx.grill.open && !ctx.grill.later) wait = { text: 'PMOS asks what the BRD needs that the Why does not say yet.', primary: { act: 'grill', label: 'Answer the questions' } };
|
|
@@ -1193,12 +1193,24 @@ html.ad-still .mi-svg * { animation: none !important; }
|
|
|
1193
1193
|
/* ── a task changed where it sits in the List (batch 18 item 16, agreed A; canvas In development St*) ──
|
|
1194
1194
|
* One line per task (StA1Rows): the box, the title, its days at the right (a phone: the due day only), the owner's face on a desktop. A title
|
|
1195
1195
|
* press opens the row in place (StA2Edit): the name field, then one quiet line: status, Start – Due, ⋯ right after the dates (StA8Desk). */
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
.iv-tr.q
|
|
1199
|
-
.iv-tr.q .iv-
|
|
1200
|
-
.iv-tr.q .
|
|
1201
|
-
.iv-tr.q .iv-
|
|
1196
|
+
/* batch 25 item 4 (canvas TaskNamesWrap, agreed A): a task's name is never cut. It wraps onto as many lines as it needs; its days stay at the
|
|
1197
|
+
* right of the first line in a narrower column; a narrow panel puts them under the name. The prefix ("Bus shelter:") stays, quieter. */
|
|
1198
|
+
.iv-tr.q { align-items: flex-start; position: relative; box-sizing: border-box; padding: 11px 0 !important; }
|
|
1199
|
+
.iv-tr.q .iv-bxw { align-self: flex-start; margin-top: 2px; }
|
|
1200
|
+
.iv-tr.q > .w { flex-direction: row; align-items: flex-start; gap: 10px; min-width: 0; }
|
|
1201
|
+
.iv-tr.q .iv-tn { flex: 1; min-width: 0; display: flex; align-items: flex-start; gap: 6px; }
|
|
1202
|
+
.iv-tr.q .iv-tt { display: block !important; flex: 0 1 auto; text-align: left; white-space: normal; overflow-wrap: anywhere; min-width: 0; font-weight: 600; line-height: 22px; }
|
|
1203
|
+
.iv-tr.q .iv-tn > :not(.iv-tt) { margin-top: 4px; }
|
|
1204
|
+
.iv-tr.q .iv-rm { flex: none; display: inline-flex; align-items: center; gap: 0; min-height: 22px; max-width: 40%; white-space: nowrap; }
|
|
1205
|
+
.iv-pf { color: var(--cz-ink3); font-weight: 700; }
|
|
1206
|
+
@container iv (max-width: 520px) {
|
|
1207
|
+
.iv-tr.q > .w { flex-direction: column; gap: 0; }
|
|
1208
|
+
.iv-tr.q .iv-rm { max-width: 100%; flex-wrap: wrap; white-space: normal; }
|
|
1209
|
+
.iv-tr.q .iv-rm .iv-dc { margin-left: 0 !important; padding-left: 0 !important; }
|
|
1210
|
+
.iv-tr.q .iv-dst { display: inline; }
|
|
1211
|
+
/* the days sit under the name: their wider press area (a phone) reaches down, not up over the name */
|
|
1212
|
+
.iv-tr.q .iv-rm .iv-dc::after { top: 0; }
|
|
1213
|
+
}
|
|
1202
1214
|
.iv-tr.q .iv-dc { margin: 0 !important; white-space: nowrap; }
|
|
1203
1215
|
.iv-dms { display: inline-flex; margin-right: 4px; color: var(--cz-ink2); }
|
|
1204
1216
|
.iv-rav { width: 24px; height: 24px; margin-left: 10px; background: var(--cz-well); color: var(--cz-ink2); }
|
|
@@ -1246,7 +1258,19 @@ html.ad-still .mi-svg * { animation: none !important; }
|
|
|
1246
1258
|
.iv-addin { margin: 0 -14px 0 -46px; padding: 10px 14px 4px 46px; background: var(--cz-bluebg); gap: 10px; }
|
|
1247
1259
|
.iv-addin .iv-cb { margin-top: 0 !important; border-style: solid; }
|
|
1248
1260
|
.iv-addin input { border: 1.5px solid var(--cz-blue); }
|
|
1249
|
-
.iv-addd { margin: 0 -14px 0 -46px; padding: 4px 14px 12px 46px; background: var(--cz-bluebg); }
|
|
1261
|
+
.iv-addd { margin: 0 -14px 0 -46px; padding: 4px 14px 12px 46px; background: var(--cz-bluebg); display: flex; flex-wrap: wrap; gap: 8px; }
|
|
1262
|
+
/* the add field's chips (batch 25 item 5, canvas TaskAddChips): Stage (picked, blue), Type, Dates, Owner; a milestone's box is ◇ */
|
|
1263
|
+
.iv .iv-adc { gap: 5px; }
|
|
1264
|
+
.iv .iv-adc.on { color: var(--cz-blueink) !important; box-shadow: 0 0 0 1.5px var(--cz-blue); }
|
|
1265
|
+
.iv .iv-adc.on b { font-weight: 700; max-width: 180px; overflow: hidden; text-overflow: ellipsis; }
|
|
1266
|
+
.iv .iv-adc.ms { color: var(--cz-blueink) !important; }
|
|
1267
|
+
.iv .iv-adc .iv-av { width: 20px; height: 20px; font-size: 11px; }
|
|
1268
|
+
.iv-adms { width: 20px; height: 20px; flex: none; display: inline-flex; align-items: center; justify-content: center; color: var(--cz-blue); }
|
|
1269
|
+
.tkc.ivq .ive-m .nn { width: 22px; height: 20px; border-radius: 7px; background: var(--cz-well); font-size: 11px; font-weight: 800; color: var(--cz-ink3); display: inline-flex; align-items: center; justify-content: center; flex: none; }
|
|
1270
|
+
.tkc.ivq .ive-m .nn.ic { background: transparent; color: var(--cz-ink2); }
|
|
1271
|
+
.tkc.ivq .ive-m .o.hl .nn:not(.ic) { background: var(--cz-bluebg); color: var(--cz-blueink); }
|
|
1272
|
+
.tkc.ivq .ive-m .iv-av { width: 22px; height: 22px; border-radius: 50%; background: var(--cz-well); color: var(--cz-ink2); font-size: 11px; font-weight: 800; display: inline-flex; align-items: center; justify-content: center; flex: none; }
|
|
1273
|
+
.tkc.ivq .ive-own { padding: 4px 6px 6px; }
|
|
1250
1274
|
/* the drag (StA10Drag): the row lifts and follows the pointer; its slot stays as a dashed ghost; the target stage lights up; a blue line marks the drop */
|
|
1251
1275
|
.iv-tr.iv-ghost { outline: 1.5px dashed color-mix(in srgb, var(--cz-ink3) 50%, var(--cz-card)); outline-offset: -2px; opacity: .55; }
|
|
1252
1276
|
.iv-fly { position: fixed; z-index: 1001; left: var(--fly-x, 0); top: var(--fly-y, 0); width: var(--fly-w, 320px); pointer-events: none; background: var(--cz-card); border-radius: 12px; padding: 7px 12px !important;
|
|
@@ -1142,8 +1142,8 @@ function openTask(r, k) {
|
|
|
1142
1142
|
async function addTask(r, title, section, dates, name) {
|
|
1143
1143
|
const body = { ws: r.ws || M.board.ws, board: r.boardId || M.board.id };
|
|
1144
1144
|
// dates: a gap's + (batch 11 item 13) fills the task's start and due with the gap's days ahead; section: the stage it goes under (the
|
|
1145
|
-
// add route places it, batch 18 item 16)
|
|
1146
|
-
const res = await pmPost('/api/boards/tasks/add', { ...body, initiative: r.id, title, ...(dates && dates.start ? { start: dates.start } : {}), ...(dates && dates.due ? { due: dates.due } : {}), ...(section ? { section } : {}) }).catch(() => null);
|
|
1145
|
+
// add route places it, batch 18 item 16); the add field's chips also send milestone and owner in it (batch 25 item 5)
|
|
1146
|
+
const res = await pmPost('/api/boards/tasks/add', { ...body, initiative: r.id, title, ...(dates && dates.start ? { start: dates.start } : {}), ...(dates && dates.due ? { due: dates.due } : {}), ...(dates && dates.milestone ? { milestone: true } : {}), ...(dates && dates.owner ? { owner: dates.owner } : {}), ...(section ? { section } : {}) }).catch(() => null);
|
|
1147
1147
|
if (!res || res.ok === false) { say((res && res.error) || 'The task was not added.'); return false; }
|
|
1148
1148
|
const uid = res.item && res.item.uid;
|
|
1149
1149
|
// the + on a stage (batch 18 item 16, StA5Add): "Added to Build" with Undo
|
|
@@ -135,15 +135,16 @@ const TICKETS = [{ key: 'IDPF-3387', title: 'Refactor the AppAuthService', statu
|
|
|
135
135
|
const b1 = row('task-b1'), b4 = row('task-b4'), d1 = row('task-d1'), b2 = row('task-b2');
|
|
136
136
|
const tt = b1 && b1.querySelector('.iv-tt').getBoundingClientRect(), dc = b1 && b1.querySelector('.iv-dc').getBoundingClientRect();
|
|
137
137
|
return {
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
// batch 25 item 4 (TaskNamesWrap, agreed A): a narrow panel puts the days under the full name, start and due
|
|
139
|
+
under: !!(tt && dc && dc.top >= tt.bottom - 2 && Math.abs(dc.left - tt.left) < 8), wraps: !!(b1 && getComputedStyle(b1.querySelector('.iv-tt')).whiteSpace === 'normal'),
|
|
140
|
+
phoneDue: b1 && b1.querySelector('.iv-dc').innerText.trim(), startShown: vis(b1 && b1.querySelector('.iv-dst')),
|
|
140
141
|
undated: b4 && !b4.querySelector('.iv-dc'), ms: d1 && d1.querySelector('.iv-dc').innerText.trim(), msTitleGlyph: !!(d1 && d1.querySelector('.iv-tn .iv-ms')), msIcon: !!(d1 && d1.querySelector('.iv-dc .iv-dms svg')),
|
|
141
142
|
plus: [...document.querySelectorAll('#czViewer .iv-plus')].map((b) => b.getAttribute('aria-label')), headPlus: !!document.querySelector('#czViewer .iv-th [data-iv-add]'),
|
|
142
143
|
jira: b2 && !!b2.querySelector('.iv-jq.ig-sq') && !b2.querySelector('[data-iv-check]'),
|
|
143
144
|
grips: [...document.querySelectorAll('#czViewer .iv-grip')].filter(vis).length,
|
|
144
145
|
};
|
|
145
146
|
});
|
|
146
|
-
ok('S1 a phone row
|
|
147
|
+
ok('S1 a phone row: the whole title (it wraps, never cut), its days under it, start and due (batch 25 item 4)', R1.under && R1.wraps && /Nov 6$/.test(R1.phoneDue || '') && R1.startShown, JSON.stringify(R1));
|
|
147
148
|
ok('S1 a row with no dates shows none (zero is silence)', R1.undated, JSON.stringify(R1));
|
|
148
149
|
ok('S1 a milestone row says "◇ Nov 14", its ◇ once (not after the title)', R1.ms === 'Nov 14' && R1.msIcon && !R1.msTitleGlyph, JSON.stringify(R1));
|
|
149
150
|
ok('S1 each open stage has its own icon-only + ("New task in …"); the Tasks header has none', R1.plus.join('|') === 'New task in Build|New task in Deliver' && !R1.headPlus, JSON.stringify(R1));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Batch 25 item 3: a finished chat turn re-reads what the Viewer shows, with no manual refresh (pm/chat-turn-end.js, home-chat.js, home-app.js). Pure builder plus the wiring.",
|
|
3
|
+
"modules": [
|
|
4
|
+
"chat-turn-end.test.js"
|
|
5
|
+
],
|
|
6
|
+
"seconds": {
|
|
7
|
+
"chat-turn-end.test.js": 1
|
|
8
|
+
}
|
|
9
|
+
}
|