@profpowell/vb-project-planning 0.2.1 → 0.2.3
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/README.md +10 -5
- package/dist/vb-project-planning.css +69 -3
- package/dist/vb-project-planning.js +3191 -2544
- package/dist/vb-project-planning.min.css +1 -1
- package/dist/vb-project-planning.min.js +221 -197
- package/package.json +7 -4
- package/src/lib/board-surface.js +158 -0
- package/src/lib/bundle-registry.js +2 -75
- package/src/lib/dates.js +125 -0
- package/src/lib/escape.js +34 -0
- package/src/lib/hash.js +16 -0
- package/src/lib/portrait-url.js +3 -29
- package/src/lib/src-loader.js +78 -0
- package/src/lib/vb-view-transition.js +7 -1
- package/src/utils/copy-init.js +56 -17
- package/src/web-components/_ux-base.js +409 -24
- package/src/web-components/adr-wc/README.md +18 -16
- package/src/web-components/adr-wc/api.json +7 -5
- package/src/web-components/adr-wc/logic.js +50 -136
- package/src/web-components/burndown-chart/api.json +8 -6
- package/src/web-components/burndown-chart/logic.js +75 -39
- package/src/web-components/empathy-map/api.json +13 -13
- package/src/web-components/empathy-map/logic.js +161 -168
- package/src/web-components/empathy-map/static.html +3 -3
- package/src/web-components/empathy-map/styles.js +1 -0
- package/src/web-components/gantt-chart/README.md +12 -6
- package/src/web-components/gantt-chart/api.json +23 -7
- package/src/web-components/gantt-chart/logic.js +456 -431
- package/src/web-components/gantt-chart/styles.css +14 -1
- package/src/web-components/impact-effort/api.json +9 -7
- package/src/web-components/impact-effort/logic.js +238 -209
- package/src/web-components/impact-effort/static.html +12 -4
- package/src/web-components/iron-triangle/_capacity.js +0 -18
- package/src/web-components/kanban-board/api.json +8 -6
- package/src/web-components/kanban-board/logic.js +289 -333
- package/src/web-components/product-roadmap/api.json +14 -13
- package/src/web-components/product-roadmap/logic.js +393 -246
- package/src/web-components/product-roadmap/styles.css +40 -0
- package/src/web-components/quadrant-grid/api.json +9 -4
- package/src/web-components/quadrant-grid/logic.js +80 -12
- package/src/web-components/quadrant-grid/styles.css +7 -0
- package/src/web-components/quality-target/_quality-utils.js +2 -46
- package/src/web-components/review-surface/README.md +9 -5
- package/src/web-components/review-surface/api.json +10 -8
- package/src/web-components/review-surface/logic.js +349 -218
- package/src/web-components/risk-register/api.json +1 -6
- package/src/web-components/risk-register/logic.js +71 -24
- package/src/web-components/risk-register/styles.css +9 -1
- package/src/web-components/story-map/api.json +8 -6
- package/src/web-components/story-map/logic.js +280 -281
- package/src/web-components/story-map/static.html +15 -5
- package/src/web-components/traceability-matrix/api.json +10 -3
- package/src/web-components/traceability-matrix/logic.js +112 -63
- package/src/web-components/traceability-matrix/styles.css +22 -1
- package/src/web-components/user-journey/api.json +25 -24
- package/src/web-components/user-journey/logic.js +76 -127
- package/src/web-components/user-journey/static.html +2 -2
- package/src/web-components/user-persona/api.json +15 -14
- package/src/web-components/user-persona/logic.js +71 -117
- package/src/web-components/user-persona/styles.js +1 -0
- package/src/web-components/user-story/api.json +13 -11
- package/src/web-components/user-story/logic.js +164 -195
- package/src/web-components/user-story/styles.js +28 -1
- package/src/web-components/work-item/README.md +3 -3
- package/src/web-components/work-item/api.json +13 -11
- package/src/web-components/work-item/logic.js +58 -154
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "../../../schemas/api.schema.json",
|
|
3
3
|
"element": "risk-register",
|
|
4
4
|
"type": "web-component",
|
|
5
|
-
"description": "Composing preset that renders a sortable risk table + a likelihood
|
|
5
|
+
"description": "Composing preset that renders a sortable risk table + a likelihood × impact quadrant grid from one source",
|
|
6
6
|
"attributes": [
|
|
7
7
|
{
|
|
8
8
|
"name": "src",
|
|
@@ -31,11 +31,6 @@
|
|
|
31
31
|
"name": "risk-register:ready",
|
|
32
32
|
"detail": "{ count }",
|
|
33
33
|
"description": "Fired after the table + grid render"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"name": "risk-register:change",
|
|
37
|
-
"detail": "{ row, field, value }",
|
|
38
|
-
"description": "Reserved: emitted on inline edits (future)"
|
|
39
34
|
}
|
|
40
35
|
],
|
|
41
36
|
"htmlvalidate": {
|
|
@@ -18,17 +18,63 @@
|
|
|
18
18
|
* <tr data-id="..."> on the row provides a stable id; falls back to
|
|
19
19
|
* the title.
|
|
20
20
|
*
|
|
21
|
+
* Severity = likelihood × impact is computed here and written into the
|
|
22
|
+
* Severity cell (text + data-sort-value) so <data-table> sorts and
|
|
23
|
+
* heat-maps it. It deliberately does NOT use data-rollup="product": that
|
|
24
|
+
* multiplies every numeric-looking cell in the row ("2FA rollout",
|
|
25
|
+
* "3 vendor quotes"). A row missing either score has an empty severity.
|
|
26
|
+
*
|
|
27
|
+
* Quadrant placement: scores 1..SCALE map to 0..1 via (v - 1) / (SCALE - 1),
|
|
28
|
+
* so 1 sits on the low edge and SCALE on the high edge. A score is "high"
|
|
29
|
+
* only when it is above the midpoint (> 3 on the 1–5 scale); the midpoint
|
|
30
|
+
* itself (3) counts as low and is drawn on the centre line. Each dot gets
|
|
31
|
+
* an explicit data-quadrant so <quadrant-grid> doesn't apply its own
|
|
32
|
+
* "midpoint belongs to the higher quadrant" rule.
|
|
33
|
+
*
|
|
21
34
|
* @attr {string} src - URL to JSON: [{ id, title, likelihood, impact, owner, mitigation }]
|
|
22
35
|
* @attr {string} label - Optional heading shown above the table
|
|
23
36
|
*
|
|
24
|
-
* @fires risk-register:ready
|
|
25
|
-
* @fires risk-register:change - { row, field, value } on inline edit
|
|
37
|
+
* @fires risk-register:ready - { count } after first render
|
|
26
38
|
*/
|
|
27
39
|
|
|
28
40
|
import { registerComponent } from '../../lib/bundle-registry.js';
|
|
29
41
|
import { VBElement } from '../../lib/vb-element.js';
|
|
42
|
+
import { esc } from '../../lib/escape.js';
|
|
30
43
|
|
|
31
44
|
const SCALE = 5;
|
|
45
|
+
/** Scores strictly above this are in the high half of an axis. */
|
|
46
|
+
const MIDPOINT = (SCALE + 1) / 2;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Normalize a likelihood/impact score to an integer 1..SCALE, or 0 when
|
|
50
|
+
* missing / not a number.
|
|
51
|
+
* @param {unknown} v
|
|
52
|
+
* @returns {number}
|
|
53
|
+
*/
|
|
54
|
+
function score(v) {
|
|
55
|
+
const n = parseInt(String(v ?? ''), 10);
|
|
56
|
+
if (!Number.isFinite(n) || n < 1) return 0;
|
|
57
|
+
return Math.min(SCALE, n);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Map a 1..SCALE score to a 0..1 coordinate.
|
|
62
|
+
* @param {number} v
|
|
63
|
+
*/
|
|
64
|
+
const toUnit = (v) => (v - 1) / (SCALE - 1);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Quadrant index (Q1=top-right, Q2=top-left, Q3=bottom-left,
|
|
68
|
+
* Q4=bottom-right) for a likelihood (x) / impact (y) pair.
|
|
69
|
+
* @param {number} likelihood
|
|
70
|
+
* @param {number} impact
|
|
71
|
+
*/
|
|
72
|
+
function quadrantFor(likelihood, impact) {
|
|
73
|
+
const right = likelihood > MIDPOINT;
|
|
74
|
+
const top = impact > MIDPOINT;
|
|
75
|
+
if (top) return right ? 0 : 1;
|
|
76
|
+
return right ? 3 : 2;
|
|
77
|
+
}
|
|
32
78
|
|
|
33
79
|
class RiskRegister extends VBElement {
|
|
34
80
|
setup() {
|
|
@@ -63,8 +109,8 @@ class RiskRegister extends VBElement {
|
|
|
63
109
|
rows.push({
|
|
64
110
|
id,
|
|
65
111
|
title,
|
|
66
|
-
likelihood:
|
|
67
|
-
impact:
|
|
112
|
+
likelihood: score(cells[1]?.textContent),
|
|
113
|
+
impact: score(cells[2]?.textContent),
|
|
68
114
|
owner: cells[3]?.textContent?.trim() || '',
|
|
69
115
|
mitigation: cells[4]?.textContent?.trim() || '',
|
|
70
116
|
});
|
|
@@ -73,7 +119,12 @@ class RiskRegister extends VBElement {
|
|
|
73
119
|
}
|
|
74
120
|
|
|
75
121
|
#render(rows) {
|
|
76
|
-
this.#rows = rows
|
|
122
|
+
this.#rows = rows.map((r) => ({
|
|
123
|
+
...r,
|
|
124
|
+
likelihood: score(r?.likelihood),
|
|
125
|
+
impact: score(r?.impact),
|
|
126
|
+
}));
|
|
127
|
+
rows = this.#rows;
|
|
77
128
|
|
|
78
129
|
// Wipe prior children except the source <template> so re-renders work.
|
|
79
130
|
[...this.children].forEach((c) => { if (c.tagName !== 'TEMPLATE') c.remove(); });
|
|
@@ -110,22 +161,24 @@ class RiskRegister extends VBElement {
|
|
|
110
161
|
<th data-sort="string">Title</th>
|
|
111
162
|
<th data-sort="number">Likelihood</th>
|
|
112
163
|
<th data-sort="number">Impact</th>
|
|
113
|
-
<th data-sort="number" data-
|
|
164
|
+
<th data-sort="number" data-severity data-heatmap="low-good">Severity</th>
|
|
114
165
|
<th data-sort="string">Owner</th>
|
|
115
166
|
<th data-sort="string">Mitigation</th>
|
|
116
167
|
</tr>
|
|
117
168
|
</thead>
|
|
118
169
|
<tbody>
|
|
119
|
-
${rows.map((r) =>
|
|
120
|
-
|
|
121
|
-
|
|
170
|
+
${rows.map((r) => {
|
|
171
|
+
const sev = r.likelihood && r.impact ? r.likelihood * r.impact : null;
|
|
172
|
+
return `
|
|
173
|
+
<tr data-id="${esc(r.id)}">
|
|
174
|
+
<td>${esc(r.title)}</td>
|
|
122
175
|
<td>${r.likelihood || ''}</td>
|
|
123
176
|
<td>${r.impact || ''}</td>
|
|
124
|
-
<td
|
|
125
|
-
<td>${
|
|
126
|
-
<td>${
|
|
127
|
-
</tr
|
|
128
|
-
|
|
177
|
+
<td${sev === null ? '' : ` data-sort-value="${sev}"`}>${sev ?? ''}</td>
|
|
178
|
+
<td>${esc(r.owner)}</td>
|
|
179
|
+
<td>${esc(r.mitigation)}</td>
|
|
180
|
+
</tr>`;
|
|
181
|
+
}).join('')}
|
|
129
182
|
</tbody>
|
|
130
183
|
`;
|
|
131
184
|
wrapper.appendChild(table);
|
|
@@ -148,23 +201,17 @@ class RiskRegister extends VBElement {
|
|
|
148
201
|
for (const r of rows) {
|
|
149
202
|
if (!r.likelihood || !r.impact) continue;
|
|
150
203
|
const dot = document.createElement('span');
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
dot.setAttribute('data-
|
|
154
|
-
dot.setAttribute('data-y', y.toFixed(3));
|
|
204
|
+
dot.setAttribute('data-x', toUnit(r.likelihood).toFixed(3));
|
|
205
|
+
dot.setAttribute('data-y', toUnit(r.impact).toFixed(3));
|
|
206
|
+
dot.setAttribute('data-quadrant', String(quadrantFor(r.likelihood, r.impact)));
|
|
155
207
|
dot.setAttribute('data-id', r.id);
|
|
156
|
-
dot.setAttribute('title', `${r.title} — likelihood ${r.likelihood}, impact ${r.impact}`);
|
|
208
|
+
dot.setAttribute('title', `${r.title} — likelihood ${r.likelihood}, impact ${r.impact}, severity ${r.likelihood * r.impact}`);
|
|
157
209
|
dot.textContent = r.title;
|
|
158
210
|
grid.appendChild(dot);
|
|
159
211
|
}
|
|
160
212
|
return grid;
|
|
161
213
|
}
|
|
162
214
|
|
|
163
|
-
static #escape(s) {
|
|
164
|
-
return String(s ?? '').replace(/[&<>"']/g, (c) =>
|
|
165
|
-
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
215
|
/** @type {Array<{id:string,title:string,likelihood:number,impact:number,owner:string,mitigation:string}>} */
|
|
169
216
|
#rows = [];
|
|
170
217
|
|
|
@@ -23,10 +23,18 @@ risk-register {
|
|
|
23
23
|
|
|
24
24
|
.rr-layout {
|
|
25
25
|
display: grid;
|
|
26
|
-
grid-template-columns: 1fr;
|
|
26
|
+
grid-template-columns: minmax(0, 1fr);
|
|
27
27
|
gap: var(--size-l, 1.5rem);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/* The table scrolls inside its own box on narrow screens instead of
|
|
31
|
+
pushing the whole page sideways. */
|
|
32
|
+
.rr-layout > data-table {
|
|
33
|
+
display: block;
|
|
34
|
+
min-inline-size: 0;
|
|
35
|
+
overflow-x: auto;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
/* Table on top, quadrant below by default; side-by-side on wide screens */
|
|
31
39
|
@media (width >= 64rem) {
|
|
32
40
|
.rr-layout {
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
"description": "Horizontal user story map with activity columns and drag-and-drop between columns",
|
|
6
6
|
"htmlvalidate": { "flow": true, "permittedContent": ["section"] },
|
|
7
7
|
"attributes": [
|
|
8
|
-
{ "name": "src", "kind": "host-api", "purpose": "config", "type": "string", "description": "URL to JSON data with activities and stories" },
|
|
9
|
-
{ "name": "title", "kind": "host-api", "purpose": "config", "type": "string", "description": "Optional heading above the story map" },
|
|
10
|
-
{ "name": "compact", "kind": "host-api", "purpose": "config", "type": "boolean", "description": "
|
|
8
|
+
{ "name": "src", "kind": "host-api", "purpose": "config", "type": "string", "description": "URL to JSON data with activities and stories. Fetched once per value; a new value replaces the map and aborts an older in-flight request" },
|
|
9
|
+
{ "name": "title", "kind": "host-api", "purpose": "config", "type": "string", "description": "Optional heading above the story map; also names the scroll region" },
|
|
10
|
+
{ "name": "compact", "kind": "host-api", "purpose": "config", "type": "boolean", "description": "Reduces column widths, padding, and header font sizes for denser layouts" }
|
|
11
11
|
],
|
|
12
12
|
"childAttributes": [
|
|
13
13
|
{ "name": "data-activity", "on": "section", "type": "string", "description": "Activity column identifier" },
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
{ "name": "data-journey-phase", "on": "section", "type": "string", "description": "Optional link to a user-journey phase" }
|
|
16
16
|
],
|
|
17
17
|
"events": [
|
|
18
|
-
{ "name": "story-map:reorder",
|
|
19
|
-
{ "name": "story-map:transfer",
|
|
20
|
-
{ "name": "story-map:ready",
|
|
18
|
+
{ "name": "story-map:reorder", "detail": "{ itemId, activity, oldIndex, newIndex }", "description": "Item reordered within a column" },
|
|
19
|
+
{ "name": "story-map:transfer", "detail": "{ itemId, fromActivity, toActivity, newIndex }", "description": "Item moved between columns" },
|
|
20
|
+
{ "name": "story-map:ready", "detail": "{ activityCount, storyCount }", "description": "Fired once after the first render. With src, fired once the JSON has loaded and rendered. Once per element: not re-fired by a later src change (watch activities-changed with source 'src') or when the map is moved in the DOM" },
|
|
21
|
+
{ "name": "story-map:activities-changed", "detail": "{ activities, source: 'property' | 'src' }", "description": "Fired after each .activities assignment (source 'property') and after each src load has rendered (source 'src')" },
|
|
22
|
+
{ "name": "story-map:upgraded", "detail": "", "description": "Fired (in a microtask) each time the map connects and upgrades" }
|
|
21
23
|
]
|
|
22
24
|
}
|