@bongos/core 1.19.394 → 1.19.396

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 (50) hide show
  1. package/.bongos-core.json +114 -59
  2. package/.claude/skills/design/SKILL.md +68 -0
  3. package/.claude/skills/design-sync/SKILL.md +2 -2
  4. package/.claude/skills/figma-design-sync/SKILL.md +3 -3
  5. package/clients/bongos-client/index.d.ts +1 -1
  6. package/config/design-tokens.neutral.json +1 -1
  7. package/config/design-tokens.schema.json +1 -1
  8. package/docs/adr/0197-the-ui-design-module.md +52 -0
  9. package/docs/adr/README.md +1 -0
  10. package/docs/api/openapi.json +4 -0
  11. package/docs/api-reference.md +1 -1
  12. package/docs/architecture.md +1 -1
  13. package/docs/copy-inventory.md +11 -9
  14. package/docs/copy-registry.json +30 -12
  15. package/docs/file-map.md +2 -0
  16. package/docs/modules-contract.md +1 -0
  17. package/docs/onboarding/slash-commands.md +1 -1
  18. package/docs/recipes/claude-design-loop.md +3 -3
  19. package/docs/recipes/figma-design-loop.md +4 -4
  20. package/modules/hall-ui/public/my-projects.css +22 -0
  21. package/modules/hall-ui/public/my-projects.html +7 -1
  22. package/modules/hall-ui/public/my-projects.js +17 -11
  23. package/modules/platform-identity/routes/my-projects.js +50 -3
  24. package/modules/ui-design/CLAUDE.md +15 -0
  25. package/{src/ui → modules/ui-design}/adapters/claude-design/index.js +1 -1
  26. package/{src/ui → modules/ui-design}/adapters/figma/index.js +3 -3
  27. package/{docs → modules/ui-design/docs}/design-contract.md +12 -12
  28. package/modules/ui-design/kit/README.md +3 -0
  29. package/modules/ui-design/module.json +11 -0
  30. package/modules/ui-design/scripts/design-sync.js +65 -0
  31. package/modules/ui-design/scripts/figma-design-sync.js +64 -0
  32. package/modules/ui-design/scripts/validate-design.js +258 -0
  33. package/modules/ui-design/skills/README.md +3 -0
  34. package/modules/ui-design/styles/README.md +3 -0
  35. package/package-lock.json +2 -2
  36. package/package.json +1 -1
  37. package/scripts/gds/copy-inventory.js +7 -5
  38. package/scripts/gds/design-sync.js +9 -59
  39. package/scripts/gds/doc-cli-guard.js +11 -5
  40. package/scripts/gds/figma-design-sync.js +9 -59
  41. package/scripts/gds/fitness-checks-identity.js +5 -3
  42. package/scripts/gds/publish-manifest.js +1 -1
  43. package/scripts/gds/validate-design.js +7 -249
  44. package/src/bongos/module-scope-map.js +18 -0
  45. package/src/module-api.js +5 -1
  46. package/tests/design_tokens_sync.mjs +1 -1
  47. package/tests/doc_cli_guard.mjs +19 -0
  48. package/tests/figma_adapter.mjs +1 -1
  49. package/tests/join_application_note.mjs +112 -0
  50. package/tests/module_loader.mjs +4 -1
@@ -284,5 +284,6 @@ This keeps the decision history honest and traceable.
284
284
  | 0194 | [**A project's membership door is one decision, answered the same way everywhere a stranger meets it** ([task 1003044](https://cloudbongos.com/builders#/task/1003044), goal 1000058). The owner's 2026-08-16 joinability setting — open / apply / invite_only — was stored (1003040), pushed (1003139/1003140) and resolved into `branding().project.joinability`, enforced by nothing. Decision: ONE reader, `joinabilityMode()` in `auth-admission.js`, and `openEnrollmentEnabled()` becomes `mode === 'open'`, so the four sign-in call sites, `webAdmissionStatus` and the manifest's `admission.policy` follow untouched (SR-19: one gate, no drift). The legacy `OPEN_XENOS_ENROLLMENT` flag still opens an APPLY project — a self-hosted instance that set it keeps its door across the upgrade — but never reopens an explicit invite_only; an unreadable pack or a value this core cannot read fails CLOSED (invite_only), the ADR 0192 rule. The public `POST /access-requests` answers the door FIRST — before validation, before any lookup — `403 invite_only` / `409 open_enrollment` with plain sentences: no query, nothing revealed about who exists, and DB-free enough for the real booted app to prove. An Archon's invite is the row the gate already reads: `POST /access-requests/invite` writes `access_requests` at 'invited' (resolving a pending one in place), so sign-in consults nothing new — surfaced as "Invite by GitHub username" on the Watch page. The door is SAID wherever a stranger meets it, in the project's words: the hall landing renders one door per mode from the injected pack, the web 403 names the invite-only door and the way in, the CLI stops instead of filing, the hub's join relay names the door in the hub's own words (a project is trusted to build, not to write prose into the platform's UI) instead of painting a refusal as "requested" or "unreachable". `admission.policy` stays binary (pinned; agrees for free); `joinabilityMode` joins the doorway — a human MINOR by ADR 0161 §2, which the publish lane's LIVE test refused (HEAD's version must be a CI carrier's), so it rides the next CI patch and the contradiction is filed as an idea. The hub's `#mJoinability` clones `#mVisibility` rather than a factory — a third setting is where a factory pays. Rejected: folding the flag into `branding()` (memoized vs per-call; the manifest must stay the pushed value); a 200 for the open door (an older CLI would wait thirty minutes for an approval); re-deriving the mode inside the module; a second invite table.](<redacted>.md) | instance / project joinability |
285
285
  | 0195 | [**ADR numbers are checked like migration numbers, and the twenty existing collisions are grandfathered by name** ([task 1003164](https://cloudbongos.com/builders#/task/1003164)). Two ADRs could share a number and nothing flagged it — git sees no path collision, the index auto-merges into two rows, and every gate passes; it was found by a human reading `ls docs/adr/`, after a task note had already cited a number the collision made point at an unrelated document. Migrations had this guard already (`stemCollisions()` hard-fails CI, because a re-used migration number silently skips a migration); ADRs had only a best-effort allocator at claim time and nothing afterwards. `scripts/gds/adr-namespace.js` is the sibling module, wired into fitness as Check 31 and so into the required `unit` gate. Building it found twenty live collisions, not one, plus fourteen index defects of the same merge class — two ADRs listed twice and twelve with no row at all, ten of them the second file of a colliding pair, which is how a collision hides. The twenty pairs are grandfathered by EXACT FILENAME rather than renumbered: renumbering breaks every citation to them, which is the harm the check exists to prevent, and set-equality keeps the exemption as narrow as the history that earned it — a third file on a legacy number, or a rename of either half, still fails, and the list may only shrink. The index half is keyed on LINK TARGET, not number (a number rule cannot express a grandfathered pair): every file has exactly one row, every row points at a file that exists. Row ordering warns and never blocks — ten legacy rows are out of order and no PR author caused that (the ADR 0189 split). Rejected: renumbering; grandfathering by number; a numeric cutoff like LEGACY_BARE_CORE_MAX (a new ADR takes a number above the max, so it would catch nothing); fixing it in the claim-time allocator (which already tries, against a stale tree — that is how the collision happened).](<redacted>.md) | docs / numbering discipline |
286
286
  | 0196 | [**The chrome world becomes the token contract; the landing gets its own world sheet beside the hub's** ([task 1003298](https://cloudbongos.com/builders#/task/1003298), goal 1000080). The owner picked the chrome-blobs world (1003296) and the v3 composition with light rebuilt to the dark standard (1003297); this build makes it the platform's token contract. Decisions: (1) the neutral pack moves to the world's LIGHT values (pale grey paper, black ink, salmon accent, Manrope + JetBrains Mono) and design-tokens mirrors it; --accent-deep is #a3481a rather than the mock's #d96a35 because the hub and the gate pages paint TEXT with it and #a3481a is the lightest burnt orange clearing 4.5:1 on all three light grounds; the rgba hairlines are flattened to hex so hex-maths consumers survive. (2) theme.ui.backdrop becomes var(--bg): a literal there paints the light ground under the dark page in both modes, since the body rule outranks every sheet. (3) The landing gets its own world sheet public-landing/assets/world.css (contract tier = pack, derived tier both modes, root-absolute plate token PAIRS, the kill switch; NO composition switches) and cosmos.css stays the hub's component sheet until A4 — folding the two tiers would have collided (--line is a width in the world and a colour in the hub; the world's warm --ok/--warn/--err would repaint the hub's fixed green/amber/red chips). cosmos.css's dark tier moves to the world's black and its derived tier is retuned for the salmon accent (dark ink on the fill, never white). (4) Plates ship under public-landing/assets/world/ with provenance embedded: black plates verbatim JPEG, cutouts as WebP with alpha at 1600px (85–170 KB vs 1.1–2.4 MB PNG), sidecars; plate URLs in custom properties escape the ?v= stamp (edge cache 4h), accepted. (5) Live figures are BAKED inside apex-only sentinels (real, dated; a child renders empty until its own feed answers) and refreshed at render time from the five public feeds with fixed definitions: tasks shipped = the sum over every version row, builders = leaderboard entries holding credits, the ledger line = the first building version; live titles have em dashes normalised to a comma and a trailing parenthetical dropped. Every pack-reading fallback site (the claude-design adapter, the status explore page, provisioning onboarding, dev-box brand artifacts + tray icons, the two gate pages) is moved in the same task so no page keeps a private palette. Rejected: one merged token tier; a landing-private palette; PNG cutouts; a script-only ledger; #d96a35 as accent-deep.](<redacted>.md) | apex / design system |
287
+ | 0197 | [**The ui-design module: design is a platform capability; the instance owns its world** ([task 1003324](https://cloudbongos.com/builders#/task/1003324), goal 1000080 — the first of the five ui-design tasks scoped with the owner 2026-08-28). ADR 0081 put the tool-agnostic design layer in core as Cloud Bongos's machinery; the whole-frontend pass then built the chrome world on it and left the three things a builder needs — the contract and its tooling, a way to look before shipping, the taste and the looks — scattered across core, scratchpads and personal skill installs. The owner's reframing: this is NOT the front door, it is a PLATFORM capability every builder on every instance gets, and Cloud Bongos's front door is its first customer, not its owner. Decisions: (1) `modules/ui-design/` is a default-on core-domain module beside grading/economy/memory — it ships METHOD, never a world; (2) ADR 0081's layer moves in (`src/ui/adapters/` → `adapters/`, the three `scripts/gds/` CLIs → `scripts/`, `docs/design-contract.md` → `docs/`) while `config/design-tokens.*`, `design-sources.json` and `DESIGN.md` STAY out as the host boundary — the ADR 0062 §7 split written for design; (3) declaration-only: three contributed skills, no routes/port/migrations, so `GET /modules` lists it and disabling it withdraws the skills and touches no served surface — and NO `ui` discipline yet, because the hall settings page and newcomer-restock don't know it; (4) the `/design` playbook is finally written — `discipline-modes.json` had routed `ui` tasks to a skill that did not exist since task 1267; (5) the module CLAUDE.md draws the platform FLOORS (the Fifteen Rule, the dark twins, no page `:root`, the 24px floor, AA in both modes, the Kill Switch) against the instance's WORLD (its packs + DESIGN.md), tightenable and never loosenable; (6) the CLIs keep thin `scripts/gds/` shims — the form `claude-materialize` rewrites to `bongos exec` on a packaged core, the form `doc-cli-guard` resolves, and the carve precedent — while inside the module they walk up from `__dirname` instead of the computed `require()` into `src/instance-config.js` the static boundary scanner could not see; (7) three reserved homes, each README'd for the task that fills it, the vendoring policy pointed at the skills task's ADR; (8) `public-landing` keeps its own key. Rejected: leaving the layer in core; moving the token files in; the module owning the front door; module-hosted CLIs with no shims; contributing the `ui` discipline now.](<redacted>.md) | modules / design system |
287
288
 
288
289
  > ⚠️ **Numbering collisions are now machine-enforced, not narrated.** Twenty numbers are shared by two ADRs each — assigned in parallel sessions before anything checked. The files keep their filenames (renumbering would break every existing citation, and a number once assigned is never reused), and this table disambiguates each pair as `NNNN-a` / `NNNN-b`. The authoritative list is `LEGACY_DUPLICATE_ADRS` in [`scripts/gds/adr-namespace.js`](../../scripts/gds/adr-namespace.js), frozen by exact filename: a **new** collision — or a third file joining a legacy number, or a rename of either half — hard-fails the `unit` CI gate, as does an index row that points at no file or an ADR with no row. The list may only shrink. This paragraph used to narrate the collisions one by one and had fallen eight behind reality; see [ADR 0195](<redacted>.md) for why the check exists and why the pairs are grandfathered rather than renumbered.
@@ -18967,6 +18967,10 @@
18967
18967
  "origin": {
18968
18968
  "type": "string",
18969
18969
  "maxLength": 512
18970
+ },
18971
+ "note": {
18972
+ "type": "string",
18973
+ "maxLength": 400
18970
18974
  }
18971
18975
  },
18972
18976
  "required": [
@@ -395,7 +395,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
395
395
  | GET | `/api/bongos/my-projects/invites` | `any-builder` | — | the caller's OWN pending invitations (ADR 0141 §5). |
396
396
  | POST | `/api/bongos/my-projects/invites/:clientId/accept` | `any-builder` | — | accept an invite (pending → builder) for the caller's OWN account. |
397
397
  | POST | `/api/bongos/my-projects/invites/:clientId/decline` | `any-builder` | — | decline an invite (delete the pending row) for the caller's OWN account. |
398
- | POST | `/api/bongos/my-projects/join` | `any-builder` | `origin` | request to join a project. |
398
+ | POST | `/api/bongos/my-projects/join` | `any-builder` | `origin`, `note` | request to join a project. |
399
399
 
400
400
  ## `override-requests` (2)
401
401
 
@@ -38,7 +38,7 @@ The product is organized as a small kernel (`src/`) + **19 modules** under `modu
38
38
  - `discord` — ship broadcast + inbound bot + role-sync; port: `discord.isLinked`
39
39
  - `art-pipeline` — pixel-art generation + artist discipline; port: `art.sharedKeyConfigured`
40
40
  - (+ `character-anim`, declaration-only — ADR 0088)
41
- - **Core-domain modules** (`default: true` — always on unless disabled): `memory`, `grading`, `economy`, `ideas`, `security`, `builder-settings`, `onboarding`, `sessions`, plus `lifecycle` (the task/claim/version state machine), `autonomy` (the MAS), the `hall-ui` + `status-ui` internal surfaces, and the `demo` acceptance-proof module. See [docs/modules-contract.md](modules-contract.md) for the live roster and which default on.
41
+ - **Core-domain modules** (`default: true` — always on unless disabled): `memory`, `grading`, `economy`, `ideas`, `security`, `builder-settings`, `onboarding`, `sessions`, plus `lifecycle` (the task/claim/version state machine), `autonomy` (the MAS), the `hall-ui` + `status-ui` internal surfaces, `ui-design` (the design contract, its adapters and the `/design` playbook as a platform capability — [ADR 0197](adr/<redacted>.md)), and the `demo` acceptance-proof module. See [docs/modules-contract.md](modules-contract.md) for the live roster and which default on.
42
42
  - **Module-owned migrations** — `modules/<key>/migrations/*.sql` applied only when the module is enabled. Stem-keyed so moving a migration never re-runs it.
43
43
  - **`bongos module new <key>`** — scaffolds a new module directory in one command.
44
44
  - **`bongos upgrade`** — the update-consumption channel a two-repo consumer uses to pull the core from version N→N+1 (task 1690, ADR 0100 §2 / ADR 0103 §4): pre-flight (module-compat + clean tree) → bump the vendored-tarball pin → `npm install` → `npm run migrate` (additive `core_*` migrations) → re-materialize `.claude/` → restart → verify. Run bare (no `--to`) it is still just the module-compat pre-check that exits 1 on mismatch; `--to <version>` runs the real bump. (`scripts/gds/upgrade.js`.)
@@ -4,13 +4,13 @@
4
4
  > Deterministic on a given tree — regenerate and commit rather than hand-editing.
5
5
  > Machine-readable twin: [`copy-registry.json`](copy-registry.json).
6
6
 
7
- **1533 strings** across 3 surfaces · 1334 distinct pieces of copy · 1533 certain, 0 heuristic · 154 shown in more than one place.
7
+ **1535 strings** across 3 surfaces · 1336 distinct pieces of copy · 1535 certain, 0 heuristic · 154 shown in more than one place.
8
8
 
9
9
  ## Surfaces
10
10
 
11
11
  | surface | lives at | files | strings |
12
12
  |---|---|---|---|
13
- | `builders-hall` | `modules/hall-ui/public` | 71 | 1128 |
13
+ | `builders-hall` | `modules/hall-ui/public` | 71 | 1130 |
14
14
  | `status-dashboard` | `modules/status-ui/public` | 4 | 98 |
15
15
  | `landing` | `public-landing` | 5 | 307 |
16
16
 
@@ -28,7 +28,7 @@
28
28
  - Template literals containing a nested backtick: the literal scanner stops at the first closing backtick, so a nested template inside `${...}` truncates that one literal. Rare in this tree and cheap to spot (a row that ends mid-sentence); a real JS parser is the fix if it ever matters.
29
29
  - Runtime-composed strings: anything assembled from fragments or interpolated at render time is captured only as its literal parts. A sentence that exists only once the browser runs cannot be inventoried by reading the tree.
30
30
  - Generated pages: `docs/api-reference.md` and the OpenAPI surface are generated from route prose; that copy belongs to its generator, not to a designer.
31
- - The design-contract surface layout: `docs/design-contract.md` expects `src/ui/<surface>/`, which this tree does not use (`src/ui` holds only `adapters/`). Surface ids here are a MAPPING onto real paths, not evidence the contract layout exists.
31
+ - The design-contract surface layout: `modules/ui-design/docs/design-contract.md` expects `src/ui/<surface>/`, which this tree does not use (there is no `src/ui/`; the adapters live in `modules/ui-design/adapters/`). Surface ids here are a MAPPING onto real paths, not evidence the contract layout exists.
32
32
 
33
33
  ## The same words, shown twice
34
34
 
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
78
78
  | `c10806b50c61` | 2x | Claim task {…} | modules/hall-ui/public/goals-render.js:280, modules/hall-ui/public/work.js:417 |
79
79
  | … | | _114 more in the JSON registry_ | |
80
80
 
81
- ## `builders-hall` — 1128 strings
81
+ ## `builders-hall` — 1130 strings
82
82
 
83
83
  | id | conf | text | where |
84
84
  |---|---|---|---|
@@ -135,6 +135,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
135
135
  | `6ca31dd5eb91` | certain | Acknowledge the primer | `modules/hall-ui/public/primer.html:65` |
136
136
  | `4461b13f95c2` | certain | Actions apply at once. Closing stops and deletes the box; you can start a fresh one anytime. | `modules/hall-ui/public/settings.html:162` |
137
137
  | `f6036360c90c` | certain | Activity, by craft | `modules/hall-ui/public/profile.html:84` |
138
+ | `b8476c677e7c` | certain | Add a note (optional) | `modules/hall-ui/public/my-projects.html:52` |
138
139
  | `62eded408097` | certain | Add {…} | `modules/hall-ui/public/profile-nudge.js:68` |
139
140
  | `7cf0163d4cde` | certain | Aggregate totals across every recorded work session. | `modules/hall-ui/public/sessions.html:35` |
140
141
  | `8b736e365191` | certain | All builders | `modules/hall-ui/public/hall-render.js:1039` |
@@ -148,6 +149,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
148
149
  | `69f0ca4a4d84` | certain | Analytics scope | `modules/hall-ui/public/hall-render.js:1037` |
149
150
  | `14bdceaac078` | certain | Answer the five questions and this spark becomes a Full Idea. | `modules/hall-ui/public/ideas.js:423` |
150
151
  | `52ed0e2e3470` | certain | Any rank | `modules/hall-ui/public/task-lib.js:62` |
152
+ | `1ab0e6d5b079` | certain | Anything the project's owner should know — what you'd like to work on, who you are. | `modules/hall-ui/public/my-projects.html:54` |
151
153
  | `c89833aa985a` | certain | Approval failed — try again. | `modules/hall-ui/public/pair.js:92` |
152
154
  | `0832ba017c90` | certain | Approve & re-run | `modules/hall-ui/public/gate.js:100` |
153
155
  | `222121f67525` | certain | Approve only if you just clicked “Connect” in the Dev Box app on your own machine. Approving signs that app in | `modules/hall-ui/public/pair.html:30` |
@@ -306,8 +308,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
306
308
  | `8b470cebf53d` | certain | Could not remove your key. Try again. | `modules/hall-ui/public/settings.js:1093` |
307
309
  | `4cf6bc8b377b` | certain | Could not remove. Try again. | `modules/hall-ui/public/settings.js:1092` |
308
310
  | `7a778d188e87` | certain | Could not remove: {…} | `modules/hall-ui/public/government.js:336` |
309
- | `ebeda72a6c15` | certain | Could not request access right now. | `modules/hall-ui/public/my-projects.js:140` |
310
- | `ebeda72a6c15` | certain | Could not request access right now. | `modules/hall-ui/public/my-projects.js:142` |
311
+ | `ebeda72a6c15` | certain | Could not request access right now. | `modules/hall-ui/public/my-projects.js:146` |
312
+ | `ebeda72a6c15` | certain | Could not request access right now. | `modules/hall-ui/public/my-projects.js:148` |
311
313
  | `bb5a7753e62b` | certain | Could not reset: {…} | `modules/hall-ui/public/government.js:275` |
312
314
  | `b526ee0b8c48` | certain | Could not save just now. Try again. | `modules/hall-ui/public/builders.js:459` |
313
315
  | `41a351aea8d4` | certain | Could not save the volume. Try again. | `modules/hall-ui/public/settings.js:214` |
@@ -338,7 +340,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
338
340
  | `278e8281c82f` | certain | Could not vote: {…} | `modules/hall-ui/public/government.js:913` |
339
341
  | `a48e9f098392` | certain | Could not {…} just now. | `modules/hall-ui/public/goals-render.js:951` |
340
342
  | `cb09ee26014f` | certain | Could not {…}: {…} | `modules/hall-ui/public/government.js:288` |
341
- | `7f600651bbf1` | certain | Couldn’t load your projects right now. | `modules/hall-ui/public/my-projects.js:115` |
343
+ | `7f600651bbf1` | certain | Couldn’t load your projects right now. | `modules/hall-ui/public/my-projects.js:117` |
342
344
  | `28ec06f6060a` | certain | Counted, not judged: each question is worth the same, and earns its share in proportion to how much real text | `modules/hall-ui/public/ideas.html:104` |
343
345
  | `38827a954f3b` | certain | Create a custom rank first. | `modules/hall-ui/public/government.js:419` |
344
346
  | `dd8ead7adc9a` | certain | Create rank | `modules/hall-ui/public/government.html:69` |
@@ -383,7 +385,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
383
385
  | `7f1b1c32d360` | certain | Each slash-command can run on a different model. The system has a default for every skill; choose another to o | `modules/hall-ui/public/settings.html:245` |
384
386
  | `854a21d2eb39` | certain | Eight achievements recognize sustained contribution. Earned ones are highlighted; the rest show what’s left to | `modules/hall-ui/public/hall-widgets.js:197` |
385
387
  | `58d381535285` | certain | Engagement reports | `modules/hall-ui/public/watch.html:120` |
386
- | `35d1ae09a6b3` | certain | Enter a project URL. | `modules/hall-ui/public/my-projects.js:122` |
388
+ | `35d1ae09a6b3` | certain | Enter a project URL. | `modules/hall-ui/public/my-projects.js:124` |
387
389
  | `bd7a40639a1c` | certain | Estimate (min) | `modules/hall-ui/public/task-detail.js:362` |
388
390
  | `c15d1f0ddfb6` | certain | Estimation accuracy | `modules/hall-ui/public/watch.html:133` |
389
391
  | `44f928d13699` | certain | Event sounds | `modules/hall-ui/public/settings.html:282` |
@@ -826,7 +828,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
826
828
  | `44579eef0c8d` | certain | Tasks — {{worldName}} | `modules/hall-ui/public/work.html:6` |
827
829
  | `44579eef0c8d` | certain | Tasks — {{worldName}} | `modules/hall-ui/public/work.html:6` |
828
830
  | `2f4f047dc1b4` | certain | Text size | `modules/hall-ui/public/shell.js:321` |
829
- | `4a8d1beb6f98` | certain | That project isn’t on the platform. | `modules/hall-ui/public/my-projects.js:139` |
831
+ | `4a8d1beb6f98` | certain | That project isn’t on the platform. | `modules/hall-ui/public/my-projects.js:145` |
830
832
  | `c0e28f30524d` | certain | That project's own standing, as reported by that project. | `modules/hall-ui/public/profile.js:230` |
831
833
  | `50198c465225` | certain | The 16-value rank enum in three bands, which ranks are live, and the permissions each rank holds. | `modules/hall-ui/public/diagrams.html:68` |
832
834
  | `1569b8838862` | certain | The Dev Box app now acts as | `modules/hall-ui/public/pair.js:40` |
@@ -3,9 +3,9 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1533,
7
- "unique_ids": 1334,
8
- "certain": 1533,
6
+ "strings": 1535,
7
+ "unique_ids": 1336,
8
+ "certain": 1535,
9
9
  "heuristic": 0,
10
10
  "duplicate_ids": 154
11
11
  },
@@ -13,7 +13,7 @@
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
15
  "files": 71,
16
- "strings": 1128
16
+ "strings": 1130
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -35,7 +35,7 @@
35
35
  "Template literals containing a nested backtick: the literal scanner stops at the first closing backtick, so a nested template inside `${...}` truncates that one literal. Rare in this tree and cheap to spot (a row that ends mid-sentence); a real JS parser is the fix if it ever matters.",
36
36
  "Runtime-composed strings: anything assembled from fragments or interpolated at render time is captured only as its literal parts. A sentence that exists only once the browser runs cannot be inventoried by reading the tree.",
37
37
  "Generated pages: `docs/api-reference.md` and the OpenAPI surface are generated from route prose; that copy belongs to its generator, not to a designer.",
38
- "The design-contract surface layout: `docs/design-contract.md` expects `src/ui/<surface>/`, which this tree does not use (`src/ui` holds only `adapters/`). Surface ids here are a MAPPING onto real paths, not evidence the contract layout exists."
38
+ "The design-contract surface layout: `modules/ui-design/docs/design-contract.md` expects `src/ui/<surface>/`, which this tree does not use (there is no `src/ui/`; the adapters live in `modules/ui-design/adapters/`). Surface ids here are a MAPPING onto real paths, not evidence the contract layout exists."
39
39
  ],
40
40
  "duplicates": [
41
41
  {
@@ -589,8 +589,8 @@
589
589
  "text": "Could not request access right now.",
590
590
  "occurrences": 2,
591
591
  "at": [
592
- "modules/hall-ui/public/my-projects.js:140",
593
- "modules/hall-ui/public/my-projects.js:142"
592
+ "modules/hall-ui/public/my-projects.js:146",
593
+ "modules/hall-ui/public/my-projects.js:148"
594
594
  ]
595
595
  },
596
596
  {
@@ -2102,6 +2102,15 @@
2102
2102
  "origin": "html-text",
2103
2103
  "confidence": "certain"
2104
2104
  },
2105
+ {
2106
+ "id": "b8476c677e7c",
2107
+ "surface": "builders-hall",
2108
+ "text": "Add a note (optional)",
2109
+ "file": "modules/hall-ui/public/my-projects.html",
2110
+ "line": 52,
2111
+ "origin": "html-text",
2112
+ "confidence": "certain"
2113
+ },
2105
2114
  {
2106
2115
  "id": "62eded408097",
2107
2116
  "surface": "builders-hall",
@@ -2219,6 +2228,15 @@
2219
2228
  "origin": "js-markup",
2220
2229
  "confidence": "certain"
2221
2230
  },
2231
+ {
2232
+ "id": "1ab0e6d5b079",
2233
+ "surface": "builders-hall",
2234
+ "text": "Anything the project's owner should know — what you'd like to work on, who you are.",
2235
+ "file": "modules/hall-ui/public/my-projects.html",
2236
+ "line": 54,
2237
+ "origin": "html-attr:placeholder",
2238
+ "confidence": "certain"
2239
+ },
2222
2240
  {
2223
2241
  "id": "c89833aa985a",
2224
2242
  "surface": "builders-hall",
@@ -3646,7 +3664,7 @@
3646
3664
  "surface": "builders-hall",
3647
3665
  "text": "Could not request access right now.",
3648
3666
  "file": "modules/hall-ui/public/my-projects.js",
3649
- "line": 140,
3667
+ "line": 146,
3650
3668
  "origin": "js-text-assign",
3651
3669
  "confidence": "certain"
3652
3670
  },
@@ -3655,7 +3673,7 @@
3655
3673
  "surface": "builders-hall",
3656
3674
  "text": "Could not request access right now.",
3657
3675
  "file": "modules/hall-ui/public/my-projects.js",
3658
- "line": 142,
3676
+ "line": 148,
3659
3677
  "origin": "js-text-assign",
3660
3678
  "confidence": "certain"
3661
3679
  },
@@ -3934,7 +3952,7 @@
3934
3952
  "surface": "builders-hall",
3935
3953
  "text": "Couldn’t load your projects right now.",
3936
3954
  "file": "modules/hall-ui/public/my-projects.js",
3937
- "line": 115,
3955
+ "line": 117,
3938
3956
  "origin": "js-text-assign",
3939
3957
  "confidence": "certain"
3940
3958
  },
@@ -4339,7 +4357,7 @@
4339
4357
  "surface": "builders-hall",
4340
4358
  "text": "Enter a project URL.",
4341
4359
  "file": "modules/hall-ui/public/my-projects.js",
4342
- "line": 122,
4360
+ "line": 124,
4343
4361
  "origin": "js-text-assign",
4344
4362
  "confidence": "certain"
4345
4363
  },
@@ -8326,7 +8344,7 @@
8326
8344
  "surface": "builders-hall",
8327
8345
  "text": "That project isn’t on the platform.",
8328
8346
  "file": "modules/hall-ui/public/my-projects.js",
8329
- "line": 139,
8347
+ "line": 145,
8330
8348
  "origin": "js-text-assign",
8331
8349
  "confidence": "certain"
8332
8350
  },
package/docs/file-map.md CHANGED
@@ -129,6 +129,7 @@
129
129
  │ ├── 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.
130
130
  │ ├── 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).
131
131
  │ ├── 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. CLAUDE.md, module.json, registry.js, flags.js, routes/copy-desk.js, migrations/copy_desk_001_flags.sql, tests/{copy_flags,copy_queue,copy_no_cms}.mjs. Its hall surface lives with hall-ui (public/copy-desk.{html,js,css}), per the one-key-per-web-surface precedent.
132
+ │ ├── 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). Reserved homes kit/ (task 1003320), skills/ (1003321), styles/ (1003323), each with a README. config/design-tokens.* + design-sources.json STAY in config/ as the host boundary; public-landing keeps its own key (first customer, not owner).
132
133
  │ ├── character-anim/ ← consistent-character generative animation (ADR 0088) — graded painterly frame sets + manifest, the otb-character-review skill. CLAUDE.md, module.json.
133
134
  │ ├── provisioning/ ← stands up + operates a Cloud Bongos instance's own infra (server/DB/domain/DNS/TLS) via the API (ADR 0111); web-tier request+state only, no cloud tokens — a separate control-plane runner drains the intents. Distinct from the `hosting` module (ADR 0092), which brokers compute workloads. CLAUDE.md, module.json.
134
135
  │ └── demo/ ← BV1.R52 acceptance-proof module: one rank-gated write route + one namespaced migration proving the loader mounts a scaffolded module with zero core edits. CLAUDE.md, module.json.
@@ -373,6 +374,7 @@ tests/
373
374
  ├── builder-start/SKILL.md ← /builder-start — list claimable tasks (parallel-safe vs active claims)
374
375
  ├── builder-sync/SKILL.md ← /builder-sync — 'sync now': re-run the box source-fetch on demand so a freshly-claimed task's code lands within seconds, re-scoped to the active claim (BV1.R96, ADR 0148)
375
376
  ├── close-box/SKILL.md ← /close-box — tear down the dev box you're working on, from inside the box terminal (POST /box/close)
377
+ ├── design/SKILL.md ← /design — the UI design session: the operating playbook for ui-discipline work on ANY instance (load the instance's pack + DESIGN.md first, look before you ship, build inside the platform floors, ship the tests with the UI); routed from a `ui` claim via discipline-modes.json; shipped by the ui-design module (task 1003324, ADR 0197)
376
378
  ├── design-sync/SKILL.md ← /design-sync — export repo design tokens + surfaces into Claude Design (token-preview bundle) and land generated UI code back as a GDS task (ADR 0081)
377
379
  ├── dev/SKILL.md ← /dev — engineering-session playbook for engineer-discipline tasks; the default understand→scoped-change→verify→ship loop made explicit ([#1272](https://example.com/builders#/task/1272))
378
380
  ├── feedback/SKILL.md ← /feedback — pull the latest Bongos feedback bundle (prompt.md + screenshot file paths) into a Claude Code session; backed by scripts/gds/feedback-latest.js (BV1.R13, ADR 0087)
@@ -246,6 +246,7 @@ Rules (enforced by the `BV1.R45` fitness check):
246
246
  | **`hall-ui`** *(core-domain web surface, default-on; R85)* | webSurfaces: `{ host: "builders.", dir: "public" }` (the authenticated builders' hall) | — (registers NO port; keeps a DEDICATED serving block in `serve-internal.js`, unlike `status-ui`) | `^1.11.0` |
247
247
  | **`lifecycle`** *(core-domain, default-on; R86)* | routes: `tasks`, `claims`, `versions`, `done-when`, `goals`, `dependencies`, `gate-approvals`, `analytics` | provides `lifecycle` (createTask, classifyTaskKind, tallyPeerVotes, work-tracking reads); resolves `reward`, `onboarding` | `^1.12.0` |
248
248
  | **`provisioning`** *(default-off; [ADR 0111](adr/<redacted>.md))* | routes: `provisioning` (own+Archon rank-gated) | provides `provisioning.instanceState` | `^1.14.0` |
249
+ | **`ui-design`** *(core-domain, default-on; [ADR 0197](adr/<redacted>.md))* | skills: `design`, `design-sync`, `figma-design-sync` (the /design playbook + the two tool round-trips); the ADR 0081 adapters, sync CLIs and validator live in the module dir | — (registers NO port, NO routes; declaration-only — disabling it withdraws the skills and touches no served surface) | `^1.19.0` |
249
250
 
250
251
  Each module's `modules/<key>/CLAUDE.md` is the authoritative per-module reference (files, seams, tests, what is NOT in the module).
251
252
 
@@ -66,7 +66,7 @@ Every one of these talks to the build system. They're grouped by when you'd reac
66
66
 
67
67
  ### Design and art pipeline
68
68
 
69
- `/design-sync` and `/figma-design-sync` round-trip the UI design system. The `/otb-*` family — `otb-tile-generate`, `otb-design-review`, `otb-character-review`, `otb-feedback-capture`, `otb-figma-sync` — is the reference instance's pixel-art pipeline; it's only meaningful on an instance that runs that pipeline.
69
+ `/design` is the ui-discipline playbook (world-first, show-first — shipped by the `ui-design` module, ADR 0197); `/design-sync` and `/figma-design-sync` round-trip the UI design system. The `/otb-*` family — `otb-tile-generate`, `otb-design-review`, `otb-character-review`, `otb-feedback-capture`, `otb-figma-sync` — is the reference instance's pixel-art pipeline; it's only meaningful on an instance that runs that pipeline.
70
70
 
71
71
  ### Session and meta
72
72
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  > End-to-end guide for the repo ↔ Claude Design sync cycle.
4
4
  > ADR: [ADR 0081](../adr/<redacted>.md).
5
- > Adapter: `src/ui/adapters/claude-design/index.js`.
6
- > CLI: `scripts/gds/design-sync.js`.
5
+ > Adapter: `modules/ui-design/adapters/claude-design/index.js` (the `ui-design` module — ADR 0197).
6
+ > CLI: `modules/ui-design/scripts/design-sync.js` (`scripts/gds/design-sync.js` is a thin shim over it).
7
7
  > Skill: `.claude/skills/design-sync/SKILL.md`.
8
8
 
9
9
  ---
@@ -93,7 +93,7 @@ When Claude Design's output is ready:
93
93
  **b. Run the export step** — either via the skill or directly:
94
94
 
95
95
  ```js
96
- const adapter = require('./src/ui/adapters/claude-design/index.js');
96
+ const adapter = require('./modules/ui-design/adapters/claude-design/index.js');
97
97
  const result = await adapter.export({
98
98
  repoRoot: process.cwd(),
99
99
  surfaceId: 'hall-ui',
@@ -2,8 +2,8 @@
2
2
 
3
3
  > End-to-end guide for the repo ↔ Figma sync cycle — the human escape hatch in ADR 0081.
4
4
  > ADR: [ADR 0081](../adr/<redacted>.md).
5
- > Adapter: `src/ui/adapters/figma/index.js`.
6
- > CLI: `scripts/gds/figma-design-sync.js`.
5
+ > Adapter: `modules/ui-design/adapters/figma/index.js` (the `ui-design` module — ADR 0197).
6
+ > CLI: `modules/ui-design/scripts/figma-design-sync.js` (`scripts/gds/figma-design-sync.js` is a thin shim over it).
7
7
  > Skill: `.claude/skills/figma-design-sync/SKILL.md`.
8
8
  >
9
9
  > Not to be confused with `otb-figma-sync` (pixel-art tile review, a separate Figma file, a separate concern).
@@ -72,7 +72,7 @@ The designer opens the pushed frames (or an existing component) and makes a manu
72
72
 
73
73
  **Code Connect output (ready code):**
74
74
  ```js
75
- const adapter = require('./src/ui/adapters/figma/index.js');
75
+ const adapter = require('./modules/ui-design/adapters/figma/index.js');
76
76
  const result = await adapter.export({
77
77
  repoRoot: process.cwd(),
78
78
  surfaceId: 'hall-ui',
@@ -113,7 +113,7 @@ node scripts/gds/ship.js 1234 --notes-file notes.md --summary-file summary.txt
113
113
 
114
114
  ## Code-canonical vs. Figma-canonical
115
115
 
116
- Code-canonical is the default: the repo is authoritative, the Figma file is a synced view. A surface may be flagged `"figmaCanonical": true` in its `config/design-sources.json` entry (docs/design-contract.md §3.4) as an explicit exception for a design-system foundation a dedicated designer actively iterates on in Figma. All other surfaces stay code-canonical.
116
+ Code-canonical is the default: the repo is authoritative, the Figma file is a synced view. A surface may be flagged `"figmaCanonical": true` in its `config/design-sources.json` entry (modules/ui-design/docs/design-contract.md §3.4) as an explicit exception for a design-system foundation a dedicated designer actively iterates on in Figma. All other surfaces stay code-canonical.
117
117
 
118
118
  ---
119
119
 
@@ -37,4 +37,26 @@
37
37
  background: var(--surface);
38
38
  color: var(--ink);
39
39
  }
40
+ /* The application's optional note (task 1002330). Sits under the one-click join
41
+ row so the click stays the primary act and the note is plainly skippable. */
42
+ .join-note-label {
43
+ display: block;
44
+ margin: 10px 0 4px;
45
+ color: var(--ink-soft);
46
+ font-size: 12px;
47
+ }
48
+ .join-note {
49
+ display: block;
50
+ width: 100%;
51
+ box-sizing: border-box;
52
+ font: inherit;
53
+ font-size: 13px;
54
+ line-height: 1.4;
55
+ padding: 6px 10px;
56
+ resize: vertical;
57
+ border: 1px solid var(--border-strong);
58
+ border-radius: var(--radius-sm);
59
+ background: var(--surface);
60
+ color: var(--ink);
61
+ }
40
62
  .note { margin: 10px 0 0; color: var(--ink-soft); font-size: 13px; min-height: 1.2em; }
@@ -46,7 +46,13 @@
46
46
  <input id="join-origin" type="url" placeholder="https://project.cloudbongos.com" autocomplete="off">
47
47
  <button id="join-btn" type="button" class="btn-ghost">Request to join</button>
48
48
  </div>
49
- <p id="join-note" class="note"></p>
49
+ <!-- An application is one click; the note is the applicant's own words,
50
+ and it is optional on purpose (task 1002330 — lightweight, never an
51
+ owner-defined form). It reaches the project owner's queue. -->
52
+ <label class="join-note-label" for="join-note">Add a note (optional)</label>
53
+ <textarea id="join-note" class="join-note" rows="2" maxlength="400"
54
+ placeholder="Anything the project's owner should know — what you'd like to work on, who you are."></textarea>
55
+ <p id="join-status" class="note"></p>
50
56
  </section>
51
57
  </main>
52
58
 
@@ -1,6 +1,7 @@
1
1
  // my-projects.js — the signed-in builder's cross-project view (ADR 0141 §3, task
2
2
  // #1002112). Fetches /api/gds/my-projects (session cookie, same-origin) and renders
3
- // owned + building-on lists with self-service leave, plus a join-by-origin form.
3
+ // owned + building-on lists with self-service leave, plus a join-by-origin form
4
+ // whose application is one click plus an optional note (task 1002330).
4
5
  // All network values are rendered via textContent — never innerHTML.
5
6
  (function () {
6
7
  'use strict';
@@ -8,7 +9,8 @@
8
9
  var buildingEl = document.getElementById('building');
9
10
  var joinBtn = document.getElementById('join-btn');
10
11
  var joinInput = document.getElementById('join-origin');
11
- var joinNote = document.getElementById('join-note');
12
+ var joinNoteInput = document.getElementById('join-note'); // the applicant's own words (optional)
13
+ var joinStatus = document.getElementById('join-status'); // what the project answered
12
14
 
13
15
  function el(tag, cls) { var e = document.createElement(tag); if (cls) e.className = cls; return e; }
14
16
 
@@ -119,27 +121,31 @@
119
121
 
120
122
  joinBtn.addEventListener('click', function () {
121
123
  var origin = (joinInput.value || '').trim();
122
- if (!origin) { joinNote.textContent = 'Enter a project URL.'; return; }
124
+ if (!origin) { joinStatus.textContent = 'Enter a project URL.'; return; }
125
+ var note = (joinNoteInput.value || '').trim();
123
126
  joinBtn.disabled = true;
124
- joinNote.textContent = 'Requesting…';
127
+ joinStatus.textContent = 'Requesting…';
125
128
  fetch('/api/gds/my-projects/join', {
126
129
  method: 'POST',
127
130
  headers: { 'Content-Type': 'application/json' },
128
- body: JSON.stringify({ origin: origin }),
131
+ /* note is omitted entirely when blank — an application with nothing written
132
+ is still an application (task 1002330). */
133
+ body: JSON.stringify(note ? { origin: origin, note: note } : { origin: origin }),
129
134
  })
130
135
  .then(function (r) { return r.json().then(function (b) { return { ok: r.ok, body: b }; }); })
131
136
  .then(function (res) {
132
137
  joinBtn.disabled = false;
133
138
  if (res.ok && res.body && res.body.message) {
134
- joinNote.textContent = res.body.message;
135
- /* a door refusal rides a 200 (task 1003044): keep the address, and don't repaint as if a request went out */
139
+ joinStatus.textContent = res.body.message;
140
+ /* a door refusal rides a 200 (task 1003044): keep the address AND the
141
+ note the applicant wrote, and don't repaint as if a request went out */
136
142
  var refused = res.body.status === 'invite_only' || res.body.status === 'open_enrollment' || res.body.status === 'members_only';
137
- if (!refused) { joinInput.value = ''; load(); }
143
+ if (!refused) { joinInput.value = ''; joinNoteInput.value = ''; load(); }
138
144
  }
139
- else if (res.body && res.body.error && res.body.error.code === 'unknown_project') { joinNote.textContent = 'That project isn’t on the platform.'; }
140
- else { joinNote.textContent = 'Could not request access right now.'; }
145
+ else if (res.body && res.body.error && res.body.error.code === 'unknown_project') { joinStatus.textContent = 'That project isn’t on the platform.'; }
146
+ else { joinStatus.textContent = 'Could not request access right now.'; }
141
147
  })
142
- .catch(function () { joinBtn.disabled = false; joinNote.textContent = 'Could not request access right now.'; });
148
+ .catch(function () { joinBtn.disabled = false; joinStatus.textContent = 'Could not request access right now.'; });
143
149
  });
144
150
 
145
151
  load();
@@ -2,7 +2,9 @@
2
2
  // project "My Projects" view + self-service join / leave (ADR 0141 §3, task #1002112).
3
3
  //
4
4
  // GET /my-projects — projects this account builds on, owned grouped apart
5
- // POST /my-projects/join — request to join a project (executed by the project)
5
+ // POST /my-projects/join — apply to a project: one click plus an optional
6
+ // note in the applicant's own words (task 1002330).
7
+ // Relayed; the project executes and decides.
6
8
  // POST /my-projects/:clientId/leave — self-service offboard (reflected to the hub)
7
9
  //
8
10
  // All gated on a live HUB session (requireBuilder). Authority stays LOCAL: JOIN only
@@ -44,6 +46,40 @@ function joinRelayOutcome(status, body) {
44
46
  return { status: outcome, message: RELAY_SENTENCE[outcome] };
45
47
  }
46
48
 
49
+ // The provenance line the project's owner reads when an application arrives
50
+ // through the platform rather than off their own landing page. It is the HUB's
51
+ // sentence, not the applicant's — see relayApplicationNote.
52
+ const RELAY_PROVENANCE = 'via platform My Projects';
53
+
54
+ // What an applicant may write, and what the project will store. The project's
55
+ // public POST /access-requests caps `note` at 500 chars and `display_name` at
56
+ // 100, and refuses anything longer with a 400 — which joinRelayOutcome cannot
57
+ // read, so the hub would tell the applicant the project "could not be reached".
58
+ // A lie about someone else's door because our own field was too long is the bug
59
+ // these two caps exist to prevent: the hub asks for less than the project takes,
60
+ // and clamps anyway.
61
+ const APPLICANT_NOTE_MAX = 400;
62
+ const APPLICANT_NAME_MAX = 100;
63
+
64
+ // relayApplicationNote — the note that reaches the project's queue: the
65
+ // applicant's own words plus where the request came from (task 1002330). An
66
+ // application with nothing written is still an application, so an absent note
67
+ // relays the provenance line alone — exactly what this route sent before the
68
+ // applicant could write anything. Pure; clamped so the composed string always
69
+ // fits the project's 500-char column.
70
+ function relayApplicationNote(note) {
71
+ const written = String(note == null ? '' : note).trim().slice(0, APPLICANT_NOTE_MAX);
72
+ return written ? `${written} (${RELAY_PROVENANCE})` : RELAY_PROVENANCE;
73
+ }
74
+
75
+ // relayApplicantName — the applicant's display name as the project should show
76
+ // it, or null when they have none (the queue then reads as the bare @login it
77
+ // always did). Clamped to the project's column.
78
+ function relayApplicantName(displayName) {
79
+ const name = String(displayName == null ? '' : displayName).trim().slice(0, APPLICANT_NAME_MAX);
80
+ return name || null;
81
+ }
82
+
47
83
  module.exports = function myProjectsRoutes() {
48
84
  const router = express.Router();
49
85
 
@@ -75,7 +111,10 @@ module.exports = function myProjectsRoutes() {
75
111
  // stays local). SSRF guard: only relay to an origin that is a REGISTERED, active
76
112
  // client — never an arbitrary builder-supplied URL.
77
113
  router.post('/my-projects/join', requireBuilder, async (req, res) => {
78
- if (validateOrRespond(req, res, { origin: { required: true, type: 'string', maxLength: 512 } })) return;
114
+ if (validateOrRespond(req, res, {
115
+ origin: { required: true, type: 'string', maxLength: 512 },
116
+ note: { type: 'string', maxLength: APPLICANT_NOTE_MAX },
117
+ })) return;
79
118
  try {
80
119
  const client = await pi.getClientByOrigin(req.body.origin, { pool });
81
120
  if (!client || client.status !== 'active' || !client.origin) {
@@ -89,7 +128,11 @@ module.exports = function myProjectsRoutes() {
89
128
  const r = await fetch(`${client.origin}/api/gds/access-requests`, {
90
129
  method: 'POST',
91
130
  headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'User-Agent': userAgent() },
92
- body: JSON.stringify({ github_login: req.builder.github_login, note: 'via platform My Projects' }),
131
+ body: JSON.stringify({
132
+ github_login: req.builder.github_login,
133
+ display_name: relayApplicantName(req.builder.display_name),
134
+ note: relayApplicationNote(req.body.note),
135
+ }),
93
136
  });
94
137
  const body = await r.json().catch(() => null);
95
138
  outcome = joinRelayOutcome(r.status, body);
@@ -164,3 +207,7 @@ module.exports = function myProjectsRoutes() {
164
207
  return router;
165
208
  };
166
209
  module.exports.joinRelayOutcome = joinRelayOutcome;
210
+ module.exports.relayApplicationNote = relayApplicationNote;
211
+ module.exports.relayApplicantName = relayApplicantName;
212
+ module.exports.APPLICANT_NOTE_MAX = APPLICANT_NOTE_MAX;
213
+
@@ -0,0 +1,15 @@
1
+ # @bongos/core — installed platform dependency (not your project's CLAUDE.md)
2
+
3
+ This file is a placeholder shipped inside the **@bongos/core** package. The core's own
4
+ internal agent notes are intentionally omitted from the published artifact so they cannot
5
+ load into your instance's context.
6
+
7
+ **It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
8
+ directory, and the live Bongos database are the source of truth for how work happens here
9
+ and who may do what.
10
+
11
+ You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
12
+ Nothing in here needs to be read to use the platform — run `bongos help` for commands.
13
+
14
+ _To work ON the platform core itself, use the standalone @bongos/core source repository,
15
+ not this vendored copy._
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  // Claude Design adapter — wires the repo design system to Claude Design.
3
3
  //
4
- // Conforms to the adapter interface in docs/design-contract.md §4.
4
+ // Conforms to the adapter interface in modules/ui-design/docs/design-contract.md §4.
5
5
  //
6
6
  // import(opts): repo → Claude Design
7
7
  // Resolves design tokens, scans src/ui/ surfaces, and writes a token-preview
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  // Figma adapter — the human escape hatch in ADR 0081's tool-agnostic design layer.
3
3
  //
4
- // Conforms to the adapter interface in docs/design-contract.md §4.
4
+ // Conforms to the adapter interface in modules/ui-design/docs/design-contract.md §4.
5
5
  //
6
6
  // import(opts): repo → Figma
7
7
  // Resolves design tokens + surfaces and builds a "push plan" (frames to
@@ -26,7 +26,7 @@ const os = require('node:os');
26
26
  const ID = 'figma';
27
27
 
28
28
  // Figma node types that carry no code-representable content — the round-trip
29
- // is honestly lossy here (docs/design-contract.md §2.3 / ADR 0081).
29
+ // is honestly lossy here (modules/ui-design/docs/design-contract.md §2.3 / ADR 0081).
30
30
  const ASSET_ONLY_NODE_TYPES = new Set(['VECTOR', 'BOOLEAN_OPERATION', 'STAR', 'ELLIPSE', 'LINE']);
31
31
 
32
32
  // ── token helpers ─────────────────────────────────────────────────────────────
@@ -170,7 +170,7 @@ function componentNameFor(node) {
170
170
  // Walks a Figma node snapshot and produces { files, warnings } — files are
171
171
  // only emitted for nodes whose content is genuinely code-representable
172
172
  // (frames, text, solid-fill rectangles). Image/vector content is skipped with
173
- // a warning, matching the asset-not-code rule (docs/design-contract.md §2.3).
173
+ // a warning, matching the asset-not-code rule (modules/ui-design/docs/design-contract.md §2.3).
174
174
  function snapshotToFiles(surfaceId, snapshot, colorTokenIndex) {
175
175
  const files = [];
176
176
  const warnings = [];