@bongos/core 1.19.878 → 1.19.880

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.
Files changed (42) hide show
  1. package/.bongos-core.json +43 -83
  2. package/docs/copy-inventory.md +368 -456
  3. package/docs/copy-registry.json +457 -1344
  4. package/docs/file-map.md +2 -2
  5. package/docs/module-api-changelog.md +4 -0
  6. package/modules/hall-ui/module.json +1 -1
  7. package/modules/hall-ui/public/builders.js +1 -13
  8. package/modules/hall-ui/public/dom-utils.js +22 -6
  9. package/modules/hall-ui/public/goals-render.js +8 -3
  10. package/modules/hall-ui/public/hall-render.js +2 -18
  11. package/modules/hall-ui/public/index.html +0 -3
  12. package/modules/hall-ui/public/panel.css +8 -9
  13. package/modules/hall-ui/public/settings.css +0 -5
  14. package/modules/hall-ui/public/settings.html +0 -29
  15. package/modules/hall-ui/public/settings.js +1 -26
  16. package/modules/hall-ui/public/shell.js +0 -1
  17. package/modules/hall-ui/public/style.css +0 -44
  18. package/modules/hall-ui/public/task.js +6 -2
  19. package/modules/hall-ui/public/watch.html +0 -7
  20. package/modules/hall-ui/public/watch.js +1 -37
  21. package/modules/hall-ui/public/work.js +4 -2
  22. package/package-lock.json +2 -2
  23. package/package.json +1 -1
  24. package/src/bongos/serve-internal.js +11 -11
  25. package/src/module-api.js +1 -1
  26. package/tests/government_page_gates.mjs +1 -1
  27. package/tests/hall_landing_boot.mjs +1 -1
  28. package/tests/hall_nav.mjs +1 -1
  29. package/tests/hall_oversight_world.mjs +5 -5
  30. package/tests/hall_page_gate_map.mjs +11 -2
  31. package/tests/hall_record_world.mjs +5 -0
  32. package/tests/hall_settings_world.mjs +21 -18
  33. package/tests/kind_axis_unset.mjs +195 -0
  34. package/modules/hall-ui/public/box-panel.js +0 -456
  35. package/modules/hall-ui/public/harbor.css +0 -9
  36. package/modules/hall-ui/public/harbor.html +0 -69
  37. package/modules/hall-ui/public/harbor.js +0 -367
  38. package/modules/hall-ui/public/harbor.states.json +0 -77
  39. package/modules/hall-ui/public/pair.css +0 -23
  40. package/modules/hall-ui/public/pair.html +0 -58
  41. package/modules/hall-ui/public/pair.js +0 -132
  42. package/modules/hall-ui/public/pair.states.json +0 -14
package/docs/file-map.md CHANGED
@@ -135,7 +135,7 @@
135
135
  │ ├── sessions/ ← CORE-DOMAIN carve (BV1.R82, default-on): the session_records corpus + data plane (upload, cross-builder search/deep-dive), the BFG read-the-room scorer, session-pulse/stale-claim helpers. Registers no port — reached via HTTP POST to its own /sessions route; resolves the economy `reward` port for the cost-plus token reward. CLAUDE.md, module.json, routes/sessions.js, bfg.js, pulse.js.
136
136
  │ ├── autonomy/ ← CORE-DOMAIN carve (BV1.R83, default-on): the MAS autonomy surfaces — the $0 deterministic autonomy gate (System 3, inert until armed), the conductor/architect CLIs, and the in-process routine timers (incl. the weekly peer-vote-tally poller). Registers no port; resolves the lifecycle `tallyPeerVotes` doorway. CLAUDE.md, module.json, routes/autonomy.js, autonomy.js.
137
137
  │ ├── status-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R84, default-on): the public status dashboard at status.<apex>, moved from public-status/. NO routes/db/port — declares contributes.webSurfaces:[{host:"status.",dir:"public"}], served by the loader web-surface seam (moduleWebSurfaces → serve-internal). CLAUDE.md, module.json, public/{index.html,status.js,style.css,cursors/,snapshots/}.
138
- │ ├── hall-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R85, default-on): the authenticated builders' hall at builders.<apex>, moved from public-builders/ (~13k LOC, the largest). NO routes/db/port — declares contributes.webSurfaces:[{host:"builders.",dir:"public"}] for discovery, but UNLIKE status-ui keeps its DEDICATED serving block in serve-internal (the /builders shim + canonical redirects + citizen/Archon page gates + asset-stamping + hall-widget injection), partitioned out of the generic web-surface loop and served from BUILDERS_DIR. CLAUDE.md, module.json, public/{index.html,builders.js,style.css,work,watch,harbor,gate,pair,settings,ranks,sessions,primer,diagrams,atlas,...}.{html,js},cursors/,chime.wav. Four ARCHETYPE sheets sit beside the per-page ones: `record.css` (the task + idea records, task 1003305), `room.css` (the READING ROOMS — the primer + the diagrams index, task 1003306: the reading measure and rhythm, the table of contents, the progress hairline and the framed figure diagram-viewer.js upgrades on both pages), `oversight.css` (the OVERSIGHT family — government, watch, harbor, gate, task 1003469: the row, the figure band inside a panel, the group head, the inline form, the grant toggle and the shared confirm modal; named oversight because the vocabulary wall bans "governance" on the government surface) and `panel.css` (the PANEL family — settings, profile, pair, task 1003308: the row, the switch, the field with its label above, the chip control, the four voices and the sign-in gate; the hall's my-projects page was DELETED in the same task — "my projects" is the hub's user-level page, and the hall keeps no link or redirect; the family's fourth page `not-ready.html` was deleted by task 1003450, along with the emptied `requireNonXenosPage` gate that was the only thing redirecting to it). A page sheet then holds only what one page has and the others do not — `primer.css` the acknowledge gate, `diagrams.css` the legend strip, `gate.css` the hold reasons and file chips, `watch.css` the severities, the report reader and the grade trend, `settings.css` the rail layout, the group head, the token ladder and the voices grid, `profile.css` the identity band and the shelf, `pair.css` the device row, `people.css` the directory's identity cell (task 1003470 made the legacy door `/scouting` ship the same body and script as `/people`; task 1003440 then deleted the door — `scouting.html` and `scouting.states.json` are gone and the URL 301s to `/people`).
138
+ │ ├── hall-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R85, default-on): the authenticated builders' hall at builders.<apex>, moved from public-builders/ (~13k LOC, the largest). NO routes/db/port — declares contributes.webSurfaces:[{host:"builders.",dir:"public"}] for discovery, but UNLIKE status-ui keeps its DEDICATED serving block in serve-internal (the /builders shim + canonical redirects + citizen/Archon page gates + asset-stamping + hall-widget injection), partitioned out of the generic web-surface loop and served from BUILDERS_DIR. CLAUDE.md, module.json, public/{index.html,builders.js,style.css,work,watch,gate,settings,ranks,sessions,primer,diagrams,atlas,...}.{html,js},cursors/,chime.wav. Four ARCHETYPE sheets sit beside the per-page ones: `record.css` (the task + idea records, task 1003305), `room.css` (the READING ROOMS — the primer + the diagrams index, task 1003306: the reading measure and rhythm, the table of contents, the progress hairline and the framed figure diagram-viewer.js upgrades on both pages), `oversight.css` (the OVERSIGHT family — government, watch, gate, task 1003469 (harbor was its fourth until task 1003890 removed the dev box): the row, the figure band inside a panel, the group head, the inline form, the grant toggle and the shared confirm modal; named oversight because the vocabulary wall bans "governance" on the government surface) and `panel.css` (the PANEL family — settings, profile, task 1003308 (pair was its third until task 1003890 removed the dev box): the row, the switch, the field with its label above, the chip control, the four voices and the sign-in gate; the hall's my-projects page was DELETED in the same task — "my projects" is the hub's user-level page, and the hall keeps no link or redirect; the family's fourth page `not-ready.html` was deleted by task 1003450, along with the emptied `requireNonXenosPage` gate that was the only thing redirecting to it). A page sheet then holds only what one page has and the others do not — `primer.css` the acknowledge gate, `diagrams.css` the legend strip, `gate.css` the hold reasons and file chips, `watch.css` the severities, the report reader and the grade trend, `settings.css` the rail layout, the group head, the token ladder and the voices grid, `profile.css` the identity band and the shelf, `people.css` the directory's identity cell (task 1003470 made the legacy door `/scouting` ship the same body and script as `/people`; task 1003440 then deleted the door — `scouting.html` and `scouting.states.json` are gone and the URL 301s to `/people`).
139
139
  │ ├── lifecycle/ ← CORE-DOMAIN carve (BV1.R86, default-on): the build state machine — tasks · claims · versions · done-when · goals · dependencies · claim→ship→grade→credit · github-push + merge-lock + conflict-resolve + publish-reconciler. The LAST, most-coupled carve; registers the `lifecycle` kernel port (createTask, classifyTaskKind, tallyPeerVotes, work-tracking reads). CLAUDE.md, module.json, routes/{tasks,claims,versions,done-when,goals,dependencies,gate-approvals,analytics}.js, lifecycle.js, github-push.js, merge-lock.js, conflict-resolve.js, publish-reconciler.js, task-visuals.js + task-visual-db.js + routes/visuals.js (the optional ship-time visual, task 1003109 — the route file is NOT named task-*.js on purpose; see the module CLAUDE.md).
140
140
  │ ├── copy-desk/ ← CORE-DOMAIN module (task 1003113 / R02 of goal 1000074, default-on): the COPY half of the design contract — ADR 0081 gave design TOKENS a repo-owned source of truth, text had none. Owns the FLAG (anyone in any role marks a string or a whole surface as needing an artist's attention, with a reason required in BOTH the route and a CHECK constraint) and the QUEUE an artist reads it from. READS R01's committed registry (docs/copy-registry.json, generated by scripts/gds/copy-inventory.js) and never writes it. NO LIVE CMS is the hard non-goal: no column here could hold replacement text, and copy_no_cms.mjs pins that. Key is `copy-desk`, not `copy` — the branding contract already owns a `copy` block, so the bare word reds the "core carries no module code" fitness check. R03 (task 1003114, ADR 0233) added the PROPOSAL: an artist writes the replacement wording and it becomes a Bongos task carrying a fenced copy-proposal patch — no row, NO second migration (a proposals table is the forbidden column with extra steps), and scripts/gds/copy-apply.js lands it as a real diff under a claim. CLAUDE.md, module.json, registry.js, flags.js, proposals.js, routes/copy-desk.js, migrations/copy_desk_001_flags.sql, tests/{copy_flags,copy_queue,copy_no_cms,copy_proposals}.mjs. Its hall surface lives with hall-ui (public/copy-desk.{html,js,css}), per the one-key-per-web-surface precedent.
141
141
  │ ├── ui-design/ ← CORE-DOMAIN module (task 1003324 / ADR 0197, default-on): the design capability every builder on every instance gets — ships METHOD, never a world. Absorbs ADR 0081's layer: adapters/{claude-design,figma}/index.js (moved from src/ui/adapters/), scripts/{design-sync,figma-design-sync,validate-design}.js (moved from scripts/gds/, which keeps thin shims), docs/design-contract.md (moved from docs/). Declaration-only: contributes.skills design (the /design playbook, new) + design-sync + figma-design-sync; no routes/port/migrations, so disabling it touches no served surface. CLAUDE.md separates the platform FLOORS (the Fifteen Rule, the dark twins, no page :root, the 24px floor, AA in both modes, the kill switch) from the instance's WORLD (its packs + DESIGN.md). kit/ (task 1003320) is the look-before-you-ship kit: lib.js (the floors, the states/probes contracts, the action + expectation vocabularies, the WCAG maths, the stub spawner), serve.js (any surface through the doorway's real transforms + fixtures/), render.js (states × 1440/390/320 × dark/light, audited), probe.js (PASS/FAIL contracts), check-mock.js (platform rules + the instance's DESIGN.md taste bans, then the detector), tells.js (task 1003329 / ADR 0223: the anti-pattern detector as two tiers — floors FAIL, craft tells WARN, kit-ignore waivers per file; the rendered twins live in lib.js factsInPage), contrast.js + png.js; a page's <page>.states.json / <page>.probes.json sit BESIDE the surface (modules/public-landing/public/, modules/hall-ui/public/). Recipe: docs/recipes/ui-look-before-you-ship.md. skills/ (task 1003321 / ADR 0198) is the vendoring policy: policy.json (licence allowlist, verdicts that may land, sidecar fields, preamble markers), PREAMBLE.md (the platform preamble every vendored SKILL.md opens with), README.md (the roster — both origins scanned dangerous on 2026-08-28, landed as rebuild specs; nothing vendored — and the landing recipe); a landable copy goes under skills/vendor/<name>/ with PROVENANCE.md + LICENSE and is materialised into an instance's .claude/skills/ by claude-materialize.js step 1b. skills/<name>/ (task 1003325 / ADR 0220) are the in-house rebuilds from the specs, first-party and counted: design-taste-frontend (the default taste skill), design-taste-frontend-v1 (the earlier generation, kept distinct), high-end-visual-design (the polish pass), redesign-existing-projects (the in-place upgrade); each opens with the same preamble and carries a "Rebuilt from" section instead of a sidecar. impeccable/ (task 1003326 / ADR 0221) is the design-craft method as ONE skill with sub-commands: SKILL.md (the router, the menu, the craft floor, the bounded finish) + reference/<sub-command>.md (eighteen playbooks, one loaded per request); no hooks, no bypass agent, no telemetry, no live server. minimalist-ui/, industrial-brutalist-ui/, gpt-taste/, stitch-design-taste/ (task 1003327 / ADR 0230) are the four looks, composition grammars with no colours of their own: the palette is the instance's pack and the texture is a named entry in styles/; a substrate is an archetype that still ships both modes, gpt-taste's selection is derived from the route rather than rolled, and stitch-design-taste writes a DESIGN.md-shaped contract document whose thirteen are transcribed from the pack. image-to-code/, imagegen-frontend-web/, imagegen-frontend-mobile/, brandkit/ (task 1003328 / ADR 0222) are the image family under the hero-only rule: plates and hero objects only, the pair (JPEG dark plate + WebP alpha cutout) with a <file>.json sidecar beside each, through the instance's art pipeline key; the grove specimen's styles/grove/assets/ is the worked example. style/ (task 1003475 / ADR 0231) is the module's first FIRST-PARTY skill (no upstream, so a `## First party` section instead of `## Rebuilt from`): the /style session in which an owner authors their own look in four bounded steps, rendering the nearest library entries before asking, drafting inside the contract, retuning by measurement, and handing over the one-step adoption without ever writing config/branding.json. styles/ (task 1003323 / ADR 0219) is the style library: <name>/pack.json (the theme.ui overlay, exactly the fifteen) + DESIGN.md (the look's derived recipes, thirteen in lockstep with the pack) + mock.html (the specimen); chrome-world (pinned to the neutral pack), expedition, grove, blueprint (the first cool look, authored through /style at task 1003475); STYLE=<name> on the kit lays a look over the instance's pack through GDS_BRANDING_FILE; tests/ui_design_styles.mjs runs the world/cosmos/hall token suites once per alternate (UI_DESIGN_PACK). config/design-tokens.* + design-sources.json STAY in config/ as the host boundary; public-landing keeps its own key (first customer, not owner).
@@ -265,7 +265,7 @@
265
265
  │ ├── terrain.js ← (Phase D + ADR 0012, CJS): TERRAIN + OBJECT enums + BLOCKING_TERRAINS + BLOCKING_OBJECTS (server twin of public/game/world/terrain.js)
266
266
  │ └── map.js ← (Phase D + ADR 0012): two-grid INITIAL_MAP {ground, objects}; isBlocked checks both layers (mirrors public/game/world/{terrain,map}.js)
267
267
  │ (public-status/ moved to modules/status-ui/public/ in BV1.R84 — see the modules/ block above)
268
- │ (public-builders/ moved to modules/hall-ui/public/ in BV1.R85 — the authenticated builders' hall; see the modules/ block above. It keeps its DEDICATED serving block in src/bongos/serve-internal.js: the builders.<apex> + apex /builders/* shim, canonical redirects, the citizen/Archon page gates, asset-stamping, and the module hall-widget injection — partitioned out of the generic web-surface loop. index.html=the hall dashboard; work/board/roadmap/goals/watch/harbor/gate/pair/settings/ranks/sessions/primer/diagrams/atlas pages; ideas.{html,js,css}=the Full Idea composer (task 1002928 / R152 of goal 1000062 — the five R141 fields with a live R142 completeness meter, both fetched from the server so the page carries no copy of the rules; drafts in localStorage so navigation costs nothing. Task 1002930 / R154 added the SPARK QUEUE above it and a second mode: `?develop=<id>` reuses the same form to develop a spark — the spark's words shown above the fields, its title read-only, and the file routed to POST /inbox/:id/develop instead of POST /inbox, under its own per-idea draft key); idea.html/idea.js=the idea-detail PAGE (/idea/:id, task 1002929 / R153 — the SECOND two-segment hall page after /task/:id: grade, the five answers in template order, the score, and both attributions. The GRADE decides what the page contains — a quick idea gets no five-field skeleton. task-detail.js keeps the #/idea/N overlay because ~320 of its lines are the Metic+ triage panel; the overlay now links here); profile.html/profile.js=the public, shareable builder profile page (/profile?id=N — rank, works, credits, karma, achievements; task 1467); profile-connections.js=section 6 of that page, the hub-only connections block (task 1002296 / R20 of goal 1000045 — the viewer-relative 1st/2nd/3rd/out-of-network label, the mutuals it was computed from, the connect button, and on your own profile both request queues plus your connections ledger. Loaded BEFORE profile.js, which calls window.OTBConnections.render behind a guard, so a page that omits the script simply has no section; the section reveals only when the hub's /connections routes answer, exactly as the cross-project record does. ADR 0212 records why it is on this page rather than the not-yet-built /u/&lt;handle&gt;, and why it addresses a builder by github_login and then uses the canonical handle the API echoes back); profile-canonical.js=the canonical-profile wiring for that page (task 1002804 / P08, ADR 0214 — on a FEDERATED instance the rel=canonical plus the visible "see the cross-project record" link-out ADR 0171 D1 asks for; on the HUB the numeric ?id= form redirects to the handle URL instead, because a page cannot both redirect to a URL and declare it canonical. All three read ONE resolver, modules/platform-identity/canonical-profile.js, which answers null while /u/&lt;handle&gt; does not exist — so everything is dark today and one boolean turns it all on together); task.html/task.js=the task-detail page (/task/:id, task 751 — replaced the old #/task/N overlay); builders.js=the controller; dom-utils.js/hall-widgets.js=the widget+settings-panel registries.)
268
+ │ (public-builders/ moved to modules/hall-ui/public/ in BV1.R85 — the authenticated builders' hall; see the modules/ block above. It keeps its DEDICATED serving block in src/bongos/serve-internal.js: the builders.<apex> + apex /builders/* shim, canonical redirects, the citizen/Archon page gates, asset-stamping, and the module hall-widget injection — partitioned out of the generic web-surface loop. index.html=the hall dashboard; work/board/roadmap/goals/watch/gate/settings/ranks/sessions/primer/diagrams/atlas pages (harbor + pair left with the dev box, task 1003890); ideas.{html,js,css}=the Full Idea composer (task 1002928 / R152 of goal 1000062 — the five R141 fields with a live R142 completeness meter, both fetched from the server so the page carries no copy of the rules; drafts in localStorage so navigation costs nothing. Task 1002930 / R154 added the SPARK QUEUE above it and a second mode: `?develop=<id>` reuses the same form to develop a spark — the spark's words shown above the fields, its title read-only, and the file routed to POST /inbox/:id/develop instead of POST /inbox, under its own per-idea draft key); idea.html/idea.js=the idea-detail PAGE (/idea/:id, task 1002929 / R153 — the SECOND two-segment hall page after /task/:id: grade, the five answers in template order, the score, and both attributions. The GRADE decides what the page contains — a quick idea gets no five-field skeleton. task-detail.js keeps the #/idea/N overlay because ~320 of its lines are the Metic+ triage panel; the overlay now links here); profile.html/profile.js=the public, shareable builder profile page (/profile?id=N — rank, works, credits, karma, achievements; task 1467); profile-connections.js=section 6 of that page, the hub-only connections block (task 1002296 / R20 of goal 1000045 — the viewer-relative 1st/2nd/3rd/out-of-network label, the mutuals it was computed from, the connect button, and on your own profile both request queues plus your connections ledger. Loaded BEFORE profile.js, which calls window.OTBConnections.render behind a guard, so a page that omits the script simply has no section; the section reveals only when the hub's /connections routes answer, exactly as the cross-project record does. ADR 0212 records why it is on this page rather than the not-yet-built /u/&lt;handle&gt;, and why it addresses a builder by github_login and then uses the canonical handle the API echoes back); profile-canonical.js=the canonical-profile wiring for that page (task 1002804 / P08, ADR 0214 — on a FEDERATED instance the rel=canonical plus the visible "see the cross-project record" link-out ADR 0171 D1 asks for; on the HUB the numeric ?id= form redirects to the handle URL instead, because a page cannot both redirect to a URL and declare it canonical. All three read ONE resolver, modules/platform-identity/canonical-profile.js, which answers null while /u/&lt;handle&gt; does not exist — so everything is dark today and one boolean turns it all on together); task.html/task.js=the task-detail page (/task/:id, task 751 — replaced the old #/task/N overlay); builders.js=the controller; dom-utils.js/hall-widgets.js=the widget+settings-panel registries.)
269
269
  │
270
270
  ├── infra/ ← ON-BOX scripts (task 1002699): the four reporter/heartbeat crons every dev box runs from its /workspace clone — box-heartbeat.sh (idle-sweep activity signal), box-report-version.sh (HEAD → POST /box/version), box-report-host-keys.sh (SSH host keys → POST /box/host-keys), box-report-terminal.sh (ttyd URL+credential → POST /box/terminal). Delivered by the workspace clone itself (modules/dev-box/box-access.js sparse scope includes infra/); crons self-installed by box-source-fetch.sh's per-tick bring-up. API base resolves GDS_BASE env → session-file api_base → skip (no hardcoded host in the neutral core).
271
271
  ├── devbox-app/ ← OTB Dev Box — Electron menu-bar/tray switch for the builder's box (#904, ADR 0045; internal). SELF-CONTAINED: own package.json (never feeds the droplet's npm ci); built by .github/workflows/build-devbox-app.yml; published to the public releases repo per config/devbox-app.json; release recipe docs/recipes/devbox-app-release.md
@@ -2230,5 +2230,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2230
2230
  landed since 1.19.876 with no explicit bump. run 35438492966. (task 1002620)
2231
2231
  1.19.878 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2232
2232
  landed since 1.19.877 with no explicit bump. run 35439004814. (task 1002620)
2233
+ 1.19.879 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2234
+ landed since 1.19.878 with no explicit bump. run 35439529685. (task 1002620)
2235
+ 1.19.880 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2236
+ landed since 1.19.879 with no explicit bump. run 35453118533. (task 1002620)
2233
2237
  ---------------------------------------------------------------------------
2234
2238
  ```
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "key": "hall-ui",
3
3
  "title": "Builders' Hall",
4
- "description": "The authenticated builders' hall served at builders.<apex> (builders.example.com) — the roster, work board, standing/karma, leaderboard, achievements, primer, system maps, settings, dev-box pair page, and the Archon-only watch/harbor/gate pages, all polling /api/bongos/* and the read-only /api/bongos/public/* surface. The largest WEB-SURFACE core-domain module (~13k LOC), carved out of the GDS core in BV1.R85 (ADR 0093 §1; 02-module-map §6). default-on: a Cloud Bongos instance ships its hall like it ships its status page. Registers no port and contributes no routes — it declares a webSurface (host prefix + static dir). Unlike the generic status surface, the hall keeps its dedicated serving block in serve-internal.js (the builders.<apex> + apex /builders/* shim, canonical redirects, the citizen/Archon page gates, asset-stamping, and the module hall-widget injection) — the webSurfaces declaration is the discovery + scope-map + window.__MODULES__ record; serve-internal serves it from this module's public/ dir with that richer machinery. The me/public/leaderboard read endpoints it renders stay SHARED core in routes/{me,public,builders}.js (the status surface + the off-box mirror read them too).",
4
+ "description": "The authenticated builders' hall served at builders.<apex> (builders.example.com) — the roster, work board, standing/karma, leaderboard, achievements, primer, system maps, settings, and the Archon-only watch/gate pages, all polling /api/bongos/* and the read-only /api/bongos/public/* surface. The largest WEB-SURFACE core-domain module (~13k LOC), carved out of the GDS core in BV1.R85 (ADR 0093 §1; 02-module-map §6). default-on: a Cloud Bongos instance ships its hall like it ships its status page. Registers no port and contributes no routes — it declares a webSurface (host prefix + static dir). Unlike the generic status surface, the hall keeps its dedicated serving block in serve-internal.js (the builders.<apex> + apex /builders/* shim, canonical redirects, the citizen/Archon page gates, asset-stamping, and the module hall-widget injection) — the webSurfaces declaration is the discovery + scope-map + window.__MODULES__ record; serve-internal serves it from this module's public/ dir with that richer machinery. The me/public/leaderboard read endpoints it renders stay SHARED core in routes/{me,public,builders}.js (the status surface + the off-box mirror read them too).",
5
5
  "coreVersion": "^1.11.0",
6
6
  "default": true,
7
7
  "maintenance": { "status": "core-maintained" },
@@ -408,15 +408,6 @@
408
408
  }
409
409
 
410
410
 
411
- // #777/#783/#807: the dev-box management UI now lives in the shared OTBBoxPanel
412
- // module (public-builders/box-panel.js) so it can be reused verbatim on the
413
- // Settings page. Here it runs in browser-only mode (showSsh:false) — a newcomer
414
- // sees only the online terminal, never raw SSH details. We hold ONE instance;
415
- // it owns its own self-rescheduling poll + terminal-window handle. Lazily
416
- // created so it survives the very first renderWelcome regardless of which
417
- // <script> finished loading first; a no-op shim keeps callers safe if the
418
- // module somehow failed to load.
419
-
420
411
  // ---- onboarding checklist (V3.R75 #294; reshaped #777) ------------------
421
412
  // Renders the "Path into the City" checklist for the CURRENT logged-in
422
413
  // builder. Hidden once graduated (current_stage === 'graduated') so it
@@ -1284,8 +1275,6 @@
1284
1275
 
1285
1276
  // #777: setup-config toggle in the welcome panel. Flips which config pane
1286
1277
  // (Desktop app vs Claude Online) is shown — pure client-side, no round-trip.
1287
- // The dev-box buttons + credential-copy inside the online pane are handled by
1288
- // the OTBBoxPanel module's own scoped listener (#807), not here.
1289
1278
  $('#welcome-body')?.addEventListener('click', (e) => {
1290
1279
  const tab = e.target.closest('.setup__tab[data-config]');
1291
1280
  if (!tab) return;
@@ -1305,7 +1294,6 @@
1305
1294
  if (!scroll) return;
1306
1295
  const minimized = scroll.classList.toggle('scroll--welcome--minimized');
1307
1296
  if (btn) btn.textContent = minimized ? '+' : '−';
1308
- if (minimized) onboardingBox().stop(); else onboardingBox().refresh();
1309
1297
  });
1310
1298
 
1311
1299
  // V3.R25 (#245): delegated click handler for the peer-acclaim vote buttons.
@@ -1425,7 +1413,7 @@
1425
1413
  // reads what this page holds NOW rather than what it held at boot. Everything
1426
1414
  // else is stable for the life of the page. This call sits AFTER every
1427
1415
  // declaration it passes — moving it earlier is a temporal-dead-zone crash.
1428
- const { SETUP_CONFIG_KEY, acclaimEvidenceHtml, analyticsState, lbState, mountHallWidgets, onboardingBox, renderCurveBody, renderGreeting, renderLanding, renderNav, renderOnboarding, renderProfile, renderRecentShips, renderWelcomeCore, shipState, shipsPager, welcomeDismissalKey } = window.OTBHallRender({
1416
+ const { SETUP_CONFIG_KEY, acclaimEvidenceHtml, analyticsState, lbState, mountHallWidgets, renderCurveBody, renderGreeting, renderLanding, renderNav, renderOnboarding, renderProfile, renderRecentShips, renderWelcomeCore, shipState, shipsPager, welcomeDismissalKey } = window.OTBHallRender({
1429
1417
  API, RANK_ORDER, api, brandWorld, curLabel, emptyRow, fmtDate, mins, normalizeRank, pct, postJSON, rankLabel, rankWeight, signClasses, toast, usd,
1430
1418
  get instanceRepo() { return instanceRepo; },
1431
1419
  get lastClaimableCount() { return lastClaimableCount; },
@@ -6,8 +6,8 @@
6
6
  // NOT a consumer: modules/status-ui/public/ (the off-box status mirror) stays
7
7
  // self-contained by design — do not wire this there. Network helpers
8
8
  // (getJSON/postJSON/patchJSON) and renderRankBadge are deliberately NOT here:
9
- // the former carry auth-redirect drift that differs per page (and box-panel
10
- // catches its redirect by message), the latter is single-use and coupled to
9
+ // the former carry auth-redirect drift that differs per page, the latter is
10
+ // single-use and coupled to
11
11
  // the rank-ladder constant (a D1 concern). See
12
12
  // docs/audits/2026-06-14-repo-cleanup-audit.md §2A/§2B (frontend-utils).
13
13
  //
@@ -146,7 +146,10 @@
146
146
  for (const k of list) {
147
147
  const opt = document.createElement('option');
148
148
  opt.value = k;
149
- opt.textContent = label(k);
149
+ // The unset bucket is named for a reader on EVERY surface, filters included
150
+ // — `label` is the caller's choice for the real kinds, never for this one
151
+ // (task 1004038). roleOptions() names the role twin the same way.
152
+ opt.textContent = kindIsUnset(k) ? kindLabel(k) : label(k);
150
153
  sel.appendChild(opt);
151
154
  }
152
155
  // A refill must not drop the reader's pick; `selected` is the filing default.
@@ -154,10 +157,23 @@
154
157
  if (want) sel.value = want;
155
158
  }
156
159
 
160
+ // A task with no kind carries the DB default 'unclassified' — the twin of
161
+ // ROLE_UNSET one axis over (task 1004038, filed by task 1003936 which fixed the
162
+ // role axis and left this one). A word a column chose is not a word a person
163
+ // should read (docs/voice.md V2), so a picker shows "No kind yet" and a row tag
164
+ // renders NOTHING. kindIsUnset() is how a caller asks. The STORED value and the
165
+ // filter VALUE stay 'unclassified'; only what is read changes.
166
+ const KIND_UNSET = 'unclassified';
167
+ function kindIsUnset(k) { return !k || String(k) === KIND_UNSET; }
168
+
169
+ // NOT ESCAPE-SAFE: like roleLabel() below, this returns a transformed copy of
170
+ // its input, so a caller rendering it into markup must escapeHtml() the result.
171
+ // Every caller today assigns it to textContent, which escapes for them.
157
172
  // How a kind is named in a picker — 'blocker-resolution' → 'Blocker resolution'.
158
173
  // The same shape roleLabel takes below, and it reproduces every label the
159
174
  // hand-written <option> list in ideas.html carried, so no copy changes.
160
175
  function kindLabel(k) {
176
+ if (kindIsUnset(k)) return 'No kind yet';
161
177
  const s = String(k || '').replace(/-/g, ' ');
162
178
  return s ? s.charAt(0).toUpperCase() + s.slice(1) : '';
163
179
  }
@@ -212,8 +228,8 @@
212
228
  // Module→core settings-panel contribution seam (BV1.R42, ADR 0083) — the
213
229
  // browser twin of the server's 'profile.fields' seam (src/module-seams.js). A
214
230
  // module that has left core registers a Settings panel from its own client
215
- // script (loaded before settings.js, the box-panel.js pattern); settings.js
216
- // then renders every registered panel instead of hardcoding box/discord/art.
231
+ // script (loaded before settings.js); settings.js then renders every
232
+ // registered panel instead of hardcoding discord/art.
217
233
  // Empty until a module registers → the page renders exactly what it does today.
218
234
  // A panel: { id, title?, order?, mount(el) } — mount() is handed a container
219
235
  // element to render into. order sorts ascending (default 0).
@@ -407,7 +423,7 @@
407
423
  escapeHtml, $, fmtNum, num, fmtInt, fmtCompact, fmtDate, rankLabel, toast, moduleOn, disciplines,
408
424
  // task 1003936 — ONE word for the role axis, all six surfaces
409
425
  roleAxisLabel, roleLabel, roleOptions, roleIsUnset, ROLE_UNSET,
410
- fillKindFilter, kindLabel, // task 1003453 — one Kind filler, all three surfaces
426
+ fillKindFilter, kindLabel, kindIsUnset, KIND_UNSET, // task 1003453 — one Kind filler, all three surfaces
411
427
  errCode, errMessage, errDetails, // task 1002462 — ADR 0116 envelope readers
412
428
  contributeSettingsPanel, settingsPanels,
413
429
  contributeHallWidget, hallWidgets, hallWidgetIds,
@@ -321,6 +321,11 @@
321
321
  const disciplineTag = window.OTB.roleIsUnset(t.discipline)
322
322
  ? ''
323
323
  : `<span class="task__tag">${escapeHtml(window.OTB.roleLabel(t.discipline))}</span>`;
324
+ // A kind nobody set is no tag at all, not the column's default word — the
325
+ // role tag above is the precedent this follows (task 1004038).
326
+ const kindTag = window.OTB.kindIsUnset(t.kind)
327
+ ? ''
328
+ : `<span class="task__tag">${escapeHtml(t.kind)}</span>`;
324
329
  const newcomerTag = t.newcomer_friendly ? '<span class="task__tag task__tag--newcomer">newcomer</span>' : '';
325
330
  const rankTag = rankTagHtml(t.requires_rank);
326
331
  const touches = Array.isArray(t.touches) ? t.touches : [];
@@ -361,7 +366,7 @@
361
366
  <p class="task__title"><a class="task__link" href="${escapeHtml(taskHref(id))}">${escapeHtml(t.title || '')}</a></p>
362
367
  <div class="task__meta">
363
368
  <span class="task__tag">${escapeHtml(t.version_id || '?')}</span>
364
- <span class="task__tag">${escapeHtml(t.kind || 'unclassified')}</span>
369
+ ${kindTag}
365
370
  ${disciplineTag}
366
371
  ${newcomerTag}
367
372
  ${rankTag}
@@ -403,7 +408,7 @@
403
408
  <tr class="ledger__row--task" data-task-id="${escapeHtml(String(t.id))}" style="cursor:pointer" title="Open task #${escapeHtml(String(t.id))}">
404
409
  <td class="ledger__num">#${escapeHtml(String(t.id))}</td>
405
410
  <td>${escapeHtml(t.title || '')}</td>
406
- <td>${escapeHtml(t.kind || '—')}</td>
411
+ <td>${window.OTB.kindIsUnset(t.kind) ? '—' : escapeHtml(t.kind)}</td>
407
412
  <td class="ledger__drachmae">${fmtNum(creditVal(t))}${taskSettled(t) ? '' : ' <span class="ledger__est" title="Estimate — credits settle when the task is confirmed">est.</span>'}</td>
408
413
  <td class="ledger__date">${fmtDate(t.shipped_at)}</td>
409
414
  </tr>`).join('');
@@ -502,7 +507,7 @@
502
507
  const sortBy = $('#f-goal-sort')?.value || 'cpm';
503
508
  let list = state.work.todo.filter((t) => {
504
509
  if (!matchesTodoSearch(t, sQuery)) return false;
505
- if (kFilter && (t.kind || 'unclassified') !== kFilter) return false;
510
+ if (kFilter && (t.kind || window.OTB.KIND_UNSET) !== kFilter) return false;
506
511
  if (dFilter && (t.discipline || window.OTB.ROLE_UNSET) !== dFilter) return false;
507
512
  if (nFilter === 'yes' && !t.newcomer_friendly) return false;
508
513
  return true;
@@ -854,9 +854,7 @@
854
854
  <div class="setup__pane setup__pane--online">
855
855
  <ol class="setup__steps">
856
856
  <li><strong>Create your <a href="https://claude.com" target="_blank" rel="noopener noreferrer">Claude</a> account.</strong></li>
857
- <li><strong>Open your online environment.</strong> This is the environment you will build in — no SSH, nothing to install.
858
- <div class="setup__box" data-box-panel></div>
859
- </li>
857
+ <li><strong>Open your online environment.</strong> This is the environment you will build in — no SSH, nothing to install.</li>
860
858
  <li><strong>Sign in when Claude prompts you.</strong> Claude starts automatically when you open the terminal.
861
859
  <span class="setup__hint">To paste the sign-in code: <strong>Ctrl+Shift+V</strong> or right-click — plain Ctrl+V will not paste here. Claude takes over the terminal with its own interface once you're signed in. This guide stays open in its own tab. If Claude didn't start automatically, type <code>claude</code> and press Enter.</span></li>
862
860
  <li><strong>Then drive it however you like:</strong>
@@ -912,20 +910,6 @@
912
910
  // Remember the stage we rendered for, so the dismiss handler can record it.
913
911
  scroll.dataset.stageIdx = String(currentIdx);
914
912
  scroll.hidden = false;
915
- // #783/#807: mount the shared dev-box panel into the online pane and paint
916
- // it from live server state. Browser-only onboarding hides SSH details
917
- // (showSsh:false) — a newcomer sees only the online terminal. The panel runs
918
- // its own poll while anything is in flight (survives tab flips / re-renders /
919
- // reloads / background approvals); mount re-binds to the freshly-rendered DOM.
920
- const boxMount = body.querySelector('[data-box-panel]');
921
- if (boxMount) onboardingBox().mount(boxMount);
922
- }
923
- let _onboardingBox = null;
924
- function onboardingBox() {
925
- if (!_onboardingBox && window.OTBBoxPanel) {
926
- _onboardingBox = window.OTBBoxPanel.create({ api: API, showSsh: false, toast });
927
- }
928
- return _onboardingBox || { mount() {}, refresh() {}, stop() {} };
929
913
  }
930
914
  function renderOnboarding(onboarding, discord) {
931
915
  const scroll = $('#onboarding-scroll');
@@ -1351,6 +1335,6 @@
1351
1335
  return idx === -1 ? null : { place: idx + 1, total: ranked.length };
1352
1336
  }
1353
1337
 
1354
- return { SETUP_CONFIG_KEY, acclaimEvidenceHtml, analyticsState, lbState, mountHallWidgets, onboardingBox, renderCurveBody, renderGreeting, renderLanding, renderNav, renderOnboarding, renderProfile, renderRecentShips, renderWelcomeCore, shipState, shipsPager, welcomeDismissalKey };
1338
+ return { SETUP_CONFIG_KEY, acclaimEvidenceHtml, analyticsState, lbState, mountHallWidgets, renderCurveBody, renderGreeting, renderLanding, renderNav, renderOnboarding, renderProfile, renderRecentShips, renderWelcomeCore, shipState, shipsPager, welcomeDismissalKey };
1355
1339
  };
1356
1340
  })();
@@ -105,9 +105,6 @@
105
105
  <script src="/builders/shell.js?v=2026-07-02-shell1" defer></script>
106
106
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
107
107
  <script src="/builders/palette.js" defer></script>
108
- <!-- #807: shared dev-box management panel — loaded BEFORE builders.js so the
109
- OTBBoxPanel global is defined when the welcome panel mounts it. -->
110
- <script src="/builders/box-panel.js?v=2026-06-14-domutils" defer></script>
111
108
  <!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
112
109
  Loaded BEFORE hall-widgets.js, whose skeletons call OTBKit.pagerBarHtml(). -->
113
110
  <script src="/builders/hall-visual.js?v=2026-09-16-visual1" defer></script>
@@ -48,9 +48,8 @@
48
48
  .settings-card__hint { font-size: 13px; color: var(--ink-faint); }
49
49
  .panel-status,
50
50
  .settings-status,
51
- .pair-status { margin: var(--sp-xs) 0 0; min-height: 1.2em; font-size: 13.5px; color: var(--ink-soft); }
52
- .panel-status--ok, .settings-card__ok, .pair-status--ok { color: var(--ok); }
53
- .panel-status--danger, .pair-status--err { color: var(--danger); }
51
+ .panel-status--ok, .settings-card__ok { color: var(--ok); }
52
+ .panel-status--danger { color: var(--danger); }
54
53
  .panel-status:empty, .settings-status:empty { margin-top: 0; min-height: 0; }
55
54
  /* a prose link in any of the four voices is a control and owes the 24px floor */
56
55
  .panel-lede a, .panel-note a, .panel-hint a, .panel-status a, .settings-card__hint a, .section-note a {
@@ -114,7 +113,7 @@
114
113
  accent door — the panel's one primary act — and takes --accent-on, the dark
115
114
  ink for the salmon; white on it fails AA and DESIGN.md bans it. */
116
115
  .pbtn,
117
- .settings-btn, .sessions-row__end, .event-sound-preview, .pair-approve {
116
+ .settings-btn, .sessions-row__end, .event-sound-preview {
118
117
  display: inline-flex;
119
118
  align-items: center;
120
119
  justify-content: center;
@@ -135,14 +134,14 @@
135
134
  transition: background 200ms cubic-bezier(.2,.7,.3,1), border-color 200ms cubic-bezier(.2,.7,.3,1), color 200ms cubic-bezier(.2,.7,.3,1);
136
135
  }
137
136
  .pbtn:hover:not(:disabled), .settings-btn:hover:not(:disabled), .sessions-row__end:hover:not(:disabled),
138
- .event-sound-preview:hover:not(:disabled), .pair-approve:hover:not(:disabled) { background: var(--glass-strong); color: var(--ink); text-decoration: none; }
139
- .pbtn:disabled, .settings-btn:disabled, .sessions-row__end:disabled, .pair-approve:disabled,
137
+ .event-sound-preview:hover:not(:disabled) { background: var(--glass-strong); color: var(--ink); text-decoration: none; }
138
+ .pbtn:disabled, .settings-btn:disabled, .sessions-row__end:disabled,
140
139
  .pbtn[aria-disabled="true"], .settings-btn[aria-disabled="true"] { color: var(--ink-faint); background: var(--glass); cursor: default; }
141
140
  .pbtn--quiet, .settings-btn--quiet, .sessions-row__end, .event-sound-preview { background: transparent; }
142
141
  .pbtn--save,
143
- .craft-save, .pair-approve { background: var(--accent); border-color: var(--accent); color: var(--accent-on); font-weight: 500; }
144
- .pbtn--save:hover:not(:disabled), .craft-save:hover:not(:disabled), .pair-approve:hover:not(:disabled) { background: var(--accent-lift); border-color: var(--accent-lift); color: var(--accent-on); }
145
- .craft-save:disabled, .pbtn--save:disabled, .pair-approve:disabled { background: var(--glass); border-color: var(--glass-edge); color: var(--ink-faint); }
142
+ .craft-save { background: var(--accent); border-color: var(--accent); color: var(--accent-on); font-weight: 500; }
143
+ .pbtn--save:hover:not(:disabled), .craft-save:hover:not(:disabled) { background: var(--accent-lift); border-color: var(--accent-lift); color: var(--accent-on); }
144
+ .craft-save:disabled, .pbtn--save:disabled { background: var(--glass); border-color: var(--glass-edge); color: var(--ink-faint); }
146
145
  /* the shell's `.craft-save` was a bordered box; it is the family's save button
147
146
  now, so it carries the pill's own box model too */
148
147
  button.craft-save { display: inline-flex; align-items: center; justify-content: center; min-height: 38px; min-width: 38px; padding: 0 15px; border-radius: var(--radius-pill); font: inherit; font-size: 14px; cursor: pointer; }
@@ -157,11 +157,6 @@ select.voices__select { min-width: 140px; }
157
157
  .settings-btn--discord { background: #5865F2; border-color: #4853c4; color: #ffffff; }
158
158
  .settings-btn--discord:hover:not(:disabled) { background: #4853c4; border-color: #4853c4; color: #ffffff; }
159
159
 
160
- /* ---- dev box ------------------------------------------------------------- */
161
- /* the box panel is the shell's (.setup__box, box-panel.js); inside a settings
162
- panel it sits on the glass rather than a second card */
163
- .settings-groups .setup__box { margin: var(--sp-sm) 0 0; background: var(--glass); border-color: var(--glass-edge); border-radius: var(--radius-sm); }
164
-
165
160
  /* Specialities panel (task 1003855, ADR 0296). A speciality is a named bundle of
166
161
  knowledge and terms under a craft; this styles the list, the train form and the
167
162
  create form. Tokens only — no literal colours, so light/dark follow the page. */
@@ -195,20 +195,6 @@
195
195
 
196
196
  <!-- ============ group: devbox ============ -->
197
197
 
198
- <!-- DEV BOX — full box management (#807, ADR 0040). Request/wake, open
199
- the online terminal, see SSH details, and close the box. Settings
200
- shows BOTH the online terminal AND the SSH IP + command. The
201
- OTBBoxPanel module (box-panel.js) renders + polls inside #box-mount. -->
202
- <section class="scroll" id="box-scroll" aria-labelledby="box-h" data-group="devbox" hidden>
203
- <h2 id="box-h" class="scroll__h">Your dev box</h2>
204
- <p class="scroll__lede">
205
- The hosted machine your work runs on. Open it in your browser (the
206
- online terminal) or connect over SSH — whichever you prefer.
207
- </p>
208
- <div class="setup__box" id="box-mount"></div>
209
- <p class="section-note">Actions apply at once. Closing stops and deletes the box; you can start a fresh one anytime.</p>
210
- </section>
211
-
212
198
  <!-- DEV BOX APP — the desktop switch (#904, ADR 0044). Static download
213
199
  links; the /downloads/devbox/* redirects resolve to the current
214
200
  release (config/devbox-app.json). -->
@@ -236,19 +222,6 @@
236
222
  </p>
237
223
  </section>
238
224
 
239
- <!-- PAIRING — link out to the dedicated pair page (task 2172): keeps
240
- the whole dev-box story reachable from this group. -->
241
- <section class="scroll" id="pairing-scroll" aria-labelledby="pairing-h" data-group="devbox" hidden>
242
- <h2 id="pairing-h" class="scroll__h">Pair Claude Desktop</h2>
243
- <p class="scroll__lede">
244
- Connect your Claude Desktop to your dev box with a one-time pairing
245
- code — the guided flow lives on its own page.
246
- </p>
247
- <div class="settings-card">
248
- <a class="settings-btn" href="/pair">Open the pairing page</a>
249
- </div>
250
- </section>
251
-
252
225
  <!-- ============ group: preferences ============ -->
253
226
 
254
227
  <!-- INTERACTION PROFILE — how replies are pitched for you (task 2010,
@@ -453,8 +426,6 @@
453
426
  <!-- shared record overlay (#/idea|blocker/N) — every hall page carries it; this
454
427
  page was the one gap (task 2172). -->
455
428
  <script src="/builders/task-detail.js?v=2026-07-03-taskdetail"></script>
456
- <!-- #807: shared dev-box panel — loaded before settings.js so OTBBoxPanel exists. -->
457
- <script src="/builders/box-panel.js?v=2026-06-14-domutils"></script>
458
429
  <script src="/builders/settings.js?v=2026-09-02-panel"></script>
459
430
  <!-- task 1003341: the Active sessions panel. Its own file rather than more of
460
431
  settings.js, which sits close to the 1500-line size ratchet. -->
@@ -6,8 +6,6 @@
6
6
  //
7
7
  // 1. VOICES — per-skill model picker (GET/PATCH /api/bongos/me/skill-prefs).
8
8
  // 2. CRAFT — preferred disciplines (GET /api/bongos/me, PATCH /api/bongos/me/disciplines).
9
- // 3. DEV BOX — full box management via the shared OTBBoxPanel (#807, ADR 0040):
10
- // request/open the online terminal, SSH details, and close.
11
9
  // 5. SOUNDS — per-sound on/off toggles (GET/PATCH /api/bongos/me/sound-prefs, #786).
12
10
  //
13
11
  // On a 401 it redirects into the web sign-in flow, like the other hall pages.
@@ -813,24 +811,6 @@
813
811
  $('#display-name-scroll').hidden = false;
814
812
  }
815
813
 
816
- // ---- section 1b: DEV BOX (full management, #807 ADR 0040) ----------------
817
- //
818
- // Mounts the shared OTBBoxPanel (box-panel.js) with showSsh:true so a builder
819
- // sees BOTH the online terminal and the SSH IP + command, and can request,
820
- // open, or close their box. The module owns its own polling + state painting;
821
- // we just hand it the mount element. Hidden if the module failed to load.
822
- function setupBox() {
823
- // R60 (task 1199): dev boxes are a feature module. On a vanilla instance the
824
- // module is off — there is no box to manage, so the whole section stays
825
- // hidden (its #box-scroll defaults to hidden in the markup).
826
- if (!window.OTB.moduleOn('dev-box')) return;
827
- const mount = $('#box-mount');
828
- if (!mount || !window.OTBBoxPanel) return;
829
- const panel = window.OTBBoxPanel.create({ api: API, showSsh: true, toast });
830
- $('#box-scroll').hidden = false;
831
- panel.mount(mount);
832
- }
833
-
834
814
  // ---- section 3: CONNECTIONS (Discord link/unlink, F7 ADR 0033) -----------
835
815
  //
836
816
  // The /me payload carries `discord: { configured, linked }`. The section is
@@ -1243,9 +1223,7 @@
1243
1223
  'disciplines': 'craft-scroll',
1244
1224
  'cli': 'cli-scroll',
1245
1225
  'art-key': 'art-key',
1246
- 'dev-box': 'box-scroll',
1247
1226
  'dev-box-app': 'boxapp-scroll',
1248
- 'pairing': 'pairing-scroll',
1249
1227
  'interaction': 'interaction-scroll',
1250
1228
  'wandering': 'wandering-scroll',
1251
1229
  'rendering': 'render-scroll',
@@ -1358,14 +1336,11 @@
1358
1336
  // DISPLAY NAME — the label beside your work (task 1669); seeded from /me.
1359
1337
  setupDisplayName(builder);
1360
1338
 
1361
- // DEV BOX — full box management (online terminal + SSH + close), #807.
1362
- setupBox();
1363
- // DEV BOX APP + PAIRING — revealed once we know the viewer is a real builder,
1339
+ // DEV BOX APP — revealed once we know the viewer is a real builder,
1364
1340
  // and only where the dev-box module runs (the group's rail entry is gated
1365
1341
  // the same way above — task 2172).
1366
1342
  if (window.OTB.moduleOn('dev-box')) {
1367
1343
  $('#boxapp-scroll').hidden = false;
1368
- $('#pairing-scroll').hidden = false;
1369
1344
  loadDevboxLatestVersion(); // #924: best-effort "latest version: X" courtesy line
1370
1345
  }
1371
1346
  // CONNECTIONS — Discord (hidden unless the server has Discord configured).
@@ -314,7 +314,6 @@
314
314
  // Metic+ oversight could not honestly hold it. Everything below IS a rank
315
315
  // surface, so this group's gate and audience are unchanged.
316
316
  { id: 'watch', label: 'Watch', icon: 'admin', href: '/watch', gate: 'government' },
317
- { id: 'harbor', label: 'Harbor', icon: 'admin', href: '/harbor', gate: 'government', module: 'dev-box' },
318
317
  { id: 'gate', label: 'Gate', icon: 'admin', href: '/gate', gate: 'government' },
319
318
  { id: 'sessions', label: 'Sessions', icon: 'admin', href: '/sessions', gate: 'government' },
320
319
  // The project's own fundamentals (task 1003280, goal 1000072). Mirrors the
@@ -2421,50 +2421,6 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
2421
2421
  }
2422
2422
  .setup__btn:hover:not(:disabled) { background: var(--accent-deep); border-color: var(--accent-deep); color: #fff; text-decoration: none; }
2423
2423
  .setup__btn:disabled, .setup__btn--open[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }
2424
- .setup__btn--box { background: var(--surface); border-color: var(--border-strong); color: var(--ink); }
2425
- .setup__btn--box:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }
2426
- .setup__btn--danger { background: var(--surface); border-color: color-mix(in srgb, var(--danger) 45%, transparent); color: var(--danger); }
2427
- .setup__btn--danger:hover:not(:disabled) { background: var(--danger-weak); }
2428
-
2429
- .setup__box { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px 14px; margin: 10px 0; background: var(--surface-2); }
2430
- .setup__box-btns { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
2431
- .setup__box-msg { font-size: 13px; margin: 6px 0; }
2432
- .setup__box-msg--ok { color: var(--ok); }
2433
- .setup__box-msg--warn { color: var(--warn); }
2434
- .setup__box-msg--err { color: var(--danger); }
2435
- .setup__box-out {
2436
- font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
2437
- font-size: 12px;
2438
- background: var(--surface);
2439
- border: 1px solid var(--border);
2440
- border-radius: var(--radius-sm);
2441
- padding: 8px 10px;
2442
- max-height: 180px;
2443
- overflow: auto;
2444
- }
2445
- .setup__box-close { margin-left: auto; }
2446
- .setup__box-close-hint { font-size: 12px; color: var(--ink-faint); }
2447
- .setup__cred-row { display: flex; align-items: center; gap: 8px; margin: 4px 0; font-size: 13px; flex-wrap: wrap; }
2448
- .setup__cred-k { color: var(--ink-faint); min-width: 90px; }
2449
- .setup__cred-v {
2450
- font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
2451
- font-size: 12px;
2452
- background: var(--surface-2);
2453
- border: 1px solid var(--border);
2454
- border-radius: 4px;
2455
- padding: 2px 8px;
2456
- }
2457
- .setup__copy {
2458
- border: 1px solid var(--border-strong);
2459
- background: var(--surface);
2460
- color: var(--ink-soft);
2461
- border-radius: var(--radius-sm);
2462
- font-size: 12px;
2463
- padding: 3px 10px;
2464
- cursor: pointer;
2465
- }
2466
- .setup__copy:hover { background: var(--surface-2); }
2467
- .setup__copy--copied { background: var(--ok-weak); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent); }
2468
2424
  .setup__getcode { margin: 10px 0; }
2469
2425
  /* A <summary> is a control, so it owes the 24px floor like any other; it
2470
2426
  measured 20px tall (task 1003304). The height is bought with padding rather
@@ -536,8 +536,12 @@
536
536
  // word is the thing the round's stat-tile reference exists to fix.
537
537
  const meta = [];
538
538
  if (t.version_id) meta.push(`<b>${escapeHtml(t.version_id)}</b>`);
539
- if (t.kind) meta.push(escapeHtml(t.kind));
540
- if (t.discipline && t.discipline !== 'unclassified') meta.push(escapeHtml(t.discipline));
539
+ // Neither axis says the word its column defaults to (task 1004038). `if
540
+ // (t.kind)` was never a guard for it: 'unclassified' is a truthy string, so
541
+ // the run read it out loud. The role line below asked the same question with
542
+ // a literal — it is right, but a copy of roleIsUnset() is free to drift.
543
+ if (!window.OTB.kindIsUnset(t.kind)) meta.push(escapeHtml(t.kind));
544
+ if (!window.OTB.roleIsUnset(t.discipline)) meta.push(escapeHtml(t.discipline));
541
545
  // rankText() answers "any rank" for an open task, which read as
542
546
  // "min rank any rank" once the label was prefixed. An open floor is not a
543
547
  // minimum, so it is said as the absence of one.
@@ -89,13 +89,6 @@
89
89
  <!-- ============ tab: Operations ============ -->
90
90
  <div id="pane-operations" role="tabpanel" aria-labelledby="tab-operations" hidden>
91
91
 
92
- <!-- 5. BOX COSTS (#602) — per-builder dev-box cost ledger + totals. -->
93
- <section class="scroll" id="box-costs-scroll" aria-labelledby="box-costs-h" hidden>
94
- <h2 id="box-costs-h" class="scroll__h">Dev-box costs</h2>
95
- <p class="scroll__lede">What each builder's dev box has cost — spin-up, compute, and storage. Tracked for oversight; not charged in the early stage.</p>
96
- <div id="box-costs-body"><span class="kit-skel"></span></div>
97
- </section>
98
-
99
92
  <!-- 6. SESSIONS — the tally, and the five most recent sessions the page
100
93
  always fetched and used to throw away (task 1003469). -->
101
94
  <section class="scroll" id="sessions-scroll" aria-labelledby="sessions-h" hidden>
@@ -897,41 +897,6 @@
897
897
  });
898
898
  }
899
899
 
900
- // ---- Box costs (#602): per-builder container-cost totals + recent ledger ----
901
- function renderBoxCosts(data, target) {
902
- const totals = Array.isArray(data.totals_by_builder) ? data.totals_by_builder : [];
903
- const recent = Array.isArray(data.ledger) ? data.ledger : [];
904
- const usd = (n) => `$${(Number(n) || 0).toFixed(2)}`;
905
- target.innerHTML = '';
906
- if (!totals.length) {
907
- target.innerHTML = emptyStateHtml('No dev-box costs accrued yet.');
908
- return;
909
- }
910
- ledger(group(target, 'Totals by builder', totals.length), {
911
- columns: ['Builder', { label: 'Total', class: 'num' }],
912
- rowHtml: (tb) => `<tr><td>${escapeHtml(tb.builder_login || ('#' + (tb.builder_id ?? '?')))}</td><td class="num">${usd(tb.total_usd)}</td></tr>`,
913
- }).render(totals);
914
- ledger(group(target, 'Recent ledger', recent.length), {
915
- columns: ['When', 'Builder', 'Kind', { label: 'Amount', class: 'num' }, 'Detail'],
916
- pager: makePager('watch-boxcosts', 'otb.watch.boxCostsPerPage'),
917
- emptyMsg: 'No ledger rows yet.',
918
- rowHtml: (r) => `<tr><td>${escapeHtml(String(r.recorded_at || '').slice(0, 16).replace('T', ' '))}</td>` +
919
- `<td>${escapeHtml(r.builder_login || ('#' + (r.builder_id ?? '?')))}</td>` +
920
- `<td><span class="ov-mono">${escapeHtml((r.source || '').replace('box-', ''))}</span></td>` +
921
- `<td class="num">${usd(r.amount_usd)}</td>` +
922
- `<td>${escapeHtml(r.description || '')}</td></tr>`,
923
- }).render(recent);
924
- target.insertAdjacentHTML('beforeend', foot('Container cost is tracked for oversight — not charged in the early stage.'));
925
- }
926
-
927
- function loadBoxCosts() {
928
- return section($('#box-costs-body'), () => fetchJson('/boxes/cost-ledger?limit=100'), renderBoxCosts, {
929
- sealedMsg: 'The cost ledger is not visible at your rank.',
930
- errMsg: 'Could not load the dev-box cost ledger.',
931
- name: 'box costs',
932
- });
933
- }
934
-
935
900
  // =========================================================================
936
901
  // 4. MARKS — reviews by builder (per-builder grade breakdown)
937
902
  // NOTE: built in parallel — code defensively (404 / [] / missing fields).
@@ -1316,7 +1281,7 @@
1316
1281
 
1317
1282
  function revealSections() {
1318
1283
  [
1319
- 'watch-attention', 'watch-tabs', 'gate-scroll', 'watch-scroll', 'roster-scroll', 'box-costs-scroll',
1284
+ 'watch-attention', 'watch-tabs', 'gate-scroll', 'watch-scroll', 'roster-scroll',
1320
1285
  'marks-scroll', 'mason-marks-scroll', 'surveyor-scroll', 'ledger-scroll', 'sessions-scroll',
1321
1286
  ].forEach((id) => {
1322
1287
  const el = document.getElementById(id);
@@ -1376,7 +1341,6 @@
1376
1341
  loadOverrides();
1377
1342
  loadAudit();
1378
1343
  loadRoster();
1379
- loadBoxCosts();
1380
1344
  loadMarks();
1381
1345
  loadMasonMarks();
1382
1346
  loadSurveyor();