@bongos/core 1.21.124 → 1.21.126
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/.bongos-core.json +91 -41
- package/bin/bongos.js +9 -0
- package/docs/api/openapi.json +4 -4
- package/docs/copy-inventory.md +128 -126
- package/docs/copy-registry.json +188 -170
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +2 -1
- package/docs/page-readings.json +152 -143
- package/modules/hall-ui/public/goal-forest-lib.js +36 -0
- package/modules/hall-ui/public/goal-forest-render.js +50 -10
- package/modules/hall-ui/public/goals-page.js +19 -22
- package/modules/hall-ui/public/goals-render.js +6 -16
- package/modules/hall-ui/public/goals.css +13 -4
- package/modules/hall-ui/public/goals.html +22 -24
- package/modules/hall-ui/public/goals.states.json +19 -1
- package/modules/hall-ui/records/goal-dependency-forest.md +4 -3
- package/modules/onboarding/howto-guide.js +259 -0
- package/modules/platform-identity/invite-authz.js +1 -1
- package/modules/provisioning/catalog-bridge.js +1 -1
- package/modules/provisioning/provisioning.js +1 -1
- package/modules/provisioning/route-helpers.js +50 -0
- package/modules/provisioning/routes/disconnect.js +6 -5
- package/modules/provisioning/routes/env-manifest.js +11 -27
- package/modules/provisioning/routes/provisioning.js +58 -75
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +24 -0
- package/scripts/gds/core-pin-sync.js +233 -0
- package/scripts/gds/module-assess-tests.js +40 -11
- package/scripts/gds/module-assess-version.js +10 -2
- package/scripts/gds/module-test-runner/README.md +44 -0
- package/scripts/gds/module-test-runner/module-tests.yml +87 -0
- package/scripts/gds/module-test-runner.js +358 -0
- package/src/module-api.js +1 -1
- package/tests/core_pin_sync.mjs +237 -0
- package/tests/goal_forest_lib.mjs +28 -0
- package/tests/goal_forest_page.mjs +106 -11
- package/tests/howto_guide.mjs +96 -0
- package/tests/module_assess_price_parity.mjs +2 -2
- package/tests/module_assess_score.mjs +13 -3
- package/tests/module_test_runner.mjs +294 -0
- package/tests/project_catalog_routes.mjs +1 -1
- package/tests/provisioning_module_selection.mjs +1 -1
- package/tests/provisioning_project_owner_gate.mjs +271 -0
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
// header), so a new self-contained feature is its own file — the same
|
|
6
6
|
// separation goal-map.js already keeps from goals-render.js, one file down.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// the
|
|
12
|
-
//
|
|
13
|
-
//
|
|
8
|
+
// Two surfaces (task 1004738): the band's compact Dependencies card, in the
|
|
9
|
+
// slot Conflicts used to hold — a summary, the tasks to ship first, the
|
|
10
|
+
// conflicts count — and the full picture behind it at #/goal/N/dependencies,
|
|
11
|
+
// where the forest needs real width. The full view always draws the whole
|
|
12
|
+
// forest returned by GET /goals/:id/dependency-forest (ADR 0227 §3: what it
|
|
13
|
+
// omits is exactly what has nothing to connect it). The model + layout are goal-forest-lib.js
|
|
14
14
|
// (pure); this file only draws what it returns, with createElementNS +
|
|
15
15
|
// textContent — the atlas / goal-map.js idiom — so a task title never
|
|
16
16
|
// becomes markup.
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
// A factory, the goals-render.js precedent, because `state` keeps changing
|
|
19
19
|
// after load: `createGoalForestRender(ctx)` takes the SAME `{ API, state,
|
|
20
20
|
// sameId, getJSON }` slice of ctx goals-page.js already builds, and returns
|
|
21
|
-
// `{ loadDependencyForest, renderDependencyForest }` for
|
|
22
|
-
// call
|
|
21
|
+
// `{ loadDependencyForest, renderDependencyForest, renderDependencyCard }` for
|
|
22
|
+
// goals-render.js to call (the card again whenever the conflicts land) and for
|
|
23
|
+
// tests/goal_forest_page.mjs to call directly.
|
|
23
24
|
(function () {
|
|
24
25
|
'use strict';
|
|
25
26
|
|
|
@@ -103,7 +104,44 @@
|
|
|
103
104
|
return svg;
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
const SHIP_FIRST = 3;
|
|
108
|
+
const taskHref = (id) => `#/task/${encodeURIComponent(id)}`;
|
|
109
|
+
// The band card. Reads state.conflictPairs too, so goals-render.js calls it
|
|
110
|
+
// again when the conflicts land; null there (loading, or a failed read)
|
|
111
|
+
// draws no conflicts line rather than a guess.
|
|
112
|
+
function renderDependencyCard() {
|
|
113
|
+
const lede = $('#goal-deps-card-lede');
|
|
114
|
+
const body = $('#goal-deps-card-body');
|
|
115
|
+
if (!lede || !body) return;
|
|
116
|
+
const a = state.forest && !state.forestError ? lib.analyse(state.forest) : null;
|
|
117
|
+
const n = a ? a.order.length : 0;
|
|
118
|
+
const pill = $('#goal-deps-card-count');
|
|
119
|
+
if (pill) { pill.hidden = !n; pill.textContent = n ? String(n) : ''; }
|
|
120
|
+
if (state.forestError) lede.textContent = 'Couldn’t load this goal’s task dependencies right now.';
|
|
121
|
+
else lede.textContent = a ? lib.brief(a) : 'Reading…';
|
|
122
|
+
const first = a ? lib.shipFirst(a, SHIP_FIRST) : [];
|
|
123
|
+
const pairs = Array.isArray(state.conflictPairs) ? state.conflictPairs : null;
|
|
124
|
+
body.innerHTML = (first.length
|
|
125
|
+
? `<p class="gf-brief__h">Ship these first</p><ul class="gf-brief">${first.map((r) => `<li class="gf-brief__row">
|
|
126
|
+
<a class="lb-name gf-brief__task" href="${taskHref(r.id)}" title="#${escapeHtml(r.id)} ${escapeHtml(r.title)}">#${escapeHtml(r.id)} ${escapeHtml(r.title)}</a>
|
|
127
|
+
<span class="gf-brief__n">unblocks ${r.unblocks}</span></li>`).join('')}</ul>`
|
|
128
|
+
: '')
|
|
129
|
+
+ (pairs
|
|
130
|
+
? `<p class="gf-brief__conflicts${pairs.length ? ' gf-brief__conflicts--live' : ''}">${pairs.length
|
|
131
|
+
? `${escapeHtml(lib.plural(pairs.length, 'file conflict'))} between open tasks.`
|
|
132
|
+
: 'No file conflicts between open tasks.'}</p>`
|
|
133
|
+
: '');
|
|
134
|
+
const foot = $('#goal-deps-card-foot');
|
|
135
|
+
if (!foot) return;
|
|
136
|
+
foot.hidden = !((n || (pairs && pairs.length)) && state.current != null);
|
|
137
|
+
foot.innerHTML = foot.hidden ? ''
|
|
138
|
+
: `<a class="pill-link" href="#/goal/${encodeURIComponent(state.current)}/dependencies">See the full picture →</a>`;
|
|
139
|
+
}
|
|
140
|
+
|
|
106
141
|
function renderDependencyForest() {
|
|
142
|
+
renderDependencyCard();
|
|
143
|
+
const back = $('#goal-deps-back');
|
|
144
|
+
if (back && state.current != null) back.setAttribute('href', `#/goal/${encodeURIComponent(state.current)}`);
|
|
107
145
|
const lede = $('#goal-deps-lede');
|
|
108
146
|
const body = $('#goal-deps-body');
|
|
109
147
|
if (!lede || !body) return;
|
|
@@ -130,8 +168,10 @@
|
|
|
130
168
|
setCount('#goal-deps-count', a.order.length);
|
|
131
169
|
lede.innerHTML = `${escapeHtml(lib.describe(a))} An arrow runs from the task that has to ship first to the task that waits on it. `
|
|
132
170
|
+ 'Every task here, and every dependency, is also in the table below.';
|
|
171
|
+
// The picture is drawn only while its own view is open: on the goal page
|
|
172
|
+
// the card is all that shows, and a forest drawn hidden cannot measure its titles.
|
|
173
|
+
if (state.view !== 'dependencies') { body.innerHTML = ''; return; }
|
|
133
174
|
const rows = lib.tableRows(a);
|
|
134
|
-
const taskHref = (id) => `#/task/${encodeURIComponent(id)}`;
|
|
135
175
|
const depList = (list) => (list.length
|
|
136
176
|
? `<ul class="gf-deps">${list.map((x) => `<li><a href="${taskHref(x.id)}">#${escapeHtml(x.id)} ${escapeHtml(x.title)}</a></li>`).join('')}</ul>`
|
|
137
177
|
: '<span class="gf-none">—</span>');
|
|
@@ -178,6 +218,6 @@
|
|
|
178
218
|
}
|
|
179
219
|
}
|
|
180
220
|
|
|
181
|
-
return { loadDependencyForest, renderDependencyForest };
|
|
221
|
+
return { loadDependencyForest, renderDependencyForest, renderDependencyCard };
|
|
182
222
|
};
|
|
183
223
|
})();
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
me: null,
|
|
59
59
|
goals: [], // list rows
|
|
60
60
|
details: new Map(), // goal_id -> {goal, members, criteria}
|
|
61
|
-
current: null, // open goal id (detail
|
|
62
|
-
view: 'list', // task 1828 — 'list' | 'detail' | '
|
|
61
|
+
current: null, // open goal id (detail or a full view) or null (list)
|
|
62
|
+
view: 'list', // task 1828 — 'list' | 'detail' | 'dependencies' | 'members' | 'completion'
|
|
63
63
|
inviteOpen: false, // task 1762 — the invite form's open/closed state
|
|
64
64
|
directory: null, // task 1877 — GET /builders/directory, fetched once and cached
|
|
65
65
|
// task 1828 — per-table column sort. by=null → natural order (todo: server
|
|
@@ -562,10 +562,8 @@
|
|
|
562
562
|
// full view at #/goal/N/<view> (the shared pattern behind Conflicts, Members,
|
|
563
563
|
// and Completion). Hidden when nothing is truncated.
|
|
564
564
|
|
|
565
|
-
// task
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
// task 1828 — the standalone #/goal/N/conflicts view: the whole set, no cap.
|
|
565
|
+
// task 1004738 — the conflict set renders on the #/goal/N/dependencies view;
|
|
566
|
+
// the band's Dependencies card carries its count.
|
|
569
567
|
|
|
570
568
|
/* ----- Completion (band column + #/goal/N/completion full view) ----- */
|
|
571
569
|
|
|
@@ -727,16 +725,16 @@
|
|
|
727
725
|
setWorkTab('todo');
|
|
728
726
|
workTabs?.setCount('todo', null);
|
|
729
727
|
workTabs?.setCount('done', null);
|
|
730
|
-
['#goal-conflicts-
|
|
728
|
+
['#goal-conflicts-view-count',
|
|
731
729
|
'#goal-members-count', '#goal-members-view-count', '#goal-completion-count'].forEach((sel) => {
|
|
732
730
|
const el = $(sel);
|
|
733
731
|
if (el) { el.hidden = true; el.textContent = ''; }
|
|
734
732
|
});
|
|
735
|
-
['#goal-todo-pager', '#goal-done-pager', '#goal-
|
|
733
|
+
['#goal-todo-pager', '#goal-done-pager', '#goal-members-foot', '#goal-completion-foot'].forEach((sel) => {
|
|
736
734
|
const el = $(sel);
|
|
737
735
|
if (el) el.hidden = true;
|
|
738
736
|
});
|
|
739
|
-
|
|
737
|
+
$('#goal-conflicts-view-card')?.classList.remove('goal-conflicts-card--live');
|
|
740
738
|
reflectSortHeaders('done');
|
|
741
739
|
// task 1002425 — To do is a card list now; Completed stays a table. Show a
|
|
742
740
|
// placeholder in the card list + skeleton rows in the Completed table while
|
|
@@ -749,25 +747,23 @@
|
|
|
749
747
|
if (todoCount) todoCount.textContent = '';
|
|
750
748
|
const bulkBar = $('#goal-bulk-toolbar');
|
|
751
749
|
if (bulkBar) bulkBar.hidden = true;
|
|
752
|
-
const conflicts = $('#goal-conflicts');
|
|
753
|
-
if (conflicts) conflicts.innerHTML = '<span class="kit-skel"></span>';
|
|
754
750
|
const conflictsFull = $('#goal-conflicts-full');
|
|
755
751
|
if (conflictsFull) conflictsFull.innerHTML = '<span class="kit-skel"></span>';
|
|
756
752
|
}
|
|
757
753
|
|
|
758
754
|
// task 1828 — views sharing this page: the list, a goal's detail, and the
|
|
759
|
-
// goal's three full views (
|
|
760
|
-
// from goalId (null → list).
|
|
761
|
-
const SUB_VIEWS = ['
|
|
755
|
+
// goal's three full views (dependencies / members / completion). `view`
|
|
756
|
+
// defaults from goalId (null → list).
|
|
757
|
+
const SUB_VIEWS = ['dependencies', 'members', 'completion'];
|
|
762
758
|
const VIEW_SECTION = {
|
|
763
759
|
list: '#goals-list-view',
|
|
764
760
|
detail: '#goal-detail-view',
|
|
765
|
-
|
|
761
|
+
dependencies: '#goal-dependencies-view',
|
|
766
762
|
members: '#goal-members-view',
|
|
767
763
|
completion: '#goal-completion-view',
|
|
768
764
|
};
|
|
769
765
|
const SUB_SUBTITLE = {
|
|
770
|
-
|
|
766
|
+
dependencies: 'Goal dependencies — which tasks wait on which, and which open tasks touch the same files.',
|
|
771
767
|
members: 'Goal members — who’s on this goal and what they’ve contributed to it.',
|
|
772
768
|
completion: 'Goal completion — every done-when criterion, with its full detail.',
|
|
773
769
|
};
|
|
@@ -787,7 +783,7 @@
|
|
|
787
783
|
if (sub) {
|
|
788
784
|
sub.textContent = view === 'list'
|
|
789
785
|
? 'Shared goals: progress, members, and the work remaining.'
|
|
790
|
-
: (SUB_SUBTITLE[view] || 'Goal detail — completion, members,
|
|
786
|
+
: (SUB_SUBTITLE[view] || 'Goal detail — completion, members, dependencies, and the work queue.');
|
|
791
787
|
}
|
|
792
788
|
window.scrollTo({ top: 0 });
|
|
793
789
|
}
|
|
@@ -804,14 +800,15 @@
|
|
|
804
800
|
if (state.current == null) renderList();
|
|
805
801
|
return;
|
|
806
802
|
}
|
|
807
|
-
// task 1828 — a goal's dedicated full views (
|
|
803
|
+
// task 1828 — a goal's dedicated full views (dependencies / members /
|
|
808
804
|
// completion). renderDetail renders all three full views + kicks
|
|
809
805
|
// loadGoalTasks, so a cold load straight to one still fills in once data
|
|
810
|
-
// lands (member stats / work / conflicts refresh async).
|
|
811
|
-
|
|
806
|
+
// lands (member stats / work / conflicts refresh async). /conflicts is the
|
|
807
|
+
// pre-1004738 name, still linked from the attention inbox.
|
|
808
|
+
const mSub = /^#\/goal\/(\d+)\/(dependencies|conflicts|members|completion)$/.exec(hash);
|
|
812
809
|
if (mSub) {
|
|
813
810
|
const id = mSub[1];
|
|
814
|
-
const view = mSub[2];
|
|
811
|
+
const view = mSub[2] === 'conflicts' ? 'dependencies' : mSub[2];
|
|
815
812
|
try {
|
|
816
813
|
await loadDetail(id);
|
|
817
814
|
setView(id, view);
|
|
@@ -999,7 +996,7 @@
|
|
|
999
996
|
}
|
|
1000
997
|
$('#goal-back')?.addEventListener('click', (e) => { e.preventDefault(); window.location.hash = ''; });
|
|
1001
998
|
// task 1828 — "← Back to goal" from any of the full views.
|
|
1002
|
-
['#goal-
|
|
999
|
+
['#goal-deps-back', '#goal-members-back', '#goal-completion-back'].forEach((sel) => {
|
|
1003
1000
|
$(sel)?.addEventListener('click', (e) => {
|
|
1004
1001
|
e.preventDefault();
|
|
1005
1002
|
if (state.current != null) window.location.hash = `#/goal/${state.current}`;
|
|
@@ -34,10 +34,9 @@
|
|
|
34
34
|
// still draws its criteria (state by satisfied/open, no UAT controls).
|
|
35
35
|
const uatUi = window.OTBCriterionUat || { isAwaiting: () => false, badgesHtml: () => '', actionHtml: () => '' };
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// more members, more criteria). Conflicts uses CONFLICT_CAP above.
|
|
37
|
+
// task 1828 — the band's Completion / Members columns each cap their list and
|
|
38
|
+
// offer a "View all →" into a dedicated full view (these grow: more members,
|
|
39
|
+
// more criteria). The third column is the Dependencies card (task 1004738).
|
|
41
40
|
const MEMBER_CAP = 5;
|
|
42
41
|
const CRITERIA_CAP = 4;
|
|
43
42
|
// task 1932/1956 → kit (task 2171): the goal-card grid + the goal-detail Work
|
|
@@ -620,24 +619,15 @@
|
|
|
620
619
|
foot.innerHTML = '';
|
|
621
620
|
}
|
|
622
621
|
}
|
|
622
|
+
// task 1004738 — the whole conflict set lives on the dependencies view; the
|
|
623
|
+
// band's Dependencies card carries only its count.
|
|
623
624
|
function renderConflicts() {
|
|
624
|
-
const pairs = state.conflictPairs;
|
|
625
|
-
const card = $('#goal-conflicts-card');
|
|
626
|
-
fillConflicts($('#goal-conflicts'), pairs == null ? null : pairs.slice(0, CONFLICT_CAP));
|
|
627
|
-
const total = pairs ? pairs.length : 0;
|
|
628
|
-
renderViewAllFoot('#goal-conflicts-foot', total, CONFLICT_CAP, 'conflicts', 'conflicts');
|
|
629
|
-
setCount('#goal-conflicts-count', total);
|
|
630
|
-
card?.classList.toggle('goal-conflicts-card--live', total > 0);
|
|
631
|
-
renderConflictsFull();
|
|
632
|
-
}
|
|
633
|
-
function renderConflictsFull() {
|
|
634
625
|
const pairs = state.conflictPairs;
|
|
635
626
|
const total = pairs ? pairs.length : 0;
|
|
636
627
|
fillConflicts($('#goal-conflicts-full'), pairs);
|
|
637
628
|
setCount('#goal-conflicts-view-count', total);
|
|
638
629
|
$('#goal-conflicts-view-card')?.classList.toggle('goal-conflicts-card--live', total > 0);
|
|
639
|
-
|
|
640
|
-
if (back && state.current != null) back.setAttribute('href', `#/goal/${state.current}`);
|
|
630
|
+
forestRender.renderDependencyCard();
|
|
641
631
|
}
|
|
642
632
|
// task 1003480 — the per-goal task dependency forest is drawn by its own
|
|
643
633
|
// module now (goal-forest-render.js, carved out to keep this file under the
|
|
@@ -87,10 +87,8 @@
|
|
|
87
87
|
.goal-band__col + .goal-band__col { border-top: none; border-left: var(--line) solid var(--rule-soft); }
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/* Live conflicts: the
|
|
91
|
-
|
|
92
|
-
(Count pills on these headers are the shared .count-pill.) */
|
|
93
|
-
.goal-conflicts-card--live { background: color-mix(in srgb, var(--warn) 6%, transparent); }
|
|
90
|
+
/* Live conflicts: the conflicts card on the dependencies view wears the amber
|
|
91
|
+
border (task 1004738 moved it there from the band). */
|
|
94
92
|
.goal-conflicts-card--live .count-pill { background: var(--warn-weak); color: var(--warn); }
|
|
95
93
|
#goal-conflicts-view-card.goal-conflicts-card--live { background: var(--bg-card); border-color: color-mix(in srgb, var(--warn) 42%, transparent); }
|
|
96
94
|
|
|
@@ -554,3 +552,14 @@
|
|
|
554
552
|
.gf-table .gf-deps { margin: 0; padding: 0; list-style: none; min-width: 12rem; }
|
|
555
553
|
.gf-table .gf-deps li { font-size: 12.5px; line-height: 1.4; }
|
|
556
554
|
.gf-none { color: var(--ink-faint); }
|
|
555
|
+
|
|
556
|
+
/* task 1004738 — the band's compact Dependencies card: a third of the width,
|
|
557
|
+
so each "ship first" title holds one line and ellipsises (the whole title is
|
|
558
|
+
its tooltip and the full view's table). */
|
|
559
|
+
.gf-brief__h { margin: 10px 0 2px; font-size: 12px; color: var(--ink-faint); }
|
|
560
|
+
.gf-brief { list-style: none; margin: 0; padding: 0; }
|
|
561
|
+
.gf-brief__row { display: flex; align-items: center; gap: 10px; font-size: 13px; }
|
|
562
|
+
.gf-brief__task { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
563
|
+
.gf-brief__n { flex-shrink: 0; font-size: 12px; color: var(--ink-faint); }
|
|
564
|
+
.gf-brief__conflicts { margin: 10px 0 0; font-size: 13px; color: var(--ink-soft); }
|
|
565
|
+
.gf-brief__conflicts--live { color: var(--warn); font-weight: 600; }
|
|
@@ -112,28 +112,18 @@
|
|
|
112
112
|
<ul class="member-list" id="goal-members"></ul>
|
|
113
113
|
<div class="goal-col__foot" id="goal-members-foot" hidden></div>
|
|
114
114
|
</div>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<
|
|
115
|
+
<!-- task 1004738 — the compact Dependencies card takes the slot
|
|
116
|
+
Conflicts held: a summary, the tasks to ship first and the
|
|
117
|
+
conflicts count. The forest, its table and the conflict list
|
|
118
|
+
are one click away on #/goal/N/dependencies. -->
|
|
119
|
+
<div class="goal-band__col" id="goal-deps-card">
|
|
120
|
+
<h2 class="scroll__h">Dependencies<span class="count-pill" id="goal-deps-card-count" hidden></span></h2>
|
|
121
|
+
<p class="scroll__lede" id="goal-deps-card-lede">Reading…</p>
|
|
122
|
+
<div id="goal-deps-card-body"></div>
|
|
123
|
+
<div class="goal-col__foot" id="goal-deps-card-foot" hidden></div>
|
|
122
124
|
</div>
|
|
123
125
|
</div>
|
|
124
126
|
|
|
125
|
-
<!-- task 1003480 (ADR 0227 §6 item 4 + §10.5): the per-goal task
|
|
126
|
-
dependency forest. Its own full-width section, not a band
|
|
127
|
-
column — a node-link picture needs real width, and (unlike
|
|
128
|
-
Completion/Members/Conflicts) this is bounded small BY DESIGN
|
|
129
|
-
(ADR 0227 §3's own finding), so there is no "view all" split:
|
|
130
|
-
what renders here is the whole forest, every time. -->
|
|
131
|
-
<div class="scroll" style="margin-top:20px" id="goal-deps-scroll">
|
|
132
|
-
<h2 class="scroll__h" id="goal-deps-h">Task dependencies<span class="count-pill" id="goal-deps-count" hidden></span></h2>
|
|
133
|
-
<p class="scroll__lede" id="goal-deps-lede">Reading…</p>
|
|
134
|
-
<div id="goal-deps-body"></div>
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
127
|
<div class="scroll" style="margin-top:20px">
|
|
138
128
|
<div class="goal-work__head">
|
|
139
129
|
<h2 class="scroll__h">Tasks</h2>
|
|
@@ -188,11 +178,19 @@
|
|
|
188
178
|
</div>
|
|
189
179
|
</section>
|
|
190
180
|
|
|
191
|
-
<!--
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
181
|
+
<!-- DEPENDENCIES full view (hash-routed: #/goal/N/dependencies; the old
|
|
182
|
+
#/goal/N/conflicts lands here too) — task 1004738. The per-goal task
|
|
183
|
+
dependency forest (task 1003480, ADR 0227 §6 item 4 + §10.5) gets
|
|
184
|
+
the page's full width a node-link picture needs, then the whole
|
|
185
|
+
conflict set (task 1828). -->
|
|
186
|
+
<section id="goal-dependencies-view" hidden aria-labelledby="goal-deps-h">
|
|
187
|
+
<p class="card-foot" style="margin:0 0 14px"><a href="#" id="goal-deps-back">← Back to goal</a></p>
|
|
188
|
+
<div class="scroll" style="margin:0" id="goal-deps-scroll">
|
|
189
|
+
<h2 class="scroll__h" id="goal-deps-h">Task dependencies<span class="count-pill" id="goal-deps-count" hidden></span></h2>
|
|
190
|
+
<p class="scroll__lede" id="goal-deps-lede">Reading…</p>
|
|
191
|
+
<div id="goal-deps-body"></div>
|
|
192
|
+
</div>
|
|
193
|
+
<div class="scroll" style="margin-top:20px" id="goal-conflicts-view-card">
|
|
196
194
|
<h2 class="scroll__h" id="goal-conflicts-view-h">Conflicts<span class="count-pill" id="goal-conflicts-view-count" hidden></span></h2>
|
|
197
195
|
<p class="scroll__lede">Open tasks whose declared files overlap — sequence these before two builders collide.</p>
|
|
198
196
|
<div id="goal-conflicts-full"></div>
|
|
@@ -122,7 +122,25 @@
|
|
|
122
122
|
],
|
|
123
123
|
"expect": {
|
|
124
124
|
"visible": [
|
|
125
|
-
"#goal-detail-view"
|
|
125
|
+
"#goal-detail-view",
|
|
126
|
+
"#goal-deps-card"
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"dependencies": {
|
|
131
|
+
"auth": true,
|
|
132
|
+
"url": "/builders/goals#/goal/25/dependencies",
|
|
133
|
+
"actions": [
|
|
134
|
+
[
|
|
135
|
+
"wait",
|
|
136
|
+
1200
|
|
137
|
+
]
|
|
138
|
+
],
|
|
139
|
+
"expect": {
|
|
140
|
+
"visible": [
|
|
141
|
+
"#goal-dependencies-view",
|
|
142
|
+
"#goal-deps-scroll",
|
|
143
|
+
"#goal-conflicts-view-card"
|
|
126
144
|
]
|
|
127
145
|
}
|
|
128
146
|
},
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# The per-goal dependency forest — on the Goals page (tasks 1003480, 1004200)
|
|
1
|
+
# The per-goal dependency forest — on the Goals page (tasks 1003480, 1004200, 1004738)
|
|
2
2
|
|
|
3
3
|
**THE PER-GOAL DEPENDENCY FOREST landed on the Goals page (task 1003480, goal 1000089; ADR 0227 §6 item 4 and its §10.5 amendment)** — the drill-down the goal map's own header pointed at (the map is the project sky since task 1004232): a task-level picture of which of THIS goal's tasks depend on which, from `GET /goals/:id/dependency-forest`. `goal-forest-lib.js` was the goal map's sibling — the same layered-layout idea (a task sits one column right of what it depends on, deterministic) at a smaller scale, since a per-goal forest is small BY MEASUREMENT (ADR 0227 §10.5: largest 127 tasks, median 9) rather than needing the goal map's cluster-and-shelf packing. Five things worth knowing:
|
|
4
4
|
|
|
5
|
-
- **
|
|
6
|
-
- **
|
|
5
|
+
- **A compact card on the goal page, the whole picture one click away (task 1004738, owner's call).** The forest first shipped as its own full-width block between the band and Tasks — ~840px for a five-task forest, with a table repeating every task — and the owner called it way too long. Now the band's third column (Conflicts' old slot) is a **Dependencies card**: `brief` (one line), up to three `shipFirst` tasks (open, blocked by nothing open, ranked by how many open tasks wait on them down the chain), the conflicts count, and "See the full picture →". The forest, its table and the whole conflict list live on **`#/goal/N/dependencies`**; `#/goal/N/conflicts` still lands there because the attention inbox links it. Conflicts folded in rather than kept beside it: both answer "what order do we build in".
|
|
6
|
+
- **The full view is never capped.** ADR 0227 §3's finding still holds — the tasks a forest omits are precisely the ones with nothing to connect them to — so the full view draws everything the server returns; only the card summarises. The figure is drawn with `createElementNS` + `textContent` (the atlas idiom — a task title never becomes markup) inside `.gf-scroll`, which names BOTH overflow axes (task 1004130), and it is drawn only while its view is open (`state.view === 'dependencies'`): the goal page shows just the card, so drawing there would be thrown away, and a forest drawn hidden cannot measure its titles.
|
|
7
|
+
- **The card reads two feeds.** It renders from `state.forest` AND `state.conflictPairs`, so `renderConflicts` in `goals-render.js` calls `renderDependencyCard` again when the conflicts land; a null `conflictPairs` (loading, or a failed read) draws no conflicts line rather than a zero.
|
|
7
8
|
- **The states are three, and distinct on purpose:** "Reading…" while the request is in flight, "No task in this goal depends on another task in this goal" for a real empty forest (never a blank canvas), and "Couldn't load…" only for an actual failure — a redirecting-to-auth error is swallowed silently instead, the `loadGoalTasks` precedent, because the page is already navigating away.
|
|
8
9
|
- **`loadDependencyForest`/`renderDependencyForest` are exported from `createGoalsRender`'s return** specifically so `tests/goal_forest_page.mjs` can call them directly against a minimal DOM stub, without needing the much larger fixture a full `renderDetail()` boot would need for members/completion/invites this task never touches.
|
|
9
10
|
- **The harness fixture is a single generic `goals__dependency-forest.json`** (task ids real, drawn from goal 25's own rows in `tasks.json`) — every goal id in the harness sees the same forest, which is fine for looking at the picture and not meant to describe any goal's real dependencies.
|