@bongos/core 1.21.30 → 1.21.32
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 +47 -37
- 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 +7 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/api/openapi.json +125 -3
- package/docs/api-reference.md +4 -2
- package/docs/copy-inventory.md +393 -408
- package/docs/copy-registry.json +498 -663
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +655 -665
- package/modules/government/catalog.js +1 -0
- package/modules/government/migrations/government_024_version_focus_set.sql +19 -0
- package/modules/hall-ui/public/roadmap.css +5 -0
- package/modules/hall-ui/public/roadmap.js +64 -5
- package/modules/lifecycle/routes/versions.js +20 -0
- package/modules/public-landing/public/projects-hall-live.states.json +1 -41
- package/modules/public-landing/public/projects.html +109 -72
- package/modules/public-landing/public/projects.probes.json +5 -5
- package/modules/public-landing/public/projects.states.json +7 -7
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/bongos/project-settings.js +28 -0
- package/src/module-api.js +3 -1
- package/tests/focus_version.mjs +96 -0
- package/tests/projects_hub.mjs +7 -4
- package/tests/projects_hub_app_step.mjs +4 -3
- package/tests/projects_hub_module_picker.mjs +12 -10
- package/tests/projects_hub_pre_uat.mjs +1 -1
- package/tests/wizard_physics_more.mjs +58 -34
- package/tests/wizard_six_screens.mjs +10 -8
|
@@ -100,6 +100,7 @@ const PERMISSIONS = [
|
|
|
100
100
|
// delegates it (ADR 0172, the owner's lever). The three WITH `substrate: true` are
|
|
101
101
|
// enforced out of band and can never be delegated by a row in this database.
|
|
102
102
|
{ key: 'builder.rank.set', system: true, floor: 'archon', guards: 'PATCH /builders/:id/rank — the single rank writer' },
|
|
103
|
+
{ key: 'version.focus.set', system: true, floor: 'archon', guards: "PUT /versions/focus — star a version as the project's focus (Archon only, never delegated; task 1004551)" },
|
|
103
104
|
{ key: 'version.close', system: true, floor: 'archon', guards: 'version close disposition (never delegated below Archon)' },
|
|
104
105
|
// core.pin.move (task 1003159, ADR 0293) — MOVE AN INSTANCE'S CORE PIN: the act
|
|
105
106
|
// that makes shipped work visible to users, and the most privileged thing an HTTP
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- government_024_version_focus_set.sql — grant `version.focus.set` to the Archon
|
|
2
|
+
-- rank (task 1004551).
|
|
3
|
+
--
|
|
4
|
+
-- The atom gates PUT /versions/focus, which stars a version on the Roadmap as the
|
|
5
|
+
-- project's focus. ARCHON ONLY and system:true, so no custom rank can be handed it
|
|
6
|
+
-- without a visible catalog edit — the owner's decision on the task. The compat seed
|
|
7
|
+
-- is run-once, so a new atom needs this follow-on grant (see government_014).
|
|
8
|
+
--
|
|
9
|
+
-- Additive, idempotent (ON CONFLICT DO NOTHING), forward-safe: INSERT only.
|
|
10
|
+
|
|
11
|
+
BEGIN;
|
|
12
|
+
|
|
13
|
+
INSERT INTO government_rank_permissions (rank_key, permission_key)
|
|
14
|
+
SELECT 'archon', unnest(ARRAY[
|
|
15
|
+
'version.focus.set'
|
|
16
|
+
])
|
|
17
|
+
ON CONFLICT (rank_key, permission_key) DO NOTHING;
|
|
18
|
+
|
|
19
|
+
COMMIT;
|
|
@@ -237,3 +237,8 @@ a.rm-goal__t:hover { color:var(--accent-ink); text-decoration:underline; }
|
|
|
237
237
|
.rm-goal__dot { display:none; }
|
|
238
238
|
.rm-goal__t { flex:1 0 100%; white-space:normal; }
|
|
239
239
|
}
|
|
240
|
+
/* Focus star (task 1004551): ink-faint when off, accent when on. */
|
|
241
|
+
.rm-star { margin-left:auto; font-size:17px; line-height:1; color:var(--ink-faint); background:none; border:0; padding:0 2px; }
|
|
242
|
+
button.rm-star { cursor:pointer; }
|
|
243
|
+
button.rm-star:hover, button.rm-star:focus-visible { color:var(--accent); }
|
|
244
|
+
.rm-star--on { color:var(--accent); }
|
|
@@ -147,6 +147,59 @@
|
|
|
147
147
|
return `<a class="rm-gmore" href="${tasksLink(v.id)}" aria-label="${escapeHtml(label)}" title="${escapeHtml(label)}">View tasks ${arrowSvg}</a>`;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// The focus star (task 1004551). The focus version is the project's default — chosen by
|
|
151
|
+
// an Archon here, stored server-side, never inferred. Filled = current focus. Archons
|
|
152
|
+
// get a button that sets/clears it; everyone else gets the same star read-only. The
|
|
153
|
+
// button is a convenience: PUT /versions/focus refuses non-Archons on the server.
|
|
154
|
+
const focus = { id: null, canSet: false };
|
|
155
|
+
const FOCUS_FAIL = 'Could not change the focus version. Reload and try again.';
|
|
156
|
+
function starHtml(v) {
|
|
157
|
+
const on = focus.id === v.id;
|
|
158
|
+
const glyph = on ? '★' : '☆';
|
|
159
|
+
const base = `class="rm-star${on ? ' rm-star--on' : ''}" data-vid="${escapeHtml(v.id)}"`;
|
|
160
|
+
if (focus.canSet) {
|
|
161
|
+
const tip = on ? 'Clear the focus version.' : 'Make this the project’s focus version.';
|
|
162
|
+
return `<button type="button" ${base} aria-pressed="${on}" title="${tip}" aria-label="${tip}">${glyph}</button>`;
|
|
163
|
+
}
|
|
164
|
+
const tip = (on ? 'This is the project’s focus version. ' : 'Not the focus version. ') + 'Only an Archon can set the focus.';
|
|
165
|
+
return `<span ${base} role="img" title="${tip}" aria-label="${tip}">${glyph}</span>`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Never throws except to hand a sign-in redirect back to boot(); a failed focus read
|
|
169
|
+
// just means no star is lit.
|
|
170
|
+
async function loadFocus() {
|
|
171
|
+
try {
|
|
172
|
+
const f = await getJSON(`${API}/versions/focus`);
|
|
173
|
+
focus.id = f.focus_version_id || null;
|
|
174
|
+
focus.canSet = f.can_set === true;
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (err && err.message === 'redirecting to auth') throw err;
|
|
177
|
+
focus.id = null; focus.canSet = false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Flip the stars in place: the PUT already answered with the new focus, so there is
|
|
182
|
+
// nothing to refetch.
|
|
183
|
+
function paintStars() {
|
|
184
|
+
document.querySelectorAll('.rm-star').forEach((el) => {
|
|
185
|
+
const tmp = document.createElement('div');
|
|
186
|
+
tmp.innerHTML = starHtml({ id: el.dataset.vid });
|
|
187
|
+
el.replaceWith(tmp.firstElementChild);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function toggleFocus(vid) {
|
|
192
|
+
const next = focus.id === vid ? null : vid;
|
|
193
|
+
const res = await api.request('PUT', `${API}/versions/focus`, { body: { version_id: next } });
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
const e = res.data && res.data.error;
|
|
196
|
+
toast((e && e.message) || FOCUS_FAIL);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
focus.id = res.data.focus_version_id || null;
|
|
200
|
+
paintStars();
|
|
201
|
+
}
|
|
202
|
+
|
|
150
203
|
// ---- card renderers -------------------------------------------------------
|
|
151
204
|
|
|
152
205
|
function featCard(v) {
|
|
@@ -155,7 +208,7 @@
|
|
|
155
208
|
<div class="rm-featgrid__l">
|
|
156
209
|
<div class="rm-feat-top">
|
|
157
210
|
<div class="rt">
|
|
158
|
-
<div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span
|
|
211
|
+
<div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span>${starHtml(v)}</div>
|
|
159
212
|
<h3 class="rm-name">${escapeHtml(v.name)}</h3>
|
|
160
213
|
<p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
|
|
161
214
|
</div>
|
|
@@ -176,7 +229,7 @@
|
|
|
176
229
|
return `<article class="rm-card">
|
|
177
230
|
<div class="rm-feat-top">
|
|
178
231
|
<div class="rt">
|
|
179
|
-
<div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span
|
|
232
|
+
<div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span>${starHtml(v)}</div>
|
|
180
233
|
<h3 class="rm-name">${escapeHtml(v.name)}</h3>
|
|
181
234
|
<p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
|
|
182
235
|
</div>
|
|
@@ -218,7 +271,7 @@
|
|
|
218
271
|
return `<article class="rm-card rm-card--staged" style="grid-column:1 / -1">
|
|
219
272
|
<div class="rm-featgrid">
|
|
220
273
|
<div class="rm-featgrid__l">
|
|
221
|
-
<div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span
|
|
274
|
+
<div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span>${starHtml(v)}</div>
|
|
222
275
|
<h3 class="rm-name">${escapeHtml(v.name)}</h3>
|
|
223
276
|
<p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
|
|
224
277
|
<div class="rm-staged-state">
|
|
@@ -236,7 +289,7 @@
|
|
|
236
289
|
return `<article class="rm-card rm-card--parked" style="grid-column:1 / -1">
|
|
237
290
|
<div class="rm-feat-top">
|
|
238
291
|
<div class="rt">
|
|
239
|
-
<div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span
|
|
292
|
+
<div class="rm-row">${phase(v)}<span class="rm-id">${escapeHtml(v.id)}</span>${starHtml(v)}</div>
|
|
240
293
|
<h3 class="rm-name">${escapeHtml(v.name)}</h3>
|
|
241
294
|
<p class="rm-desc rm-desc--clamp">${escapeHtml(v.desc)}</p>
|
|
242
295
|
</div>
|
|
@@ -381,9 +434,10 @@
|
|
|
381
434
|
async function render() {
|
|
382
435
|
const root = $('#rm-root');
|
|
383
436
|
const [progress, versions] = await Promise.all([
|
|
437
|
+
loadFocus(),
|
|
384
438
|
getJSON(`${API}/public/progress`).then((d) => d.progress || []),
|
|
385
439
|
getJSON(`${API}/public/versions`).then((d) => d.versions || []).catch(() => []),
|
|
386
|
-
]);
|
|
440
|
+
]).then(([, p, v]) => [p, v]);
|
|
387
441
|
const all = mergeVersions(progress, versions);
|
|
388
442
|
|
|
389
443
|
// Goal summaries only for versions we actually show goals on (everything but
|
|
@@ -442,5 +496,10 @@
|
|
|
442
496
|
}
|
|
443
497
|
}
|
|
444
498
|
|
|
499
|
+
document.addEventListener('click', (e) => {
|
|
500
|
+
const b = e.target.closest && e.target.closest('button.rm-star');
|
|
501
|
+
if (b) toggleFocus(b.dataset.vid).catch(() => toast(FOCUS_FAIL));
|
|
502
|
+
});
|
|
503
|
+
|
|
445
504
|
boot();
|
|
446
505
|
})();
|
|
@@ -45,6 +45,26 @@ module.exports = function buildVersionsRouter() {
|
|
|
45
45
|
res.json({ versions: all.slice(offset, offset + limit), page: api.pageMeta(limit, offset, all.length) });
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
// The project's focus version (task 1004551) — the one the Roadmap stars. Read: any signed-in builder. Write: Archon only,
|
|
49
|
+
// enforced here on the server, not just by the button hiding. No row = no focus.
|
|
50
|
+
// rank: any-builder — read-only; returns { focus_version_id: string|null, can_set }.
|
|
51
|
+
// can_set tells the page whether to draw a button — a hint only; PUT is the gate.
|
|
52
|
+
router.get('/versions/focus', auth.requireBuilder, asyncHandler('GET /versions/focus', async (req, res) => {
|
|
53
|
+
const rank = String((req.builder && req.builder.rank) || '').toLowerCase();
|
|
54
|
+
res.json({ focus_version_id: await api.projectSettings.getFocusVersionId(), can_set: rank === 'archon' });
|
|
55
|
+
}, { message: false }));
|
|
56
|
+
|
|
57
|
+
// rank: archon (atom version.focus.set, system) — body { version_id } sets the focus; { version_id: null } clears it.
|
|
58
|
+
// An unknown version id is refused rather than stored.
|
|
59
|
+
router.put('/versions/focus', auth.requireBuilder, auth.requirePermission('version.focus.set'), asyncHandler('PUT /versions/focus', async (req, res) => {
|
|
60
|
+
if (validateOrRespond(req, res, { version_id: { type: 'string', maxLength: 64 } })) return;
|
|
61
|
+
const raw = (req.body || {}).version_id;
|
|
62
|
+
if (raw === undefined || (raw !== null && !raw.trim())) return res.fail('bad_version_id', { status: 400, message: 'Pick a version to star, or send null to clear the focus.' });
|
|
63
|
+
if (raw !== null && !(await db.getVersion(raw.trim()))) return res.fail('unknown_version', { status: 404, message: `There is no version "${raw.trim()}" to star.` });
|
|
64
|
+
const focus = await api.projectSettings.setFocusVersionId(raw === null ? null : raw.trim(), req.builder && req.builder.id);
|
|
65
|
+
res.json({ ok: true, focus_version_id: focus });
|
|
66
|
+
}, { message: false }));
|
|
67
|
+
|
|
48
68
|
// rank: public — read-only progress aggregate; mirrors /public/progress.
|
|
49
69
|
router.get('/versions/progress', async (_req, res) => {
|
|
50
70
|
const progress = await db.versionProgress();
|
|
@@ -99,14 +99,6 @@
|
|
|
99
99
|
"wait",
|
|
100
100
|
700
|
|
101
101
|
],
|
|
102
|
-
[
|
|
103
|
-
"click",
|
|
104
|
-
"#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"
|
|
105
|
-
],
|
|
106
|
-
[
|
|
107
|
-
"wait",
|
|
108
|
-
200
|
|
109
|
-
],
|
|
110
102
|
[
|
|
111
103
|
"click",
|
|
112
104
|
"#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"
|
|
@@ -135,39 +127,7 @@
|
|
|
135
127
|
],
|
|
136
128
|
[
|
|
137
129
|
"click",
|
|
138
|
-
"#reviewRows .setRow[data-row=\"address\"] [data-row-
|
|
139
|
-
],
|
|
140
|
-
[
|
|
141
|
-
"wait",
|
|
142
|
-
200
|
|
143
|
-
],
|
|
144
|
-
[
|
|
145
|
-
"click",
|
|
146
|
-
"#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"
|
|
147
|
-
],
|
|
148
|
-
[
|
|
149
|
-
"wait",
|
|
150
|
-
150
|
|
151
|
-
],
|
|
152
|
-
[
|
|
153
|
-
"click",
|
|
154
|
-
"#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"
|
|
155
|
-
],
|
|
156
|
-
[
|
|
157
|
-
"wait",
|
|
158
|
-
150
|
|
159
|
-
],
|
|
160
|
-
[
|
|
161
|
-
"click",
|
|
162
|
-
"#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"
|
|
163
|
-
],
|
|
164
|
-
[
|
|
165
|
-
"wait",
|
|
166
|
-
150
|
|
167
|
-
],
|
|
168
|
-
[
|
|
169
|
-
"click",
|
|
170
|
-
"#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"
|
|
130
|
+
"#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"
|
|
171
131
|
],
|
|
172
132
|
[
|
|
173
133
|
"wait",
|
|
@@ -269,12 +269,13 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
|
|
|
269
269
|
.fillOffer{margin:0 0 12px;}
|
|
270
270
|
.wizRecap{margin:22px 0 0;font-size:15px;line-height:1.5;color:var(--ink-soft);}
|
|
271
271
|
#view-new .doorPill .btn[disabled]{opacity:.35;cursor:default;}
|
|
272
|
-
/* the review: one hairline ledger of rows. Each row is its label, its
|
|
273
|
-
plain line under it, and
|
|
274
|
-
|
|
275
|
-
|
|
272
|
+
/* the review: one hairline ledger of rows (task 1004550). Each row is its label, its
|
|
273
|
+
value with one plain line under it, and one action — Edit, which reads Done while the
|
|
274
|
+
row is open over the old step's own controls. One "Looks right, continue" under the
|
|
275
|
+
ledger confirms them all. Inside an open row the choices are compact toggles, not the
|
|
276
|
+
full-width hairline cards the question screens use. */
|
|
276
277
|
.setRows{margin:24px 0 0;border-top:1px solid var(--rule);}
|
|
277
|
-
.setRow{display:grid;grid-template-columns:120px minmax(0,1fr) auto;gap:0 16px;align-items:start;padding:
|
|
278
|
+
.setRow{display:grid;grid-template-columns:120px minmax(0,1fr) auto;gap:0 16px;align-items:start;padding:14px 0;border-bottom:1px solid var(--rule);}
|
|
278
279
|
.setRow[hidden]{display:none;}
|
|
279
280
|
.setRow .k{font-size:13px;line-height:22px;color:var(--ink-faint);padding-top:2px;}
|
|
280
281
|
.setRow .v{min-width:0;}
|
|
@@ -283,16 +284,31 @@ h1[tabindex="-1"]:focus:not(:focus-visible){outline:none;}
|
|
|
283
284
|
.setRow .rowS:empty{display:none;}
|
|
284
285
|
.setRow .acts{display:flex;gap:6px;flex:none;}
|
|
285
286
|
.setRow .acts button{min-height:34px;padding:0 14px;border-radius:999px;font:inherit;font-size:13px;white-space:nowrap;cursor:pointer;}
|
|
286
|
-
.setRow [data-row-ok]{border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--ink);}
|
|
287
287
|
.setRow [data-row-chg]{border:1px solid var(--glass-edge);background:var(--glass);color:var(--ink-soft);}
|
|
288
288
|
.setRow [data-row-chg]:hover{color:var(--ink);}
|
|
289
289
|
.setRow [data-row-chg][hidden]{display:none;}
|
|
290
|
-
.setRow.
|
|
291
|
-
.setRow.
|
|
292
|
-
.setRow.
|
|
293
|
-
.setRow.
|
|
294
|
-
|
|
295
|
-
.setRow .opts .
|
|
290
|
+
.setRow.open [data-row-chg]{border-color:color-mix(in srgb,var(--accent) 55%,transparent);background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--ink);}
|
|
291
|
+
.setRow .opts{margin-top:12px;}
|
|
292
|
+
.setRow .opts .wizField{margin-bottom:12px;}
|
|
293
|
+
.setRow .opts .hint:empty{display:none;}
|
|
294
|
+
/* compact toggles: the repo choice and every short pill group in an open row */
|
|
295
|
+
.setRow .opts .choices,
|
|
296
|
+
.setRow .opts .typeCards.physCells:not(.physSuiteGrid),
|
|
297
|
+
.setRow .opts #teamCards{display:inline-flex;flex-wrap:wrap;gap:0;border:1px solid var(--rule);border-radius:999px;padding:3px;margin:0 8px 12px 0;vertical-align:top;}
|
|
298
|
+
.setRow .opts .choices .choice,
|
|
299
|
+
.setRow .opts .typeCards.physCells:not(.physSuiteGrid) .tpill,
|
|
300
|
+
.setRow .opts #teamCards .tpill{display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:0 14px;border:0;border-radius:999px;font-size:14px;font-weight:500;color:var(--ink-soft);background:none;}
|
|
301
|
+
.setRow .opts .choices .choice b{margin:0;font-size:14px;color:inherit;}
|
|
302
|
+
.setRow .opts .choices .choice b::before,
|
|
303
|
+
.setRow .opts .typeCards.physCells:not(.physSuiteGrid) .tpill::before,
|
|
304
|
+
.setRow .opts #teamCards .tpill::before{display:none;}
|
|
305
|
+
.setRow .opts .choices .choice p{display:none;}
|
|
306
|
+
.setRow .opts .reassure{font-size:13px;margin:0 0 12px;}
|
|
307
|
+
.setRow .opts .choices .choice[aria-pressed=true],
|
|
308
|
+
.setRow .opts .typeCards.physCells:not(.physSuiteGrid) .tpill[aria-pressed=true],
|
|
309
|
+
.setRow .opts #teamCards .tpill[aria-pressed=true]{background:color-mix(in srgb,var(--accent) 18%,transparent);color:var(--ink);}
|
|
310
|
+
/* a reason for a question the table hides explains nothing on screen */
|
|
311
|
+
.physExisting[hidden] + .physWhy{display:none;}
|
|
296
312
|
@media (max-width:560px){
|
|
297
313
|
.setRow{grid-template-columns:minmax(0,1fr);gap:6px 0;}
|
|
298
314
|
.setRow .k{padding-top:0;}
|
|
@@ -1600,14 +1616,15 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1600
1616
|
</div>
|
|
1601
1617
|
</section>
|
|
1602
1618
|
|
|
1603
|
-
<!-- 4 · check what we set up: every prefilled answer is a row the
|
|
1604
|
-
|
|
1605
|
-
controls, moved here whole, so every handler and id is the one
|
|
1606
|
-
|
|
1607
|
-
|
|
1619
|
+
<!-- 4 · check what we set up: every prefilled answer is a one-line row the
|
|
1620
|
+
founder reads, and changes in place if it is wrong (task 1004550). Edit opens
|
|
1621
|
+
the step's own controls, moved here whole, so every handler and id is the one
|
|
1622
|
+
it always was; Done checks the row the way its old step did. One "Looks right,
|
|
1623
|
+
continue" confirms every row at once, and stops on the first row that fails
|
|
1624
|
+
its check, opened with its reason. -->
|
|
1608
1625
|
<section class="panel" id="panel-review" aria-labelledby="rev-h">
|
|
1609
1626
|
<h1 id="rev-h">Check what we set up.</h1>
|
|
1610
|
-
<p class="lede">We filled these in from your answers.
|
|
1627
|
+
<p class="lede">We filled these in from your answers. Change anything that’s wrong.</p>
|
|
1611
1628
|
<form data-next="5" id="reviewForm">
|
|
1612
1629
|
<div class="setRows" id="reviewRows">
|
|
1613
1630
|
|
|
@@ -1628,15 +1645,15 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1628
1645
|
<p class="physWhy" data-why="existing_work" hidden></p>
|
|
1629
1646
|
<div class="choices" role="group" aria-label="Starting point">
|
|
1630
1647
|
<button type="button" class="choice" id="c-green" aria-pressed="true">
|
|
1631
|
-
<b>A new
|
|
1632
|
-
<p>
|
|
1648
|
+
<b>A new repo</b>
|
|
1649
|
+
<p>Public, set up for you</p>
|
|
1633
1650
|
</button>
|
|
1634
1651
|
<button type="button" class="choice" id="c-adopt" aria-pressed="false">
|
|
1635
|
-
<b>A repo I
|
|
1636
|
-
<p>Public or private
|
|
1652
|
+
<b>A repo I have</b>
|
|
1653
|
+
<p>Public or private</p>
|
|
1637
1654
|
</button>
|
|
1638
1655
|
</div>
|
|
1639
|
-
<div class="reassure" id="adoptNote">Nothing is overwritten. You see
|
|
1656
|
+
<div class="reassure" id="adoptNote">Nothing is overwritten. You see conflicts first, and open issues become tasks.</div>
|
|
1640
1657
|
<!-- GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099.
|
|
1641
1658
|
Without repo access we fall back to the manual owner/repo inputs
|
|
1642
1659
|
(#manualHome), which stay authoritative. -->
|
|
@@ -1652,14 +1669,14 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1652
1669
|
<div id="ghList" class="ghlist" role="group" aria-label="Your repositories"></div>
|
|
1653
1670
|
<div class="hint" id="ghAdoptHint"> </div>
|
|
1654
1671
|
<!-- visibility stated on EVERY repo path (task 1002721) -->
|
|
1655
|
-
<div class="hint">We never change
|
|
1672
|
+
<div class="hint">We never change it: public stays public, private stays private.</div>
|
|
1656
1673
|
</div>
|
|
1657
1674
|
<div id="ghFresh" class="wizField" hidden>
|
|
1658
1675
|
<span>New repository name</span>
|
|
1659
1676
|
<input type="text" id="ghNewName" placeholder="moonbase" autocomplete="off" maxlength="100" aria-label="New repository name">
|
|
1660
1677
|
<!-- visibility is stated on EVERY repo path (task 1002721); updateFreshPreview()
|
|
1661
1678
|
replaces this floor with the same claim plus the resolved repo URL -->
|
|
1662
|
-
<div class="hint" id="ghFreshHint">
|
|
1679
|
+
<div class="hint" id="ghFreshHint">A new <strong>public</strong> repo. For private code, pick “A repo I have”.</div>
|
|
1663
1680
|
</div>
|
|
1664
1681
|
<div class="row2" id="manualHome">
|
|
1665
1682
|
<label class="wizField"><span>GitHub owner <small>(user or org)</small></span>
|
|
@@ -1669,13 +1686,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1669
1686
|
<input type="text" id="f-repo" placeholder="moonbase" autocomplete="off" maxlength="120">
|
|
1670
1687
|
</label>
|
|
1671
1688
|
<!-- visibility stated BEFORE GitHub's own radio is the first place it appears (task 1002721) -->
|
|
1672
|
-
<div class="hint" style="grid-column:1 / -1;">
|
|
1689
|
+
<div class="hint" style="grid-column:1 / -1;">Public or private both work, and we never change it.</div>
|
|
1673
1690
|
</div>
|
|
1674
1691
|
<div class="hint" id="h-home"> </div>
|
|
1675
|
-
<button type="button" class="btn small rowUse" data-row-use>Use this</button>
|
|
1676
1692
|
</div>
|
|
1677
1693
|
</div>
|
|
1678
|
-
<span class="acts"><button type="button" class="
|
|
1694
|
+
<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
|
|
1679
1695
|
</div>
|
|
1680
1696
|
|
|
1681
1697
|
<!-- the address (tasks 1002704, 1002898 N8, PS19): the assigned address is
|
|
@@ -1705,10 +1721,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1705
1721
|
<span>No address yet. Sign-in can’t be switched on until it has one.</span>
|
|
1706
1722
|
</label>
|
|
1707
1723
|
</div>
|
|
1708
|
-
<button type="button" class="btn small rowUse" data-row-use>Use this</button>
|
|
1709
1724
|
</div>
|
|
1710
1725
|
</div>
|
|
1711
|
-
<span class="acts"><button type="button" class="
|
|
1726
|
+
<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
|
|
1712
1727
|
</div>
|
|
1713
1728
|
|
|
1714
1729
|
<!-- where the app runs (tasks 1004351, 1004352; ADR 0345): the hall is ours to
|
|
@@ -1731,9 +1746,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1731
1746
|
<span id="appRenderWhat">We set it up on your own Render account. Render bills you directly.</span></span>
|
|
1732
1747
|
</label>
|
|
1733
1748
|
</fieldset>
|
|
1734
|
-
<p class="renderWhy" id="renderLater" role="status" hidden>Your new repo has no app yet, so
|
|
1749
|
+
<p class="renderWhy" id="renderLater" role="status" hidden>Your new repo has no app yet, so nothing is created on Render now. Set it up from your project’s page once there is one.</p>
|
|
1735
1750
|
<div class="renderSetup" id="renderSetup" hidden>
|
|
1736
|
-
<p class="renderWhy">
|
|
1751
|
+
<p class="renderWhy">Used once to create your app on your Render account, then thrown away — not kept, not even in this browser. A key can act on your whole Render account. Render bills you directly. <a href="https://dashboard.render.com/u/settings#api-keys" target="_blank" rel="noopener noreferrer">Create a key in Render</a></p>
|
|
1737
1752
|
<div class="renderKeyRow">
|
|
1738
1753
|
<label class="wizField"><span>Render API key</span>
|
|
1739
1754
|
<input type="password" id="f-renderKey" autocomplete="off" spellcheck="false" maxlength="200" placeholder="rnd_…" aria-describedby="h-renderKey">
|
|
@@ -1775,10 +1790,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1775
1790
|
</div>
|
|
1776
1791
|
<div class="hint" id="h-render" role="status"> </div>
|
|
1777
1792
|
</div>
|
|
1778
|
-
<button type="button" class="btn small rowUse" data-row-use>Use this</button>
|
|
1779
1793
|
</div>
|
|
1780
1794
|
</div>
|
|
1781
|
-
<span class="acts"><button type="button" class="
|
|
1795
|
+
<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
|
|
1782
1796
|
</div>
|
|
1783
1797
|
|
|
1784
1798
|
<!-- the extras (tasks 1002339, 1002340): the type's suggested set until the
|
|
@@ -1796,10 +1810,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1796
1810
|
</div>
|
|
1797
1811
|
<div class="modWhy" id="modWhy" aria-live="polite"></div>
|
|
1798
1812
|
<div class="reassure" id="modCoreNote"></div>
|
|
1799
|
-
<button type="button" class="btn small rowUse" data-row-use>Use this</button>
|
|
1800
1813
|
</div>
|
|
1801
1814
|
</div>
|
|
1802
|
-
<span class="acts"><button type="button" class="
|
|
1815
|
+
<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
|
|
1803
1816
|
</div>
|
|
1804
1817
|
|
|
1805
1818
|
<!-- team size (question 5, task 1004419): one person, or a team with a count
|
|
@@ -1832,10 +1845,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1832
1845
|
</div>
|
|
1833
1846
|
</div>
|
|
1834
1847
|
</div>
|
|
1835
|
-
<button type="button" class="btn small rowUse" data-row-use>Use this</button>
|
|
1836
1848
|
</div>
|
|
1837
1849
|
</div>
|
|
1838
|
-
<span class="acts"><button type="button" class="
|
|
1850
|
+
<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
|
|
1839
1851
|
</div>
|
|
1840
1852
|
|
|
1841
1853
|
<!-- rewards (question 6): intent only, never asked of a founder building
|
|
@@ -1851,10 +1863,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1851
1863
|
<button type="button" class="tpill" data-phys="reward_intents" data-val="future-payouts" aria-pressed="false">Future payouts</button>
|
|
1852
1864
|
</div>
|
|
1853
1865
|
<ul class="physWhyList" data-why-list="reward_intents" hidden></ul>
|
|
1854
|
-
<button type="button" class="btn small rowUse" data-row-use>Use this</button>
|
|
1855
1866
|
</div>
|
|
1856
1867
|
</div>
|
|
1857
|
-
<span class="acts"><button type="button" class="
|
|
1868
|
+
<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
|
|
1858
1869
|
</div>
|
|
1859
1870
|
|
|
1860
1871
|
<!-- how much we run (question 7): a hybrid or local framing opens its suite -->
|
|
@@ -1885,16 +1896,15 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1885
1896
|
</div>
|
|
1886
1897
|
<ul class="physWhyList" data-why-list="services" hidden></ul>
|
|
1887
1898
|
</fieldset>
|
|
1888
|
-
<button type="button" class="btn small rowUse" data-row-use>Use this</button>
|
|
1889
1899
|
</div>
|
|
1890
1900
|
</div>
|
|
1891
|
-
<span class="acts"><button type="button" class="
|
|
1901
|
+
<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit</button></span>
|
|
1892
1902
|
</div>
|
|
1893
1903
|
</div>
|
|
1894
1904
|
<div class="hint" id="h-review"> </div>
|
|
1895
1905
|
<div class="nav">
|
|
1896
1906
|
<span></span>
|
|
1897
|
-
<button type="submit" class="btn" id="reviewGo"
|
|
1907
|
+
<button type="submit" class="btn" id="reviewGo">Looks right, continue</button>
|
|
1898
1908
|
</div>
|
|
1899
1909
|
</form>
|
|
1900
1910
|
</section>
|
|
@@ -5335,7 +5345,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5335
5345
|
/* Visibility stated up front (task 1002694 / idea 1000684): repos WE create are
|
|
5336
5346
|
public by design (github-repos.js — open-source, capture-resistant); the
|
|
5337
5347
|
private path is adopting a private repo the owner makes themselves. */
|
|
5338
|
-
hint.className = 'hint'; hint.innerHTML = '
|
|
5348
|
+
hint.className = 'hint'; hint.innerHTML = 'A new <strong>public</strong> repo at <code style="font-family:var(--font-mono)">github.com/' + esc(myLogin()) + '/' + esc(state.repo || 'name') + '</code>. For private code, make the repo on GitHub and pick “A repo I have”.';
|
|
5339
5349
|
}
|
|
5340
5350
|
}
|
|
5341
5351
|
$('ghNewName').addEventListener('input', function () { state.repo = this.value.trim(); updateFreshPreview(); saveDraft(); });
|
|
@@ -5978,12 +5988,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5978
5988
|
}
|
|
5979
5989
|
/* the quick checks walk to the review themselves, after the server's screen */
|
|
5980
5990
|
if (step === 3) return checksAdvance();
|
|
5981
|
-
/* the review: every row that applies confirmed, each still true */
|
|
5982
|
-
if (step === 4)
|
|
5983
|
-
var open = unconfirmedRows();
|
|
5984
|
-
if (open.length) { openRow(open[0]); return false; }
|
|
5985
|
-
return true;
|
|
5986
|
-
}
|
|
5991
|
+
/* the review: every row that applies confirmed at once, each still true */
|
|
5992
|
+
if (step === 4) return confirmAllRows();
|
|
5987
5993
|
return true;
|
|
5988
5994
|
}
|
|
5989
5995
|
|
|
@@ -6157,7 +6163,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6157
6163
|
if (!row || rowFixed(key)) return;
|
|
6158
6164
|
row.classList.add('open');
|
|
6159
6165
|
row.querySelector('.opts').hidden = false;
|
|
6160
|
-
row.querySelector('[data-row-chg]')
|
|
6166
|
+
var chg = row.querySelector('[data-row-chg]');
|
|
6167
|
+
chg.setAttribute('aria-expanded', 'true');
|
|
6168
|
+
chg.textContent = 'Done';
|
|
6161
6169
|
if (key === 'address') { checkSlugAvailable(); renderAddrPanel(); }
|
|
6162
6170
|
if (key === 'extras') renderModules();
|
|
6163
6171
|
if (key === 'code') syncHome();
|
|
@@ -6167,11 +6175,14 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6167
6175
|
var row = rowEl(key);
|
|
6168
6176
|
row.classList.remove('open');
|
|
6169
6177
|
row.querySelector('.opts').hidden = true;
|
|
6170
|
-
row.querySelector('[data-row-chg]')
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6178
|
+
var chg = row.querySelector('[data-row-chg]');
|
|
6179
|
+
chg.setAttribute('aria-expanded', 'false');
|
|
6180
|
+
chg.textContent = 'Edit';
|
|
6181
|
+
}
|
|
6182
|
+
/* Done on an open row, or the page's one "Looks right, continue": the row's own
|
|
6183
|
+
checks first — a row that fails stays open with its reason showing — then it is
|
|
6184
|
+
the founder's answer. The extras row turns "the suggested set" into an answer
|
|
6185
|
+
given, a different fact from never asked. */
|
|
6175
6186
|
function confirmRow(key) {
|
|
6176
6187
|
if (!rowValid(key)) { openRow(key); return false; }
|
|
6177
6188
|
/* only once the catalog is here: without it the suggested set is unknown, and an
|
|
@@ -6181,21 +6192,54 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6181
6192
|
closeRow(key);
|
|
6182
6193
|
saveDraft();
|
|
6183
6194
|
paintSetup();
|
|
6184
|
-
var next = unconfirmedRows()[0];
|
|
6185
|
-
var go = next ? rowEl(next).querySelector('[data-row-ok]') : $('reviewGo');
|
|
6186
|
-
if (go && !go.disabled) go.focus({ preventScroll: true });
|
|
6187
6195
|
return true;
|
|
6188
6196
|
}
|
|
6197
|
+
/* the page's one confirmation (task 1004550): every row that applies, in order,
|
|
6198
|
+
through its own check; the first that fails opens with its reason and stops it */
|
|
6199
|
+
function confirmAllRows() {
|
|
6200
|
+
var rows = unconfirmedRows();
|
|
6201
|
+
for (var i = 0; i < rows.length; i++) {
|
|
6202
|
+
if (!confirmRow(rows[i])) {
|
|
6203
|
+
var row = rowEl(rows[i]);
|
|
6204
|
+
if (row && row.scrollIntoView) row.scrollIntoView({ block: 'center' });
|
|
6205
|
+
$('h-review').textContent = 'Fix the row that’s open, then continue.';
|
|
6206
|
+
$('h-review').className = 'hint err';
|
|
6207
|
+
return false;
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
$('h-review').textContent = '\u00a0'; $('h-review').className = 'hint';
|
|
6211
|
+
return true;
|
|
6212
|
+
}
|
|
6213
|
+
/* back to the review from a later screen with rows left to confirm: show the first
|
|
6214
|
+
one that fails its check opened, and confirm nothing on the founder's behalf */
|
|
6215
|
+
function reopenReview() {
|
|
6216
|
+
show(4, true);
|
|
6217
|
+
var bad = unconfirmedRows().filter(function (k) { return !rowValid(k); })[0];
|
|
6218
|
+
if (bad) openRow(bad);
|
|
6219
|
+
}
|
|
6189
6220
|
$('reviewRows').addEventListener('click', function (ev) {
|
|
6190
|
-
var t = ev.target.closest && ev.target.closest('[data-row-
|
|
6221
|
+
var t = ev.target.closest && ev.target.closest('[data-row-chg]');
|
|
6191
6222
|
if (!t) return;
|
|
6192
6223
|
var key = t.closest('.setRow').getAttribute('data-row');
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6224
|
+
/* Done: the row's check, then closed — or left open with its reason */
|
|
6225
|
+
if (rowEl(key).classList.contains('open')) {
|
|
6226
|
+
if (confirmRow(key)) t.focus({ preventScroll: true });
|
|
6196
6227
|
return;
|
|
6197
6228
|
}
|
|
6198
|
-
|
|
6229
|
+
/* Edit: one row open at a time — another open row closes if its answer holds */
|
|
6230
|
+
ROWS.forEach(function (k) { if (k !== key && rowEl(k).classList.contains('open') && rowValid(k)) confirmRow(k); });
|
|
6231
|
+
delete confirmedRows()[key]; openRow(key); paintSetup();
|
|
6232
|
+
});
|
|
6233
|
+
/* an open row's value line follows what is typed or picked under it, live — the
|
|
6234
|
+
controls' own handlers run first (they are on the targets), then the row repaints */
|
|
6235
|
+
['input', 'change'].forEach(function (type) {
|
|
6236
|
+
$('reviewRows').addEventListener(type, function (ev) {
|
|
6237
|
+
if (ev.target.closest && ev.target.closest('.setRow.open .opts')) {
|
|
6238
|
+
var row = ev.target.closest('.setRow'), t = rowText(row.getAttribute('data-row'));
|
|
6239
|
+
row.querySelector('[data-row-v]').textContent = t.v;
|
|
6240
|
+
row.querySelector('[data-row-s]').textContent = t.s;
|
|
6241
|
+
}
|
|
6242
|
+
});
|
|
6199
6243
|
});
|
|
6200
6244
|
|
|
6201
6245
|
/* what each row says: its value and one plain line under it */
|
|
@@ -6280,15 +6324,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6280
6324
|
row.classList.toggle('done', !!c[key]);
|
|
6281
6325
|
row.querySelector('[data-row-v]').textContent = t.v;
|
|
6282
6326
|
row.querySelector('[data-row-s]').textContent = t.s;
|
|
6283
|
-
var ok = row.querySelector('[data-row-ok]');
|
|
6284
|
-
ok.textContent = c[key] ? 'Confirmed' : 'Looks right';
|
|
6285
|
-
ok.disabled = !!c[key];
|
|
6286
6327
|
row.querySelector('[data-row-chg]').hidden = rowFixed(key);
|
|
6287
6328
|
});
|
|
6288
|
-
var left = unconfirmedRows().length;
|
|
6289
|
-
var go = $('reviewGo');
|
|
6290
|
-
go.disabled = left > 0;
|
|
6291
|
-
go.textContent = left ? 'Confirm ' + left + ' more' : 'Continue';
|
|
6292
6329
|
}
|
|
6293
6330
|
/* screen 5's one line: what Create is about to make */
|
|
6294
6331
|
function renderRecap() {
|
|
@@ -6382,7 +6419,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6382
6419
|
project (task 1004417) */
|
|
6383
6420
|
if (!state.termsAck) { show(1, true); validateStep(1); return; }
|
|
6384
6421
|
/* nothing is created before every row of the review is confirmed */
|
|
6385
|
-
if (unconfirmedRows().length) {
|
|
6422
|
+
if (unconfirmedRows().length) { reopenReview(); return; }
|
|
6386
6423
|
btn.disabled = true; btn.textContent = 'Raising it into the sky…';
|
|
6387
6424
|
ensureFreshRepo().then(function () {
|
|
6388
6425
|
return fetch(API + '/provisioning/instances', {
|