@bongos/core 1.25.15 → 1.25.16
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 -32
- package/.claude/skills/goal-create/SKILL.md +1 -0
- package/.claude/skills/planning-session/SKILL.md +5 -4
- package/docs/adr/0408-a-genesis-stage-is-three-doors-onto-one-record.md +1 -0
- package/docs/copy-inventory.md +120 -109
- package/docs/copy-registry.json +275 -165
- package/docs/module-api-changelog.md +2 -2
- package/docs/page-inventory.json +2 -0
- package/docs/page-readings.json +107 -107
- package/docs/project-context.template.md +1 -0
- package/modules/hall-ui/public/dom-utils.js +5 -0
- package/modules/hall-ui/public/genesis-home.js +23 -8
- package/modules/hall-ui/public/setup-gate.js +1 -1
- package/modules/hall-ui/public/style.css +21 -1
- package/modules/lifecycle/kickoff-checklist.js +17 -3
- package/modules/lifecycle/kickoff-seed.js +27 -4
- package/modules/onboarding/genesis-home.js +11 -2
- package/modules/onboarding/genesis-work.js +28 -5
- package/modules/public-landing/public/assets/guide-scene.css +245 -0
- package/modules/public-landing/public/assets/guide-scene.js +566 -0
- package/modules/public-landing/public/index.html +62 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +13 -1
- package/scripts/bongos/genesis.js +2 -0
- package/src/module-api.js +1 -1
- package/tests/genesis_home.mjs +70 -2
- package/tests/genesis_work.mjs +49 -0
- package/tests/landing_guide_scene.mjs +504 -0
|
@@ -43,6 +43,9 @@
|
|
|
43
43
|
then styles new markup with an old sheet). -->
|
|
44
44
|
<link rel="stylesheet" href="/modules/ui-design/public/tokens.css">
|
|
45
45
|
<link rel="stylesheet" href="/assets/world.css">
|
|
46
|
+
<!-- the start-a-project guide, as a scene in the game (task 1005275): drawn only when the
|
|
47
|
+
front-door-guide module is on and its server can talk -->
|
|
48
|
+
<link rel="stylesheet" href="/assets/guide-scene.css">
|
|
46
49
|
<style>
|
|
47
50
|
/* ── PAGE-LOCAL SHEET ────────────────────────────────────────────────────
|
|
48
51
|
The composition: how this page arranges the world's sculpture, and nothing
|
|
@@ -574,6 +577,12 @@ body{
|
|
|
574
577
|
--orb-x:4vw;
|
|
575
578
|
--orb-y:-6vh;
|
|
576
579
|
--doors-cx:0;--doors-cy:0;
|
|
580
|
+
/* the start-a-project guide (assets/guide-scene.css, task 1005275): one column, the
|
|
581
|
+
frame the whole screen, the plan over the talk from its Plan button */
|
|
582
|
+
--gd-pad:0;--gd-frame-w:100%;--gd-frame-h:100%;--gd-cols:minmax(0,1fr);--gd-side:0px;
|
|
583
|
+
--gd-gutter:var(--ds-space-gutter-narrow);--gd-face:56px;--gd-face-img:48px;
|
|
584
|
+
--gd-planbtn:inline-flex;--gd-planx:inline-grid;--gd-go-w:100%;
|
|
585
|
+
--gd-plan-show:none;--gd-plan-pos:absolute;--gd-plan-edge:0px;--gd-talk-under-plan:hidden;
|
|
577
586
|
}
|
|
578
587
|
:root[data-mode="dark"] body{
|
|
579
588
|
--layer-pos:relative;
|
|
@@ -779,7 +788,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
779
788
|
<div class="doors">
|
|
780
789
|
<div class="doorbar">
|
|
781
790
|
<a class="door" href="/projects">Find a project to join</a>
|
|
782
|
-
<a class="door door--go" href="/projects?view=new">Start a project</a>
|
|
791
|
+
<a class="door door--go" href="/projects?view=new" data-guide-door>Start a project</a>
|
|
783
792
|
</div>
|
|
784
793
|
</div>
|
|
785
794
|
</div>
|
|
@@ -945,7 +954,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
945
954
|
<div class="wrap">
|
|
946
955
|
<div class="closemark" aria-hidden="true"></div>
|
|
947
956
|
<div class="doorbar">
|
|
948
|
-
<a class="door door--go" href="/projects?view=new">Start a project</a>
|
|
957
|
+
<a class="door door--go" href="/projects?view=new" data-guide-door>Start a project</a>
|
|
949
958
|
<a class="door" href="/projects">Find a project</a>
|
|
950
959
|
</div>
|
|
951
960
|
<div class="cli" data-cli hidden>
|
|
@@ -957,6 +966,53 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
957
966
|
</section>
|
|
958
967
|
</main>
|
|
959
968
|
|
|
969
|
+
<!-- THE GUIDE (task 1005275): the start-a-project conversation as a scene in the game.
|
|
970
|
+
Hidden, and never opened, unless the front-door-guide module is on and its status read
|
|
971
|
+
says it can talk (assets/guide-scene.js); with it off the doors above are plain links. -->
|
|
972
|
+
<div class="gd" id="guide" role="dialog" aria-modal="true" aria-labelledby="gdTitle" hidden>
|
|
973
|
+
<div class="gd__frame">
|
|
974
|
+
<section class="gd__talk" aria-labelledby="gdTitle">
|
|
975
|
+
<div class="gd__head">
|
|
976
|
+
<h2 class="gd__title" id="gdTitle">Start a project</h2>
|
|
977
|
+
<button type="button" class="gd__x" data-gd="close" aria-label="Close"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M5 5l10 10M15 5L5 15"/></svg></button>
|
|
978
|
+
</div>
|
|
979
|
+
<div class="gd__who-row">
|
|
980
|
+
<span class="gd__face"><img src="/modules/hall-ui/public/characters/patron.png" alt="" width="64" height="64"></span>
|
|
981
|
+
<div class="gd__meta">
|
|
982
|
+
<p class="gd__name">Your guide</p>
|
|
983
|
+
<p class="gd__left" data-gd="left" aria-live="polite" hidden></p>
|
|
984
|
+
<span class="gd__segs" data-gd="segs" aria-hidden="true" hidden></span>
|
|
985
|
+
</div>
|
|
986
|
+
<button type="button" class="gd__planbtn" data-gd="plan-toggle" aria-expanded="false" aria-controls="gdPlan">Plan</button>
|
|
987
|
+
</div>
|
|
988
|
+
<ol class="gd__log" data-gd="log" aria-live="polite" aria-label="Conversation"></ol>
|
|
989
|
+
<p class="gd__err" data-gd="err" role="alert" hidden></p>
|
|
990
|
+
<form class="gd__say" data-gd="form" hidden>
|
|
991
|
+
<label class="vh" for="gdText">Your message</label>
|
|
992
|
+
<textarea class="gd__text" id="gdText" data-gd="text" rows="2" maxlength="1000" placeholder="What do you want to build?"></textarea>
|
|
993
|
+
<input class="gd__hp" type="text" name="website" data-gd="website" tabindex="-1" autocomplete="off" aria-hidden="true">
|
|
994
|
+
<button type="submit" class="gd__send" data-gd="send">Send</button>
|
|
995
|
+
<div class="gd__sayfoot">
|
|
996
|
+
<button type="button" class="gd__more" data-gd="make-plan" hidden>Make my plan</button>
|
|
997
|
+
<a class="gd__skip" data-gd="skip" href="/projects?view=new">Skip to sign up</a>
|
|
998
|
+
</div>
|
|
999
|
+
</form>
|
|
1000
|
+
<div class="gd__end" data-gd="end" hidden>
|
|
1001
|
+
<button type="button" class="gd__more" data-gd="plan-again" hidden>Make my plan</button>
|
|
1002
|
+
<a class="gd__go" data-gd="go" href="/projects?view=new">Sign up and get started</a>
|
|
1003
|
+
</div>
|
|
1004
|
+
</section>
|
|
1005
|
+
<aside class="gd__plan" id="gdPlan" aria-labelledby="gdPlanH">
|
|
1006
|
+
<div class="gd__planhead">
|
|
1007
|
+
<h3 class="gd__planh" id="gdPlanH">Your plan</h3>
|
|
1008
|
+
<button type="button" class="gd__x" data-gd="plan-close" aria-label="Back to the chat"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M5 5l10 10M15 5L5 15"/></svg></button>
|
|
1009
|
+
</div>
|
|
1010
|
+
<div class="gd__planbody" data-gd="plan-body"><p class="gd__planwait">When the chat ends, your guide writes your plan here.</p></div>
|
|
1011
|
+
<p class="gd__saved" data-gd="saved" hidden></p>
|
|
1012
|
+
</aside>
|
|
1013
|
+
</div>
|
|
1014
|
+
</div>
|
|
1015
|
+
|
|
960
1016
|
<footer>
|
|
961
1017
|
<div class="footrow">
|
|
962
1018
|
<p><!--apex-only:start-->A non-profit, capture-resistant commons. <!--apex-only:end-->Built by agents, on Cloud Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).</p>
|
|
@@ -1035,6 +1091,8 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
1035
1091
|
json(API + '/me').then(function (data) {
|
|
1036
1092
|
var me = data && (data.builder || data.me);
|
|
1037
1093
|
if (!me || !me.github_login) return;
|
|
1094
|
+
/* the start-a-project guide is for a visitor who has not signed up (task 1005275) */
|
|
1095
|
+
document.querySelectorAll('[data-guide-door]').forEach(function (d) { d.removeAttribute('data-guide-door'); });
|
|
1038
1096
|
var slot = document.getElementById('who');
|
|
1039
1097
|
if (!slot) return;
|
|
1040
1098
|
slot.textContent = '';
|
|
@@ -1302,6 +1360,8 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
1302
1360
|
});
|
|
1303
1361
|
})();
|
|
1304
1362
|
</script>
|
|
1363
|
+
<!-- the start-a-project guide (task 1005275) -->
|
|
1364
|
+
<script src="/assets/guide-scene.js"></script>
|
|
1305
1365
|
<!-- the Artist review button: signed-in builders only (task 1004499) -->
|
|
1306
1366
|
<script src="/assets/artist-review.js" data-hall="{{buildersOrigin}}"></script>
|
|
1307
1367
|
</body>
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.16",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.25.
|
|
9
|
+
"version": "1.25.16",
|
|
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.25.
|
|
3
|
+
"version": "1.25.16",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -11807,7 +11807,19 @@
|
|
|
11807
11807
|
"text": "The project guide now asks whether you are starting fresh or already have something, and for an existing project plans from your own description, without ever reading your files or code."
|
|
11808
11808
|
}
|
|
11809
11809
|
],
|
|
11810
|
-
"1.25.
|
|
11810
|
+
"1.25.16": [
|
|
11811
|
+
{
|
|
11812
|
+
"id": "1005110",
|
|
11813
|
+
"text": "Genesis setup now shows each stage's work as a checklist that fills in as tasks ship, and a step that must wait for the one before it is clearly locked until then."
|
|
11814
|
+
},
|
|
11815
|
+
{
|
|
11816
|
+
"id": "1005114",
|
|
11817
|
+
"text": "The project's first setup step now asks the founder real questions — what they are making and why, who it is for, what success looks like in two years, and what others in the area get wrong. Answers are saved as answers, 'not"
|
|
11818
|
+
},
|
|
11819
|
+
{
|
|
11820
|
+
"id": "1005275",
|
|
11821
|
+
"text": "Visitors pressing Start a project can now talk with a game-style guide about their idea, see a plan form and their messages left, then sign up; when the guide is off it works exactly as before."
|
|
11822
|
+
},
|
|
11811
11823
|
{
|
|
11812
11824
|
"id": "1005116",
|
|
11813
11825
|
"text": "Founders now write their own strategy. Each setup stage can be done three ways: answer guided questions, write it freely, or get help from your own AI — and you can switch at any time without losing anything. Your answers are"
|
|
@@ -37,7 +37,9 @@ const ORIGIN_LABEL = { assisted: ' (with AI help)', carried_over: ' (carried ove
|
|
|
37
37
|
function formatStage(s) {
|
|
38
38
|
const out = [`${s.stage[0].toUpperCase()}${s.stage.slice(1)} — working ${s.door}` +
|
|
39
39
|
(s.adopted ? ` · ${s.counts.answered} answered, ${s.counts.not_sure} not sure, ${s.counts.skipped} skipped, ${s.counts.empty} empty` : '')];
|
|
40
|
+
let group = null;
|
|
40
41
|
for (const q of s.questions) {
|
|
42
|
+
if (q.group && q.group !== group) { group = q.group; out.push(` — ${group} —`); }
|
|
41
43
|
out.push(` ${q.id}: ${q.ask}`);
|
|
42
44
|
if (!q.answer) { out.push(' (empty)'); continue; }
|
|
43
45
|
const a = q.answer;
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.25.
|
|
84
|
+
const CORE_VERSION = '1.25.16'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/genesis_home.mjs
CHANGED
|
@@ -46,17 +46,54 @@ test('genesis files a task per stage, keyed by a ref that names the step, not it
|
|
|
46
46
|
|
|
47
47
|
test('the seeder files every genesis step once, under the catch-all, and never twice', async () => {
|
|
48
48
|
const filed = [];
|
|
49
|
+
const edges = [];
|
|
49
50
|
const deps = {
|
|
50
51
|
pool: { query: async () => ({ rows: [{ id: 'V1' }] }) },
|
|
51
|
-
createTask: async (row) => { filed.push(row); },
|
|
52
|
+
createTask: async (row) => { filed.push({ ...row, id: 100 + filed.length }); },
|
|
52
53
|
findTaskBySourceRef: async ({ source, sourceRef }) => filed.find((r) => r.source === source && r.sourceRef === sourceRef) || null,
|
|
54
|
+
addTaskDependency: async (id, dep) => { edges.push([id, dep]); },
|
|
53
55
|
};
|
|
54
56
|
const first = await seed.seedGenesisStages({ builderId: 1 }, deps);
|
|
55
57
|
assert.equal(first.seeded, 5);
|
|
56
58
|
assert.ok(filed.every((r) => r.status === 'ready' && r.source === 'genesis' && r.allowCatchAll === true && r.versionId === 'V1'));
|
|
59
|
+
assert.deepEqual(edges, [], 'a new project: every stage has one step, nothing waits on anything');
|
|
57
60
|
const again = await seed.seedGenesisStages({ builderId: 1, existing: true }, deps);
|
|
58
61
|
assert.equal(again.seeded, 1, 'a second run files only the step that was not there (the brownfield one)');
|
|
59
62
|
assert.equal(filed.length, 6);
|
|
63
|
+
const scope = filed.find((r) => r.sourceRef === 'genesis:scope');
|
|
64
|
+
const gather = filed.find((r) => r.sourceRef === 'genesis:scope:existing');
|
|
65
|
+
assert.deepEqual(edges, [[scope.id, gather.id]], 'writing the scope waits on gathering what exists (task 1005110)');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('the checklist declares order only where it is real: brownfield scope (task 1005110)', () => {
|
|
69
|
+
const fresh = genesisChecklist({});
|
|
70
|
+
assert.ok(Object.values(fresh).flat().every((t) => t.after === null), 'a new project: all open');
|
|
71
|
+
const old = genesisChecklist({ existing: true });
|
|
72
|
+
assert.deepEqual(old.scope.map((t) => [t.ref, t.after]), [
|
|
73
|
+
['genesis:scope:existing', null],
|
|
74
|
+
['genesis:scope', 'genesis:scope:existing'],
|
|
75
|
+
]);
|
|
76
|
+
for (const k of ['strategize', 'charter', 'legalize', 'legislation']) assert.equal(old[k][0].after, null);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('the board read says what each task waits on, from the dependency edges (task 1005110)', async () => {
|
|
80
|
+
let seen = '';
|
|
81
|
+
const pool = { query: async (sql) => {
|
|
82
|
+
seen = sql;
|
|
83
|
+
return { rows: [
|
|
84
|
+
{ id: 1, title: 'Gather', status: 'ready', description: '', source_ref: 'genesis:scope:existing', waits_on: [] },
|
|
85
|
+
{ id: 2, title: 'Write', status: 'ready', description: '', source_ref: 'genesis:scope', waits_on: [{ id: 1, title: 'Gather' }] },
|
|
86
|
+
] };
|
|
87
|
+
} };
|
|
88
|
+
const out = await seed.genesisStageTasks({ pool });
|
|
89
|
+
assert.match(seen, /FROM dependencies d/);
|
|
90
|
+
assert.match(seen, /x\.status <> 'shipped'/, 'the claim gate\'s own rule: only a shipped task stops blocking');
|
|
91
|
+
assert.deepEqual(out.scope[1].waits_on, [{ id: '1', title: 'Gather' }]);
|
|
92
|
+
const g = gh.shapeGenesis({ mode: { stage: 'scope', stage_number: 1, stage_count: 5, day: 1 }, plan: gh.parsePlan({}).plan, tasks: out });
|
|
93
|
+
assert.deepEqual(g.stages[0].tasks.map((t) => t.state), ['open', 'locked']);
|
|
94
|
+
const done = gh.shapeGenesis({ mode: { stage: 'scope', stage_number: 1, stage_count: 5, day: 1 }, plan: gh.parsePlan({}).plan,
|
|
95
|
+
tasks: { scope: [{ id: '1', title: 'Gather', status: 'shipped' }, { id: '2', title: 'Write', status: 'ready', waits_on: [] }] } });
|
|
96
|
+
assert.deepEqual(done.stages[0].tasks.map((t) => t.state), ['done', 'open'], 'once the first ships, the second opens');
|
|
60
97
|
});
|
|
61
98
|
|
|
62
99
|
test('the board read groups genesis tasks by the stage their ref names', async () => {
|
|
@@ -389,7 +426,7 @@ function bootCard({ founding, data = { genesis: null } }) {
|
|
|
389
426
|
const attrs = founding ? { 'data-founding': 'on' } : {};
|
|
390
427
|
const sandbox = {
|
|
391
428
|
window: {
|
|
392
|
-
OTB: { escapeHtml: (s) => String(s), statusPill: (s) => `<span>${s}</span>`, contributeHallWidget: (w) => { widget = w; }, toast: (m) => toasts.push(m) },
|
|
429
|
+
OTB: { escapeHtml: (s) => String(s), statusPill: (s) => `<span>${s}</span>`, contributeHallWidget: (w) => { widget = w; }, toast: (m) => toasts.push(m), CHECK_SVG: '<svg/>' },
|
|
393
430
|
BongosClient: { createClient: () => ({ request: async (m, p) => { calls.push(`${m} ${p}`); return { ok: true, status: 200, data }; } }) },
|
|
394
431
|
},
|
|
395
432
|
navigator: { clipboard: { writeText: async (t) => { copied.push(t); } } },
|
|
@@ -537,6 +574,37 @@ test('each open stage task has Copy prompt, and it copies the task\'s own words
|
|
|
537
574
|
assert.match(b.toasts.join(' '), /copied/i);
|
|
538
575
|
});
|
|
539
576
|
|
|
577
|
+
test('the stage reads as a checklist: done, open and locked, and a locked row offers no prompt (task 1005110)', async () => {
|
|
578
|
+
const genesis = gh.shapeGenesis({
|
|
579
|
+
mode: { stage: 'scope', stage_number: 1, stage_count: 5, day: 1 },
|
|
580
|
+
plan: gh.parsePlan({}).plan,
|
|
581
|
+
tasks: { scope: [
|
|
582
|
+
{ id: '301', title: 'Gather what already exists', status: 'ready', description: 'Link it.' },
|
|
583
|
+
{ id: '302', title: 'Write the scope', status: 'ready', description: 'Write it.', waits_on: [{ id: '301', title: 'Gather what already exists' }] },
|
|
584
|
+
], strategize: [{ id: '303', title: 'Strategy', status: 'shipped' }] },
|
|
585
|
+
});
|
|
586
|
+
const b = bootCard({ founding: true, data: { genesis, viewer_id: '1' } });
|
|
587
|
+
await mountClickable(b);
|
|
588
|
+
const html = b.host.innerHTML;
|
|
589
|
+
const row = (id) => html.split('<li ').find((x) => x.includes(`/task/${id}"`)) || '';
|
|
590
|
+
assert.match(row('301'), /class="genesis__task is-open"/);
|
|
591
|
+
assert.match(row('301'), /data-genesis-copy="301"/, 'the open row has Copy prompt');
|
|
592
|
+
assert.match(row('302'), /class="genesis__task is-locked"/);
|
|
593
|
+
assert.match(row('302'), /after Gather what already exists/, 'a locked row says what it waits on');
|
|
594
|
+
assert.doesNotMatch(row('302'), /data-genesis-copy/, 'a locked row offers no prompt for work it cannot claim');
|
|
595
|
+
assert.doesNotMatch(html, /is-finished|line-through/, 'nothing is drawn struck through');
|
|
596
|
+
// The "done" row: the earlier stage is not the one on screen, so check the shape directly.
|
|
597
|
+
const done = gh.shapeGenesis({ mode: { stage: 'scope', stage_number: 1, stage_count: 5, day: 1 }, plan: gh.parsePlan({}).plan,
|
|
598
|
+
tasks: { scope: [{ id: '304', title: 'Gathered', status: 'shipped' }, { id: '305', title: 'Next', status: 'ready' }] } });
|
|
599
|
+
const b2 = bootCard({ founding: true, data: { genesis: done, viewer_id: '1' } });
|
|
600
|
+
await mountClickable(b2);
|
|
601
|
+
const r304 = b2.host.innerHTML.split('<li ').find((x) => x.includes('/task/304"')) || '';
|
|
602
|
+
assert.match(r304, /class="genesis__task is-done"/);
|
|
603
|
+
assert.match(r304, /<span class="genesis__mark" aria-hidden="true"><svg/, 'done fills its circle with the hall\'s tick');
|
|
604
|
+
assert.match(r304, /class="sr-only">Done: /, 'and says so to a screen reader');
|
|
605
|
+
assert.doesNotMatch(r304, /data-genesis-copy/);
|
|
606
|
+
});
|
|
607
|
+
|
|
540
608
|
test('the hall carries the five life plates byte-identical to the world\'s', () => {
|
|
541
609
|
const world = path.join(ROOT, 'modules', 'public-landing', 'public', 'assets', 'world');
|
|
542
610
|
const hall = path.join(ROOT, 'modules', 'hall-ui', 'public', 'genesis');
|
package/tests/genesis_work.mjs
CHANGED
|
@@ -152,6 +152,41 @@ export async function contract(test, label, deps, A) {
|
|
|
152
152
|
assert.match(markdown, /https:\/\/example\.com\/strategy/);
|
|
153
153
|
assert.doesNotMatch(markdown, /## Scope/, 'a stage with nothing in it is left out');
|
|
154
154
|
});
|
|
155
|
+
|
|
156
|
+
// ── the scope interview (task 1005114) ──
|
|
157
|
+
await test(`${label}: scope's interview is its guided door: six questions, all empty, guided first`, async () => {
|
|
158
|
+
const r = await gw.readStage('scope', deps);
|
|
159
|
+
assert.equal(r.work.door, 'guided');
|
|
160
|
+
assert.deepEqual(r.work.questions.map((q) => q.id),
|
|
161
|
+
['what_creating', 'goal_impact', 'success_two_years', 'reach_who', 'work_with_who', 'others_wrong']);
|
|
162
|
+
assert.deepEqual([...new Set(r.work.questions.map((q) => q.group))], ['What and why', 'Who', 'The bet']);
|
|
163
|
+
assert.equal(r.work.counts.empty, 6);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await test(`${label}: scope stores answers, "not sure yet", a skip, an un-answer and a revision`, async () => {
|
|
167
|
+
await gw.setAnswer('scope', 'what_creating', { text: 'A live picture of each business you run.' }, A, deps);
|
|
168
|
+
await gw.setAnswer('scope', 'success_two_years', { state: 'not_sure', text: 'depends on whether others want it' }, A, deps);
|
|
169
|
+
await gw.setAnswer('scope', 'work_with_who', { state: 'skipped' }, A, deps);
|
|
170
|
+
await gw.setAnswer('scope', 'others_wrong', { text: 'They value a business from the outside.' }, A, deps);
|
|
171
|
+
await gw.clearAnswer('scope', 'work_with_who', A, deps);
|
|
172
|
+
let r = await gw.setAnswer('scope', 'what_creating', { text: 'A live picture of each business you run, and your worth across them.' }, A, deps);
|
|
173
|
+
assert.deepEqual(r.work.counts, { total: 6, answered: 2, not_sure: 1, skipped: 0, empty: 3 });
|
|
174
|
+
assert.equal(qOf(r, 'work_with_who').answer, null, 'un-answered is empty, not skipped');
|
|
175
|
+
const h = await gw.answerHistory('scope', 'what_creating', deps);
|
|
176
|
+
assert.equal(h.history.length, 2, 'the founder can see what the answer used to say');
|
|
177
|
+
assert.equal(h.history[1].text, 'A live picture of each business you run.');
|
|
178
|
+
r = await gw.setAnswer('scope', 'success_two_years', { text: 'Three other founders run their estate on it.' }, A, deps);
|
|
179
|
+
assert.equal(qOf(r, 'success_two_years').answer.state, 'answered', '"not sure yet" is easy to come back to');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await test(`${label}: a session planning a goal reads the scope answers, scope first`, async () => {
|
|
183
|
+
const { markdown } = await gw.readContext(deps);
|
|
184
|
+
assert.ok(markdown.indexOf('## Scope') < markdown.indexOf('## Strategize'), 'genesis order');
|
|
185
|
+
assert.match(markdown, /In a few sentences, what are you creating with this project\?\*\*\n A live picture of each business you run, and your worth across them\./);
|
|
186
|
+
assert.match(markdown, /Three other founders run their estate on it\./);
|
|
187
|
+
assert.match(markdown, /They value a business from the outside\./);
|
|
188
|
+
assert.doesNotMatch(markdown, /Who do you want working on it with you/, 'an empty question is not printed');
|
|
189
|
+
});
|
|
155
190
|
}
|
|
156
191
|
|
|
157
192
|
if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
|
@@ -220,6 +255,20 @@ if (import.meta.url === url.pathToFileURL(process.argv[1]).href) {
|
|
|
220
255
|
assert.equal(gw.assistPrompt('decide'), null);
|
|
221
256
|
});
|
|
222
257
|
|
|
258
|
+
await test('the assisted door for scope walks the same interview, ids included, and drafts only from the answers', async () => {
|
|
259
|
+
const p = gw.assistPrompt('scope');
|
|
260
|
+
for (const q of gw.QUESTIONS.scope) assert.ok(p.includes(`[${q.id}] ${q.ask}`), `asks: ${q.id}`);
|
|
261
|
+
assert.match(p, /draft page built only from my answers/);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
await test('CLI: the scope interview prints under its group headings', async () => {
|
|
265
|
+
const deps = { store: memStore() };
|
|
266
|
+
const r = await gw.readStage('scope', deps);
|
|
267
|
+
const out = formatStage(r.work);
|
|
268
|
+
assert.ok(out.indexOf('— What and why —') < out.indexOf('— Who —'));
|
|
269
|
+
assert.ok(out.indexOf('— Who —') < out.indexOf('— The bet —'));
|
|
270
|
+
});
|
|
271
|
+
|
|
223
272
|
await test('question ids are unique and fit the stored shape', async () => {
|
|
224
273
|
for (const [stage, qs] of Object.entries(gw.QUESTIONS)) {
|
|
225
274
|
assert.ok(gw.STAGES.includes(stage));
|