@bongos/core 1.21.90 → 1.21.92
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 +67 -52
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +4 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/api/openapi.json +36 -1
- package/docs/api-reference.md +7 -1
- package/docs/copy-inventory.md +364 -374
- package/docs/copy-registry.json +566 -648
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +15 -18
- package/docs/page-readings.json +1500 -1191
- package/modules/hall-ui/public/craft-mode.js +60 -0
- package/modules/hall-ui/public/style.css +32 -0
- package/modules/hall-ui/records/craft-mode.md +1 -1
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/moving.js +55 -0
- package/modules/lifecycle/routes/moving.js +30 -0
- package/modules/public-landing/public/projects-hall-live.states.json +6 -35
- package/modules/public-landing/public/projects.html +1019 -983
- package/modules/public-landing/public/projects.probes.json +15 -18
- package/modules/public-landing/public/projects.states.json +22 -25
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/module-api.js +1 -1
- package/tests/craft_mode_tallies.mjs +149 -0
- package/tests/onboard.mjs +8 -8
- package/tests/project_invite_ui.mjs +6 -5
- package/tests/projects_hub.mjs +77 -98
- package/tests/projects_hub_app_status.mjs +3 -3
- package/tests/projects_hub_app_step.mjs +8 -8
- package/tests/projects_hub_cloud_hop.mjs +29 -53
- package/tests/projects_hub_look.mjs +29 -39
- package/tests/projects_hub_module_picker.mjs +32 -39
- package/tests/projects_hub_pre_uat.mjs +11 -14
- package/tests/wizard_demo.mjs +30 -25
- package/tests/wizard_draft_resume.mjs +24 -32
- package/tests/wizard_front_door.mjs +91 -243
- package/tests/wizard_intent_resume.mjs +28 -48
- package/tests/wizard_one_page.mjs +201 -0
- package/tests/wizard_physics.mjs +46 -41
- package/tests/wizard_physics_more.mjs +71 -106
- package/tests/wizard_ship_mode.mjs +1 -1
- package/tests/wizard_six_screens.mjs +0 -129
|
@@ -196,6 +196,64 @@
|
|
|
196
196
|
if (mark) mark.textContent = look;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
// ---------- Governing's header tallies (task 1004563) ----------
|
|
200
|
+
// "needs you N · moving M" beside the eyebrow, as on the four-mode prototype.
|
|
201
|
+
// Needs you is the open-blocker total GET /blockers already carries; moving is
|
|
202
|
+
// tasks being built or waiting to land (active + confirmed, owner 2026-10-08),
|
|
203
|
+
// counted by the server at GET /moving. Read once per page. A figure that cannot
|
|
204
|
+
// be read is LEFT OUT, never shown as 0: zero would say nothing waits on you.
|
|
205
|
+
var TALLY_API = '/api/bongos';
|
|
206
|
+
var tallyAsk = null;
|
|
207
|
+
function readCount(path, pick) {
|
|
208
|
+
if (typeof fetch !== 'function') return Promise.resolve(null);
|
|
209
|
+
return fetch(TALLY_API + path, { credentials: 'same-origin', headers: { Accept: 'application/json' } })
|
|
210
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
211
|
+
.then(function (j) {
|
|
212
|
+
var n = j ? pick(j) : null;
|
|
213
|
+
return (typeof n === 'number' && isFinite(n) && n >= 0) ? n : null;
|
|
214
|
+
}, function () { return null; });
|
|
215
|
+
}
|
|
216
|
+
function askTallies() {
|
|
217
|
+
if (!tallyAsk) {
|
|
218
|
+
tallyAsk = Promise.all([
|
|
219
|
+
readCount('/blockers?limit=1', function (j) { return j.page && j.page.total; }),
|
|
220
|
+
readCount('/moving', function (j) { return j.moving; }),
|
|
221
|
+
]).then(function (v) { return { needs: v[0], moving: v[1] }; });
|
|
222
|
+
}
|
|
223
|
+
return tallyAsk;
|
|
224
|
+
}
|
|
225
|
+
function tallyHtml(t) {
|
|
226
|
+
var parts = [];
|
|
227
|
+
function one(label, n, due) {
|
|
228
|
+
return '<span class="craft-tally' + (due ? ' craft-tally--due' : '') + '">'
|
|
229
|
+
+ '<span class="craft-tally__label">' + label + '</span> '
|
|
230
|
+
+ '<span class="craft-tally__n">' + n + '</span></span>';
|
|
231
|
+
}
|
|
232
|
+
if (t && t.needs != null) parts.push(one('needs you', t.needs, t.needs > 0));
|
|
233
|
+
if (t && t.moving != null) parts.push(one('moving', t.moving, false));
|
|
234
|
+
return parts.join(' <span class="craft-tallies__sep" aria-hidden="true">·</span> ');
|
|
235
|
+
}
|
|
236
|
+
function applyTallies(mode) {
|
|
237
|
+
var box = document.getElementById('craft-tallies');
|
|
238
|
+
if (mode !== 'governing') { remove(box); return; }
|
|
239
|
+
var mark = document.getElementById('craft-eyebrow');
|
|
240
|
+
if (!box && mark && mark.parentNode) {
|
|
241
|
+
box = document.createElement('span');
|
|
242
|
+
box.id = 'craft-tallies';
|
|
243
|
+
box.className = 'craft-tallies';
|
|
244
|
+
box.setAttribute('hidden', '');
|
|
245
|
+
mark.parentNode.insertBefore(box, mark.nextSibling);
|
|
246
|
+
}
|
|
247
|
+
if (!box) return;
|
|
248
|
+
askTallies().then(function (t) {
|
|
249
|
+
var live = document.getElementById('craft-tallies');
|
|
250
|
+
if (!live || document.documentElement.dataset.craft !== 'governing') return;
|
|
251
|
+
var html = tallyHtml(t);
|
|
252
|
+
live.innerHTML = html;
|
|
253
|
+
if (html) live.removeAttribute('hidden'); else live.setAttribute('hidden', '');
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
199
257
|
// ---------- each mode's tools (task 1004496 onward) ----------
|
|
200
258
|
// Artistry has two: the palette (a feedback doc, page-notes.js) and the
|
|
201
259
|
// paintbrush (the page's words as a wording change, paintbrush.js). Ideating
|
|
@@ -311,6 +369,7 @@
|
|
|
311
369
|
if (sw) sw.classList.toggle('craft-switch--unset', !mode);
|
|
312
370
|
applyNav();
|
|
313
371
|
applyLook(mode);
|
|
372
|
+
applyTallies(mode);
|
|
314
373
|
applyTools(mode);
|
|
315
374
|
try { document.dispatchEvent(new CustomEvent('otb:craft', { detail: { mode: mode } })); } catch (_) { /* old engine */ }
|
|
316
375
|
}
|
|
@@ -521,6 +580,7 @@
|
|
|
521
580
|
applyNav: applyNav,
|
|
522
581
|
LOOKS: LOOKS,
|
|
523
582
|
artHtml: artHtml,
|
|
583
|
+
tallyHtml: tallyHtml,
|
|
524
584
|
TOOLS: TOOLS,
|
|
525
585
|
switchHtml: switchHtml,
|
|
526
586
|
mount: mount,
|
|
@@ -1472,6 +1472,38 @@ html:is([data-craft="artistry"], [data-craft="ideating"], [data-craft="governing
|
|
|
1472
1472
|
flex-shrink: 0;
|
|
1473
1473
|
}
|
|
1474
1474
|
@media (max-width: 640px) { .craft-eyebrow { display: none; } }
|
|
1475
|
+
/* Governing's header tallies (craft-mode.js applyTallies, task 1004563):
|
|
1476
|
+
"needs you N · moving M" after the eyebrow. The warm dot marks needs-you only
|
|
1477
|
+
while something waits. Hidden at <=960px, earlier than the eyebrow: below that
|
|
1478
|
+
they squeeze the jump button under its own label (measured at 800px). */
|
|
1479
|
+
.craft-tallies {
|
|
1480
|
+
display: inline-flex;
|
|
1481
|
+
align-items: baseline;
|
|
1482
|
+
gap: 10px;
|
|
1483
|
+
white-space: nowrap;
|
|
1484
|
+
flex-shrink: 0;
|
|
1485
|
+
font-size: 12px;
|
|
1486
|
+
}
|
|
1487
|
+
.craft-tallies[hidden] { display: none; }
|
|
1488
|
+
.craft-tally { display: inline-flex; align-items: baseline; gap: 6px; }
|
|
1489
|
+
.craft-tally__label {
|
|
1490
|
+
font-family: var(--label-face);
|
|
1491
|
+
font-size: 11px;
|
|
1492
|
+
letter-spacing: .12em;
|
|
1493
|
+
text-transform: uppercase;
|
|
1494
|
+
color: var(--ink-soft);
|
|
1495
|
+
}
|
|
1496
|
+
.craft-tally__n { color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 500; }
|
|
1497
|
+
.craft-tally--due::before {
|
|
1498
|
+
content: '';
|
|
1499
|
+
align-self: center;
|
|
1500
|
+
width: 7px;
|
|
1501
|
+
height: 7px;
|
|
1502
|
+
border-radius: 999px;
|
|
1503
|
+
background: var(--accent);
|
|
1504
|
+
}
|
|
1505
|
+
.craft-tallies__sep { color: var(--ink-faint); }
|
|
1506
|
+
@media (max-width: 960px) { .craft-tallies { display: none; } }
|
|
1475
1507
|
/* The signature: the mode's door scene (craft-mode.js artHtml, the same art the
|
|
1476
1508
|
picker draws) framed under the brand, 40px in the closed rail and 64px open.
|
|
1477
1509
|
The 110px-tall scene is scaled to the plate's height and widened to fill it,
|
|
@@ -34,7 +34,7 @@ Covers: every shelled hall page (`craft-mode.js`, `shell.js`, `style.css`), and
|
|
|
34
34
|
- **The accent stays the action colour.** `--craft` paints only the switch's current pill, the current tab (tint, icon, label, marker) and the top bar's hairline.
|
|
35
35
|
- **Two pieces are drawn by `craft-mode.js` `applyLook()`, on every mode change:** the eyebrow beside `#topbar-title` (`LOOKS`: "the studio · night", "the sky", "the city"; hidden at <=640px, where it overflowed phones) and the scene plate under `.app-sidebar__brand` (40px closed rail, 64px open). The plate's markup IS `artHtml(mode)`, the picker's door art, exported rather than copied; light mode recolours it to each scene's day (the city is the dark one through `invert(1) hue-rotate(180deg)`). It does not animate.
|
|
36
36
|
- **Always-present tabs widened** (owner, 2026-10-03): `ALWAYS_TABS` is Builders, Collab and Profile, so every mode keeps the You and Community groups.
|
|
37
|
-
- **Governing's header tallies** ("needs you · moving")
|
|
37
|
+
- **Governing's header tallies** (task 1004563): `applyTallies()` draws "needs you N · moving M" right after the eyebrow, Governing only, hidden at <=960px (below that they squeezed the jump button under its own label, measured at 800px). Needs you is `GET /blockers?limit=1`'s `page.total`; moving is `GET /moving` (lifecycle, tasks `active` + `confirmed`, owner 2026-10-08), a count in SQL so the page never pulls task rows. Read once per page; a figure that cannot be read is LEFT OUT, never drawn as 0. The warm dot marks needs-you only above zero. It toggles the `hidden` ATTRIBUTE, never `.hidden =` (the nav gate owns that property; `tests/craft_mode_nav.mjs`). Pinned by `tests/craft_mode_tallies.mjs`.
|
|
38
38
|
|
|
39
39
|
## The paintbrush (task 1004497)
|
|
40
40
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/lifecycle/moving.js — how much of the project is MOVING right now, as one
|
|
4
|
+
// count (task 1004563, goal 1000095 WA6).
|
|
5
|
+
//
|
|
6
|
+
// WHY. Governing mode's top bar reads "needs you N · moving M" (the four-mode
|
|
7
|
+
// prototype the owner named the standard on 2026-10-03). "Needs you" already had
|
|
8
|
+
// a source — the public open-blocker list carries its own total — but "moving" had
|
|
9
|
+
// none, and the only way to get one from the browser was GET /tasks?status=…, which
|
|
10
|
+
// returns every row with its whole description to produce a single number. That is
|
|
11
|
+
// the read the hall's bounded-reads rule forbids (modules/hall-ui/CLAUDE.md: a Home
|
|
12
|
+
// card asks the server for what it draws). So the number is counted here, in SQL.
|
|
13
|
+
//
|
|
14
|
+
// WHAT COUNTS (owner decision, 2026-10-08): a task being built right now plus a task
|
|
15
|
+
// waiting to land — status `active` or `confirmed`. Not `completed` (a ship waiting on
|
|
16
|
+
// its grade is neither being built nor queued to land) and nothing terminal. The two
|
|
17
|
+
// halves come back separately as well as summed, so a caller can say which is which.
|
|
18
|
+
//
|
|
19
|
+
// MODULE BOUNDARY: reaches core only through the published doorway.
|
|
20
|
+
|
|
21
|
+
const api = require('../../src/module-api');
|
|
22
|
+
|
|
23
|
+
const MOVING_STATUSES = Object.freeze(['active', 'confirmed']);
|
|
24
|
+
|
|
25
|
+
// → { moving, active, confirmed }. `deps.pool` is the #538 test seam, honoured only
|
|
26
|
+
// under NODE_ENV==='test' like its siblings in this module.
|
|
27
|
+
async function countMoving(deps = {}) {
|
|
28
|
+
const pool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : api.pool;
|
|
29
|
+
const { rows } = await pool.query(
|
|
30
|
+
`SELECT status, count(*)::int AS n
|
|
31
|
+
FROM tasks
|
|
32
|
+
WHERE status = ANY($1::text[])
|
|
33
|
+
GROUP BY status`,
|
|
34
|
+
[MOVING_STATUSES],
|
|
35
|
+
);
|
|
36
|
+
const by = Object.fromEntries(rows.map((r) => [r.status, Number(r.n) || 0]));
|
|
37
|
+
const active = by.active || 0;
|
|
38
|
+
const confirmed = by.confirmed || 0;
|
|
39
|
+
return { moving: active + confirmed, active, confirmed };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The GET /moving handler, injectable so a test drives it with no express.
|
|
43
|
+
function movingHandler({ log, count = countMoving } = {}) {
|
|
44
|
+
return async function movingRoute(_req, res) {
|
|
45
|
+
try {
|
|
46
|
+
res.set('Cache-Control', 'private, max-age=30');
|
|
47
|
+
res.json(await count());
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (log) log.error('GET /moving', err);
|
|
50
|
+
res.fail('moving_failed', { status: 500, message: 'internal error' });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { MOVING_STATUSES, countMoving, movingHandler };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// modules/lifecycle/routes/moving.js — GET /moving, how many tasks are being built or
|
|
4
|
+
// waiting to land (task 1004563, goal 1000095 WA6).
|
|
5
|
+
//
|
|
6
|
+
// WHY ITS OWN ROUTE FILE. routes/tasks.js sits at the 1,500-line ratchet, and the path
|
|
7
|
+
// is top-level `/moving`, never `/tasks/moving`: the manifest mounts `tasks` first and
|
|
8
|
+
// its `/tasks/:id` would swallow a later literal segment (the `/inbox/awaiting-nod`
|
|
9
|
+
// trap). The count and the handler live in ../moving.js so a test drives them with no
|
|
10
|
+
// express and no database.
|
|
11
|
+
//
|
|
12
|
+
// MODULE BOUNDARY: reaches core only through the published doorway
|
|
13
|
+
// `../../../src/module-api`; `../moving` is this module's own read.
|
|
14
|
+
|
|
15
|
+
const express = require('express');
|
|
16
|
+
const api = require('../../../src/module-api');
|
|
17
|
+
const moving = require('../moving');
|
|
18
|
+
|
|
19
|
+
const log = api.logger('lifecycle');
|
|
20
|
+
|
|
21
|
+
module.exports = function buildMovingRouter() {
|
|
22
|
+
const router = express.Router();
|
|
23
|
+
|
|
24
|
+
// rank: any-builder — GET /moving, the count behind Governing mode's "moving" tally.
|
|
25
|
+
// A count of rows every signed-in builder can already list on the Tasks board, so no
|
|
26
|
+
// floor above sign-in; nothing here is writable.
|
|
27
|
+
router.get('/moving', api.requireBuilder, moving.movingHandler({ log }));
|
|
28
|
+
|
|
29
|
+
return router;
|
|
30
|
+
};
|
|
@@ -32,22 +32,6 @@
|
|
|
32
32
|
"wait",
|
|
33
33
|
600
|
|
34
34
|
],
|
|
35
|
-
[
|
|
36
|
-
"click",
|
|
37
|
-
"#termsAck"
|
|
38
|
-
],
|
|
39
|
-
[
|
|
40
|
-
"wait",
|
|
41
|
-
100
|
|
42
|
-
],
|
|
43
|
-
[
|
|
44
|
-
"click",
|
|
45
|
-
"#wizStart"
|
|
46
|
-
],
|
|
47
|
-
[
|
|
48
|
-
"wait",
|
|
49
|
-
400
|
|
50
|
-
],
|
|
51
35
|
[
|
|
52
36
|
"fill",
|
|
53
37
|
"#newName",
|
|
@@ -62,14 +46,6 @@
|
|
|
62
46
|
"#f-desc",
|
|
63
47
|
"A co-op farming game where the seasons never repeat."
|
|
64
48
|
],
|
|
65
|
-
[
|
|
66
|
-
"click",
|
|
67
|
-
"#panel-what button[type=submit]"
|
|
68
|
-
],
|
|
69
|
-
[
|
|
70
|
-
"wait",
|
|
71
|
-
400
|
|
72
|
-
],
|
|
73
49
|
[
|
|
74
50
|
"select",
|
|
75
51
|
"#physCountry",
|
|
@@ -91,13 +67,9 @@
|
|
|
91
67
|
"click",
|
|
92
68
|
"[data-phys=\"entity\"][data-val=\"intent-solo\"]"
|
|
93
69
|
],
|
|
94
|
-
[
|
|
95
|
-
"click",
|
|
96
|
-
"#physNext"
|
|
97
|
-
],
|
|
98
70
|
[
|
|
99
71
|
"wait",
|
|
100
|
-
|
|
72
|
+
300
|
|
101
73
|
],
|
|
102
74
|
[
|
|
103
75
|
"click",
|
|
@@ -135,11 +107,11 @@
|
|
|
135
107
|
],
|
|
136
108
|
[
|
|
137
109
|
"click",
|
|
138
|
-
"#
|
|
110
|
+
"#termsAck"
|
|
139
111
|
],
|
|
140
112
|
[
|
|
141
113
|
"wait",
|
|
142
|
-
|
|
114
|
+
100
|
|
143
115
|
],
|
|
144
116
|
[
|
|
145
117
|
"click",
|
|
@@ -159,12 +131,11 @@
|
|
|
159
131
|
"#panel-done",
|
|
160
132
|
"#wizInvite",
|
|
161
133
|
"#wizInviteWho",
|
|
162
|
-
"#humanSteps
|
|
163
|
-
"#stepPager",
|
|
164
|
-
"#stepNext"
|
|
134
|
+
"#humanSteps"
|
|
165
135
|
],
|
|
166
136
|
"hidden": [
|
|
167
|
-
"#hop"
|
|
137
|
+
"#hop",
|
|
138
|
+
"#wizParts"
|
|
168
139
|
],
|
|
169
140
|
"text": [
|
|
170
141
|
[
|