@bongos/core 1.19.469 → 1.19.470
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 +27 -22
- package/docs/copy-inventory.md +40 -43
- package/docs/copy-registry.json +155 -182
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/atlas.css +96 -88
- package/modules/hall-ui/public/atlas.html +43 -20
- package/modules/hall-ui/public/atlas.js +118 -20
- package/modules/hall-ui/public/atlas.states.json +52 -1
- package/modules/hall-ui/public/modules.css +67 -86
- package/modules/hall-ui/public/modules.html +46 -35
- package/modules/hall-ui/public/modules.js +195 -103
- package/modules/hall-ui/public/modules.states.json +48 -1
- package/modules/hall-ui/public/style.css +8 -8
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/hall_catalog_world.mjs +181 -0
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
// public-builders/modules.js — the hall "Modules" tab (/builders/modules).
|
|
2
2
|
//
|
|
3
|
-
// ADR 0107 §9 (task 1776) + §2/§3 the browsable catalog (task 1001772)
|
|
3
|
+
// ADR 0107 §9 (task 1776) + §2/§3 the browsable catalog (task 1001772);
|
|
4
|
+
// redesigned onto the oversight family's row in task 1003472. Jobs:
|
|
4
5
|
// 1. BROWSE the catalog — every module the core ships that this instance can
|
|
5
|
-
// enable, each with its author + origin credit, filterable by
|
|
6
|
-
//
|
|
7
|
-
// OWN modules by `source` (which TREE it ships from — the core
|
|
8
|
-
// this repo), which GET /api/bongos/modules reads structurally
|
|
9
|
-
// loader's discovery roots. That is a different axis from
|
|
10
|
-
// (WHO built it, read off the manifest's origin) and both are
|
|
11
|
-
//
|
|
6
|
+
// enable, each with its author + origin credit, filterable by one search
|
|
7
|
+
// over name/author/origin/licence and a state lens. Split from the
|
|
8
|
+
// instance's OWN modules by `source` (which TREE it ships from — the core
|
|
9
|
+
// package or this repo), which GET /api/bongos/modules reads structurally
|
|
10
|
+
// off the loader's discovery roots. That is a different axis from
|
|
11
|
+
// `provenance` (WHO built it, read off the manifest's origin) and both are
|
|
12
|
+
// shown: the group says where it ships from, the facts say who to credit.
|
|
12
13
|
// 2. Enable/disable — a toggle over config/modules.json (POST /modules/:key/enable).
|
|
13
14
|
// Any signed-in builder sees the state; only metic+archon can flip it (the
|
|
14
15
|
// server enforces this — a lower-rank click just gets a 403 + a friendly
|
|
15
|
-
// toast, same as every other rank-gated write in this hall).
|
|
16
|
+
// toast, same as every other rank-gated write in this hall). A viewer below
|
|
17
|
+
// the floor sees the state as a pill rather than a dead control.
|
|
16
18
|
// 3. "Submit for review" front door — files one of this project's own modules
|
|
17
19
|
// into the upstreaming review queue (POST /modules/:key/submit, task 1770).
|
|
20
|
+
// The door sits on the module's own row.
|
|
18
21
|
//
|
|
19
22
|
// The rows come from the SAME builder as `bongos module list --catalog`
|
|
20
23
|
// (src/module-loader/catalog.js), so the two surfaces cannot drift.
|
|
@@ -27,31 +30,41 @@
|
|
|
27
30
|
// /lib/bongos-client.js. baseUrl:'' passes the full `${API}/...` URLs below
|
|
28
31
|
// through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
|
|
29
32
|
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
30
|
-
const { escapeHtml, $, toast } = window.OTB;
|
|
33
|
+
const { escapeHtml, $, toast, rankLabel } = window.OTB;
|
|
34
|
+
const kit = window.OTBKit;
|
|
31
35
|
|
|
32
36
|
let viewerRank = 'xenos';
|
|
33
37
|
let canToggle = false;
|
|
34
|
-
|
|
38
|
+
let submitLive = false;
|
|
39
|
+
// The last payload, held so the filter bar can re-render without a refetch.
|
|
35
40
|
let allModules = [];
|
|
41
|
+
// The filter bar's current values (search + state lens).
|
|
42
|
+
let query = '';
|
|
43
|
+
let stateLens = '';
|
|
44
|
+
// Which rows a builder has opened, by key, so a re-render (a toggle, a
|
|
45
|
+
// filter) does not fold the description they were reading.
|
|
46
|
+
const openKeys = new Set();
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
const floor = () => rankLabel('metic');
|
|
49
|
+
|
|
50
|
+
function provenancePill(m) {
|
|
38
51
|
return m.provenance === 'core'
|
|
39
|
-
? '<span class="
|
|
40
|
-
: '<span class="
|
|
52
|
+
? '<span class="fact-pill fact-pill--info">core-loaded</span>'
|
|
53
|
+
: '<span class="fact-pill">built here</span>';
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
// Author + origin credit (ADR 0107 §2/§3 — credit now, money later). Reads
|
|
44
57
|
// `credit`, not the raw manifest fields, so a core module whose manifest
|
|
45
|
-
// declares no author still shows who ships it rather than a blank
|
|
58
|
+
// declares no author still shows who ships it rather than a blank row; an
|
|
46
59
|
// INFERRED credit is marked so it never reads as something the author typed.
|
|
47
|
-
function
|
|
60
|
+
function attributionFacts(m) {
|
|
48
61
|
const credit = m.credit || { author: m.author, origin: m.origin, inferred: false };
|
|
49
62
|
const mark = credit.inferred ? '<abbr class="mod-inferred" title="Not declared in module.json — this is who ships the module.">*</abbr>' : '';
|
|
50
|
-
const
|
|
51
|
-
if (credit.author)
|
|
52
|
-
if (credit.origin && credit.origin !== credit.author)
|
|
53
|
-
if (m.license)
|
|
54
|
-
return
|
|
63
|
+
const facts = [];
|
|
64
|
+
if (credit.author) facts.push(`<span class="ov-fact">by <b>${escapeHtml(credit.author)}</b>${mark}</span>`);
|
|
65
|
+
if (credit.origin && credit.origin !== credit.author) facts.push(`<span class="ov-fact">from <b>${escapeHtml(credit.origin)}</b>${mark}</span>`);
|
|
66
|
+
if (m.license) facts.push(`<span class="fact-chip">${escapeHtml(m.license)}</span>`);
|
|
67
|
+
return facts.join('');
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
// Free-text browse over the fields a builder would search by. Mirrors
|
|
@@ -63,41 +76,77 @@
|
|
|
63
76
|
return [m.key, m.title, m.description, credit.author, credit.origin, m.license]
|
|
64
77
|
.some((f) => f && String(f).toLowerCase().includes(q));
|
|
65
78
|
}
|
|
79
|
+
function matchesLens(m) {
|
|
80
|
+
if (stateLens === 'enabled') return !!m.enabled;
|
|
81
|
+
if (stateLens === 'disabled') return !m.enabled;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
66
84
|
|
|
67
85
|
// Upkeep posture (ADR 0107 §7 / ADR 0166) — who keeps this module true as the
|
|
68
|
-
// core advances.
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
|
|
86
|
+
// core advances. Colour marks the EXCEPTION: an unowned entry is the thing the
|
|
87
|
+
// policy exists to catch, so it is the warn pill; a maintained one is a plain
|
|
88
|
+
// fact, since seventeen "core-maintained" pills would be the loudest thing on
|
|
89
|
+
// the page and none of them would be news.
|
|
90
|
+
function upkeepFact(m) {
|
|
72
91
|
const mt = m.maintenance;
|
|
73
|
-
if (!mt || !mt.status) return '<span class="
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
].filter(Boolean).join(' — ');
|
|
80
|
-
return `<span class="mod-chip" title="${escapeHtml(detail || mt.status)}">upkeep: ${escapeHtml(mt.status)}</span>`;
|
|
92
|
+
if (!mt || !mt.status) return '<span class="fact-pill fact-pill--warn" title="No maintenance declared — nobody is on the hook for this module as the core advances.">upkeep undeclared</span>';
|
|
93
|
+
const status = String(mt.status);
|
|
94
|
+
if (status === 'deprecated' || status === 'superseded' || mt.successor || mt.removeAfter) {
|
|
95
|
+
return `<span class="fact-pill fact-pill--warn">${escapeHtml(status)}</span>`;
|
|
96
|
+
}
|
|
97
|
+
return `<span class="ov-fact">upkeep <b>${escapeHtml(status)}</b></span>`;
|
|
81
98
|
}
|
|
82
99
|
|
|
83
|
-
|
|
100
|
+
// The lines the row's details disclosure opens onto: the facts the payload
|
|
101
|
+
// carries that the card never rendered — what the module contributes, the
|
|
102
|
+
// core it needs, who maintains it and what it hands off to.
|
|
103
|
+
function detailLines(m) {
|
|
104
|
+
const lines = [];
|
|
105
|
+
const line = (k, v) => { if (v) lines.push(`<li><span class="ov-key ov-key--faint">${escapeHtml(k)}</span><span class="ov-lines__v">${v}</span></li>`); };
|
|
106
|
+
line('key', `<span class="ov-mono">${escapeHtml(m.key)}</span>`);
|
|
107
|
+
if (m.coreVersion) line('needs core', `<span class="ov-mono">${escapeHtml(String(m.coreVersion))}</span>`);
|
|
108
|
+
const c = m.contributes || {};
|
|
109
|
+
for (const kind of Object.keys(c)) {
|
|
110
|
+
const items = Array.isArray(c[kind]) ? c[kind] : [];
|
|
111
|
+
if (items.length) line(kind, items.map((x) => `<span class="fact-chip">${escapeHtml(String(x))}</span>`).join(''));
|
|
112
|
+
}
|
|
113
|
+
const mt = m.maintenance || {};
|
|
114
|
+
if (m.maintainer) line('maintainer', escapeHtml(String(m.maintainer)));
|
|
115
|
+
if (mt.successor) line('superseded by', escapeHtml(String(mt.successor)));
|
|
116
|
+
if (mt.removeAfter) line('removable after core', `<span class="ov-mono">${escapeHtml(String(mt.removeAfter))}</span>`);
|
|
117
|
+
if (mt.note) line('note', escapeHtml(String(mt.note)));
|
|
118
|
+
if (m.default != null) line('ships', m.default ? 'default on' : 'default off');
|
|
119
|
+
return lines.length ? `<ul class="ov-lines">${lines.join('')}</ul>` : '';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The right column: ONE control. The family's grant toggle when the viewer
|
|
123
|
+
// can flip it (enabled in ink, disabled faint, the accent on hover); the
|
|
124
|
+
// state as a pill when they cannot. A module built here also carries its
|
|
125
|
+
// submit door, since that door is about this module and no other.
|
|
126
|
+
function rightHtml(m) {
|
|
127
|
+
const on = !!m.enabled;
|
|
128
|
+
const control = canToggle
|
|
129
|
+
? `<button type="button" class="ov-toggle mod-toggle" aria-pressed="${on ? 'true' : 'false'}" data-key="${escapeHtml(m.key)}">${on ? 'enabled' : 'disabled'}</button>`
|
|
130
|
+
: `<span class="fact-pill ${on ? 'fact-pill--ok' : ''}" title="${escapeHtml(floor())} and above can enable or disable a module">${on ? 'enabled' : 'disabled'}</span>`;
|
|
131
|
+
const submit = sourceOf(m) === 'instance'
|
|
132
|
+
? `<button type="button" class="btn-ghost btn--sm mod-submit" data-submit-key="${escapeHtml(m.key)}"${submitLive ? '' : ' disabled title="Submit pipeline not live yet — tracked by task 1770"'}>submit for review</button>`
|
|
133
|
+
: '';
|
|
134
|
+
return control + submit;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function rowHTML(m) {
|
|
138
|
+
const desc = String(m.description || '').trim();
|
|
139
|
+
const lines = detailLines(m);
|
|
84
140
|
return `
|
|
85
|
-
<
|
|
86
|
-
<div class="
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
</
|
|
91
|
-
</div>
|
|
92
|
-
<p class="mod-card__desc">${escapeHtml(m.description || '')}</p>
|
|
93
|
-
<div class="mod-chips">${provenanceChip(m)}${attributionChips(m)}${maintenanceChip(m)}</div>
|
|
94
|
-
<div class="mod-card__actions">
|
|
95
|
-
<button type="button" class="mod-toggle" data-on="${m.enabled ? '1' : '0'}"
|
|
96
|
-
${canToggle ? '' : 'disabled title="Metic+ can enable or disable a module"'}>
|
|
97
|
-
${m.enabled ? 'Enabled' : 'Disabled'}
|
|
98
|
-
</button>
|
|
141
|
+
<li class="ov-row mod-row" data-key="${escapeHtml(m.key)}">
|
|
142
|
+
<div class="ov-row__main">
|
|
143
|
+
<span class="ov-row__title">${escapeHtml(m.title || m.key)}<span class="ov-mono">${escapeHtml(m.key)}</span></span>
|
|
144
|
+
${desc ? `<p class="ov-row__sub mod-row__desc">${escapeHtml(desc)}</p>` : ''}
|
|
145
|
+
<div class="ov-row__facts">${provenancePill(m)}${attributionFacts(m)}${upkeepFact(m)}</div>
|
|
146
|
+
${lines ? `<details class="mod-more"${openKeys.has(m.key) ? ' open' : ''}><summary aria-label="Details for ${escapeHtml(m.title || m.key)}"></summary>${lines}</details>` : ''}
|
|
99
147
|
</div>
|
|
100
|
-
|
|
148
|
+
<div class="ov-row__right mod-row__right">${rightHtml(m)}</div>
|
|
149
|
+
</li>`;
|
|
101
150
|
}
|
|
102
151
|
|
|
103
152
|
// `source` splits the page: 'core' = shipped by the core package (the
|
|
@@ -109,81 +158,126 @@
|
|
|
109
158
|
return m.provenance === 'core' ? 'core' : 'instance';
|
|
110
159
|
}
|
|
111
160
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
const
|
|
161
|
+
// The figure band: the four numbers the page head used to carry as prose.
|
|
162
|
+
function figsHTML() {
|
|
163
|
+
const core = allModules.filter((m) => sourceOf(m) === 'core');
|
|
164
|
+
const own = allModules.filter((m) => sourceOf(m) === 'instance');
|
|
165
|
+
const enabled = allModules.filter((m) => m.enabled).length;
|
|
166
|
+
const undeclared = allModules.filter((m) => !m.maintenance || !m.maintenance.status).length;
|
|
167
|
+
const fig = (l, v, s) => `<div class="ov-fig"><span class="ov-fig__l">${l}</span><span class="ov-fig__v">${v}</span>${s ? `<span class="ov-fig__s">${s}</span>` : ''}</div>`;
|
|
168
|
+
// Short labels on purpose: the band is two columns at 390px and a label
|
|
169
|
+
// that wraps a phone's column is cut with an ellipsis.
|
|
170
|
+
return fig('modules', allModules.length, `${core.length} from the core`)
|
|
171
|
+
+ fig('enabled', enabled, `of ${allModules.length}`)
|
|
172
|
+
+ fig('built here', own.length, own.length ? 'can go upstream' : 'none on disk')
|
|
173
|
+
+ fig('no upkeep', undeclared, undeclared ? 'nobody on the hook' : 'every module owned');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function renderRows() {
|
|
177
|
+
const matching = allModules.filter((m) => matchesQuery(m, query) && matchesLens(m));
|
|
116
178
|
const core = matching.filter((m) => sourceOf(m) === 'core');
|
|
117
179
|
const own = matching.filter((m) => sourceOf(m) === 'instance');
|
|
180
|
+
const narrowed = !!(query || stateLens);
|
|
118
181
|
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
: `<
|
|
182
|
+
const coreList = $('#core-grid');
|
|
183
|
+
if (coreList) {
|
|
184
|
+
coreList.innerHTML = core.length ? core.map(rowHTML).join('')
|
|
185
|
+
: `<li>${kit.emptyStateHtml(narrowed ? 'No catalog module matches that search.' : 'The core ships no modules this instance can see.')}</li>`;
|
|
123
186
|
}
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
: `<
|
|
187
|
+
const ownList = $('#own-grid');
|
|
188
|
+
if (ownList) {
|
|
189
|
+
ownList.innerHTML = own.length ? own.map(rowHTML).join('')
|
|
190
|
+
: `<li>${kit.emptyStateHtml(narrowed ? 'None of this project\'s own modules match that search.' : 'None on disk — everything here ships with the core.')}</li>`;
|
|
128
191
|
}
|
|
192
|
+
const coreN = $('#core-n');
|
|
193
|
+
if (coreN) coreN.textContent = String(core.length);
|
|
194
|
+
const ownN = $('#own-n');
|
|
195
|
+
if (ownN) ownN.textContent = String(own.length);
|
|
129
196
|
|
|
130
|
-
|
|
197
|
+
// The count line speaks only while a filter narrows the list — the figure
|
|
198
|
+
// band under it already says how many there are.
|
|
199
|
+
const count = $('#mod-count');
|
|
131
200
|
if (count) {
|
|
132
|
-
|
|
133
|
-
|
|
201
|
+
const n = allModules.length;
|
|
202
|
+
count.textContent = narrowed
|
|
203
|
+
? `${matching.length} of ${n} ${n === 1 ? 'module' : 'modules'} match${query ? ` “${query}”` : ''}${stateLens ? ` · ${stateLens}` : ''}.`
|
|
134
204
|
: '';
|
|
135
205
|
}
|
|
206
|
+
}
|
|
136
207
|
|
|
137
|
-
|
|
208
|
+
// One listener on the panel outlives every re-render: the toggle, the submit
|
|
209
|
+
// door and the details disclosure all bubble to it.
|
|
210
|
+
let wired = false;
|
|
211
|
+
function wire() {
|
|
212
|
+
if (wired) return;
|
|
213
|
+
wired = true;
|
|
214
|
+
const panel = $('#core-scroll');
|
|
215
|
+
if (!panel) return;
|
|
216
|
+
panel.addEventListener('click', (ev) => {
|
|
217
|
+
const toggle = ev.target.closest('.mod-toggle');
|
|
218
|
+
if (toggle) { onToggle(toggle); return; }
|
|
219
|
+
const submit = ev.target.closest('.mod-submit');
|
|
220
|
+
if (submit) onSubmit(submit);
|
|
221
|
+
});
|
|
222
|
+
panel.addEventListener('toggle', (ev) => {
|
|
223
|
+
const d = ev.target;
|
|
224
|
+
if (!(d instanceof HTMLDetailsElement) || !d.classList.contains('mod-more')) return;
|
|
225
|
+
const row = d.closest('.mod-row');
|
|
226
|
+
const key = row && row.dataset.key;
|
|
227
|
+
if (!key) return;
|
|
228
|
+
if (d.open) openKeys.add(key); else openKeys.delete(key);
|
|
229
|
+
}, true);
|
|
138
230
|
}
|
|
139
231
|
|
|
140
232
|
function render(data) {
|
|
141
233
|
allModules = Array.isArray(data.modules) ? data.modules : [];
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
234
|
+
submitLive = !!data.submit_pipeline_live;
|
|
235
|
+
|
|
236
|
+
const figs = $('#mod-figs');
|
|
237
|
+
if (figs) figs.innerHTML = figsHTML();
|
|
145
238
|
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
239
|
+
const mount = $('#mod-filters');
|
|
240
|
+
if (mount && !mount.childElementCount) {
|
|
241
|
+
kit.makeFilterBar({
|
|
242
|
+
mount,
|
|
243
|
+
search: { id: 'mod-search', label: 'Search', placeholder: 'name, author, origin or licence' },
|
|
244
|
+
primary: [
|
|
245
|
+
{ id: 'mod-state', label: 'State', options: [
|
|
246
|
+
{ value: '', label: 'all' }, { value: 'enabled', label: 'enabled' }, { value: 'disabled', label: 'disabled' }] },
|
|
247
|
+
],
|
|
248
|
+
onChange: (vals) => {
|
|
249
|
+
query = String(vals['mod-search'] || '').trim().toLowerCase();
|
|
250
|
+
stateLens = String(vals['mod-state'] || '');
|
|
251
|
+
renderRows();
|
|
252
|
+
},
|
|
253
|
+
});
|
|
150
254
|
}
|
|
151
255
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// already ships with the core is, by definition, already in the catalog.
|
|
156
|
-
const submitStatus = $('#submit-status');
|
|
157
|
-
if (submitStatus) {
|
|
158
|
-
submitStatus.innerHTML = own.length
|
|
159
|
-
? own.map((m) => `
|
|
160
|
-
<span style="display:inline-flex;align-items:center;gap:8px;margin:4px 12px 4px 0;">
|
|
161
|
-
<button type="button" class="mod-submit" data-submit-key="${escapeHtml(m.key)}"
|
|
162
|
-
${data.submit_pipeline_live ? '' : 'disabled title="Submit pipeline not live yet — tracked by task 1770"'}>
|
|
163
|
-
Submit "${escapeHtml(m.key)}" for review
|
|
164
|
-
</button>
|
|
165
|
-
</span>`).join('')
|
|
166
|
-
: '<span class="mod-note">Nothing to submit — every module here already ships with the core.</span>';
|
|
167
|
-
document.querySelectorAll('[data-submit-key]').forEach((btn) => btn.addEventListener('click', onSubmit));
|
|
256
|
+
const lede = $('#submit-lede');
|
|
257
|
+
if (lede && !allModules.some((m) => sourceOf(m) === 'instance')) {
|
|
258
|
+
lede.textContent = 'Nothing to submit — every module here ships with the core.';
|
|
168
259
|
}
|
|
169
260
|
|
|
170
|
-
|
|
171
|
-
|
|
261
|
+
wire();
|
|
262
|
+
renderRows();
|
|
172
263
|
}
|
|
173
264
|
|
|
174
|
-
async function onToggle(
|
|
175
|
-
const
|
|
176
|
-
const card = btn.closest('.mod-card');
|
|
177
|
-
const key = card && card.dataset.key;
|
|
265
|
+
async function onToggle(btn) {
|
|
266
|
+
const key = btn.dataset.key;
|
|
178
267
|
if (!key) return;
|
|
179
|
-
const next = btn.
|
|
268
|
+
const next = btn.getAttribute('aria-pressed') !== 'true';
|
|
180
269
|
btn.disabled = true;
|
|
181
270
|
try {
|
|
182
271
|
const res = await api.request('POST', `${API}/modules/${encodeURIComponent(key)}/enable`, { body: { enabled: next } });
|
|
183
272
|
const body = res.data || {};
|
|
184
273
|
if (!res.ok) throw new Error(body.message || `enable → ${res.status}`);
|
|
185
|
-
|
|
186
|
-
|
|
274
|
+
const m = allModules.find((x) => x.key === key);
|
|
275
|
+
if (m) m.enabled = next;
|
|
276
|
+
btn.setAttribute('aria-pressed', next ? 'true' : 'false');
|
|
277
|
+
btn.textContent = next ? 'enabled' : 'disabled';
|
|
278
|
+
const figs = $('#mod-figs');
|
|
279
|
+
if (figs) figs.innerHTML = figsHTML();
|
|
280
|
+
renderRows();
|
|
187
281
|
toast(body.note || (next ? `${key} enabled.` : `${key} disabled.`), { duration: 3200 });
|
|
188
282
|
} catch (err) {
|
|
189
283
|
console.error('[modules] toggle failed', err);
|
|
@@ -193,8 +287,7 @@
|
|
|
193
287
|
}
|
|
194
288
|
}
|
|
195
289
|
|
|
196
|
-
async function onSubmit(
|
|
197
|
-
const btn = ev.currentTarget;
|
|
290
|
+
async function onSubmit(btn) {
|
|
198
291
|
const key = btn.dataset.submitKey;
|
|
199
292
|
try {
|
|
200
293
|
const res = await api.request('POST', `${API}/modules/${encodeURIComponent(key)}/submit`);
|
|
@@ -217,12 +310,11 @@
|
|
|
217
310
|
const res = await api.request('GET', `${API}/modules`);
|
|
218
311
|
if (res.status === 401) { window.location.assign(`${API}/auth/web/start`); return; }
|
|
219
312
|
if (!res.ok) throw new Error(`modules → ${res.status}`);
|
|
220
|
-
|
|
221
|
-
render(data);
|
|
313
|
+
render(res.data);
|
|
222
314
|
} catch (err) {
|
|
223
315
|
console.error('[modules] failed to load', err);
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
316
|
+
const list = $('#core-grid');
|
|
317
|
+
if (list) list.innerHTML = `<li>${kit.emptyStateHtml('Could not load modules. Return to the hall and try again.')}</li>`;
|
|
226
318
|
toast('Could not load modules.', { duration: 3200 });
|
|
227
319
|
}
|
|
228
320
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "Modules — the
|
|
2
|
+
"_": "Modules — the catalog as ONE panel of oversight rows (task 1003472): the kit filter bar, the figure band, two groups under heads. The empty state matters more than the populated one: a filter that matches nothing is the state a browse page spends real time in. Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once; the redesign added the `lens` and `details` states. Populated look: the hall-preview harness (scripts/hall-preview/fixtures/modules.json carries eighteen modules, one built by the project).",
|
|
3
3
|
"page": "/builders/modules",
|
|
4
4
|
"surface": "hall-ui",
|
|
5
5
|
"stub": {
|
|
@@ -46,6 +46,10 @@
|
|
|
46
46
|
"search-empty": {
|
|
47
47
|
"auth": true,
|
|
48
48
|
"actions": [
|
|
49
|
+
[
|
|
50
|
+
"wait",
|
|
51
|
+
600
|
|
52
|
+
],
|
|
49
53
|
[
|
|
50
54
|
"fill",
|
|
51
55
|
"#mod-search",
|
|
@@ -57,6 +61,49 @@
|
|
|
57
61
|
]
|
|
58
62
|
]
|
|
59
63
|
},
|
|
64
|
+
"lens": {
|
|
65
|
+
"auth": true,
|
|
66
|
+
"actions": [
|
|
67
|
+
[
|
|
68
|
+
"wait",
|
|
69
|
+
600
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
"focus",
|
|
73
|
+
"#mod-state"
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
"press",
|
|
77
|
+
"ArrowDown"
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
"wait",
|
|
81
|
+
300
|
|
82
|
+
]
|
|
83
|
+
],
|
|
84
|
+
"expect": {
|
|
85
|
+
"visible": [
|
|
86
|
+
"#mod-state"
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"details": {
|
|
91
|
+
"auth": true,
|
|
92
|
+
"actions": [
|
|
93
|
+
[
|
|
94
|
+
"wait",
|
|
95
|
+
600
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
"clickIfVisible",
|
|
99
|
+
"#core-grid .mod-row:first-child .mod-more > summary"
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"wait",
|
|
103
|
+
300
|
|
104
|
+
]
|
|
105
|
+
]
|
|
106
|
+
},
|
|
60
107
|
"out": {
|
|
61
108
|
"auth": false
|
|
62
109
|
}
|
|
@@ -3057,18 +3057,18 @@ html.palette-open { overflow: hidden; }
|
|
|
3057
3057
|
The state colours are the hall's fixed verification vocabulary, never the
|
|
3058
3058
|
world's warm accent states (ADR 0196 §2): a status must not re-tint when an
|
|
3059
3059
|
instance re-brands. */
|
|
3060
|
-
/* The
|
|
3061
|
-
government, harbor, gate
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3060
|
+
/* The names below are the SAME pill, found by the task 1003307 audit:
|
|
3061
|
+
government, harbor, gate and watch each invented one because the shell had
|
|
3062
|
+
none to take (modules had one too; its rows take .fact-pill by name since
|
|
3063
|
+
task 1003472). They are aliased rather than renamed because renaming would
|
|
3064
|
+
mean editing the JS row builders, and a class name is not what was wrong —
|
|
3065
|
+
the duplicated, hand-rolled label voice was. Their own state modifiers stay
|
|
3066
|
+
in their own sheets. */
|
|
3066
3067
|
.fact-pill,
|
|
3067
3068
|
.gov-pill,
|
|
3068
3069
|
.harbor-pill,
|
|
3069
3070
|
.gate-pill,
|
|
3070
|
-
.badge
|
|
3071
|
-
.mod-chip {
|
|
3071
|
+
.badge {
|
|
3072
3072
|
display: inline-flex;
|
|
3073
3073
|
align-items: center;
|
|
3074
3074
|
min-height: 24px;
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.470",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.470",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.470",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/src/module-api.js
CHANGED
|
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
33
33
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
34
34
|
// the entry to that file. Look for a version's history there, not here.
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
|
-
const CORE_VERSION = '1.19.
|
|
36
|
+
const CORE_VERSION = '1.19.470'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
37
37
|
|
|
38
38
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
39
39
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|