@bongos/core 1.19.429 → 1.19.431
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 +71 -51
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +6 -3
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md +116 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +74 -3
- package/docs/api-reference.md +4 -3
- package/docs/copy-inventory.md +162 -154
- package/docs/copy-registry.json +271 -198
- package/docs/design/mocks/hub/picker.html +421 -0
- package/docs/design/projects-hub-direction-v2.md +22 -0
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +4 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/settings.js +17 -8
- package/modules/platform-identity/account-visibility.js +47 -0
- package/modules/platform-identity/platform-identity.js +4 -4
- package/modules/platform-identity/recruiter-sliver.js +197 -0
- package/modules/platform-identity/routes/projects.js +24 -0
- package/modules/platform-identity/routes/scouting.js +58 -0
- package/modules/provisioning/module.json +1 -1
- package/modules/provisioning/provisioning.js +63 -2
- package/modules/provisioning/routes/provisioning.js +28 -6
- package/modules/provisioning/tests/provisioning.mjs +34 -0
- package/modules/ui-design/kit/fixtures/featured-hub.json +1 -1
- package/modules/ui-design/kit/fixtures/featured.json +1 -1
- package/modules/ui-design/kit/fixtures/provisioning-instances.json +1 -1
- package/modules/ui-design/kit/serve.js +28 -4
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +37 -0
- package/public-landing/projects.html +164 -5
- package/public-landing/projects.probes.json +12 -0
- package/public-landing/projects.states.json +3 -2
- package/src/module-api.js +1 -1
- package/tests/account_privacy_flags.mjs +115 -9
- package/tests/effective_visibility_predicate.mjs +58 -1
- package/tests/government_require_permission.mjs +22 -0
- package/tests/profile_route.mjs +6 -0
- package/tests/projects_hub.mjs +122 -7
- package/tests/provisioning_settings.mjs +81 -2
- package/tests/provisioning_settings_env.mjs +6 -0
- package/tests/recruiter_sliver_boundary.mjs +841 -0
- package/tests/scouting_owner_access_and_reach.mjs +4 -0
|
@@ -330,6 +330,40 @@ t('projectDetail: a stored value that has since LEFT the vocabulary never leaks
|
|
|
330
330
|
assert.deepEqual(P.projectDetail(null), {});
|
|
331
331
|
});
|
|
332
332
|
|
|
333
|
+
t('the planet (task 1003347): PLANET_TEMPLATES mirrors platform-identity\'s PROJECT_TEMPLATES name for name; the accents are the world\'s five token names', () => {
|
|
334
|
+
const pi = require('../../platform-identity/platform-identity.js');
|
|
335
|
+
assert.deepEqual([...P.PLANET_TEMPLATES], [...pi.PROJECT_TEMPLATES], 'the two modules never import each other — the mirror is pinned instead (the PROJECT_TYPES precedent)');
|
|
336
|
+
assert.equal(P.PLANET_TEMPLATES.length, 36);
|
|
337
|
+
assert.deepEqual([...P.PLANET_ACCENTS], ['sea', 'terracotta', 'gold-soft', 'sun-lit', 'accent']);
|
|
338
|
+
for (const a of P.PLANET_ACCENTS) assert.doesNotMatch(a, /^#|rgb|hsl/, 'a name, never a colour (the Fifteen Rule)');
|
|
339
|
+
assert.deepEqual(Object.keys(P.PLANET_VOCAB), ['planet_template', 'planet_accent']);
|
|
340
|
+
// the deliberate asymmetry: the planet is NOT a setting — no default, never in SETTINGS_VOCAB
|
|
341
|
+
for (const k of Object.keys(P.PLANET_VOCAB)) {
|
|
342
|
+
assert.ok(!(k in P.SETTINGS_VOCAB), `${k} is not a policy the instance obeys`);
|
|
343
|
+
assert.ok(!(k in P.DEFAULT_SETTINGS), `${k} has no default — null is the platform's own pick`);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
t('planetChoice: null for a bare row or an unset key; a stored on-vocabulary value reads back; junk reads null, never leaks', () => {
|
|
348
|
+
assert.deepEqual(P.planetChoice(null), { planet_template: null, planet_accent: null });
|
|
349
|
+
assert.deepEqual(P.planetChoice({}), { planet_template: null, planet_accent: null });
|
|
350
|
+
assert.deepEqual(P.planetChoice({ settings: { platform_visibility: 'gated' } }), { planet_template: null, planet_accent: null });
|
|
351
|
+
assert.deepEqual(P.planetChoice({ settings: { planet_template: 'pair-frost-tint', planet_accent: 'sun-lit' } }), { planet_template: 'pair-frost-tint', planet_accent: 'sun-lit' });
|
|
352
|
+
assert.deepEqual(P.planetChoice({ settings: { planet_template: 'cube', planet_accent: '#fff' } }), { planet_template: null, planet_accent: null });
|
|
353
|
+
assert.deepEqual(P.planetChoice({ settings: ['nope'] }), { planet_template: null, planet_accent: null });
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
t('publicInstance carries the planet — both keys, null when unset — so the list and the manage page read it off the row', () => {
|
|
357
|
+
const chosen = P.publicInstance({ id: 1, slug: 'x', status: 'active', settings: { planet_template: 'bead-gold-tint', planet_accent: 'sea' } });
|
|
358
|
+
assert.equal(chosen.planet_template, 'bead-gold-tint');
|
|
359
|
+
assert.equal(chosen.planet_accent, 'sea');
|
|
360
|
+
const unset = P.publicInstance({ id: 2, slug: 'y', status: 'active' });
|
|
361
|
+
assert.ok('planet_template' in unset && 'planet_accent' in unset, 'the keys are always present');
|
|
362
|
+
assert.equal(unset.planet_template, null);
|
|
363
|
+
assert.equal(unset.planet_accent, null);
|
|
364
|
+
assert.ok(!('settings' in unset), 'the raw jsonb never rides the projection');
|
|
365
|
+
});
|
|
366
|
+
|
|
333
367
|
t('PROJECT_DETAIL_VOCAB carries NO defaults — an unanswered question stays unanswered', () => {
|
|
334
368
|
// The deliberate asymmetry with SETTINGS_VOCAB: a setting is a policy the
|
|
335
369
|
// instance must obey, so it needs a default; a detail answer is something the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"_":"The hub's catalog (task 1003345): the landing's featured.json plus Tidewater, an all-null row nobody owns — the card-floor state opens ITS card, because Demo is owned by the signed-in stub (provisioning-instances.json) and an owned orb's plain click opens the manage view, never the card. Served under the name \"featured\" by projects.states.json's stub.fixtures alias; the landing keeps featured.json.","projects":[{"origin":"https://amazonprimea.com","name":"Off the Boats","tagline":"A singular shop, and a broader videogame in a persistent ancient Greek world that collides with modern day commerce & products.","art_url":"/assets/world/r2-chrome-blob-one.jpg","type":"game","template":"bead-wash","created_at":"2026-07-15T22:12:57.732Z","builders":11,"tags":["art-pipeline","character-anim","demo","game"],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":["https://avatars.githubusercontent.com/u/283020391?v=4","https://avatars.githubusercontent.com/u/64553469?v=4","https://avatars.githubusercontent.com/u/290184380?v=4","https://avatars.githubusercontent.com/u/295990394?v=4","https://avatars.githubusercontent.com/u/35337514?v=4"],"shipped_30d":0,"shipped_30d_capped":false,"hall_origin":"https://builders.amazonprimea.com"},{"origin":"https://demo.cloudbongos.com","name":"Demo","tagline":"A live demo instance on Cloud Bongos.","art_url":null,"type":null,"template":"pair-gold-wash","created_at":"2026-07-14T19:16:38.160Z","builders":null,"tags":null,"owner":null,"owner_avatar":null,"faces":null,"shipped_30d":null,"shipped_30d_capped":null,"hall_origin":null},{"origin":"https://emersonian-circles.cloudbongos.com","name":"Emersonian Circles","tagline":"An independent project built on Cloud Bongos.","art_url":null,"type":null,"template":null,"created_at":"2026-07-25T02:21:55.641Z","builders":1,"tags":[],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":[],"shipped_30d":30,"shipped_30d_capped":true,"hall_origin":null},{"origin":"https://tidewater.cloudbongos.com","name":"Tidewater","tagline":"A project that has declared nothing yet.","art_url":null,"type":null,"template":null,"created_at":"2026-07-20T08:00:00.000Z","builders":null,"tags":null,"owner":null,"owner_avatar":null,"faces":null,"shipped_30d":null,"shipped_30d_capped":null,"hall_origin":null}]}
|
|
1
|
+
{"_":"The hub's catalog (task 1003345): the landing's featured.json plus Tidewater, an all-null row nobody owns — the card-floor state opens ITS card, because Demo is owned by the signed-in stub (provisioning-instances.json) and an owned orb's plain click opens the manage view, never the card. Served under the name \"featured\" by projects.states.json's stub.fixtures alias; the landing keeps featured.json.","projects":[{"origin":"https://amazonprimea.com","name":"Off the Boats","tagline":"A singular shop, and a broader videogame in a persistent ancient Greek world that collides with modern day commerce & products.","art_url":"/assets/world/r2-chrome-blob-one.jpg","type":"game","template":"bead-wash","planet_template":null,"planet_accent":null,"created_at":"2026-07-15T22:12:57.732Z","builders":11,"tags":["art-pipeline","character-anim","demo","game"],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":["https://avatars.githubusercontent.com/u/283020391?v=4","https://avatars.githubusercontent.com/u/64553469?v=4","https://avatars.githubusercontent.com/u/290184380?v=4","https://avatars.githubusercontent.com/u/295990394?v=4","https://avatars.githubusercontent.com/u/35337514?v=4"],"shipped_30d":0,"shipped_30d_capped":false,"hall_origin":"https://builders.amazonprimea.com"},{"origin":"https://demo.cloudbongos.com","name":"Demo","tagline":"A live demo instance on Cloud Bongos.","art_url":null,"type":null,"template":"pair-gold-wash","planet_template":"pair-obsidian-tint","planet_accent":"sea","created_at":"2026-07-14T19:16:38.160Z","builders":null,"tags":null,"owner":null,"owner_avatar":null,"faces":null,"shipped_30d":null,"shipped_30d_capped":null,"hall_origin":null},{"origin":"https://emersonian-circles.cloudbongos.com","name":"Emersonian Circles","tagline":"An independent project built on Cloud Bongos.","art_url":null,"type":null,"template":null,"planet_template":null,"planet_accent":null,"created_at":"2026-07-25T02:21:55.641Z","builders":1,"tags":[],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":[],"shipped_30d":30,"shipped_30d_capped":true,"hall_origin":null},{"origin":"https://tidewater.cloudbongos.com","name":"Tidewater","tagline":"A project that has declared nothing yet.","art_url":null,"type":null,"template":null,"planet_template":null,"planet_accent":null,"created_at":"2026-07-20T08:00:00.000Z","builders":null,"tags":null,"owner":null,"owner_avatar":null,"faces":null,"shipped_30d":null,"shipped_30d_capped":null,"hall_origin":null}]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"projects":[{"origin":"https://amazonprimea.com","name":"Off the Boats","tagline":"A singular shop, and a broader videogame in a persistent ancient Greek world that collides with modern day commerce & products.","art_url":"/assets/world/r2-chrome-blob-one.jpg","type":"game","template":"bead-wash","created_at":"2026-07-15T22:12:57.732Z","builders":11,"tags":["art-pipeline","character-anim","demo","game"],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":["https://avatars.githubusercontent.com/u/283020391?v=4","https://avatars.githubusercontent.com/u/64553469?v=4","https://avatars.githubusercontent.com/u/290184380?v=4","https://avatars.githubusercontent.com/u/295990394?v=4","https://avatars.githubusercontent.com/u/35337514?v=4"],"shipped_30d":0,"shipped_30d_capped":false,"hall_origin":"https://builders.amazonprimea.com"},{"origin":"https://demo.cloudbongos.com","name":"Demo","tagline":"A live demo instance on Cloud Bongos.","art_url":null,"type":null,"template":"pair-gold-wash","created_at":"2026-07-14T19:16:38.160Z","builders":null,"tags":null,"owner":null,"owner_avatar":null,"faces":null,"shipped_30d":null,"shipped_30d_capped":null,"hall_origin":null},{"origin":"https://emersonian-circles.cloudbongos.com","name":"Emersonian Circles","tagline":"An independent project built on Cloud Bongos.","art_url":null,"type":null,"template":null,"created_at":"2026-07-25T02:21:55.641Z","builders":1,"tags":[],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":[],"shipped_30d":30,"shipped_30d_capped":true,"hall_origin":null}]}
|
|
1
|
+
{"projects":[{"origin":"https://amazonprimea.com","name":"Off the Boats","tagline":"A singular shop, and a broader videogame in a persistent ancient Greek world that collides with modern day commerce & products.","art_url":"/assets/world/r2-chrome-blob-one.jpg","type":"game","template":"bead-wash","planet_template":null,"planet_accent":null,"created_at":"2026-07-15T22:12:57.732Z","builders":11,"tags":["art-pipeline","character-anim","demo","game"],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":["https://avatars.githubusercontent.com/u/283020391?v=4","https://avatars.githubusercontent.com/u/64553469?v=4","https://avatars.githubusercontent.com/u/290184380?v=4","https://avatars.githubusercontent.com/u/295990394?v=4","https://avatars.githubusercontent.com/u/35337514?v=4"],"shipped_30d":0,"shipped_30d_capped":false,"hall_origin":"https://builders.amazonprimea.com"},{"origin":"https://demo.cloudbongos.com","name":"Demo","tagline":"A live demo instance on Cloud Bongos.","art_url":null,"type":null,"template":"pair-gold-wash","planet_template":"pair-obsidian-tint","planet_accent":"sea","created_at":"2026-07-14T19:16:38.160Z","builders":null,"tags":null,"owner":null,"owner_avatar":null,"faces":null,"shipped_30d":null,"shipped_30d_capped":null,"hall_origin":null},{"origin":"https://emersonian-circles.cloudbongos.com","name":"Emersonian Circles","tagline":"An independent project built on Cloud Bongos.","art_url":null,"type":null,"template":null,"planet_template":null,"planet_accent":null,"created_at":"2026-07-25T02:21:55.641Z","builders":1,"tags":[],"owner":"lars589","owner_avatar":"https://avatars.githubusercontent.com/u/283020391?v=4","faces":[],"shipped_30d":30,"shipped_30d_capped":true,"hall_origin":null}]}
|
|
@@ -133,11 +133,33 @@ const FLOW_RECEIPTS = {
|
|
|
133
133
|
// after a receipt (doDelete → loadManage), and what it must then see is the queued intent —
|
|
134
134
|
// the same open_intent shape the route stamps — not the untouched fixture row
|
|
135
135
|
const ASKED = new Map();
|
|
136
|
+
// the planet picker (task 1003347): a settings PATCH that carries planet_* is REMEMBERED for
|
|
137
|
+
// the run — the page repaints from the row it re-reads, so the row must carry the choice; a
|
|
138
|
+
// PATCH with only planet keys gets the route's no-restart receipt, a policy key the push
|
|
139
|
+
const PLANETS = new Map();
|
|
140
|
+
const PLANET_KEYS = ['planet_template', 'planet_accent'];
|
|
141
|
+
const NO_PLANET = { planet_template: null, planet_accent: null };
|
|
142
|
+
function readBody(req, cb) {
|
|
143
|
+
let d = '';
|
|
144
|
+
req.on('data', (c) => { d += c; });
|
|
145
|
+
req.on('end', () => { let b = {}; try { b = JSON.parse(d || '{}'); } catch { b = {}; } cb(b); });
|
|
146
|
+
}
|
|
147
|
+
function settingsPatch(res, id, body) {
|
|
148
|
+
if (PLANET_KEYS.some((k) => k in body)) PLANETS.set(String(id), { planet_template: body.planet_template || null, planet_accent: body.planet_accent || null });
|
|
149
|
+
const policy = ['platform_visibility', 'joinability'].some((k) => k in body);
|
|
150
|
+
const push = policy ? FLOW_RECEIPTS.settings.push
|
|
151
|
+
: { queued: false, restart: false, message: 'Saved. The sky, your list and the card draw it this way from here; nothing on your project restarts for it.' };
|
|
152
|
+
return json(res, 200, { ok: true, saved: FLOW_RECEIPTS.settings.settings, planet: PLANETS.get(String(id)) || NO_PLANET, push });
|
|
153
|
+
}
|
|
154
|
+
// the row carries its planet: a saved pick for the run, else the fixture's own (or null)
|
|
155
|
+
function withPlanet(inst) {
|
|
156
|
+
const saved = PLANETS.get(String(inst.id));
|
|
157
|
+
return { ...inst, planet_template: inst.planet_template || null, planet_accent: inst.planet_accent || null, ...(saved || {}) };
|
|
158
|
+
}
|
|
136
159
|
function flowReceipt(rest, method) {
|
|
137
160
|
if (method === 'POST' && rest === '/provisioning/instances') return FLOW_RECEIPTS.provision;
|
|
138
|
-
const m = rest.match(/^\/provisioning\/instances\/(\d+)\/(teardown|restart|domain
|
|
161
|
+
const m = rest.match(/^\/provisioning\/instances\/(\d+)\/(teardown|restart|domain)$/);
|
|
139
162
|
if (!m) return null;
|
|
140
|
-
if (m[2] === 'settings') return method === 'PATCH' ? FLOW_RECEIPTS.settings : null;
|
|
141
163
|
if (method !== 'POST') return null;
|
|
142
164
|
if (m[2] === 'teardown' || m[2] === 'restart') ASKED.set(m[1], m[2]);
|
|
143
165
|
return FLOW_RECEIPTS[m[2]];
|
|
@@ -151,11 +173,11 @@ function instances(res, id, settings) {
|
|
|
151
173
|
if (FAIL.has(name)) return json(res, 500, { error: { code: 'stub_failure', message: `FAIL=${name}` } });
|
|
152
174
|
const file = path.join(FIXTURES, (ALIAS[name] || name) + '.json');
|
|
153
175
|
if (!fs.existsSync(file)) return json(res, 404, { error: { code: 'no_fixture', message: name } });
|
|
154
|
-
const list = stamp((JSON.parse(fs.readFileSync(file, 'utf8')).instances || []).map(withAsk));
|
|
176
|
+
const list = stamp((JSON.parse(fs.readFileSync(file, 'utf8')).instances || []).map(withAsk).map(withPlanet));
|
|
155
177
|
if (id === null) return json(res, 200, { instances: list });
|
|
156
178
|
const inst = list.find((i) => String(i.id) === String(id));
|
|
157
179
|
if (!inst) return json(res, 404, { error: { code: 'not_found', message: 'no such instance' } });
|
|
158
|
-
if (settings) return json(res, 200, { settings: { platform_visibility: 'public', joinability: 'open' }, options: { platform_visibility: ['public', 'gated'], joinability: ['open', 'apply', 'invite_only'] } });
|
|
180
|
+
if (settings) return json(res, 200, { settings: { platform_visibility: 'public', joinability: 'open' }, planet: PLANETS.get(String(id)) || NO_PLANET, options: { platform_visibility: ['public', 'gated'], joinability: ['open', 'apply', 'invite_only'] } });
|
|
159
181
|
return json(res, 200, { instance: inst, open_intent: inst.open_intent || null });
|
|
160
182
|
}
|
|
161
183
|
// A project's art in the featured fixture may be root-relative (the surface's
|
|
@@ -215,6 +237,8 @@ const server = http.createServer((req, res) => {
|
|
|
215
237
|
// (ADR 0111) — the stub answers each ask with the route's receipt so the probes can drive
|
|
216
238
|
// attach / restart / teardown / revive / join end to end; nothing changes in the fixture
|
|
217
239
|
if (req.method !== 'GET') {
|
|
240
|
+
const ps = rest.match(/^\/provisioning\/instances\/(\d+)\/settings$/);
|
|
241
|
+
if (ps && req.method === 'PATCH') return AUTH ? readBody(req, (body) => settingsPatch(res, ps[1], body)) : json(res, 401, UNAUTH);
|
|
218
242
|
const flow = flowReceipt(rest, req.method);
|
|
219
243
|
if (flow) return AUTH ? json(res, 200, flow) : json(res, 401, UNAUTH);
|
|
220
244
|
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.431",
|
|
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.431",
|
|
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.431",
|
|
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",
|
|
@@ -1357,6 +1357,29 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
1357
1357
|
#view-manage .visOpt:has(input:checked) b::before{background:var(--accent);border-color:var(--accent);outline:4px solid color-mix(in srgb,var(--accent) 22%,transparent);}
|
|
1358
1358
|
#view-manage .visOpt:focus-within{outline:2px solid var(--accent);outline-offset:3px;}
|
|
1359
1359
|
#view-manage #mVisRuns b,#view-manage #mJoinRuns b{color:var(--ok);font-weight:500;}
|
|
1360
|
+
/* ── its planet (task 1003347; direction record §16, v1 the bands): the preview
|
|
1361
|
+
planet left; four bands right — shape · material · style as hairline cells of
|
|
1362
|
+
one band, a word each, the chosen one carrying the dot; the colour as five
|
|
1363
|
+
swatches, the chosen one ringed by a hairline of the ink. The cells outrank
|
|
1364
|
+
the section's generic radiogroup rule by their class. ── */
|
|
1365
|
+
#view-manage .pick{display:grid;grid-template-columns:168px 1fr;gap:0 34px;align-items:start;}
|
|
1366
|
+
#view-manage .pickPreview{display:grid;gap:12px;justify-items:center;padding-top:6px;}
|
|
1367
|
+
#view-manage .pickWords{margin:0;font-size:14px;font-weight:400;line-height:1.45;color:var(--ink);text-align:center;max-width:168px;}
|
|
1368
|
+
#view-manage .pickWords small{display:block;font-size:12.5px;color:var(--ink-soft);}
|
|
1369
|
+
#view-manage .pickBands{display:grid;border-bottom:1px solid var(--rule);}
|
|
1370
|
+
#view-manage .pickBand{display:grid;grid-template-columns:88px 1fr;gap:0 18px;align-items:center;border-top:1px solid var(--rule);}
|
|
1371
|
+
#view-manage .pickLabel{font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);padding:14px 0;}
|
|
1372
|
+
#view-manage .pickCells[role="radiogroup"]{display:grid;grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:1fr;border:0;}
|
|
1373
|
+
#view-manage .pickCell{position:relative;display:flex;align-items:center;gap:10px;min-height:48px;margin:0;padding:10px 16px;border:0;border-left:1px solid var(--rule-soft);border-radius:0;cursor:pointer;font-family:var(--font-body);font-weight:400;font-size:14.5px;color:var(--ink-soft);}
|
|
1374
|
+
#view-manage .pickCell::before{content:"";width:7px;height:7px;border-radius:50%;border:1px solid var(--rule);background:var(--bg);flex:none;}
|
|
1375
|
+
#view-manage .pickCell:hover,#view-manage .pickCell.on{color:var(--ink);}
|
|
1376
|
+
#view-manage .pickCell.on::before{background:var(--accent);border-color:var(--accent);outline:4px solid color-mix(in srgb,var(--accent) 22%,transparent);}
|
|
1377
|
+
#view-manage .pickCell input,#view-manage .pickSw input,#view-manage .pickSw span{position:absolute;width:1px;height:1px;margin:0;overflow:hidden;clip-path:inset(50%);}
|
|
1378
|
+
#view-manage .pickCell:focus-within,#view-manage .pickSw:focus-within{outline:2px solid var(--accent);outline-offset:3px;}
|
|
1379
|
+
#view-manage .pickSwatches[role="radiogroup"]{display:flex;gap:14px;align-items:center;padding:12px 0 12px 16px;border:0;border-left:1px solid var(--rule-soft);}
|
|
1380
|
+
#view-manage .pickSw{position:relative;display:block;width:28px;height:28px;margin:0;padding:0;border-radius:50%;border:1px solid var(--rule);background:var(--sw);cursor:pointer;}
|
|
1381
|
+
#view-manage .pickSw.on{outline:1px solid var(--ink);outline-offset:3px;}
|
|
1382
|
+
#view-manage #mPick .actions{gap:14px;align-items:center;}
|
|
1360
1383
|
|
|
1361
1384
|
/* this block stays HERE, after the rules it narrows: a same-specificity mobile
|
|
1362
1385
|
override only wins by source order, so folding it into the hub's earlier
|
|
@@ -1382,6 +1405,20 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
1382
1405
|
#view-manage [role="radiogroup"]{grid-template-columns:1fr;}
|
|
1383
1406
|
#view-manage .visOpt{border-left:0;padding-left:0;border-top:1px solid var(--rule-soft);}
|
|
1384
1407
|
#view-manage .visOpt:first-child{border-top:0;}
|
|
1408
|
+
/* the picker stacks: the preview above; each band's label above its choices; the
|
|
1409
|
+
cells become the list's filter pills (the chosen one inverted to ink), so the
|
|
1410
|
+
four bands stay one screen */
|
|
1411
|
+
#view-manage .pick{grid-template-columns:1fr;gap:22px 0;}
|
|
1412
|
+
#view-manage .pickPreview{justify-items:start;}
|
|
1413
|
+
#view-manage .pickWords{text-align:left;max-width:none;}
|
|
1414
|
+
#view-manage .pickBand{grid-template-columns:1fr;gap:0;}
|
|
1415
|
+
#view-manage .pickLabel{padding:14px 0 6px;}
|
|
1416
|
+
#view-manage .pickCells[role="radiogroup"]{display:flex;flex-wrap:wrap;gap:8px;padding:0 0 14px;}
|
|
1417
|
+
#view-manage .pickCell{min-height:36px;padding:0 14px;border:1px solid var(--rule);border-radius:var(--radius-pill);gap:0;font-size:14px;}
|
|
1418
|
+
#view-manage .pickCell::before{display:none;}
|
|
1419
|
+
#view-manage .pickCell.on{background:var(--ink);color:var(--bg);border-color:var(--ink);}
|
|
1420
|
+
#view-manage .pickSwatches[role="radiogroup"]{padding:6px 0 14px;border-left:0;}
|
|
1421
|
+
#view-manage #mPick .actions{justify-content:flex-start;}
|
|
1385
1422
|
}
|
|
1386
1423
|
@media (prefers-reduced-motion:reduce){
|
|
1387
1424
|
*,*::before,*::after{animation:none !important;transition:none !important;}
|
|
@@ -744,6 +744,22 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
744
744
|
</details>
|
|
745
745
|
</div>
|
|
746
746
|
|
|
747
|
+
<!-- the planet picker (task 1003347; direction record §16, v1 the bands): the
|
|
748
|
+
owner's own choice of how the platform draws this project — the sky,
|
|
749
|
+
the list, this page's head and the card read the same row. The pick is
|
|
750
|
+
a settings save with a receipt, like the two above; unlike them it
|
|
751
|
+
reaches no instance and restarts nothing (the planet is the platform's
|
|
752
|
+
to draw). The fold is a SIBLING of the volatile slot so no re-render
|
|
753
|
+
can drop it. -->
|
|
754
|
+
<div class="card mSec" id="mPlanetPick">
|
|
755
|
+
<h2>Its planet</h2>
|
|
756
|
+
<div id="mPick"><p class="finePrint">Reading the project…</p></div>
|
|
757
|
+
<details class="finePrint" id="mPickWhat">
|
|
758
|
+
<summary>What the choice changes</summary>
|
|
759
|
+
<p>How this project is drawn: on the sky, in your list, at the top of this page and on its card. A planet is a <b>shape</b> (the blob, the bongo pair, the bead), a <b>material</b> (chrome, frost, gold, obsidian), a <b>style</b> (tinted: the colour replaces the material’s own; washed: the material lit by the colour; plain: as it is) and a <b>colour</b>, one of the world’s five. Until you choose, the platform picks one for it. Saving here changes nothing on the project itself: no restart, nothing to apply.</p>
|
|
760
|
+
</details>
|
|
761
|
+
</div>
|
|
762
|
+
|
|
747
763
|
<!-- live since task 1003042: the teardown control, for a project in ANY
|
|
748
764
|
state that still holds something. The server used to refuse a
|
|
749
765
|
'requested' or an 'error' project outright — and force-teardown reused
|
|
@@ -1300,6 +1316,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1300
1316
|
were orphans). Keeping the per-project variety is what makes a sky legible;
|
|
1301
1317
|
deriving it from contract tokens is what makes it survive a re-brand. */
|
|
1302
1318
|
var ORB_ACCENTS = ['var(--sea)', 'var(--terracotta)', 'var(--gold-soft)', 'var(--sun-lit)', 'var(--accent)'];
|
|
1319
|
+
/* the five BY NAME — what an owner's choice stores (task 1003347: a token name,
|
|
1320
|
+
never a colour), in the same order, so a name maps to its var() */
|
|
1321
|
+
var ORB_ACCENT_NAMES = ['sea', 'terracotta', 'gold-soft', 'sun-lit', 'accent'];
|
|
1322
|
+
function accentVar(name) { var i = ORB_ACCENT_NAMES.indexOf(name); return i === -1 ? '' : ORB_ACCENTS[i]; }
|
|
1323
|
+
/* a template name the sky knows, or '' — an unknown name is not a choice */
|
|
1324
|
+
function knownTemplate(name) { return ORB_TEMPLATES.indexOf(name) !== -1 ? name : ''; }
|
|
1303
1325
|
/* THE PLANET TEMPLATES (task 1003319; the materials, task 1003337). A project's
|
|
1304
1326
|
orb is one of the world's three whole objects (blob / pair / bead) in one of
|
|
1305
1327
|
four materials (chrome — the first round, named by omission — frost, gold,
|
|
@@ -1357,9 +1379,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1357
1379
|
p._tpl = ORB_WALK[i % ORB_WALK.length];
|
|
1358
1380
|
});
|
|
1359
1381
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1382
|
+
/* the owner's own choice first (task 1003347: planet_accent / planet_template ride
|
|
1383
|
+
the featured feed and the owned row alike), then the walk */
|
|
1384
|
+
function accentOf(p) { return accentVar(p.planet_accent) || p._acc || ORB_ACCENTS[0]; }
|
|
1385
|
+
/* the owner's choice wins, then a curated template, then the walk; an unknown name is not a choice */
|
|
1362
1386
|
function templateOf(p) {
|
|
1387
|
+
if (knownTemplate(p.planet_template)) return p.planet_template;
|
|
1363
1388
|
return ORB_TEMPLATES.indexOf(p.template) !== -1 ? p.template : (p._tpl || ORB_WALK[0]);
|
|
1364
1389
|
}
|
|
1365
1390
|
/* the manage cards' accent rule — one project, one colour, in the sky AND
|
|
@@ -4270,13 +4295,19 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4270
4295
|
by its slug, the rule the map's appended entries use — so one project is
|
|
4271
4296
|
one object in both views and never re-colours between them. ── */
|
|
4272
4297
|
function planetOf(inst) {
|
|
4298
|
+
/* the owner's own choice (task 1003347) rides the row itself, so it wins in
|
|
4299
|
+
every view before the catalog or the walk is asked; the sky reads the same
|
|
4300
|
+
choice off the featured feed (templateOf / accentOf), so the two agree */
|
|
4301
|
+
var own = { tpl: knownTemplate(inst.planet_template), acc: accentVar(inst.planet_accent) };
|
|
4273
4302
|
var h = inst.domain ? hostOf(inst.domain) : '';
|
|
4274
4303
|
for (var i = 0; h && i < catalog.length; i++) {
|
|
4275
4304
|
if (hostOf(catalog[i].origin) === h) {
|
|
4276
|
-
|
|
4305
|
+
var c = catalog[i];
|
|
4306
|
+
return { tpl: own.tpl || templateOf(c), acc: own.acc || accentOf(c), curated: ORB_TEMPLATES.indexOf(c.template) !== -1,
|
|
4307
|
+
chosen: !!(own.tpl || own.acc || knownTemplate(c.planet_template) || accentVar(c.planet_accent)) };
|
|
4277
4308
|
}
|
|
4278
4309
|
}
|
|
4279
|
-
return { tpl: slugTemplate(inst.slug), acc: ORB_ACCENTS[slugAccent(inst.slug)], curated: false };
|
|
4310
|
+
return { tpl: own.tpl || slugTemplate(inst.slug), acc: own.acc || ORB_ACCENTS[slugAccent(inst.slug)], curated: false, chosen: !!(own.tpl || own.acc) };
|
|
4280
4311
|
}
|
|
4281
4312
|
/* shape[-material]-style → the data attributes cosmos.css draws from (the map's rule) */
|
|
4282
4313
|
function planetAttrs(pl) {
|
|
@@ -4299,6 +4330,10 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4299
4330
|
function repaintPlanets() {
|
|
4300
4331
|
Object.keys(byId).forEach(function (id) { renderCard(byId[id]); });
|
|
4301
4332
|
if (manageInst) paintManagePlanet(manageInst);
|
|
4333
|
+
/* the picker may have seeded from the walk before the catalog landed: reseed it
|
|
4334
|
+
only if the resolution actually changed — the owner's pending pick, and a
|
|
4335
|
+
receipt just shown, stand otherwise */
|
|
4336
|
+
if (manageInst && !mPickTouched && JSON.stringify(pickFrom(planetOf(manageInst))) !== mPickSeed) { mPick = null; renderPick(manageInst); }
|
|
4302
4337
|
}
|
|
4303
4338
|
|
|
4304
4339
|
/* ── the row's object: the planet, its state on the object (ghost = torn
|
|
@@ -4808,7 +4843,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4808
4843
|
function paintManagePlanet(inst) {
|
|
4809
4844
|
var pl = planetOf(inst);
|
|
4810
4845
|
$('mOrb').innerHTML = orbFor(inst);
|
|
4811
|
-
$('mPlanet').innerHTML = ledgerRow(['planet', planetWords(pl) + (pl.curated ? '' : '<small>chosen for it by the platform.</small>')]);
|
|
4846
|
+
$('mPlanet').innerHTML = ledgerRow(['planet', planetWords(pl) + (pl.chosen ? '<small>your choice.</small>' : pl.curated ? '' : '<small>chosen for it by the platform.</small>')]);
|
|
4812
4847
|
$('mPlanet').hidden = false;
|
|
4813
4848
|
}
|
|
4814
4849
|
function renderManage(inst) {
|
|
@@ -4846,6 +4881,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4846
4881
|
only, so a queued settings push shows up on the tick without a reload */
|
|
4847
4882
|
renderVisState(inst);
|
|
4848
4883
|
renderJoinState(inst);
|
|
4884
|
+
/* its planet (task 1003347) reads off the row — fetch-free too; a pick in
|
|
4885
|
+
progress survives the tick because only a different project re-renders it */
|
|
4886
|
+
renderPick(inst);
|
|
4849
4887
|
/* the page names its project — the heading holds #mName; and in the tab */
|
|
4850
4888
|
document.title = (inst.slug ? inst.slug + ' — ' : '') + BASE_TITLE;
|
|
4851
4889
|
/* the card's own story, on the project's own page (task 1003232): the
|
|
@@ -5299,6 +5337,127 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5299
5337
|
if (t && t.hasAttribute('data-vis-save')) doVisSave();
|
|
5300
5338
|
});
|
|
5301
5339
|
|
|
5340
|
+
/* ── its planet (task 1003347; direction record §16, v1 the bands): the owner's
|
|
5341
|
+
own choice of how the platform draws the project. Reads OFF THE ROW
|
|
5342
|
+
(publicInstance carries planet_template / planet_accent), so the section is
|
|
5343
|
+
fetch-free like the ledger and safe on the minute tick; the save is the
|
|
5344
|
+
settings PATCH the two sections above use, and its reply is a receipt — the
|
|
5345
|
+
head, the ledger row and the list repaint from the row, never from words in
|
|
5346
|
+
the reply. A planet is shape × material × style (ORB_TEMPLATES, the sky's own
|
|
5347
|
+
names) in one of the five accents BY NAME. ── */
|
|
5348
|
+
var PICK_SHAPES = [['blob', 'the blob'], ['pair', 'the bongo pair'], ['bead', 'the bead']];
|
|
5349
|
+
var PICK_MATERIALS = [['chrome', 'chrome'], ['frost', 'frost'], ['gold', 'gold'], ['obsidian', 'obsidian']];
|
|
5350
|
+
var PICK_STYLES = [['tint', 'tinted'], ['wash', 'washed'], ['plain', 'plain']];
|
|
5351
|
+
var PICK_ACCENTS = [['sea', 'sea'], ['terracotta', 'terracotta'], ['gold-soft', 'soft gold'], ['sun-lit', 'sunlit'], ['accent', 'the accent']];
|
|
5352
|
+
var PICK_FIELDS = { mPickShape: 'shape', mPickMaterial: 'material', mPickStyle: 'style', mPickAccent: 'accent' };
|
|
5353
|
+
var mPick = null; /* the pending choice: { shape, material, style, accent } */
|
|
5354
|
+
var mPickFor = null; /* which project it belongs to — a different one starts afresh */
|
|
5355
|
+
var mPickTouched = false; /* the owner changed something since the seed — a late catalog must not undo it */
|
|
5356
|
+
var mPickSeed = ''; /* what the seed resolved to, so a late catalog rebuilds only on a real change */
|
|
5357
|
+
/* shape × material × style → the template name (the chrome material's plain is spelled `chrome`) */
|
|
5358
|
+
function pickTemplate(s) {
|
|
5359
|
+
if (s.material === 'chrome') return s.shape + '-' + (s.style === 'plain' ? 'chrome' : s.style);
|
|
5360
|
+
return s.shape + '-' + s.material + '-' + s.style;
|
|
5361
|
+
}
|
|
5362
|
+
/* a drawn planet → the four fields (the walk's var() back to its name) */
|
|
5363
|
+
function pickFrom(pl) {
|
|
5364
|
+
var parts = pl.tpl.split('-');
|
|
5365
|
+
var style = parts[parts.length - 1];
|
|
5366
|
+
return { shape: parts[0], material: parts.length === 3 ? parts[1] : 'chrome', style: style === 'chrome' ? 'plain' : style,
|
|
5367
|
+
accent: ORB_ACCENT_NAMES[ORB_ACCENTS.indexOf(pl.acc)] || ORB_ACCENT_NAMES[0] };
|
|
5368
|
+
}
|
|
5369
|
+
/* one band: the label, then the choices as hairline cells of one band, a word each */
|
|
5370
|
+
function pickBand(label, name, opts, on) {
|
|
5371
|
+
return '<div class="pickBand"><span class="pickLabel">' + label + '</span><div class="pickCells" role="radiogroup" aria-label="' + label + '">' +
|
|
5372
|
+
opts.map(function (o) {
|
|
5373
|
+
return '<label class="pickCell' + (o[0] === on ? ' on' : '') + '"><input type="radio" name="' + name + '" value="' + esc(o[0]) + '"' +
|
|
5374
|
+
(o[0] === on ? ' checked' : '') + '>' + esc(o[1]) + '</label>';
|
|
5375
|
+
}).join('') + '</div></div>';
|
|
5376
|
+
}
|
|
5377
|
+
function renderPick(inst) {
|
|
5378
|
+
var pl = planetOf(inst);
|
|
5379
|
+
if (!mPick || mPickFor !== inst.id) { mPick = pickFrom(pl); mPickSeed = JSON.stringify(mPick); mPickFor = inst.id; mPickTouched = false; $('mPick').innerHTML = ''; }
|
|
5380
|
+
else if ($('mPick').querySelector('.pick')) return; /* the tick: a pick in progress stands */
|
|
5381
|
+
var s = mPick;
|
|
5382
|
+
var preview = { tpl: pickTemplate(s), acc: accentVar(s.accent) };
|
|
5383
|
+
$('mPick').innerHTML =
|
|
5384
|
+
'<div class="pick">' +
|
|
5385
|
+
'<div class="pickPreview"><span class="worldOrb"' + planetAttrs(preview) + ' style="--acc:' + preview.acc + ';--d:132px" aria-hidden="true"><span class="disc"></span></span>' +
|
|
5386
|
+
'<p class="pickWords">' + planetWords(preview) + '<small>' + (pl.chosen ? 'your choice' : 'the platform’s pick for it') + '</small></p></div>' +
|
|
5387
|
+
'<div class="pickBands">' +
|
|
5388
|
+
pickBand('Shape', 'mPickShape', PICK_SHAPES, s.shape) +
|
|
5389
|
+
pickBand('Material', 'mPickMaterial', PICK_MATERIALS, s.material) +
|
|
5390
|
+
pickBand('Style', 'mPickStyle', PICK_STYLES, s.style) +
|
|
5391
|
+
'<div class="pickBand"><span class="pickLabel">Colour</span><div class="pickSwatches" role="radiogroup" aria-label="Colour">' +
|
|
5392
|
+
PICK_ACCENTS.map(function (a) {
|
|
5393
|
+
return '<label class="pickSw' + (a[0] === s.accent ? ' on' : '') + '" style="--sw:' + accentVar(a[0]) + '"><input type="radio" name="mPickAccent" value="' + a[0] + '"' +
|
|
5394
|
+
(a[0] === s.accent ? ' checked' : '') + '><span>' + esc(a[1]) + '</span></label>';
|
|
5395
|
+
}).join('') + '</div></div>' +
|
|
5396
|
+
'</div>' +
|
|
5397
|
+
'</div>' +
|
|
5398
|
+
'<div class="actions"><button type="button" class="btn small" data-pick-save>Save its planet</button>' +
|
|
5399
|
+
(pl.chosen ? '<button type="button" class="btn small ghost" data-pick-reset>Let the platform choose again</button>' : '') + '</div>' +
|
|
5400
|
+
'<div class="projMsg" id="mPickMsg" role="status" aria-live="polite"></div>';
|
|
5401
|
+
}
|
|
5402
|
+
$('mPick').addEventListener('change', function (ev) {
|
|
5403
|
+
var t = ev.target;
|
|
5404
|
+
var key = t && t.type === 'radio' ? PICK_FIELDS[t.name] : null;
|
|
5405
|
+
if (!key || !manageInst || !mPick) return;
|
|
5406
|
+
mPick[key] = t.value;
|
|
5407
|
+
mPickTouched = true;
|
|
5408
|
+
$('mPick').innerHTML = ''; /* the preview follows the pick: redraw, keeping the cell's focus */
|
|
5409
|
+
renderPick(manageInst);
|
|
5410
|
+
var again = document.querySelector('#mPick input[name="' + t.name + '"][value="' + t.value + '"]');
|
|
5411
|
+
if (again) again.focus({ preventScroll: true });
|
|
5412
|
+
});
|
|
5413
|
+
$('mPick').addEventListener('click', function (ev) {
|
|
5414
|
+
var t = ev.target.closest && ev.target.closest('button');
|
|
5415
|
+
if (!t) return;
|
|
5416
|
+
if (t.hasAttribute('data-pick-save')) doPickSave(false);
|
|
5417
|
+
else if (t.hasAttribute('data-pick-reset')) doPickSave(true);
|
|
5418
|
+
});
|
|
5419
|
+
/* the save: the settings PATCH; null hands the pick back to the platform */
|
|
5420
|
+
function doPickSave(reset) {
|
|
5421
|
+
if (!manageInst || !mPick) return;
|
|
5422
|
+
var body = reset ? { planet_template: null, planet_accent: null } : { planet_template: pickTemplate(mPick), planet_accent: mPick.accent };
|
|
5423
|
+
var box = $('mPickMsg');
|
|
5424
|
+
document.querySelectorAll('#mPick .actions button').forEach(function (b) { b.disabled = true; });
|
|
5425
|
+
box.className = 'projMsg'; box.textContent = 'Saving…';
|
|
5426
|
+
fetch(API + '/provisioning/instances/' + encodeURIComponent(manageId) + '/settings', {
|
|
5427
|
+
method: 'PATCH', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' },
|
|
5428
|
+
body: JSON.stringify(body)
|
|
5429
|
+
}).then(function (res) {
|
|
5430
|
+
return jsonOrEmpty(res).then(function (data) {
|
|
5431
|
+
if (res.ok) {
|
|
5432
|
+
/* the row now holds the choice: the head, the ledger row and the list
|
|
5433
|
+
row repaint from it, and the sky re-reads the feed that carries it */
|
|
5434
|
+
var planet = data.planet || {};
|
|
5435
|
+
manageInst.planet_template = planet.planet_template || null;
|
|
5436
|
+
manageInst.planet_accent = planet.planet_accent || null;
|
|
5437
|
+
var row = byId[manageInst.id];
|
|
5438
|
+
if (row) { row.planet_template = manageInst.planet_template; row.planet_accent = manageInst.planet_accent; }
|
|
5439
|
+
mPick = null; mPickTouched = false;
|
|
5440
|
+
$('mPick').innerHTML = '';
|
|
5441
|
+
repaintPlanets();
|
|
5442
|
+
var done = $('mPickMsg');
|
|
5443
|
+
done.className = 'projMsg ok';
|
|
5444
|
+
done.textContent = (data.push && data.push.message) || 'Saved.';
|
|
5445
|
+
loadCatalog();
|
|
5446
|
+
return;
|
|
5447
|
+
}
|
|
5448
|
+
var e = apiError(data, res.status);
|
|
5449
|
+
if (res.status === 401) { box.className = 'projMsg err'; box.innerHTML = signInAgain(); return; }
|
|
5450
|
+
box.className = 'projMsg err';
|
|
5451
|
+
box.textContent = e.message || 'The choice could not be saved — try again in a moment.';
|
|
5452
|
+
});
|
|
5453
|
+
}).catch(function () {
|
|
5454
|
+
box.className = 'projMsg err';
|
|
5455
|
+
box.textContent = 'Couldn’t reach the platform — check your connection and try again.';
|
|
5456
|
+
}).then(function () {
|
|
5457
|
+
document.querySelectorAll('#mPick .actions button').forEach(function (b) { b.disabled = false; });
|
|
5458
|
+
});
|
|
5459
|
+
}
|
|
5460
|
+
|
|
5302
5461
|
/* ── how people join (task 1003044): the joinability toggle — the visibility
|
|
5303
5462
|
section's shape, key for key: the CHOICE from the platform's settings row,
|
|
5304
5463
|
what the project RUNS from its own manifest (brand.project.joinability),
|
|
@@ -129,6 +129,18 @@
|
|
|
129
129
|
{ "name": "manage flows: how people join — pick a cell, save, the receipt lands under the choice", "auth": true, "width": 1440, "url": "/projects?view=manage&id=101",
|
|
130
130
|
"steps": [["wait",900],["check","input[name=\"mJoinPick\"][value=\"apply\"]"],["click","[data-join-save]"],["wait",600]],
|
|
131
131
|
"expect": {"text":[["#mJoinMsg","Saved. The project restarts to apply it."],["#mName","demo"]],"checked":"input[name=\"mJoinPick\"][value=\"apply\"]","visible":["#mOrb .worldOrb","#mFacts .ledger .k"]} },
|
|
132
|
+
{ "name": "manage flows: its planet (task 1003347) — pick the bongo pair in gold, washed, on terracotta; save; the receipt lands and the head and the ledger row repaint from the row",
|
|
133
|
+
"auth": true, "width": 1440, "url": "/projects?view=manage&id=101",
|
|
134
|
+
"steps": [["wait",900],["check","#mPick input[name=\"mPickShape\"][value=\"pair\"]"],["wait",100],["check","#mPick input[name=\"mPickMaterial\"][value=\"gold\"]"],["wait",100],["check","#mPick input[name=\"mPickStyle\"][value=\"wash\"]"],["wait",100],["check","#mPick input[name=\"mPickAccent\"][value=\"terracotta\"]"],["wait",100],["click","[data-pick-save]"],["wait",700]],
|
|
135
|
+
"expect": {"text":[["#mPickMsg","Saved. The sky, your list and the card draw it this way from here; nothing on your project restarts for it."],["#mPlanet .v","the bongo pair in gold, washedyour choice."],["#mName","demo"]],"hasClass":[["#mPickMsg","ok",true]],"attr":[["#mOrb .worldOrb","data-template","pair-gold-wash"],["#mPick .pickPreview .worldOrb","data-template","pair-gold-wash"]],"checked":"#mPick input[name=\"mPickAccent\"][value=\"terracotta\"]","visible":["#mPick [data-pick-reset]"]} },
|
|
136
|
+
{ "name": "manage flows: its planet — the pick handed back to the platform: the row's choice clears and the head falls back to the catalog's",
|
|
137
|
+
"auth": true, "width": 1440, "url": "/projects?view=manage&id=101",
|
|
138
|
+
"steps": [["wait",900],["click","[data-pick-reset]"],["wait",700]],
|
|
139
|
+
"expect": {"text":[["#mPickMsg","Saved. The sky, your list and the card draw it this way from here; nothing on your project restarts for it."],["#mName","demo"]],"hasClass":[["#mPickMsg","ok",true]],"attr":[["#mOrb .worldOrb","data-template","pair-obsidian-tint"]]} },
|
|
140
|
+
{ "name": "manage flows: its planet at 390 — the bands fold to pills and a pick still saves",
|
|
141
|
+
"auth": true, "width": 390, "url": "/projects?view=manage&id=101",
|
|
142
|
+
"steps": [["wait",900],["check","#mPick input[name=\"mPickShape\"][value=\"blob\"]"],["wait",100],["click","[data-pick-save]"],["wait",700]],
|
|
143
|
+
"expect": {"hasClass":[["#mPickMsg","ok",true]],"attr":[["#mOrb .worldOrb","data-shape","blob"]],"noOverflow":true} },
|
|
132
144
|
{ "name": "manage flows: the ender asks for the name typed back, queues the teardown, and the page re-reads the project as queued", "auth": true, "width": 1440, "url": "/projects?view=manage&id=105",
|
|
133
145
|
"steps": [["wait",900],["click","[data-del-ask]"],["wait",200],["fill","#mDelName","mooring"],["wait",100],["click","[data-del-yes]"],["wait",900]],
|
|
134
146
|
"expect": {"text":[["#mDel .addr","A teardown is queued for this project."],["#mName","mooring"]],"hidden":["#mDelName","[data-del-ask]"],"visible":["#mStrip .strip.flowDown"]} },
|
|
@@ -9,15 +9,16 @@
|
|
|
9
9
|
"ignoreControls": [".skip", ".vh"],
|
|
10
10
|
"states": {
|
|
11
11
|
"out-map": { "auth": false, "expect": { "expanded": false } },
|
|
12
|
-
"in-map": { "auth": true, "expect": { "expanded": false, "count": [["#orbField .worldOrb[data-shape][data-style]", 7]], "attr": [["#selfOrb", "data-template", "pair-chrome"], ["#orbField a.worldOrb[data-origin=\"https://amazonprimea.com\"]", "data-template", "bead-wash"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-shape", "pair"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-material", "
|
|
12
|
+
"in-map": { "auth": true, "expect": { "expanded": false, "count": [["#orbField .worldOrb[data-shape][data-style]", 7]], "attr": [["#selfOrb", "data-template", "pair-chrome"], ["#orbField a.worldOrb[data-origin=\"https://amazonprimea.com\"]", "data-template", "bead-wash"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-shape", "pair"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-material", "obsidian"], ["#orbField a.worldOrb[data-origin=\"https://demo.cloudbongos.com\"]", "data-template", "pair-obsidian-tint"], ["#orbField a.worldOrb[data-origin=\"https://emersonian-circles.cloudbongos.com\"]", "data-template", "bead-obsidian-plain"]] } },
|
|
13
13
|
"in-map-projects": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 350]], "expect": { "expanded": true, "open": "#menuProjects" } },
|
|
14
14
|
"in-map-profile": { "auth": true, "actions": [["click", "#who"], ["wait", 350]], "expect": { "expanded": true, "open": "#menuProfile" } },
|
|
15
15
|
"in-map-kbd": { "auth": true, "actions": [["focus", "#projTrig"], ["press", "Enter"], ["wait", 350], ["press", "ArrowDown"], ["wait", 120]], "expect": { "expanded": true, "activeText": "Table" } },
|
|
16
16
|
"in-map-search": { "auth": true, "actions": [["clickIfVisible", "#qToggle"], ["wait", 200], ["fill", "#mapQ", "emer"], ["wait", 300]], "expect": { "value": [["#mapQ", "emer"]] } },
|
|
17
17
|
"in-map-mine-on": { "auth": true, "actions": [["check", "#onlyMine"], ["wait", 300]], "expect": { "checked": "#onlyMine" } },
|
|
18
18
|
"in-table": { "auth": true, "actions": [["click", "#projTrig"], ["wait", 250], ["click", "a[data-sub=\"table\"]"], ["wait", 400]], "expect": { "visible": ["#sub-table-body", ".tblMast", ".mastObj", "#selfHall"], "closed": "#menuProjects", "count": [["#mapRows tr[data-generated] .rowHall", 1]] } },
|
|
19
|
-
"in-mine": { "auth": true, "url": "/projects?view=mine", "expect": { "hidden": ["#qWrap", "#empty"], "visible": ["#mineBar", "#projectsList", "#projectsList .proj .worldOrb .disc"], "count": [["#projectsList .proj", 5], ["#projectsList .proj.gone", 1], ["#projectsList .proj .worldOrb[data-shape][data-material][data-style]", 5], ["#projectsList .proj .strip", 1], ["#projectsList .proj .worldOrb.ashen", 2], ["#projectsList [data-restart]", 1]] } },
|
|
19
|
+
"in-mine": { "auth": true, "url": "/projects?view=mine", "expect": { "hidden": ["#qWrap", "#empty"], "visible": ["#mineBar", "#projectsList", "#projectsList .proj .worldOrb .disc"], "count": [["#projectsList .proj", 5], ["#projectsList .proj.gone", 1], ["#projectsList .proj .worldOrb[data-shape][data-material][data-style]", 5], ["#projectsList .proj .worldOrb[data-template=\"blob-frost-tint\"]", 1], ["#projectsList .proj .strip", 1], ["#projectsList .proj .worldOrb.ashen", 2], ["#projectsList [data-restart]", 1]] } },
|
|
20
20
|
"in-manage": { "auth": true, "url": "/projects?view=manage&id=101", "expect": { "visible": ["#mMain", "#mChip .chip", "#mOrb .worldOrb", "#mFacts .ledger .k", "#mActs"], "hidden": ["#qWrap", "#mGate", "#mMissing", "#mErr"], "text": [["#mName", "demo"], ["#mTitle", "demo"]] } },
|
|
21
|
+
"in-manage-planet": { "auth": true, "url": "/projects?view=manage&id=101", "expect": { "visible": ["#mPlanetPick", "#mPick .pick .pickPreview .worldOrb .disc", "#mPick [data-pick-save]", "#mPick [data-pick-reset]", "#mPickWhat summary"], "count": [["#mPick .pickCells[role=\"radiogroup\"]", 3], ["#mPick .pickCell", 10], ["#mPick .pickCell.on", 3], ["#mPick .pickSw", 5], ["#mPick .pickSw.on", 1]], "attr": [["#mPick .pickPreview .worldOrb", "data-template", "pair-obsidian-tint"], ["#mOrb .worldOrb", "data-template", "pair-obsidian-tint"]], "checked": "#mPick input[name=\"mPickAccent\"][value=\"sea\"]", "text": [["#mPlanet .v small", "your choice."]] } },
|
|
21
22
|
"in-manage-snag": { "auth": true, "url": "/projects?view=manage&id=103", "expect": { "visible": ["#mMain", "#mErr", "#mActs"], "text": [["#mName", "tidepool"]] } },
|
|
22
23
|
"in-manage-gone": { "auth": true, "url": "/projects?view=manage&id=104", "expect": { "visible": ["#mMain", "#mActs"], "hidden": ["#mErr"], "text": [["#mName", "old-lantern"]] } },
|
|
23
24
|
"in-manage-missing": { "auth": true, "url": "/projects?view=manage&id=999", "expect": { "visible": ["#mMissing"], "hidden": ["#mMain", "#mGate"] } },
|
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.431'; // 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');
|