@bongos/core 1.19.1027 → 1.19.1028
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 +30 -20
- package/docs/copy-inventory.md +206 -198
- package/docs/copy-registry.json +323 -251
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/builders.js +7 -4
- package/modules/public-landing/public/assets/cosmos.css +36 -0
- package/modules/public-landing/public/projects.html +229 -26
- package/modules/ui-design/kit/fixtures/my-projects__invites.json +1 -0
- package/modules/ui-design/kit/serve.js +12 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/card_apply_ui.mjs +8 -1
- package/tests/curve_controls_delegation.mjs +87 -0
- package/tests/my_projects_surface.mjs +26 -14
- package/tests/public_landing_projects.mjs +40 -0
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
// 2. WHETHER ANYTHING LEAKED. No: every /my-projects* route is sign-in-gated
|
|
20
20
|
// and reads the caller's OWN identity off the session. This is the half the
|
|
21
21
|
// audit could not confirm, so it is asserted here rather than described.
|
|
22
|
-
// 3. WHAT
|
|
23
|
-
// surface
|
|
24
|
-
//
|
|
25
|
-
//
|
|
22
|
+
// 3. WHAT WAS STILL MISSING. The invitations half had no caller anywhere — no
|
|
23
|
+
// surface fetched the invite list, so a builder holding an invitation could
|
|
24
|
+
// not act on it. Closed by task 1003774: the hub's /projects?view=mine now
|
|
25
|
+
// reads GET /my-projects/invites (idea 1000903). The pin below flipped from
|
|
26
|
+
// "no surface calls this" to "some surface does" for the same reason.
|
|
26
27
|
//
|
|
27
28
|
// Static reads only (no import of the app, no DB) → runs in the `unit` gate.
|
|
28
29
|
//
|
|
@@ -88,13 +89,11 @@ test('every /my-projects handler reads the caller identity from the session', ()
|
|
|
88
89
|
'no /my-projects handler may take the account id from the request body or query');
|
|
89
90
|
});
|
|
90
91
|
|
|
91
|
-
// ── 3. the gap
|
|
92
|
-
test('
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
// 1000903 own the replacement; this pin exists so the gap stays visible until
|
|
97
|
-
// one of them lands, and so it is impossible to close this quietly.
|
|
92
|
+
// ── 3. the gap is closed — a surface now calls the invite list ──────────────
|
|
93
|
+
test('an invitations surface exists — the hub reads GET /my-projects/invites (task 1003774)', () => {
|
|
94
|
+
// The quoted form is the actual fetch-call shape (`API + '/my-projects/invites'`);
|
|
95
|
+
// matching on that, rather than the bare path, keeps a doc comment that merely
|
|
96
|
+
// NAMES the route from satisfying this on its own.
|
|
98
97
|
const surfaces = [];
|
|
99
98
|
const modules = path.join(ROOT, 'modules');
|
|
100
99
|
for (const key of fs.readdirSync(modules)) {
|
|
@@ -102,11 +101,24 @@ test('the invitations API still has no caller — the tracked gap, pinned', () =
|
|
|
102
101
|
if (!fs.existsSync(pub)) continue;
|
|
103
102
|
for (const f of fs.readdirSync(pub)) {
|
|
104
103
|
if (!/\.(js|html)$/.test(f)) continue;
|
|
105
|
-
if (fs.readFileSync(path.join(pub, f), 'utf8').includes('/my-projects/invites')) {
|
|
104
|
+
if (fs.readFileSync(path.join(pub, f), 'utf8').includes("'/my-projects/invites'")) {
|
|
106
105
|
surfaces.push(`modules/${key}/public/${f}`);
|
|
107
106
|
}
|
|
108
107
|
}
|
|
109
108
|
}
|
|
110
|
-
assert.
|
|
111
|
-
|
|
109
|
+
assert.ok(surfaces.length > 0,
|
|
110
|
+
'no surface calls GET /my-projects/invites — the gap task 1003774 closed is back open');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── 4. the accept flip stays retired (ADR 0335) ─────────────────────────────
|
|
114
|
+
// Task 1003774 restores the invitations list but deliberately does NOT wire an
|
|
115
|
+
// Accept button to POST /my-projects/invites/:id/accept: that hub-only flip
|
|
116
|
+
// never admits anyone into the project's own instance (ADR 0335), so it is a
|
|
117
|
+
// dead end the moment a caller exists to press it. Each invite renders as a
|
|
118
|
+
// way IN (a link to the project) instead — signing in there is the acceptance
|
|
119
|
+
// act (task 1002969).
|
|
120
|
+
test('the hub never calls the accept route — ADR 0335 retired that flip', () => {
|
|
121
|
+
const html = read('modules/public-landing/public/projects.html');
|
|
122
|
+
assert.doesNotMatch(html, /invites\/[^'"]*\/accept/,
|
|
123
|
+
'a hub caller of the accept route reopens the dead end ADR 0335 closed — render the invite as a link into the project instead');
|
|
112
124
|
});
|
|
@@ -508,3 +508,43 @@ test('a stamped asset still serves — the query string is not part of the filen
|
|
|
508
508
|
assert.equal(res.status, 200, 'express.static must ignore the ?v= when resolving the file');
|
|
509
509
|
assert.match(res.body, /\.shell\{/, 'and it must be the real stylesheet');
|
|
510
510
|
});
|
|
511
|
+
|
|
512
|
+
// ── #view-mine: invitations, projects-you-build-on, join-by-URL (task 1003774) ──
|
|
513
|
+
// The hall's my-projects page was deleted in task 1003308 on the owner's own
|
|
514
|
+
// decision that this is a hub surface; these three reads/actions were left with
|
|
515
|
+
// nowhere to live until now. loadMemberships() already fetched GET /my-projects
|
|
516
|
+
// for the map's Apply button (R10, task 1002286) — this just renders the rest
|
|
517
|
+
// of what that same read carries, plus the separate invites list.
|
|
518
|
+
test('#view-mine reads the invitations list, alongside the building-on and join surfaces', async () => {
|
|
519
|
+
const { body } = await request('/projects', 'game.test.local');
|
|
520
|
+
assert.match(body, /\/my-projects\/invites/, 'GET /my-projects/invites must be called');
|
|
521
|
+
assert.match(body, /id="invitesList"/);
|
|
522
|
+
assert.match(body, /id="buildingList"/);
|
|
523
|
+
assert.match(body, /\/my-projects\/join/, 'the join-by-URL field must post to /my-projects/join');
|
|
524
|
+
assert.match(body, /id="joinOrigin"/);
|
|
525
|
+
assert.match(body, /id="joinNote"/, 'the applicant note (task 1002330) rides along, and stays optional');
|
|
526
|
+
assert.match(body, /\/my-projects\/'\s*\+\s*encodeURIComponent/, 'Leave must address the project by its own client id');
|
|
527
|
+
assert.match(body, /origin = 'https:\/\/' \+ origin/,
|
|
528
|
+
'a bare host ("grove.cloudbongos.com") must be assumed https:// rather than sent as-is — the server\'s origin lookup is an exact match');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test('an invite renders as a way INTO the project, never the retired accept flip (ADR 0335)', async () => {
|
|
532
|
+
const { body } = await request('/projects', 'game.test.local');
|
|
533
|
+
// the hub-only accept flip never admits anyone to the project's own instance
|
|
534
|
+
// (ADR 0335) — a caller of it here would reopen the exact dead end that ADR
|
|
535
|
+
// retired, so the invite's action is an outbound link instead
|
|
536
|
+
assert.doesNotMatch(body, /invites\/[^'"]*\/accept/, 'no caller of the retired hub accept route');
|
|
537
|
+
assert.match(body, /Open project/, 'the invite links into the project — signing in there is the acceptance act');
|
|
538
|
+
assert.match(body, /'\/my-projects\/invites\/' \+ encodeURIComponent\([^)]+\) \+ '\/decline'/,
|
|
539
|
+
'decline still works: it only clears the hub\'s own pending row');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test('the map card\'s Apply button reads as truly inert once applied, with a live status region (R10 fixups)', async () => {
|
|
543
|
+
const page = await request('/projects', 'game.test.local');
|
|
544
|
+
assert.match(page.body, /id="cardApplyMsg" role="status" aria-live="polite"/, 'the apply outcome must be announced to assistive tech');
|
|
545
|
+
const stamped = (page.body.match(/\/assets\/cosmos\.css\?v=[0-9a-f]+/) || [])[0];
|
|
546
|
+
assert.ok(stamped, 'need a stamped URL to fetch');
|
|
547
|
+
const css = await request(stamped);
|
|
548
|
+
assert.match(css.body, /\.btn\.ghost:disabled\{/, 'a disabled ghost button (the Applied state) must not keep full-strength styling');
|
|
549
|
+
assert.match(css.body, /\.projCard \.cardActions \.projMsg\{flex-basis:100%;?\}/, 'the outcome must sit under the buttons, not beside them');
|
|
550
|
+
});
|