create-pmos 0.509.0 → 0.510.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 +20 -14
- 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/board-programs-test.js +3 -0
- package/workspace/changelog.json +19 -0
- package/workspace/guide/programs.md +2 -1
- package/workspace/guide/settings.md +2 -2
- package/workspace/lib/kit-update.js +16 -4
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/bd-prog.js +10 -5
- package/workspace/public/pm/cozy-board.css +8 -2
- package/workspace/public/pm/premium-board.js +4 -0
- package/workspace/public/pm/readonly.js +3 -1
- package/workspace/public/pm/upd-weeks.js +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.510.0",
|
|
4
4
|
"description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,25 @@
|
|
|
32
32
|
"audit"
|
|
33
33
|
],
|
|
34
34
|
"pmosChanges": [
|
|
35
|
+
{
|
|
36
|
+
"v": "0.510.0",
|
|
37
|
+
"at": "2026-11-03T14:29:03.666Z",
|
|
38
|
+
"f": 3,
|
|
39
|
+
"prs": [
|
|
40
|
+
{
|
|
41
|
+
"n": 1197,
|
|
42
|
+
"t": "Clean Updates lines: no, one PR number, no repeated title, right group",
|
|
43
|
+
"s": "Clean Updates lines: no, one PR number, no repeated title, right group",
|
|
44
|
+
"k": "fix"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"n": 1199,
|
|
48
|
+
"t": "A Board product head's name opens its page in the Viewer",
|
|
49
|
+
"s": "A Board product head's name opens its page in the Viewer",
|
|
50
|
+
"k": "new"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
35
54
|
{
|
|
36
55
|
"v": "0.509.0",
|
|
37
56
|
"at": "2026-11-03T14:11:23.227Z",
|
|
@@ -440,19 +459,6 @@
|
|
|
440
459
|
"k": "fix"
|
|
441
460
|
}
|
|
442
461
|
]
|
|
443
|
-
},
|
|
444
|
-
{
|
|
445
|
-
"v": "0.490.0",
|
|
446
|
-
"at": "2026-11-02T08:52:24.978Z",
|
|
447
|
-
"f": 3,
|
|
448
|
-
"prs": [
|
|
449
|
-
{
|
|
450
|
-
"n": 1153,
|
|
451
|
-
"t": "Sign-in session takes keys; login check names the login",
|
|
452
|
-
"s": "Sign-in session takes keys; login check names the login",
|
|
453
|
-
"k": "fix"
|
|
454
|
-
}
|
|
455
|
-
]
|
|
456
462
|
}
|
|
457
463
|
]
|
|
458
464
|
}
|
package/payload/KIT-VERSION
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Eval calibration log"
|
|
4
4
|
description: "Append-only record of every PM override of an evaluator verdict."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-11-03T14:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-11-03T14:29:00Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Health budgets"
|
|
4
4
|
description: "Closed registry of health-budget anchors (D59), edited only by the PM."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-11-03T14:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-11-03T14:29:00Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Discoveries and friction log"
|
|
4
4
|
description: "Append-only record of what runs discovered and where the process rubbed."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-11-03T14:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-11-03T14:29:00Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -99,6 +99,9 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
99
99
|
ok('O pressing the band dispatches bd:program with the program id and opens no initiative panel', (await events(p)).join() === 'project-ascent' && await p.evaluate(() => !/Initiative/.test(((window.czViewer && window.czViewer.current()) || {}).kind || '')), JSON.stringify(await events(p)));
|
|
100
100
|
await p.keyboard.press('Escape'); await p.waitForFunction(() => { const v = document.querySelector('#czViewer'); return !v || v.hidden; });
|
|
101
101
|
|
|
102
|
+
// batch 26 item 3: a product head's tile and name open its page in Home's Viewer; the chevron is its own button and folds
|
|
103
|
+
const ph = await p.evaluate(() => { const o = document.querySelector('#ibBody .bt-prd [data-prodopen]'), f = document.querySelector('#ibBody .bt-prd [data-fold]'); return { open: !!o && !o.contains(f) && /^Open IDPF, a product$/.test(o.getAttribute('aria-label')) && !!o.querySelector('.bd-ti'), fold: !!f && f.hasAttribute('aria-expanded') }; });
|
|
104
|
+
ok('P a product head: the tile and name are one button that opens the product, the fold is a separate button', ph.open && ph.fold, JSON.stringify(ph));
|
|
102
105
|
await p.click('#ibBody .bt-pg [data-fold]');
|
|
103
106
|
const f1 = await p.evaluate(() => ({ kids: document.querySelectorAll('#ibBody .bt-kid').length, band: !!document.querySelector('#ibBody .bt-pg'), exp: document.querySelector('#ibBody .bt-pg [data-fold]').getAttribute('aria-expanded'), st: localStorage.getItem('pmos.board.fold') }));
|
|
104
107
|
ok('F the program group folds to its band row, and the fold is stored', f1.kids === 0 && f1.band && f1.exp === 'false' && /g:project-ascent/.test(f1.st), JSON.stringify(f1));
|
package/workspace/changelog.json
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "0.510.0",
|
|
4
|
+
"at": "2026-11-03T14:29:03.666Z",
|
|
5
|
+
"f": 3,
|
|
6
|
+
"prs": [
|
|
7
|
+
{
|
|
8
|
+
"n": 1197,
|
|
9
|
+
"t": "Clean Updates lines: no, one PR number, no repeated title, right group",
|
|
10
|
+
"s": "Clean Updates lines: no, one PR number, no repeated title, right group",
|
|
11
|
+
"k": "fix"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"n": 1199,
|
|
15
|
+
"t": "A Board product head's name opens its page in the Viewer",
|
|
16
|
+
"s": "A Board product head's name opens its page in the Viewer",
|
|
17
|
+
"k": "new"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
2
21
|
{
|
|
3
22
|
"v": "0.509.0",
|
|
4
23
|
"at": "2026-11-03T14:11:23.227Z",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Programs on the Board: All products, groupings and the program panel"
|
|
4
4
|
description: "A program groups initiatives across products. How it shows on the Board (stage track, Kanban, timeline and List), the All products mode, the By product grouping, the program panel and the side panel width."
|
|
5
|
-
timestamp: 2026-11-
|
|
5
|
+
timestamp: 2026-11-03
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Programs on the Board
|
|
@@ -18,6 +18,7 @@ A cancelled or archived initiative leaves its program's views: the band, the pro
|
|
|
18
18
|
- The product picker is the same chip and list as on Home (see [Home](home.md)). It shows the tiles of the ticked products, or **All**. When the list closes, the Board shows the ticked products, merged as on All products. The pick is the saved setting that Home and Insights share, so a reload keeps it, and a product that you untick here goes from Home too.
|
|
19
19
|
- An address with `?ws=`, `?board=`, `?all=1` or `?pick=` is a one-off view: the Board shows what the address names and does not change the saved pick. A change in the list saves the pick and opens the Board without those parts.
|
|
20
20
|
- A program comes first. Its band shows the stage spread of its initiatives, the next milestone and how many initiatives are ready for it. Under the band, the initiatives sit under a small heading for each product. Then each product has its own group.
|
|
21
|
+
- A product's group head works like a program's: press the product's tile or name to open its page in Home's Viewer. Press the chevron to fold the group. This works in every view: the stage track, Kanban, the timeline and List. On a shared board, the product's name is plain text.
|
|
21
22
|
- To group by product only, open **Filter** and choose **By product**. A program member then shows a small layers icon and stays under its product in every view, the timeline included. A chip shows while By product is on.
|
|
22
23
|
- **Kanban** shows programs as swimlanes, then products. Press a column header to fold the column to a strip with its count. An empty column starts folded. The Board remembers each fold.
|
|
23
24
|
- The **timeline** draws a program row that brackets its initiatives' bars, on every span. Program milestones are diamonds. Milestones that are close together merge into one diamond with a count.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Settings in console3: one home for each setting"
|
|
4
4
|
description: "How to open Settings in console3, what each section holds, what the scope tags and the environment notes mean, and where the settings are kept."
|
|
5
|
-
timestamp: 2026-11-
|
|
5
|
+
timestamp: 2026-11-03
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Settings in console3: one home for each setting
|
|
@@ -70,7 +70,7 @@ Open **Updates**. PMOS asks the npm registry for the latest version at once, and
|
|
|
70
70
|
Under the update card, **Updates** shows what changed in the published kits. **This week** is open: it is the one you need. A week runs Monday to Sunday. Every earlier week, last week included, is folded into **Archive**. Press **Archive** to list the earlier weeks (**Week of** a date), each with its counts, and press a week to read it. When there is nothing for this week yet, the newest week with changes is the open one.
|
|
71
71
|
|
|
72
72
|
- Each week has up to three groups: **New** (a feature), **Improved** (a faster or better thing that already existed) and **Fixed** (a problem that is gone). A group with nothing in it is not shown, and a week with nothing to show is not listed.
|
|
73
|
-
- Each line is a bold name and one sentence, copied from the pull request that made the change, with its number at the end. Work that you cannot see in PMOS (notes, tests, run logs) is left out.
|
|
73
|
+
- Each line is a bold name and one sentence, copied from the pull request that made the change, with its number at the end. When the name and the sentence say the same thing, the line shows it once. Work that you cannot see in PMOS (notes, tests, run logs) is left out, and so are notes for the people who build PMOS, such as "[skip publish]" or "(batch 25 item 11)".
|
|
74
74
|
- A week shows **Ready to install** when one of its kits is newer than the version you run.
|
|
75
75
|
- A kit published before PMOS kept the kind of each change has no kind. Its title decides: a word such as "fix" or "no longer" puts it under **Fixed**, and anything else under **Improved**.
|
|
76
76
|
- **What's new** is not a section of its own any more. The button on the update card in **General** opens **Updates**.
|
|
@@ -122,12 +122,24 @@ const npxArgs = (src, version, rest) => (src && src.kind === 'local'
|
|
|
122
122
|
? ['-y', `--package=${src.path}`, '--', PKG_NAME, ...rest]
|
|
123
123
|
: ['-y', `${PKG_NAME}@${version}`, ...rest]);
|
|
124
124
|
|
|
125
|
-
/** One changelog entry as the page may show it, or null: a version, a time, and PRs with a number, a summary
|
|
126
|
-
* kit-changelog format 2, a
|
|
125
|
+
/** One changelog entry as the page may show it, or null: a version, a time, and PRs with a number, a summary, from
|
|
126
|
+
* kit-changelog format 2 a title, and from format 3 a kind (k: new, imp, fix or x), which decides New, Improved or Fixed.
|
|
127
|
+
* Kits published before kit-changelog dropped "[skip publish]" kept it in the text and lost the PR number to an inline
|
|
128
|
+
* "(#123)"; both are repaired here, so every line ends in one muted number. */
|
|
129
|
+
const KINDS = new Set(['new', 'imp', 'fix', 'x']);
|
|
130
|
+
const SKIP_TAG = /\s*\[(?:skip|no)[ -][a-z]+\]/gi;
|
|
131
|
+
const PR_REF = /\s*\(#(\d+)\)/;
|
|
132
|
+
const WORK_TAG = /\s*\((?:batch \d+|canvas |D\d+\b)[^()]*\)\s*$/i; // "(batch 25 item 11)", "(canvas TplA)", "(D105)": how it was made, not what it does
|
|
133
|
+
const tidy = (t) => t.replace(SKIP_TAG, '').replace(new RegExp(PR_REF.source, 'g'), '').replace(/\s+/g, ' ').trim().replace(WORK_TAG, '').trim();
|
|
134
|
+
function cleanPr(p) {
|
|
135
|
+
const t = typeof p.t === 'string' ? p.t : '', ref = PR_REF.exec(t) || PR_REF.exec(p.s);
|
|
136
|
+
const n = Number.isInteger(p.n) ? p.n : ref ? Number(ref[1]) : null;
|
|
137
|
+
const tt = tidy(t).slice(0, 200), s = tidy(p.s).slice(0, 400);
|
|
138
|
+
return { n, ...(tt ? { t: tt } : {}), s: s || tt, ...(KINDS.has(p.k) ? { k: p.k } : {}) };
|
|
139
|
+
}
|
|
127
140
|
function cleanEntry(e) {
|
|
128
141
|
if (!e || !validVersion(e.v)) return null;
|
|
129
|
-
const prs = (Array.isArray(e.prs) ? e.prs : []).filter((p) => p && typeof p.s === 'string' && p.s.trim())
|
|
130
|
-
.map((p) => ({ n: Number.isInteger(p.n) ? p.n : null, ...(typeof p.t === 'string' && p.t.trim() ? { t: p.t.trim().slice(0, 200) } : {}), s: p.s.trim().slice(0, 400) }));
|
|
142
|
+
const prs = (Array.isArray(e.prs) ? e.prs : []).filter((p) => p && typeof p.s === 'string' && p.s.trim()).map(cleanPr).filter((p) => p.s);
|
|
131
143
|
return { v: e.v, at: typeof e.at === 'string' && !Number.isNaN(Date.parse(e.at)) ? e.at : null, prs };
|
|
132
144
|
}
|
|
133
145
|
const cleanList = (list) => (Array.isArray(list) ? list.map(cleanEntry).filter(Boolean) : []);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.510.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.510.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@anthropic-ai/sdk": "^1.3.0",
|
package/workspace/package.json
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* readyHtml(m, whole) a milestone's readiness as small diamonds, one per initiative it waits on
|
|
11
11
|
* mergeBoards(list) All products: one Board model from every product's model, each row keeping its own product
|
|
12
12
|
* bandHtml(g, st, o) the stage track's program row: tile, title, products, your count, its spread, its next milestones
|
|
13
|
-
* prodHtml(g, o) · subHtml(name) a product group's head row (tile
|
|
14
|
-
* prodCell(g, dots) the head's fold
|
|
13
|
+
* prodHtml(g, o) · subHtml(name) a product group's head row (tile and name open the product, goal dots, chevron folds) and a program's product sub-row
|
|
14
|
+
* prodCell(g, dots) the head's open (tile, name) and fold buttons alone (the timeline's product lane, batch 11 item 17); dots replace the goal dots
|
|
15
15
|
* goalsFrom(fn) · goalsHtml(name) the goal dots of a product head, from Home's source (pm/home-list.js goalsOf); fn(name) gives its rows
|
|
16
16
|
*
|
|
17
17
|
* A program has no stage of its own (PM, 2026-10-23): the band shows only what its initiatives say. With one product picked, the program shows
|
|
@@ -173,9 +173,14 @@ export function prodHtml(g, cls = 'bt-prd') {
|
|
|
173
173
|
/** The fold button of a product head alone: the timeline's product lane (batch 11 item 17, PlmA1Board) puts it in the name cell, its track beside it. */
|
|
174
174
|
export function prodCell(g, dots) {
|
|
175
175
|
// the calm timeline (batch 13 item 4) passes its own dots: none while open, one per initiative off track while folded
|
|
176
|
+
// the tile and name open the product's page in the Viewer (batch 26 item 3, as Home's header and a program's title do); the rest folds
|
|
176
177
|
const open = !folded(g.key), gl = dots !== undefined ? dots : open ? goalsHtml(g.name) : '';
|
|
177
|
-
return `<button type="button" class="bd-
|
|
178
|
+
return `<span class="bd-prdb">${prodOpen(g, 'bd-prdo')}<button type="button" class="bd-prdf" data-fold="${esc(g.key)}" aria-expanded="${open}" aria-label="${esc(g.name)}, ${g.rows.length} ${plural(g.rows.length, 'initiative', 'initiatives')}. ${open ? 'Fold' : 'Show'}" title="${open ? 'Fold' : 'Show'}">${gl}${open || dots !== undefined ? '' : `<span class="n">${g.rows.length}</span>`}${SV(open ? CHEV_D : CHEV_R)}</button></span>`;
|
|
178
179
|
}
|
|
180
|
+
/** A product's workspace, from its rows (All products: each row keeps its own); '' when the rows carry none (the page uses its own board's). */
|
|
181
|
+
export const prodWs = (g) => ((g.rows || []).find((r) => r && r.ws) || {}).ws || '';
|
|
182
|
+
/** A product head's tile and name as one button: it opens that product's page in the Viewer (pm/premium-board.js data-prodopen). */
|
|
183
|
+
export const prodOpen = (g, cls) => `<button type="button" class="${cls}" data-prodopen="${esc(g.name)}" data-prodws="${esc(prodWs(g))}" aria-label="Open ${esc(g.name)}, a product">${tileHtml(g.name)}<span>${esc(g.name)}</span></button>`;
|
|
179
184
|
/** The goal dots of a product's head (PgBoard .gd), from the same source as Home's product header (pm/home-list.js goalsOf): one per KR or
|
|
180
185
|
* budget its open initiatives anchor to, green on track, amber at risk. With no goal anchored, nothing is drawn. The page sets the rows. */
|
|
181
186
|
let ROWS_OF = null;
|
|
@@ -191,11 +196,11 @@ export const subHtml = (name, cls = 'bt-psb') => `<div class="${cls}" role="row"
|
|
|
191
196
|
export const memberIcon = (programs, id) => { const p = (programs || []).find((x) => x.id === id); return p ? `<span class="bd-pgi" role="img" aria-label="In ${esc(p.title)}" title="In ${esc(p.title)}">${SV(PROG)}</span>` : ''; };
|
|
192
197
|
|
|
193
198
|
/** A Kanban swimlane's head (PgKanban .lh): a program's tile, title, your count and its next milestones merged into one counted diamond with
|
|
194
|
-
* the readiness of the first; a product's tile and name. Both fold with the chevron; the
|
|
199
|
+
* the readiness of the first; a product's tile and name. Both fold with the chevron; the title opens the program or the product. */
|
|
195
200
|
export function laneHtml(g, st) {
|
|
196
201
|
const open = !folded(g.key);
|
|
197
202
|
const fb = (label) => `<button type="button" class="bd-ib sm bd-fold" data-fold="${esc(g.key)}" aria-expanded="${open}" aria-label="${open ? 'Fold' : 'Show'} ${esc(label)}" title="${open ? 'Fold' : 'Show'}">${SV(open ? CHEV_D : CHEV_R)}</button>`;
|
|
198
|
-
if (g.kind !== 'program') return
|
|
203
|
+
if (g.kind !== 'program') return `${prodOpen(g, 'bd-lho')}<span class="bd-sp"></span>${open ? goalsHtml(g.name) : ''}${fb(g.name)}`;
|
|
199
204
|
const p = g.program, c = nextCluster(st);
|
|
200
205
|
const ms = c ? `<span class="bd-pm2${c.late ? ' late' : ''}" title="${esc(c.items.map((m) => m.name + (m.day ? ' ' + m.day : '')).join(' · '))}"><i class="bd-dm"></i><b>${esc(c.label)}</b>${esc(c.range)}${readyHtml(c.first, whole(st))}</span>` : '';
|
|
201
206
|
return `<button type="button" class="bd-lho" data-prog="${esc(p.id)}" aria-haspopup="dialog" aria-label="${esc(p.title)}, a program. Open the program">${progTile()}<span>${esc(p.title)}</span></button>${yoursHtml(st)}<span class="bd-sp"></span>${ms}${fb(p.title)}`;
|
|
@@ -1085,7 +1085,7 @@ html[data-theme="dark"] body.bd-page { --bd-prog: rgb(180,170,240); --bd-progbg:
|
|
|
1085
1085
|
.bd-rdy i { display: block; width: 6px; height: 6px; border-radius: 1px; transform: rotate(45deg); background: var(--bd-plan); }
|
|
1086
1086
|
.bd-rdy i.on { background: var(--bd-prog); }
|
|
1087
1087
|
.bd-fold { color: var(--cz-ink3); }
|
|
1088
|
-
.bd-fold:focus-visible, .bt-pgo:focus-visible, .bd-
|
|
1088
|
+
.bd-fold:focus-visible, .bt-pgo:focus-visible, .bd-prdo:focus-visible, .bd-prdf:focus-visible, .bd-lho:focus-visible, .tg-pgo:focus-visible { outline: 2px solid var(--cz-blue); outline-offset: 2px; border-radius: 10px; }
|
|
1089
1089
|
/* the grouping control in the toolbar (PgBoardProduct: "By product" on in violet) */
|
|
1090
1090
|
.bd-grpw2 { position: relative; display: inline-flex; align-items: center; }
|
|
1091
1091
|
.bd-grpw2[hidden] { display: none; }
|
|
@@ -1119,7 +1119,13 @@ html[data-theme="dark"] body.bd-page { --bd-prog: rgb(180,170,240); --bd-progbg:
|
|
|
1119
1119
|
.bt-psb > span, .tg-psb > span { display: inline-flex; align-items: center; gap: 8px; }
|
|
1120
1120
|
.bt-psb::before, .tg-psb::before { content: ""; position: absolute; left: 26px; top: 0; bottom: 0; width: 2px; background: var(--bd-progln); }
|
|
1121
1121
|
.bt-prd, .tg-prd { display: flex; align-items: center; padding: 8px 14px; background: var(--bd-row); border-top: 1px solid var(--cz-hair); }
|
|
1122
|
-
.bd-prdb { all: unset; box-sizing: border-box;
|
|
1122
|
+
.bd-prdb { all: unset; box-sizing: border-box; flex: 1; display: flex; align-items: center; gap: 10px; font-size: var(--fs-3); font-weight: 800; color: var(--cz-ink); min-height: 28px; }
|
|
1123
|
+
/* the tile and name open the product's page (batch 26 item 3, as Home's header); the rest of the head, its dots, count and chevron, folds it */
|
|
1124
|
+
.bd-prdo, .bd-prdf { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 10px; min-height: 28px; }
|
|
1125
|
+
.bd-prdo { flex: 0 1 auto; min-width: 0; }
|
|
1126
|
+
.bd-prdo > span:last-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1127
|
+
.bd-prdf { flex: 1; align-self: stretch; justify-content: flex-end; }
|
|
1128
|
+
@media (hover: hover) { .bd-prdo:hover > span:last-child, .bd-lh.pd .bd-lho:hover > span:last-child { color: var(--cz-blueink); } }
|
|
1123
1129
|
.bd-prdb svg { width: 15px; height: 15px; stroke-width: 2; color: var(--cz-ink3); margin-left: auto; }
|
|
1124
1130
|
/* a product head's goal dots (PgBoard .gd): Home's source and look (cozy-today.css .hv-gd) */
|
|
1125
1131
|
.bd-prdb .n { font-size: var(--fs-2); font-weight: 700; color: var(--cz-ink3); margin-left: auto; }
|
|
@@ -639,6 +639,8 @@ $('#ibBody').addEventListener('click', (e) => {
|
|
|
639
639
|
const tk = e.target.closest('[data-tk]'); if (tk) { openTicket(tk.dataset.ini, tk.dataset.tk, tk); return; }
|
|
640
640
|
if (e.target.closest('[data-act], [data-hold], [data-ms-pick], [data-fc-pick]')) return; // pm/bd-acts.js handles its own presses (a ⏸ on the timeline opens its hold there; a ◇ its milestones, pm/bd-pin-card.js)
|
|
641
641
|
const pg = e.target.closest('[data-prog]'); if (pg) { PG.openProgram(pg.dataset.prog, pg); return; }
|
|
642
|
+
// a product head's tile and name (batch 26 item 3): that product's page in Home's Viewer, the one place the product page is drawn
|
|
643
|
+
const pd = e.target.closest('[data-prodopen]'); if (pd) { location.assign(productHref(pd.dataset.prodws)); return; }
|
|
642
644
|
// a counted milestone ◇ lists its milestones right under it; on a phone the row's bar lane opens them in a sheet (batch 11 item 3)
|
|
643
645
|
const mc = e.target.closest('[data-ms-row]'); if (mc) { msPop(mc, rowOf(mc.dataset.msRow), mc.dataset.msIds.split(' ')); return; }
|
|
644
646
|
// a product lane's counted ◇ lists its milestones with their initiatives; on a phone a tap on the lane lists them all in a sheet (batch 11 item 17)
|
|
@@ -693,6 +695,8 @@ function askHref(r, q) {
|
|
|
693
695
|
const ws = (r && r.ws) || M.board.ws; if (ws) p.set('ws', ws);
|
|
694
696
|
return '/premium.html?' + p.toString();
|
|
695
697
|
}
|
|
698
|
+
/** A product's page (Home's Viewer, home-app.js openProductPage): Home on that product's workspace with ?ini=product. ws '': this board's own. */
|
|
699
|
+
const productHref = (ws) => { const w = ws || (M && !M.board.all ? M.board.ws : ''); return '/premium.html?' + (w ? 'ws=' + encodeURIComponent(w) + '&' : '') + 'ini=product'; };
|
|
696
700
|
const pageHref = (r) => { const ws = r.ws || M.board.ws, b = r.boardId || M.board.id; return '/premium-initiative.html?id=' + encodeURIComponent(r.id) + (ws ? '&ws=' + encodeURIComponent(ws) + '&board=' + encodeURIComponent(b) : ''); };
|
|
697
701
|
|
|
698
702
|
/* ── the initiative and its Jira ticket open in the Viewer's shell A, floating over the page (pm/bd-ini-panel.js; canvas XvABoard, XvOptAJira).
|
|
@@ -47,7 +47,9 @@ const REMOVE = [
|
|
|
47
47
|
* milestone opens the program's charter in the workspace; a share does not hold the charter. */
|
|
48
48
|
const PLAIN = ['button.dl-e[aria-label*="Program milestone"]:not([aria-label*="press to open"])', 'button.cz-vw-rn', 'button.iv-tt', 'button.iv-dc', 'button.iv-stc', 'button.iv-dtc', 'button.iv-wf', 'button.iv-cb', 'button.tg-end',
|
|
49
49
|
// the program page (batch 21 item 2): a task's check box, a goal (its Viewer is in Intelligence), a report (its file is the publisher's), a program check
|
|
50
|
-
'button.pp-ck', 'button.pv-gr', 'button.db-rep', 'button.pp-dm'
|
|
50
|
+
'button.pp-ck', 'button.pv-gr', 'button.db-rep', 'button.pp-dm',
|
|
51
|
+
// a product head's tile and name (batch 26 item 3): its page is in the publisher's Home, which a share does not hold
|
|
52
|
+
'button[data-prodopen]'];
|
|
51
53
|
/** A fold whose body goes with it: Documents and Linked are not listed. */
|
|
52
54
|
const WITH_BODY = ['[data-iv-fold="docs"]', '[data-iv-docs]', '[data-iv-fold="linked"]'];
|
|
53
55
|
|
|
@@ -15,9 +15,13 @@ export function kindOf(p) {
|
|
|
15
15
|
if (p && (p.k === 'new' || p.k === 'imp' || p.k === 'fix' || p.k === 'x')) return p.k;
|
|
16
16
|
return FIX.test((p && (p.t || p.s)) || '') ? 'fix' : 'imp';
|
|
17
17
|
}
|
|
18
|
+
/** A title and a summary are the same line when they differ only in code ticks, case, spacing or the closing stop
|
|
19
|
+
* ("`pmos x` names y" and "Pmos x names y."): the line is then the title once, without its ticks, so a command keeps its own case. */
|
|
20
|
+
const same = (a, b) => { const f = (x) => x.replace(/`/g, '').replace(/[.!?]+$/, '').replace(/\s+/g, ' ').trim().toLowerCase(); return f(a) === f(b); };
|
|
18
21
|
export function lineOf(p) {
|
|
19
|
-
const s = String((p && p.s) || ''), t = String((p && p.t) || '');
|
|
20
|
-
|
|
22
|
+
const s = String((p && p.s) || ''), t = String((p && p.t) || '').replace(/`([^`]*)`/g, '$1');
|
|
23
|
+
if (!t) return { b: '', s };
|
|
24
|
+
return same(t, s) ? { b: '', s: t } : { b: t, s };
|
|
21
25
|
}
|
|
22
26
|
const mondayOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate() - ((d.getDay() + 6) % 7));
|
|
23
27
|
const keyOf = (d) => d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|