@bongos/core 1.21.27 → 1.21.29
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 +89 -69
- package/docs/api/openapi.json +1 -1
- package/docs/api-reference.md +1 -1
- package/docs/copy-inventory.md +399 -391
- package/docs/copy-registry.json +532 -460
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +68 -0
- package/docs/page-readings.json +3737 -3587
- package/modules/hall-ui/public/atlas.html +3 -1
- package/modules/hall-ui/public/blockers.html +3 -1
- package/modules/hall-ui/public/board-room.html +3 -1
- package/modules/hall-ui/public/city.html +3 -1
- package/modules/hall-ui/public/collab.html +3 -1
- package/modules/hall-ui/public/commands.html +3 -1
- package/modules/hall-ui/public/copy-desk.html +3 -1
- package/modules/hall-ui/public/craft-mode.js +297 -0
- package/modules/hall-ui/public/deploy.html +3 -1
- package/modules/hall-ui/public/diagrams.html +3 -1
- package/modules/hall-ui/public/drachmae.html +3 -1
- package/modules/hall-ui/public/fleet.html +3 -1
- package/modules/hall-ui/public/gate.html +3 -1
- package/modules/hall-ui/public/goal-map.html +3 -1
- package/modules/hall-ui/public/goals.html +3 -1
- package/modules/hall-ui/public/government.html +3 -1
- package/modules/hall-ui/public/idea.html +3 -1
- package/modules/hall-ui/public/ideas.html +3 -1
- package/modules/hall-ui/public/index.html +3 -1
- package/modules/hall-ui/public/modules.html +3 -1
- package/modules/hall-ui/public/primer.html +3 -1
- package/modules/hall-ui/public/profile.html +3 -1
- package/modules/hall-ui/public/project-settings.html +3 -1
- package/modules/hall-ui/public/ranks.html +3 -1
- package/modules/hall-ui/public/roadmap.html +3 -1
- package/modules/hall-ui/public/roster.html +3 -1
- package/modules/hall-ui/public/sessions.html +3 -1
- package/modules/hall-ui/public/settings.html +3 -1
- package/modules/hall-ui/public/shell-trail.js +186 -0
- package/modules/hall-ui/public/shell.js +17 -172
- package/modules/hall-ui/public/street.html +3 -1
- package/modules/hall-ui/public/studio.html +3 -1
- package/modules/hall-ui/public/style.css +228 -0
- package/modules/hall-ui/public/task.html +3 -1
- package/modules/hall-ui/public/thinking.html +3 -1
- package/modules/hall-ui/public/tweak-editor.html +3 -1
- package/modules/hall-ui/public/watch.html +3 -1
- package/modules/hall-ui/public/work.html +3 -1
- package/modules/hall-ui/records/craft-mode.md +22 -0
- package/modules/hall-ui/records/hall-navigation.md +1 -1
- package/modules/provisioning/routes/callback-page.js +28 -65
- package/modules/provisioning/routes/provisioning.js +6 -6
- package/modules/public-landing/public/projects.html +68 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/module-api.js +1 -1
- package/tests/craft_mode.mjs +267 -112
- package/tests/goal_map_page.mjs +2 -2
- package/tests/hall_city.mjs +1 -1
- package/tests/hall_fullscreen.mjs +3 -0
- package/tests/hall_street.mjs +1 -1
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/provisioning_callback_page.mjs +5 -5
- package/tests/provisioning_github_app_routes.mjs +24 -25
- package/tests/wizard_auto_open.mjs +94 -0
- package/tests/wizard_intent_resume.mjs +17 -2
|
@@ -1201,6 +1201,234 @@ html[data-fullscreen="on"][data-fullscreen-plain="on"] .app-content { padding-to
|
|
|
1201
1201
|
pointer-events: none;
|
|
1202
1202
|
}
|
|
1203
1203
|
|
|
1204
|
+
/* THE CRAFT MODE (task 1004492, craft-mode.js). The top bar's switch: one pill
|
|
1205
|
+
holding the four crafts and Legacy, the current one lit. Narrower windows
|
|
1206
|
+
drop the words, and a phone shows only the current mode (or "Mode"), which
|
|
1207
|
+
opens the picker. The picker below asks once per session. Neither paints a
|
|
1208
|
+
mode's colour — the looks are their own task (1004495). */
|
|
1209
|
+
.craft-switch {
|
|
1210
|
+
display: inline-flex;
|
|
1211
|
+
align-items: center;
|
|
1212
|
+
gap: 2px;
|
|
1213
|
+
padding: 3px;
|
|
1214
|
+
border-radius: var(--radius-pill);
|
|
1215
|
+
box-shadow: inset 0 0 0 1px var(--glass-edge);
|
|
1216
|
+
flex-shrink: 0;
|
|
1217
|
+
}
|
|
1218
|
+
.craft-switch__opt,
|
|
1219
|
+
.craft-switch__open {
|
|
1220
|
+
display: inline-flex;
|
|
1221
|
+
align-items: center;
|
|
1222
|
+
gap: 6px;
|
|
1223
|
+
min-width: 28px;
|
|
1224
|
+
height: 28px;
|
|
1225
|
+
padding: 0 10px;
|
|
1226
|
+
border: 0;
|
|
1227
|
+
border-radius: var(--radius-pill);
|
|
1228
|
+
background: none;
|
|
1229
|
+
color: var(--ink-soft);
|
|
1230
|
+
font: inherit;
|
|
1231
|
+
font-size: 13px;
|
|
1232
|
+
cursor: pointer;
|
|
1233
|
+
white-space: nowrap;
|
|
1234
|
+
}
|
|
1235
|
+
.craft-switch__opt:hover,
|
|
1236
|
+
.craft-switch__open:hover { color: var(--ink); background: var(--glass); }
|
|
1237
|
+
.craft-switch__opt[aria-pressed="true"] { color: var(--ink); background: var(--glass-strong); }
|
|
1238
|
+
.craft-switch__opt:focus-visible,
|
|
1239
|
+
.craft-switch__open:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
|
|
1240
|
+
.craft-switch__icon { width: 15px; height: 15px; flex-shrink: 0; }
|
|
1241
|
+
.craft-switch__open { display: none; }
|
|
1242
|
+
@media (max-width: 1180px) {
|
|
1243
|
+
.craft-switch__opt { padding: 0 7px; }
|
|
1244
|
+
.craft-switch__opt .craft-switch__label { display: none; }
|
|
1245
|
+
}
|
|
1246
|
+
@media (max-width: 640px) {
|
|
1247
|
+
.craft-switch { padding: 2px; }
|
|
1248
|
+
.craft-switch__opt[aria-pressed="false"] { display: none; }
|
|
1249
|
+
.craft-switch--unset .craft-switch__open { display: inline-flex; padding: 0 7px; }
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/* The picker: a dialog over a scrim, four doors in a row and Legacy beside the
|
|
1253
|
+
"ask me again" box. Each door carries its scene from the approved prototype,
|
|
1254
|
+
which is night-dark in either theme because every scene is a night one. */
|
|
1255
|
+
html.craft-picker-open { overflow: hidden; }
|
|
1256
|
+
.craft-picker {
|
|
1257
|
+
position: fixed;
|
|
1258
|
+
inset: 0;
|
|
1259
|
+
z-index: var(--z-modal);
|
|
1260
|
+
display: flex;
|
|
1261
|
+
align-items: flex-start;
|
|
1262
|
+
justify-content: center;
|
|
1263
|
+
padding: max(24px, 8vh) 16px 24px;
|
|
1264
|
+
overflow-y: auto;
|
|
1265
|
+
background: var(--scrim);
|
|
1266
|
+
}
|
|
1267
|
+
.craft-picker__panel {
|
|
1268
|
+
position: relative;
|
|
1269
|
+
width: min(1120px, 100%);
|
|
1270
|
+
box-sizing: border-box;
|
|
1271
|
+
display: flex;
|
|
1272
|
+
flex-direction: column;
|
|
1273
|
+
gap: 12px;
|
|
1274
|
+
padding: 40px 44px 30px;
|
|
1275
|
+
border-radius: 22px;
|
|
1276
|
+
background: var(--bg-card);
|
|
1277
|
+
color: var(--ink);
|
|
1278
|
+
box-shadow: 0 0 0 1px var(--glass-edge), 0 24px 64px rgba(0, 0, 0, .35);
|
|
1279
|
+
}
|
|
1280
|
+
.craft-picker__close {
|
|
1281
|
+
position: absolute;
|
|
1282
|
+
top: 14px;
|
|
1283
|
+
right: 14px;
|
|
1284
|
+
display: inline-flex;
|
|
1285
|
+
align-items: center;
|
|
1286
|
+
justify-content: center;
|
|
1287
|
+
width: 36px;
|
|
1288
|
+
height: 36px;
|
|
1289
|
+
border: 0;
|
|
1290
|
+
border-radius: var(--radius-pill);
|
|
1291
|
+
background: none;
|
|
1292
|
+
color: var(--ink-soft);
|
|
1293
|
+
cursor: pointer;
|
|
1294
|
+
}
|
|
1295
|
+
.craft-picker__close svg { width: 18px; height: 18px; }
|
|
1296
|
+
.craft-picker__close:hover { color: var(--ink); background: var(--glass); }
|
|
1297
|
+
.craft-picker__eyebrow {
|
|
1298
|
+
margin: 0;
|
|
1299
|
+
font-family: var(--label-face);
|
|
1300
|
+
font-size: var(--label-size);
|
|
1301
|
+
letter-spacing: var(--label-space);
|
|
1302
|
+
text-transform: var(--label-case);
|
|
1303
|
+
color: var(--ink-faint);
|
|
1304
|
+
}
|
|
1305
|
+
.craft-picker__title {
|
|
1306
|
+
margin: 0;
|
|
1307
|
+
font-family: var(--font-display);
|
|
1308
|
+
font-size: clamp(28px, 4vw, 40px);
|
|
1309
|
+
font-weight: 300;
|
|
1310
|
+
letter-spacing: -0.03em;
|
|
1311
|
+
line-height: 1.15;
|
|
1312
|
+
}
|
|
1313
|
+
.craft-picker__lede { margin: 0 0 16px; font-size: 16px; line-height: 1.5; color: var(--ink-soft); }
|
|
1314
|
+
.craft-picker__doors {
|
|
1315
|
+
display: grid;
|
|
1316
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1317
|
+
gap: 16px;
|
|
1318
|
+
}
|
|
1319
|
+
.craft-door {
|
|
1320
|
+
display: flex;
|
|
1321
|
+
flex-direction: column;
|
|
1322
|
+
align-items: stretch;
|
|
1323
|
+
gap: 8px;
|
|
1324
|
+
padding: 0 0 20px;
|
|
1325
|
+
overflow: hidden;
|
|
1326
|
+
border: 0;
|
|
1327
|
+
border-radius: 14px;
|
|
1328
|
+
background: var(--bg);
|
|
1329
|
+
color: var(--ink);
|
|
1330
|
+
font: inherit;
|
|
1331
|
+
text-align: left;
|
|
1332
|
+
cursor: pointer;
|
|
1333
|
+
box-shadow: inset 0 0 0 1px var(--glass-edge);
|
|
1334
|
+
transition: transform 200ms cubic-bezier(.2, .7, .3, 1), box-shadow 200ms;
|
|
1335
|
+
}
|
|
1336
|
+
.craft-door:hover { transform: translateY(-3px); box-shadow: inset 0 0 0 1px var(--rule); }
|
|
1337
|
+
.craft-door:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
|
|
1338
|
+
.craft-door--pre { box-shadow: inset 0 0 0 2px var(--accent); }
|
|
1339
|
+
.craft-door__head { display: flex; align-items: center; gap: 10px; padding: 10px 20px 0; }
|
|
1340
|
+
.craft-door__icon { width: 20px; height: 20px; flex-shrink: 0; }
|
|
1341
|
+
.craft-door__name { font-size: 20px; font-weight: 500; letter-spacing: -0.02em; }
|
|
1342
|
+
.craft-door__line { padding: 0 20px; font-size: 14px; line-height: 1.45; color: var(--ink-soft); }
|
|
1343
|
+
.craft-art {
|
|
1344
|
+
position: relative;
|
|
1345
|
+
display: block;
|
|
1346
|
+
height: 110px;
|
|
1347
|
+
overflow: hidden;
|
|
1348
|
+
background: #101012;
|
|
1349
|
+
}
|
|
1350
|
+
.craft-art--artistry { background: #0c1122; }
|
|
1351
|
+
.craft-art--ideating { background: #05060d; }
|
|
1352
|
+
.craft-art--governing { background: #000; }
|
|
1353
|
+
.craft-art__dot { position: absolute; border-radius: 999px; box-sizing: border-box; }
|
|
1354
|
+
.craft-art__dot--star { background: #d8dcf0; }
|
|
1355
|
+
.craft-art__dot--lit { background: var(--accent); border-radius: 0; }
|
|
1356
|
+
.craft-art__dot--gold { background: #f2d27a; }
|
|
1357
|
+
.craft-art__dot--glow { background: #f2d27a; box-shadow: 0 0 10px rgba(242, 210, 122, .7); }
|
|
1358
|
+
.craft-art__dot--proto { box-shadow: inset 0 0 0 1px rgba(242, 210, 122, .55); }
|
|
1359
|
+
.craft-art__bar { position: absolute; height: 10px; border-radius: 4px; background: rgba(255, 255, 255, .12); }
|
|
1360
|
+
.craft-art__bar--dim { background: rgba(255, 255, 255, .08); }
|
|
1361
|
+
.craft-art__pill { position: absolute; width: 84px; height: 26px; border-radius: 999px; background: var(--accent); }
|
|
1362
|
+
.craft-art__card {
|
|
1363
|
+
position: absolute; right: 20px; top: 20px; width: 60px; height: 72px; border-radius: 8px;
|
|
1364
|
+
background: rgba(255, 255, 255, .06); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12);
|
|
1365
|
+
}
|
|
1366
|
+
.craft-art__moon {
|
|
1367
|
+
position: absolute; right: 30px; top: 18px; width: 28px; height: 28px; border-radius: 999px;
|
|
1368
|
+
background: #f1e6d2; box-shadow: 0 0 0 9px rgba(241, 230, 210, .10);
|
|
1369
|
+
}
|
|
1370
|
+
.craft-art__ground { position: absolute; left: 0; right: 0; bottom: 0; height: 32px; background: #090b16; }
|
|
1371
|
+
.craft-art__house { position: absolute; bottom: 0; background: #0a0d1a; }
|
|
1372
|
+
.craft-art__ring {
|
|
1373
|
+
position: absolute; left: 50%; top: 14px; width: 82px; height: 82px; margin-left: -41px;
|
|
1374
|
+
border-radius: 999px; box-shadow: inset 0 0 0 1px rgba(242, 210, 122, .22);
|
|
1375
|
+
}
|
|
1376
|
+
.craft-art__city { position: absolute; left: 50%; top: 0; width: 300px; height: 110px; margin-left: -150px; }
|
|
1377
|
+
.craft-picker__foot {
|
|
1378
|
+
display: flex;
|
|
1379
|
+
flex-wrap: wrap;
|
|
1380
|
+
align-items: center;
|
|
1381
|
+
justify-content: space-between;
|
|
1382
|
+
gap: 12px 24px;
|
|
1383
|
+
margin-top: 14px;
|
|
1384
|
+
padding-top: 18px;
|
|
1385
|
+
border-top: 1px solid var(--rule-soft);
|
|
1386
|
+
}
|
|
1387
|
+
.craft-picker__again {
|
|
1388
|
+
display: inline-flex;
|
|
1389
|
+
align-items: center;
|
|
1390
|
+
gap: 10px;
|
|
1391
|
+
min-height: 24px;
|
|
1392
|
+
font-size: 14px;
|
|
1393
|
+
color: var(--ink-soft);
|
|
1394
|
+
cursor: pointer;
|
|
1395
|
+
}
|
|
1396
|
+
.craft-picker__again input { width: 18px; height: 18px; margin: 0; accent-color: var(--accent); }
|
|
1397
|
+
.craft-picker__side { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 12px 18px; }
|
|
1398
|
+
.craft-picker__hint { font-size: 13px; color: var(--ink-faint); }
|
|
1399
|
+
.craft-picker__legacy {
|
|
1400
|
+
display: inline-flex;
|
|
1401
|
+
align-items: center;
|
|
1402
|
+
height: 40px;
|
|
1403
|
+
padding: 0 18px;
|
|
1404
|
+
border: 0;
|
|
1405
|
+
border-radius: var(--radius-pill);
|
|
1406
|
+
background: none;
|
|
1407
|
+
color: var(--ink);
|
|
1408
|
+
font: inherit;
|
|
1409
|
+
font-size: 14px;
|
|
1410
|
+
cursor: pointer;
|
|
1411
|
+
box-shadow: inset 0 0 0 1px var(--rule);
|
|
1412
|
+
}
|
|
1413
|
+
.craft-picker__legacy:hover { background: var(--glass); }
|
|
1414
|
+
.craft-picker__legacy:focus-visible,
|
|
1415
|
+
.craft-picker__close:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
1416
|
+
@media (max-width: 900px) {
|
|
1417
|
+
.craft-picker__doors { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
1418
|
+
}
|
|
1419
|
+
@media (max-width: 640px) {
|
|
1420
|
+
.craft-picker { padding: 12px; }
|
|
1421
|
+
.craft-picker__panel { padding: 28px 18px 20px; gap: 8px; border-radius: 18px; }
|
|
1422
|
+
.craft-picker__doors { grid-template-columns: minmax(0, 1fr); gap: 10px; }
|
|
1423
|
+
.craft-door { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px 0; padding: 0 0 14px; }
|
|
1424
|
+
.craft-art { flex: 0 0 100%; height: 96px; }
|
|
1425
|
+
.craft-art__city { top: -8px; }
|
|
1426
|
+
.craft-door__head { padding: 10px 16px 0; }
|
|
1427
|
+
.craft-door__name { font-size: 18px; }
|
|
1428
|
+
.craft-door__line { flex: 0 0 100%; padding: 0 16px; box-sizing: border-box; }
|
|
1429
|
+
.craft-picker__legacy { height: auto; min-height: 40px; white-space: normal; text-align: left; }
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1204
1432
|
/* THE BACK LINK (task 1004430): at the head of the bar when the page was
|
|
1205
1433
|
reached from inside another hall page. 24px floor on both axes. */
|
|
1206
1434
|
.topbar-back {
|
|
@@ -54,7 +54,9 @@
|
|
|
54
54
|
|
|
55
55
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
56
56
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
57
|
-
<script src="/builders/shell.js?v=2026-10-02-
|
|
57
|
+
<script src="/builders/shell-trail.js?v=2026-10-02-craft"></script>
|
|
58
|
+
<script src="/builders/craft-mode.js?v=2026-10-02-craft"></script>
|
|
59
|
+
<script src="/builders/shell.js?v=2026-10-02-craft"></script>
|
|
58
60
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
59
61
|
<script src="/builders/palette.js" defer></script>
|
|
60
62
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so task.js can pull from it. -->
|
|
@@ -138,7 +138,9 @@
|
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
140
|
|
|
141
|
-
<script src="/builders/shell.js?v=2026-10-02-
|
|
141
|
+
<script src="/builders/shell-trail.js?v=2026-10-02-craft"></script>
|
|
142
|
+
<script src="/builders/craft-mode.js?v=2026-10-02-craft"></script>
|
|
143
|
+
<script src="/builders/shell.js?v=2026-10-02-craft"></script>
|
|
142
144
|
<script src="/builders/palette.js" defer></script>
|
|
143
145
|
|
|
144
146
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
@@ -142,7 +142,9 @@
|
|
|
142
142
|
|
|
143
143
|
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
144
144
|
|
|
145
|
-
<script src="/builders/shell.js?v=2026-10-02-
|
|
145
|
+
<script src="/builders/shell-trail.js?v=2026-10-02-craft"></script>
|
|
146
|
+
<script src="/builders/craft-mode.js?v=2026-10-02-craft"></script>
|
|
147
|
+
<script src="/builders/shell.js?v=2026-10-02-craft"></script>
|
|
146
148
|
<script src="/builders/palette.js" defer></script>
|
|
147
149
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
148
150
|
<script src="/builders/brand-holes.js?v=2026-09-29-aqfix"></script>
|
|
@@ -162,7 +162,9 @@
|
|
|
162
162
|
|
|
163
163
|
<!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
|
|
164
164
|
chrome exists before the page script runs. -->
|
|
165
|
-
<script src="/builders/shell.js?v=2026-10-02-
|
|
165
|
+
<script src="/builders/shell-trail.js?v=2026-10-02-craft"></script>
|
|
166
|
+
<script src="/builders/craft-mode.js?v=2026-10-02-craft"></script>
|
|
167
|
+
<script src="/builders/shell.js?v=2026-10-02-craft"></script>
|
|
166
168
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
167
169
|
<script src="/builders/palette.js" defer></script>
|
|
168
170
|
<!-- shared frontend toolkit (window.OTB) — loaded before watch.js, which pulls from it. -->
|
|
@@ -171,7 +171,9 @@
|
|
|
171
171
|
|
|
172
172
|
<!-- The app shell (sidebar + top bar + footer). Dependency-free; runs at parse
|
|
173
173
|
time so the chrome exists before the deferred page scripts execute. -->
|
|
174
|
-
<script src="/builders/shell.js?v=2026-10-02-
|
|
174
|
+
<script src="/builders/shell-trail.js?v=2026-10-02-craft"></script>
|
|
175
|
+
<script src="/builders/craft-mode.js?v=2026-10-02-craft"></script>
|
|
176
|
+
<script src="/builders/shell.js?v=2026-10-02-craft"></script>
|
|
175
177
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
176
178
|
<script src="/builders/palette.js" defer></script>
|
|
177
179
|
<!-- shared frontend toolkit (window.OTB) — loaded FIRST so work.js can pull from it. -->
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Craft mode — the entry picker and the top-bar switch (task 1004492, goal 1000095 WA6)
|
|
2
|
+
|
|
3
|
+
Covers: every shelled hall page (`craft-mode.js`, `shell.js`, `style.css`), and the carve of the Back link + trail into `shell-trail.js`.
|
|
4
|
+
|
|
5
|
+
## What it is
|
|
6
|
+
|
|
7
|
+
- On entering the hall, once per browser session, a signed-in builder is asked **"What are you doing now?"**: Building, Artistry, Ideating, Governing, or **Legacy mode** (the owner's fifth choice, 2026-10-01: the hall exactly as it was). The approved prototype is the design canvas linked on the task; the doors carry its four scenes (the work desk, the night room, the sky, the governor city).
|
|
8
|
+
- The answer is ONE attribute, `html[data-craft="building|artistry|ideating|governing|legacy"]`, readable by every hall page. Every change also fires `otb:craft` on `document` with `{ detail: { mode } }`, so a page can react without polling.
|
|
9
|
+
- The top bar carries a switch with all five; pressing one changes the mode without a reload. Under 1180px it drops the words; at phone width only the current mode shows (or a "Choose your mode" button when there is none), and tapping it opens the picker.
|
|
10
|
+
- Artistry is pre-selected for a builder whose `main_discipline` is `artist` (or, with no main role, whose first craft is).
|
|
11
|
+
|
|
12
|
+
## The rules an edit here must keep
|
|
13
|
+
|
|
14
|
+
- **It is a mode the hall wears, never a second hall** (the founding-mode precedent). This task records and shows the choice; what each mode changes — the nav (1004493), the look (1004494/1004495), the mode tools (1004496–1004498) — reads the attribute and is its own task.
|
|
15
|
+
- **No mode is the default and the fallback.** Closing the picker, storage that throws, and a page with `<body data-craft-mode="off">` all leave the attribute off, and the hall renders as it did before craft modes: `tests/craft_mode.mjs` boots the shell with and without `craft-mode.js` and requires the root attributes and the sidebar to be identical and the top bar to differ by the switch alone.
|
|
16
|
+
- **Once per session**: `sessionStorage['hall-craft']` holds the mode and `['hall-craft-asked']` that the picker was shown. Unticking "Ask me again next session" writes `localStorage['hall-craft-keep']`, which later sessions start from. A stored value that is not one of the five is ignored. A browser whose storage throws is never asked — it could not remember being asked.
|
|
17
|
+
- **The picker waits for a signed-in builder.** `shell.js` hands `onAccess(access, builder)` what `/me` said from `applyAccess`, so both callers (the index's `hall-render.js` and the shell's own probe) reach it, and a signed-out visitor is never asked.
|
|
18
|
+
- **Load order**: `shell-trail.js`, then `craft-mode.js`, then `shell.js`, deferred alike, on every hall page (test-pinned). `shell.js` reads both at boot and runs without either: the vm tests that boot `shell.js` alone still work.
|
|
19
|
+
|
|
20
|
+
## The carve
|
|
21
|
+
|
|
22
|
+
`shell.js` sat at exactly 1,500 lines, the fitness ratchet, so the Back link + trail section (tasks 1004430/1004476/1004477) moved verbatim into `shell-trail.js` (`window.OTBShellTrail.make({ esc, nav })`). Behaviour is unchanged; `tests/hall_fullscreen.mjs` loads it before the shell and still drives the trail through the real top bar.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hall navigation — the Back link and its trail, and the standard full-screen switch (tasks 1004430, 1004477, 1004476)
|
|
2
2
|
|
|
3
|
-
Covers: every shelled hall page (`shell.js`, `style.css`), plus `goal-map.html` joining the chrome.
|
|
3
|
+
Covers: every shelled hall page (`shell.js`, `style.css`), plus `goal-map.html` joining the chrome. Since task 1004492 the Back link and trail code lives in `shell-trail.js` (carved out of `shell.js` unchanged; see `craft-mode.md`).
|
|
4
4
|
|
|
5
5
|
## The Back link
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Shape: no DB, no network, no request object — given a plain options bag it returns
|
|
17
17
|
// a string. `esc` and `callbackPage` are the whole surface; PAGE_STYLE and
|
|
18
|
-
//
|
|
18
|
+
// HANDBACK_SCRIPT stay private because they are only ever this page's own body.
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
21
21
|
const api = require('../../../src/module-api');
|
|
@@ -53,73 +53,44 @@ h1{font-family:"Baloo 2",system-ui,sans-serif;font-weight:700;font-size:1.5rem;l
|
|
|
53
53
|
.sub p{margin:.6rem 0}
|
|
54
54
|
code{font-family:"Fira Code",ui-monospace,SFMono-Regular,monospace;background:rgba(56,189,248,.14);color:var(--sky600);padding:.12em .45em;border-radius:6px;font-size:.9em;overflow-wrap:anywhere}
|
|
55
55
|
@media (prefers-color-scheme:dark){code{color:var(--sky)}}
|
|
56
|
-
.steps{display:flex;align-items:flex-start;justify-content:center;margin:1.9rem 0 1.1rem}
|
|
57
|
-
.step{flex:1 1 0;max-width:9rem;position:relative}
|
|
58
|
-
.step::before{content:"";position:absolute;top:16px;left:-50%;width:100%;height:3px;background:var(--line);z-index:0;transition:background .4s ease}
|
|
59
|
-
.step:first-child::before{display:none}
|
|
60
|
-
.dot{width:34px;height:34px;border-radius:50%;margin:0 auto;display:grid;place-items:center;background:var(--bg3);border:2px solid var(--line);color:var(--inkSoft);font-weight:700;font-size:.95rem;position:relative;z-index:1;transition:all .4s ease}
|
|
61
|
-
.lbl{margin-top:.55rem;font-size:.78rem;color:var(--inkSoft);font-weight:600;line-height:1.25;padding:0 .2rem}
|
|
62
|
-
.step.done .dot{background:var(--ok);border-color:var(--ok);color:#fff}
|
|
63
|
-
.step.done::before{background:var(--ok)}
|
|
64
|
-
.step.active .dot{border-color:var(--sun);color:var(--sun600);animation:pulse 1.5s ease-in-out infinite}
|
|
65
|
-
.step.active .lbl{color:var(--ink)}
|
|
66
|
-
.step.active .dot::after{content:"";position:absolute;inset:-3px;border-radius:50%;border:2px solid transparent;border-top-color:var(--sun);animation:spin .8s linear infinite}
|
|
67
|
-
@keyframes spin{to{transform:rotate(360deg)}}
|
|
68
|
-
@keyframes pulse{0%,100%{box-shadow:0 0 0 4px rgba(249,115,22,.15)}50%{box-shadow:0 0 0 9px rgba(249,115,22,.04)}}
|
|
69
|
-
@media (prefers-reduced-motion:reduce){.step.active .dot,.step.active .dot::after{animation:none}}
|
|
70
|
-
.status-line{min-height:1.4em;color:var(--inkSoft);font-size:.94rem;margin:.4rem auto 0;max-width:24rem;line-height:1.45}
|
|
71
56
|
.cta{margin-top:1.4rem}
|
|
72
57
|
.btn{display:inline-block;background:var(--sun);color:var(--sunInk);text-decoration:none;font-weight:700;font-family:"Baloo 2",system-ui,sans-serif;padding:.72rem 1.5rem;border-radius:999px;font-size:1.02rem;box-shadow:0 10px 22px -8px rgba(249,115,22,.7);transition:transform .15s ease,box-shadow .15s ease}
|
|
73
58
|
.btn:hover{transform:translateY(-1px);box-shadow:0 14px 28px -8px rgba(249,115,22,.8)}
|
|
74
59
|
.muted{color:var(--inkSoft);opacity:.9;font-size:.85rem;margin:1.1rem auto 0;max-width:24rem;line-height:1.45}
|
|
75
|
-
.hidden{display:none!important}
|
|
76
60
|
`;
|
|
77
61
|
|
|
78
|
-
// Client script for the
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
62
|
+
// Client script for the HAND-BACK variant (task 1004548; replaces the task 2096 progress
|
|
63
|
+
// stepper). Placing the credentials takes minutes, and the founder used to wait for it
|
|
64
|
+
// HERE, in a second tab, away from the checklist that still had steps for them. Now the
|
|
65
|
+
// page hands them straight back: as the wizard's pop-up it closes itself so the checklist
|
|
66
|
+
// tab is in front again (that tab is already polling the same status and shows the wait);
|
|
67
|
+
// if the browser refuses the close, or this was a same-tab return (pop-up blocked), this
|
|
68
|
+
// tab becomes the checklist. The one-time state rides the URL FRAGMENT — never sent to a
|
|
69
|
+
// server — so a checklist opened here can keep watching the same attempt.
|
|
70
|
+
// Deliberately contains NO backticks and NO ${...} so it survives the outer template
|
|
71
|
+
// literal verbatim; the only server data it reads is the JSON config block above it.
|
|
72
|
+
const HANDBACK_SCRIPT = `
|
|
87
73
|
(function(){
|
|
88
74
|
var cfg=window.__CB_SETUP__||{};
|
|
89
|
-
var
|
|
90
|
-
if(
|
|
91
|
-
var
|
|
92
|
-
var
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
the checklist is in the other tab, so this one may auto-open the instance. Same-tab fallback (pop-up blocked,
|
|
97
|
-
form target=_self): this IS the founder's checklist tab — never navigate it away; offer both doors (task 1003236). */
|
|
98
|
-
var popup=!!window.opener||window.name==='ghAppSetup';
|
|
99
|
-
var finished=false,tries=0,manual=false;
|
|
100
|
-
function dot(n){return steps.querySelector('[data-step="'+n+'"]');}
|
|
101
|
-
function setActive(n){for(var i=1;i<=3;i++){var el=dot(i);if(!el)continue;el.classList.remove('active','done');if(i<n){el.classList.add('done');el.querySelector('.dot').textContent='\\u2713';}else if(i===n){el.classList.add('active');}}}
|
|
102
|
-
function allDone(){for(var i=1;i<=3;i++){var el=dot(i);if(!el)continue;el.classList.remove('active');el.classList.add('done');el.querySelector('.dot').textContent='\\u2713';}}
|
|
103
|
-
function ready(){if(finished)return;finished=true;allDone();statusEl.textContent='Your instance is ready \\u2014 sign in with GitHub to claim it as owner.';cta.classList.remove('hidden');
|
|
104
|
-
if(!popup){go.textContent='Open '+domain+' \\u2192';if(back)back.classList.remove('hidden');if(foot)foot.textContent='Your setup checklist is waiting where you left it \\u2014 finish it first, or open your instance now.';return;}
|
|
105
|
-
var n=4;(function tick(){if(n<=0){location.href=target;return;}go.textContent='Opening '+domain+' in '+n+'\\u2026';n--;setTimeout(tick,1000);})();}
|
|
106
|
-
function failed(){if(finished)return;finished=true;statusEl.textContent='Setup didn\\u2019t finish. Open your instance page and start the GitHub sign-in setup again.';cta.classList.remove('hidden');go.textContent='Open '+domain+' \\u2192';}
|
|
107
|
-
function revealManual(){if(manual||finished)return;manual=true;cta.classList.remove('hidden');go.textContent='Open '+domain+' \\u2192';if(foot)foot.textContent='This is taking a little longer than usual \\u2014 you can open your instance now, or keep waiting.';}
|
|
108
|
-
function schedule(){if(!finished)setTimeout(poll,3000);}
|
|
109
|
-
function poll(){tries++;fetch(pollUrl,{headers:{accept:'application/json'},cache:'no-store'}).then(function(r){return r.ok?r.json():null;}).then(function(j){if(!j){schedule();return;}if(j.ready||j.status==='done'){ready();return;}if(j.status==='error'||j.status==='expired'){failed();return;}setActive(2);if(tries===40)revealManual();schedule();}).catch(function(){schedule();});}
|
|
110
|
-
setActive(2);poll();
|
|
75
|
+
var back=cfg.back||'/projects?view=new';
|
|
76
|
+
if(cfg.state)back+='#ghstate='+encodeURIComponent(cfg.state);
|
|
77
|
+
var link=document.getElementById('back');if(link)link.href=back;
|
|
78
|
+
var popup=false;
|
|
79
|
+
try{popup=!!window.opener&&!window.opener.closed;}catch(e){}
|
|
80
|
+
if(popup){try{window.opener.focus();}catch(e){}try{window.close();}catch(e){}}
|
|
81
|
+
setTimeout(function(){location.replace(back);},popup?700:300);
|
|
111
82
|
})();
|
|
112
83
|
`;
|
|
113
84
|
|
|
114
85
|
// The standalone landing page for the public GitHub redirect (the mothership serves it;
|
|
115
86
|
// it is NOT the hall shell). Two variants share the Cloud Bongos brand shell (task 2096):
|
|
116
|
-
// •
|
|
117
|
-
//
|
|
87
|
+
// • hand-back — ok + state + domain: a brief "GitHub app created" that returns the
|
|
88
|
+
// founder to their setup checklist, where the wait is shown (task 1004548).
|
|
118
89
|
// • message — everything else (errors + the no-domain success fallback): a calm badge
|
|
119
90
|
// + `body` (server-authored, trusted HTML).
|
|
120
91
|
function callbackPage({ ok, title, body, state, domain }) {
|
|
121
|
-
const
|
|
122
|
-
const cfgJson = JSON.stringify({ state: state || '', domain: domain || '' }).replace(/</g, '\\u003c');
|
|
92
|
+
const handback = !!(ok && state && domain);
|
|
93
|
+
const cfgJson = JSON.stringify({ state: state || '', domain: domain || '', back: '/projects?view=new' }).replace(/</g, '\\u003c');
|
|
123
94
|
const head = `<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
124
95
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
125
96
|
<meta name="color-scheme" content="light dark">
|
|
@@ -127,24 +98,16 @@ function callbackPage({ ok, title, body, state, domain }) {
|
|
|
127
98
|
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
128
99
|
<link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@600;700;800&family=Quicksand:wght@400;500;600;700&family=Fira+Code&display=swap" rel="stylesheet">
|
|
129
100
|
<style>${PAGE_STYLE}</style></head><body>`;
|
|
130
|
-
if (
|
|
101
|
+
if (handback) {
|
|
131
102
|
return `${head}
|
|
132
103
|
<main class="card">
|
|
104
|
+
<div class="badge-warn">✓</div>
|
|
133
105
|
<h1>${esc(title)}</h1>
|
|
134
|
-
<p class="sub">
|
|
135
|
-
<div class="
|
|
136
|
-
<div class="step done" data-step="1"><div class="dot">✓</div><div class="lbl">GitHub app created</div></div>
|
|
137
|
-
<div class="step active" data-step="2"><div class="dot">2</div><div class="lbl">Placing credentials</div></div>
|
|
138
|
-
<div class="step" data-step="3"><div class="dot">3</div><div class="lbl">Ready to sign in</div></div>
|
|
139
|
-
</div>
|
|
140
|
-
<p class="status-line" id="status">Hang tight — keep this tab open while we finish.</p>
|
|
141
|
-
<div class="cta hidden" id="cta"><a class="btn" id="go" href="https://${esc(domain)}">Open ${esc(domain)} →</a></div>
|
|
142
|
-
<p class="muted hidden" id="back"><a href="/projects?view=new">Finish your remaining setup steps →</a></p>
|
|
143
|
-
<p class="muted" id="foot">This page opens <code>${esc(domain)}</code> for you the moment it’s ready.</p>
|
|
144
|
-
<noscript><p class="muted">When setup finishes, open <a href="https://${esc(domain)}">${esc(domain)}</a> and sign in with GitHub — that first sign-in makes you the owner.</p></noscript>
|
|
106
|
+
<p class="sub">Taking you back to your setup checklist — it finishes setting up <code>${esc(domain)}</code> there.</p>
|
|
107
|
+
<div class="cta"><a class="btn" id="back" href="/projects?view=new">Back to my checklist →</a></div>
|
|
145
108
|
</main>
|
|
146
109
|
<script>window.__CB_SETUP__=${cfgJson};</script>
|
|
147
|
-
<script>${
|
|
110
|
+
<script>${HANDBACK_SCRIPT}</script>
|
|
148
111
|
</body></html>`;
|
|
149
112
|
}
|
|
150
113
|
return `${head}
|
|
@@ -1391,12 +1391,12 @@ module.exports = function provisioningRoutes() {
|
|
|
1391
1391
|
});
|
|
1392
1392
|
|
|
1393
1393
|
// GET /provisioning/github-app/status?state=<> — the PUBLIC progress read the onboarding
|
|
1394
|
-
//
|
|
1394
|
+
// setup checklist polls (task 1004548) to know when setup is done + where to send the owner.
|
|
1395
1395
|
// Same public-by-state model as the callback: the unguessable, single-use `state` token
|
|
1396
1396
|
// is the only key, and the response reveals only the manifest lifecycle status + the
|
|
1397
1397
|
// instance's own (already-public) domain — never a secret. Per-IP rate-limited because
|
|
1398
1398
|
// the page polls it every ~3s. `ready` is true iff the manifest reached 'done' (creds
|
|
1399
|
-
// placed + /healthz verified) — the
|
|
1399
|
+
// placed + /healthz verified) — the setup checklist then opens https://<domain>.
|
|
1400
1400
|
// rank: public — no session, no mutation, no secret; an unknown state is a 404 no-op.
|
|
1401
1401
|
router.get('/provisioning/github-app/status', statusRateLimit, async (req, res) => {
|
|
1402
1402
|
try {
|
|
@@ -1461,11 +1461,11 @@ module.exports = function provisioningRoutes() {
|
|
|
1461
1461
|
}).catch(() => {});
|
|
1462
1462
|
const inst = await provisioning.getInstanceById(pool, row.instance_id).catch(() => null);
|
|
1463
1463
|
const domain = inst && inst.domain ? inst.domain : null;
|
|
1464
|
-
//
|
|
1465
|
-
//
|
|
1466
|
-
//
|
|
1464
|
+
// Hand-back variant (task 1004548): the page returns the founder to their setup
|
|
1465
|
+
// checklist, which shows the wait for the credentials and opens the instance when
|
|
1466
|
+
// it's ready. Falls back to a static success if we have no domain to point at.
|
|
1467
1467
|
return sendPage(200, domain
|
|
1468
|
-
? callbackPage({ ok: true, title: '
|
|
1468
|
+
? callbackPage({ ok: true, title: 'GitHub app created', state, domain })
|
|
1469
1469
|
: callbackPage({ ok: true, title: 'GitHub app created',
|
|
1470
1470
|
body: '<p>Nice — GitHub created the app. We’re placing its credentials on your instance now; this finishes on its own within a few minutes.</p><p class="muted">You can close this tab.</p>' }));
|
|
1471
1471
|
} catch (err) {
|