@bongos/core 1.19.349 → 1.19.350

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 (39) hide show
  1. package/.bongos-core.json +78 -38
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +8 -0
  4. package/clients/bongos-client/index.cjs +8 -0
  5. package/clients/bongos-client/index.d.ts +11 -0
  6. package/clients/bongos-client/index.mjs +8 -0
  7. package/docs/adr/0184-a-task-visual-inherits-its-summary-audience.md +49 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/api/openapi.json +208 -3
  10. package/docs/api-reference.md +10 -2
  11. package/docs/architecture.md +6 -1
  12. package/docs/copy-inventory.md +38 -38
  13. package/docs/copy-registry.json +40 -40
  14. package/docs/file-map.md +2 -1
  15. package/migrations/core_223_task_visual.sql +33 -0
  16. package/modules/dev-box/app/src/vendor/bongos-client.cjs +8 -0
  17. package/modules/hall-ui/public/hall-kit.js +33 -0
  18. package/modules/hall-ui/public/hall-render.js +1 -0
  19. package/modules/hall-ui/public/profile.html +4 -1
  20. package/modules/hall-ui/public/profile.js +7 -1
  21. package/modules/hall-ui/public/style.css +29 -0
  22. package/modules/hall-ui/public/task.js +30 -0
  23. package/modules/lifecycle/db-analytics.js +3 -1
  24. package/modules/lifecycle/module.json +2 -1
  25. package/modules/lifecycle/routes/visuals.js +216 -0
  26. package/modules/lifecycle/task-visual-db.js +46 -0
  27. package/modules/lifecycle/task-visuals.js +270 -0
  28. package/modules/status-ui/public/status.js +33 -0
  29. package/modules/status-ui/public/style.css +14 -0
  30. package/package-lock.json +2 -2
  31. package/package.json +1 -1
  32. package/scripts/gds/ship-flow.js +11 -1
  33. package/scripts/gds/ship-visual.js +68 -0
  34. package/scripts/gds/task.js +62 -0
  35. package/src/bongos/routes/public.js +8 -0
  36. package/src/bongos/routes.js +5 -1
  37. package/src/module-api.js +3 -1
  38. package/tests/task_visual_route_gate.mjs +151 -0
  39. package/tests/task_visuals.mjs +200 -0
@@ -134,8 +134,8 @@
134
134
  "text": "Copy prompt",
135
135
  "occurrences": 3,
136
136
  "at": [
137
- "modules/hall-ui/public/task.js:258",
138
- "modules/hall-ui/public/task.js:275",
137
+ "modules/hall-ui/public/task.js:288",
138
+ "modules/hall-ui/public/task.js:305",
139
139
  "modules/hall-ui/public/work.js:446"
140
140
  ]
141
141
  },
@@ -804,7 +804,7 @@
804
804
  "text": "No cross-project activity reported yet.",
805
805
  "occurrences": 2,
806
806
  "at": [
807
- "modules/hall-ui/public/profile.js:233",
807
+ "modules/hall-ui/public/profile.js:239",
808
808
  "modules/hall-ui/public/scouting.js:29"
809
809
  ]
810
810
  },
@@ -1581,7 +1581,7 @@
1581
1581
  "surface": "builders-hall",
1582
1582
  "text": "+ {…} more project{…} not publicly listed.",
1583
1583
  "file": "modules/hall-ui/public/profile.js",
1584
- "line": 230,
1584
+ "line": 236,
1585
1585
  "origin": "js-markup",
1586
1586
  "confidence": "certain"
1587
1587
  },
@@ -1626,7 +1626,7 @@
1626
1626
  "surface": "builders-hall",
1627
1627
  "text": ", or",
1628
1628
  "file": "modules/hall-ui/public/profile.js",
1629
- "line": 265,
1629
+ "line": 271,
1630
1630
  "origin": "js-markup",
1631
1631
  "confidence": "certain"
1632
1632
  },
@@ -1860,7 +1860,7 @@
1860
1860
  "surface": "builders-hall",
1861
1861
  "text": "All builders",
1862
1862
  "file": "modules/hall-ui/public/hall-render.js",
1863
- "line": 997,
1863
+ "line": 998,
1864
1864
  "origin": "js-markup",
1865
1865
  "confidence": "certain"
1866
1866
  },
@@ -1941,7 +1941,7 @@
1941
1941
  "surface": "builders-hall",
1942
1942
  "text": "Analytics scope",
1943
1943
  "file": "modules/hall-ui/public/hall-render.js",
1944
- "line": 995,
1944
+ "line": 996,
1945
1945
  "origin": "js-markup",
1946
1946
  "confidence": "certain"
1947
1947
  },
@@ -2166,7 +2166,7 @@
2166
2166
  "surface": "builders-hall",
2167
2167
  "text": "Awaiting the grader / a confirmation before it can ship.",
2168
2168
  "file": "modules/hall-ui/public/task.js",
2169
- "line": 253,
2169
+ "line": 283,
2170
2170
  "origin": "js-markup",
2171
2171
  "confidence": "certain"
2172
2172
  },
@@ -2769,7 +2769,7 @@
2769
2769
  "surface": "builders-hall",
2770
2770
  "text": "Confirm that verification for #{…} actually passed?",
2771
2771
  "file": "modules/hall-ui/public/task.js",
2772
- "line": 290,
2772
+ "line": 320,
2773
2773
  "origin": "js-notice",
2774
2774
  "confidence": "certain"
2775
2775
  },
@@ -2904,7 +2904,7 @@
2904
2904
  "surface": "builders-hall",
2905
2905
  "text": "Copy prompt",
2906
2906
  "file": "modules/hall-ui/public/task.js",
2907
- "line": 258,
2907
+ "line": 288,
2908
2908
  "origin": "js-markup",
2909
2909
  "confidence": "certain"
2910
2910
  },
@@ -2913,7 +2913,7 @@
2913
2913
  "surface": "builders-hall",
2914
2914
  "text": "Copy prompt",
2915
2915
  "file": "modules/hall-ui/public/task.js",
2916
- "line": 275,
2916
+ "line": 305,
2917
2917
  "origin": "js-text-assign",
2918
2918
  "confidence": "certain"
2919
2919
  },
@@ -3111,7 +3111,7 @@
3111
3111
  "surface": "builders-hall",
3112
3112
  "text": "Could not load #{…}",
3113
3113
  "file": "modules/hall-ui/public/task.js",
3114
- "line": 309,
3114
+ "line": 339,
3115
3115
  "origin": "js-markup",
3116
3116
  "confidence": "certain"
3117
3117
  },
@@ -3246,7 +3246,7 @@
3246
3246
  "surface": "builders-hall",
3247
3247
  "text": "Could not load the profile.",
3248
3248
  "file": "modules/hall-ui/public/profile.js",
3249
- "line": 330,
3249
+ "line": 336,
3250
3250
  "origin": "js-notice",
3251
3251
  "confidence": "certain"
3252
3252
  },
@@ -4425,7 +4425,7 @@
4425
4425
  "surface": "builders-hall",
4426
4426
  "text": "Goal #{…}",
4427
4427
  "file": "modules/hall-ui/public/task.js",
4428
- "line": 202,
4428
+ "line": 225,
4429
4429
  "origin": "js-markup",
4430
4430
  "confidence": "certain"
4431
4431
  },
@@ -5010,7 +5010,7 @@
5010
5010
  "surface": "builders-hall",
5011
5011
  "text": "Kinds of work shipped",
5012
5012
  "file": "modules/hall-ui/public/hall-render.js",
5013
- "line": 1047,
5013
+ "line": 1048,
5014
5014
  "origin": "js-markup",
5015
5015
  "confidence": "certain"
5016
5016
  },
@@ -5064,7 +5064,7 @@
5064
5064
  "surface": "builders-hall",
5065
5065
  "text": "Last claimed by",
5066
5066
  "file": "modules/hall-ui/public/task.js",
5067
- "line": 213,
5067
+ "line": 236,
5068
5068
  "origin": "js-markup",
5069
5069
  "confidence": "certain"
5070
5070
  },
@@ -5676,7 +5676,7 @@
5676
5676
  "surface": "builders-hall",
5677
5677
  "text": "No builder selected. Open a builder from the",
5678
5678
  "file": "modules/hall-ui/public/profile.js",
5679
- "line": 265,
5679
+ "line": 271,
5680
5680
  "origin": "js-markup",
5681
5681
  "confidence": "certain"
5682
5682
  },
@@ -5685,7 +5685,7 @@
5685
5685
  "surface": "builders-hall",
5686
5686
  "text": "No cross-project activity reported yet.",
5687
5687
  "file": "modules/hall-ui/public/profile.js",
5688
- "line": 233,
5688
+ "line": 239,
5689
5689
  "origin": "js-markup",
5690
5690
  "confidence": "certain"
5691
5691
  },
@@ -5937,7 +5937,7 @@
5937
5937
  "surface": "builders-hall",
5938
5938
  "text": "Not found",
5939
5939
  "file": "modules/hall-ui/public/task.js",
5940
- "line": 304,
5940
+ "line": 334,
5941
5941
  "origin": "js-text-assign",
5942
5942
  "confidence": "certain"
5943
5943
  },
@@ -6261,7 +6261,7 @@
6261
6261
  "surface": "builders-hall",
6262
6262
  "text": "Open the Work Board",
6263
6263
  "file": "modules/hall-ui/public/task.js",
6264
- "line": 258,
6264
+ "line": 288,
6265
6265
  "origin": "js-markup",
6266
6266
  "confidence": "certain"
6267
6267
  },
@@ -7512,7 +7512,7 @@
7512
7512
  "surface": "builders-hall",
7513
7513
  "text": "Ship it",
7514
7514
  "file": "modules/hall-ui/public/task.js",
7515
- "line": 249,
7515
+ "line": 279,
7516
7516
  "origin": "js-markup",
7517
7517
  "confidence": "certain"
7518
7518
  },
@@ -7521,7 +7521,7 @@
7521
7521
  "surface": "builders-hall",
7522
7522
  "text": "Shipped {…}{…}.",
7523
7523
  "file": "modules/hall-ui/public/task.js",
7524
- "line": 234,
7524
+ "line": 264,
7525
7525
  "origin": "js-markup",
7526
7526
  "confidence": "certain"
7527
7527
  },
@@ -7746,7 +7746,7 @@
7746
7746
  "surface": "builders-hall",
7747
7747
  "text": "Standing there",
7748
7748
  "file": "modules/hall-ui/public/profile.js",
7749
- "line": 224,
7749
+ "line": 230,
7750
7750
  "origin": "js-markup",
7751
7751
  "confidence": "certain"
7752
7752
  },
@@ -7818,7 +7818,7 @@
7818
7818
  "surface": "builders-hall",
7819
7819
  "text": "Task #{…}",
7820
7820
  "file": "modules/hall-ui/public/task.js",
7821
- "line": 176,
7821
+ "line": 199,
7822
7822
  "origin": "js-text-assign",
7823
7823
  "confidence": "certain"
7824
7824
  },
@@ -7881,7 +7881,7 @@
7881
7881
  "surface": "builders-hall",
7882
7882
  "text": "Tasks are numbered separately from GitHub pull requests, so this may be a PR rather than a GDS task.",
7883
7883
  "file": "modules/hall-ui/public/task.js",
7884
- "line": 299,
7884
+ "line": 329,
7885
7885
  "origin": "js-markup",
7886
7886
  "confidence": "certain"
7887
7887
  },
@@ -7944,7 +7944,7 @@
7944
7944
  "surface": "builders-hall",
7945
7945
  "text": "That project's own standing, as reported by that project.",
7946
7946
  "file": "modules/hall-ui/public/profile.js",
7947
- "line": 224,
7947
+ "line": 230,
7948
7948
  "origin": "js-markup",
7949
7949
  "confidence": "certain"
7950
7950
  },
@@ -8304,7 +8304,7 @@
8304
8304
  "surface": "builders-hall",
8305
8305
  "text": "This builder keeps their track record private.",
8306
8306
  "file": "modules/hall-ui/public/profile.js",
8307
- "line": 204,
8307
+ "line": 210,
8308
8308
  "origin": "js-markup",
8309
8309
  "confidence": "certain"
8310
8310
  },
@@ -8403,7 +8403,7 @@
8403
8403
  "surface": "builders-hall",
8404
8404
  "text": "This is you —",
8405
8405
  "file": "modules/hall-ui/public/profile.js",
8406
- "line": 200,
8406
+ "line": 206,
8407
8407
  "origin": "js-markup",
8408
8408
  "confidence": "certain"
8409
8409
  },
@@ -8529,7 +8529,7 @@
8529
8529
  "surface": "builders-hall",
8530
8530
  "text": "Try reloading in a moment.",
8531
8531
  "file": "modules/hall-ui/public/task.js",
8532
- "line": 309,
8532
+ "line": 339,
8533
8533
  "origin": "js-markup",
8534
8534
  "confidence": "certain"
8535
8535
  },
@@ -9969,7 +9969,7 @@
9969
9969
  "surface": "builders-hall",
9970
9970
  "text": "manage its visibility in Settings → Cross-project record",
9971
9971
  "file": "modules/hall-ui/public/profile.js",
9972
- "line": 200,
9972
+ "line": 206,
9973
9973
  "origin": "js-markup",
9974
9974
  "confidence": "certain"
9975
9975
  },
@@ -10059,7 +10059,7 @@
10059
10059
  "surface": "builders-hall",
10060
10060
  "text": "no data",
10061
10061
  "file": "modules/hall-ui/public/hall-render.js",
10062
- "line": 948,
10062
+ "line": 949,
10063
10063
  "origin": "js-markup",
10064
10064
  "confidence": "certain"
10065
10065
  },
@@ -10338,7 +10338,7 @@
10338
10338
  "surface": "builders-hall",
10339
10339
  "text": "sign in",
10340
10340
  "file": "modules/hall-ui/public/profile.js",
10341
- "line": 265,
10341
+ "line": 271,
10342
10342
  "origin": "js-markup",
10343
10343
  "confidence": "certain"
10344
10344
  },
@@ -10455,7 +10455,7 @@
10455
10455
  "surface": "builders-hall",
10456
10456
  "text": "to see your own profile.",
10457
10457
  "file": "modules/hall-ui/public/profile.js",
10458
- "line": 265,
10458
+ "line": 271,
10459
10459
  "origin": "js-markup",
10460
10460
  "confidence": "certain"
10461
10461
  },
@@ -10473,7 +10473,7 @@
10473
10473
  "surface": "builders-hall",
10474
10474
  "text": "trend, latest {…}",
10475
10475
  "file": "modules/hall-ui/public/hall-render.js",
10476
- "line": 960,
10476
+ "line": 961,
10477
10477
  "origin": "js-markup",
10478
10478
  "confidence": "certain"
10479
10479
  },
@@ -13164,7 +13164,7 @@
13164
13164
  "surface": "status-dashboard",
13165
13165
  "text": "Avg time per work (min)",
13166
13166
  "file": "modules/status-ui/public/status.js",
13167
- "line": 1190,
13167
+ "line": 1223,
13168
13168
  "origin": "js-markup",
13169
13169
  "confidence": "certain"
13170
13170
  },
@@ -13407,7 +13407,7 @@
13407
13407
  "surface": "status-dashboard",
13408
13408
  "text": "No builders yet — be the first.",
13409
13409
  "file": "modules/status-ui/public/status.js",
13410
- "line": 1084,
13410
+ "line": 1117,
13411
13411
  "origin": "js-text-assign",
13412
13412
  "confidence": "certain"
13413
13413
  },
@@ -13434,7 +13434,7 @@
13434
13434
  "surface": "status-dashboard",
13435
13435
  "text": "No works yet inscribed in this window.",
13436
13436
  "file": "modules/status-ui/public/status.js",
13437
- "line": 921,
13437
+ "line": 947,
13438
13438
  "origin": "js-text-assign",
13439
13439
  "confidence": "certain"
13440
13440
  },
@@ -13677,7 +13677,7 @@
13677
13677
  "surface": "status-dashboard",
13678
13678
  "text": "Works shipped / day",
13679
13679
  "file": "modules/status-ui/public/status.js",
13680
- "line": 1189,
13680
+ "line": 1222,
13681
13681
  "origin": "js-markup",
13682
13682
  "confidence": "certain"
13683
13683
  },
@@ -13776,7 +13776,7 @@
13776
13776
  "surface": "status-dashboard",
13777
13777
  "text": "page {…} of {…}",
13778
13778
  "file": "modules/status-ui/public/status.js",
13779
- "line": 977,
13779
+ "line": 1010,
13780
13780
  "origin": "js-text-assign",
13781
13781
  "confidence": "certain"
13782
13782
  },
@@ -13794,7 +13794,7 @@
13794
13794
  "surface": "status-dashboard",
13795
13795
  "text": "tablet last consulted at {…}",
13796
13796
  "file": "modules/status-ui/public/status.js",
13797
- "line": 1134,
13797
+ "line": 1167,
13798
13798
  "origin": "js-text-assign",
13799
13799
  "confidence": "certain"
13800
13800
  },
package/docs/file-map.md CHANGED
@@ -123,7 +123,7 @@
123
123
  │ ├── 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.
124
124
  │ ├── 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/}.
125
125
  │ ├── 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.
126
- │ ├── 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.
126
+ │ ├── 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).
127
127
  │ ├── 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.
128
128
  │ ├── character-anim/ ← consistent-character generative animation (ADR 0088) — graded painterly frame sets + manifest, the otb-character-review skill. CLAUDE.md, module.json.
129
129
  │ ├── 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.
@@ -155,6 +155,7 @@
155
155
  │ ├── gen-file-map.js ← ADR 0066 ([#1276](https://example.com/builders#/task/1276)): generates the `.claude/skills/` + `.claude/scheduled-tasks/` sections of docs/file-map.md from disk; per-entry notes come from docs/file-map.notes.json; `--check` fails on drift OR a skill/routine with no note (CI gate via fitness.js); regenerated on deploy by ship.js regenerateFileMap()
156
156
  │ ├── fitness.js ← Path C (ADR 0062 §9, [#1225](https://example.com/builders#/task/1225)): architecture fitness functions — core↔host import boundary (zero grandfathered exceptions as of R52/#1191), CLAUDE.md budget + nested-doc presence (ADR 0061), no route bypasses rank checks (reuses route-rank-check), repo-map freshness. Enforced in CI via tests/fitness.mjs (the `unit` gate). `node scripts/gds/fitness.js`
157
157
  │ ├── ship.js ← resolve claim as shipped, award credits (`/builder-ship`); also uploads a secret-scrubbed session digest to the corpus (6D.1, ADR 0027); on a dev box runs the sandbox-first review gate before resolving (#927, ADR 0046)
158
+ │ ├── ship-visual.js ← the OPTIONAL `--visual <image> [--visual-alt "…"]` leg of a ship (task 1003109): screens the file BEFORE the claim resolves (bad input costs no claim), uploads it AFTER (a failed picture never fails a ship)
158
159
  │ ├── package-core.js ← package the core as a versioned, installable artifact + pin manifest (`bongos package-core`; R84, ADR 0100 §1, [#1688](https://example.com/builders#/task/1688)): isPublishable() selection + mirror redaction + fail-closed no-leak gate → dist/bongos-core-<version>.{tgz,manifest.json}; version = src/module-api CORE_VERSION. Recipe: docs/recipes/packaging-the-core.md
159
160
  │ ├── sandbox-stage.js ← stage the working tree on the live game preview (box OR local) for browser review before ship; resolvePreviewContext picks the context (`/builder-stage`; #927, ADR 0046; local: task 1056)
160
161
  │ ├── local-preview.js ← builder CLI for the LOCAL sandbox: start/stop/status/restart/logs/url of the game-only preview at http://localhost:3100 (`npm run preview`; task 1056)
@@ -0,0 +1,33 @@
1
+ -- core_223_task_visual.sql — task 1003109 (goal 1000072, "Human legibility").
2
+ --
3
+ -- A CORE migration, not a lifecycle-module one, even though the lifecycle module
4
+ -- owns the feature: `tasks` is a CORE table, and ADR 0083 §Decision #5 lets a
5
+ -- module's migrations touch only its own <key>_-namespaced tables. A module
6
+ -- reaches core state through the doorway, never through DDL.
7
+ --
8
+ -- A shipped task can carry a VISUAL, not just a value summary. `value_summary`
9
+ -- already holds the plain-language "what this means for a reader" line collected
10
+ -- at ship time; these two columns hold the SAME claim in another medium — the
11
+ -- served URL of one optional image, plus its caption/alt text.
12
+ --
13
+ -- OPTIONAL BY CONSTRUCTION: both columns are nullable with no default and nothing
14
+ -- reads them as a completeness signal. A task with no visual is a complete task
15
+ -- ("where relevant" is the owner's own wording, idea 1000400), and no ship path
16
+ -- blocks on their absence.
17
+ --
18
+ -- ADVISORY, NOT A NEW PRIVACY AXIS: visual_url carries no audience of its own. The
19
+ -- serve route derives who may view an image from the TASK it belongs to (the
20
+ -- filename embeds the task id) — the same gate that already decides whether
21
+ -- value_summary is public. One claim, one audience, in both media.
22
+ --
23
+ -- PORTABLE + replay-safe: IF NOT EXISTS on a fresh or already-migrated DB alike;
24
+ -- no instance data, no backfill (there is nothing to backfill — no visual has
25
+ -- ever been uploaded).
26
+
27
+ ALTER TABLE tasks ADD COLUMN IF NOT EXISTS visual_url TEXT;
28
+ ALTER TABLE tasks ADD COLUMN IF NOT EXISTS visual_alt TEXT;
29
+
30
+ COMMENT ON COLUMN tasks.visual_url IS
31
+ 'Optional served URL (/api/bongos/task-visuals/<file>) of the ship-time visual for this task. Same audience as value_summary; never required.';
32
+ COMMENT ON COLUMN tasks.visual_alt IS
33
+ 'Optional caption / alt text rendered with tasks.visual_url.';
@@ -715,6 +715,10 @@ function createClient(opts = {}) {
715
715
  // POST /sso/token — rank: public — POST /sso/token
716
716
  postSsoToken: (args) => request("POST", "/sso/token", { hasBody: true }, args),
717
717
  },
718
+ "taskVisuals": {
719
+ // GET /task-visuals/{name} — rank: public — GET /task-visuals/:name
720
+ getTaskVisualsName: (args) => request("GET", "/task-visuals/{name}", { hasBody: false }, args),
721
+ },
718
722
  "tasks": {
719
723
  // GET /tasks — rank: any-builder — GET /tasks
720
724
  getTasks: (args) => request("GET", "/tasks", { hasBody: false }, args),
@@ -758,6 +762,10 @@ function createClient(opts = {}) {
758
762
  getTasksIdPublishStatus: (args) => request("GET", "/tasks/{id}/publish-status", { hasBody: false }, args),
759
763
  // POST /tasks/{id}/ship — rank: any-builder — POST /tasks/:id/ship
760
764
  postTasksIdShip: (args) => request("POST", "/tasks/{id}/ship", { hasBody: true }, args),
765
+ // DELETE /tasks/{id}/visual — rank: any-builder — DELETE /tasks/:id/visual
766
+ deleteTasksIdVisual: (args) => request("DELETE", "/tasks/{id}/visual", { hasBody: false }, args),
767
+ // POST /tasks/{id}/visual — rank: any-builder — POST /tasks/:id/visual
768
+ postTasksIdVisual: (args) => request("POST", "/tasks/{id}/visual", { hasBody: true }, args),
761
769
  // POST /tasks/{id}/vote — rank: metic+archon — POST /tasks/:id/vote
762
770
  postTasksIdVote: (args) => request("POST", "/tasks/{id}/vote", { hasBody: true }, args),
763
771
  // GET /tasks/claimable — rank: any-builder — GET /tasks/claimable
@@ -517,6 +517,37 @@
517
517
  };
518
518
  }
519
519
 
520
+ // ---- ship-time task visual (task 1003109, goal 1000072) -------------------
521
+ //
522
+ // A shipped task may carry an optional IMAGE alongside its value summary — the
523
+ // same claim in another medium, so a non-engineer can SEE what changed rather
524
+ // than read about it. In a FEED the summary is one line in a list, so the image
525
+ // appears as a small thumbnail linking to the full-size file; the task page
526
+ // renders it properly (task.js taskVisualHtml).
527
+ //
528
+ // Lives here because three separate feeds render it (Standing's ship ledger,
529
+ // a profile's recent ships, the status dashboard) and a fourth copy of an
530
+ // HTML-building helper is how the pre-kit drift started.
531
+
532
+ // Same-origin, SERVER-MINTED paths only. The URL arrives from our own API, but
533
+ // it lands in an href and a src, so it is matched literally against the shape
534
+ // modules/lifecycle/task-visuals.js mints rather than trusted. Anything else
535
+ // renders nothing at all.
536
+ const VISUAL_URL_RE = /^\/api\/(gds|bongos)\/task-visuals\/task-\d+-[0-9a-f]{16}\.(png|jpg|gif|webp)$/;
537
+ function isTaskVisualUrl(url) {
538
+ return VISUAL_URL_RE.test(String(url ?? ''));
539
+ }
540
+
541
+ // The compact thumbnail. Returns '' when the row carries no visual — a task
542
+ // without one must never look incomplete, so nothing is drawn (no placeholder).
543
+ function shipVisualThumbHtml(row) {
544
+ const url = String((row && row.visual_url) ?? '');
545
+ if (!isTaskVisualUrl(url)) return '';
546
+ const alt = String((row && row.visual_alt) || `Visual for task ${(row && row.id) ?? ''}`).trim();
547
+ return `<a href="${esc(url)}" target="_blank" rel="noopener" title="${esc(alt)}">` +
548
+ `<img class="ship-visual-thumb" src="${esc(url)}" alt="${esc(alt)}" loading="lazy"></a>`;
549
+ }
550
+
520
551
  const api = {
521
552
  esc,
522
553
  // pager (low-level exports so pages can adopt incrementally)
@@ -525,6 +556,8 @@
525
556
  makeLedger, makeFilterBar, makeTabs, makeSectionRail,
526
557
  // states
527
558
  emptyStateHtml, skeletonRows, collapsibleCard,
559
+ // ship-time task visual (task 1003109)
560
+ isTaskVisualUrl, shipVisualThumbHtml,
528
561
  // age (task 1002955)
529
562
  fmtAge, fmtAbsDate, ageChipHtml, AGE_STALE_DAYS,
530
563
  };
@@ -931,6 +931,7 @@
931
931
  <td>
932
932
  <div>${escapeHtml(s.title)}</div>
933
933
  ${s.value_summary ? `<div style="color:var(--ink-soft);font-size:0.88rem;font-style:italic;margin-top:2px">${escapeHtml(s.value_summary)}</div>` : ''}
934
+ ${s.visual_url ? `<div style="margin-top:6px">${window.OTBKit.shipVisualThumbHtml(s)}</div>` : ''}
934
935
  </td>
935
936
  <td class="ledger__drachmae">${fmtNum(window.OTBBranding?.taskCreditValue?.(s) ?? s.credits_reward)}</td>
936
937
  <td class="ledger__date">${fmtDate(s.shipped_at)}</td>
@@ -95,6 +95,9 @@
95
95
  <!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
96
96
  <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
97
97
  <script src="/builders/brand-apply.js?v=2026-06-27-r75"></script>
98
- <script src="/builders/profile.js?v=2026-08-16-r04"></script>
98
+ <!-- hall-kit (window.OTBKit) — profile.js renders ship-time task visuals through
99
+ its shared thumbnail helper (task 1003109), so it must load first. -->
100
+ <script src="/builders/hall-kit.js?v=2026-08-25-visual"></script>
101
+ <script src="/builders/profile.js?v=2026-08-25-visual"></script>
99
102
  </body>
100
103
  </html>
@@ -150,10 +150,16 @@
150
150
  const summary = s.value_summary
151
151
  ? `<span style="display:block;margin-top:2px;font-size:12px;color:var(--ink-faint)">${escapeHtml(s.value_summary)}</span>`
152
152
  : '';
153
+ // task 1003109: the optional ship-time visual, as a thumbnail beside the
154
+ // summary it restates. '' when the task has none — which is most of them,
155
+ // and never reads as an omission.
156
+ const visual = window.OTBKit && window.OTBKit.shipVisualThumbHtml
157
+ ? window.OTBKit.shipVisualThumbHtml(s)
158
+ : '';
153
159
  return `
154
160
  <tr>
155
161
  <td class="ledger__num">#${escapeHtml(String(s.id))}</td>
156
- <td><a href="/task/${encodeURIComponent(s.id)}">${escapeHtml(s.title || `Task ${s.id}`)}</a>${summary}</td>
162
+ <td><a href="/task/${encodeURIComponent(s.id)}">${escapeHtml(s.title || `Task ${s.id}`)}</a>${summary}${visual ? `<span style="display:block;margin-top:6px">${visual}</span>` : ''}</td>
157
163
  <td class="ledger__drachmae">${fmtNum(reward)}</td>
158
164
  <td class="ledger__date">${fmtDate(s.shipped_at)}</td>
159
165
  </tr>`;
@@ -1804,6 +1804,35 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
1804
1804
  overflow-y: auto;
1805
1805
  }
1806
1806
  .td-touches { font-size: 12.5px; color: var(--ink-faint); word-break: break-word; margin: 0 0 12px; }
1807
+ /* task 1003109: the ship-time VISUAL, rendered directly under the value summary
1808
+ it restates. Sized to be READ (this exists so a non-engineer can see a change),
1809
+ capped so a tall screenshot can't push the rest of the record off the page, and
1810
+ never drawn at all when a task has no visual. */
1811
+ .td-visual { margin: 0 0 12px; }
1812
+ .td-visual__img {
1813
+ display: block;
1814
+ max-width: 100%;
1815
+ max-height: 52vh;
1816
+ width: auto;
1817
+ height: auto;
1818
+ border: 1px solid var(--border);
1819
+ border-radius: var(--radius-sm);
1820
+ background: var(--surface-2);
1821
+ }
1822
+ .td-visual__caption { font-size: 12.5px; color: var(--ink-faint); margin: 6px 0 0; }
1823
+ /* The compact form: a thumbnail beside a value summary in a ship FEED, where the
1824
+ summary is one line in a list and a full-width image would drown it. */
1825
+ .ship-visual-thumb {
1826
+ display: inline-block;
1827
+ width: 44px;
1828
+ height: 44px;
1829
+ flex: 0 0 auto;
1830
+ object-fit: cover;
1831
+ border: 1px solid var(--border);
1832
+ border-radius: var(--radius-sm);
1833
+ background: var(--surface-2);
1834
+ vertical-align: middle;
1835
+ }
1807
1836
  /* Shared small-caps label: overlay section heads and triage form-field labels. */
1808
1837
  .td-section-h, .td-field > span { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-faint); }
1809
1838
  .td-section-h { margin: 0 0 4px; }
@@ -149,6 +149,29 @@
149
149
  return `<div class="td-deps"><p class="td-section-h">${escapeHtml(kindWord)}</p><ul class="td-dep-list">${rows}</ul></div>`;
150
150
  }
151
151
 
152
+ // task 1003109 (goal 1000072, "Human legibility") — the ship-time visual.
153
+ //
154
+ // The point of the feature is that a NON-ENGINEER can see what changed, so the
155
+ // image renders at a readable size, not as a thumbnail, and links to itself so
156
+ // a detail can be opened full-size. The caption (visual_alt) doubles as the alt
157
+ // text, which is why it is stored: an image with no alt tells a screen reader
158
+ // nothing, and "a picture of the change" is exactly the audience that needs it.
159
+ function taskVisualHtml(t) {
160
+ const url = String(t.visual_url || '');
161
+ // Same-origin, server-generated paths only. The URL comes from our own API,
162
+ // but rendering it into an href/src is a place to be literal rather than
163
+ // trusting: anything that is not the shape our serve route mints is dropped.
164
+ if (!/^\/api\/(gds|bongos)\/task-visuals\/task-\d+-[0-9a-f]{16}\.(png|jpg|gif|webp)$/.test(url)) return '';
165
+ const alt = String(t.visual_alt || `Visual for task ${t.id}`);
166
+ const caption = t.visual_alt
167
+ ? `<figcaption class="td-visual__caption">${escapeHtml(t.visual_alt)}</figcaption>`
168
+ : '';
169
+ return `<figure class="td-visual">` +
170
+ `<a href="${escapeHtml(url)}" target="_blank" rel="noopener">` +
171
+ `<img class="td-visual__img" src="${escapeHtml(url)}" alt="${escapeHtml(alt)}" loading="lazy">` +
172
+ `</a>${caption}</figure>`;
173
+ }
174
+
152
175
  async function copyText(text) {
153
176
  try { await navigator.clipboard.writeText(text); return true; }
154
177
  catch (_) {
@@ -217,6 +240,13 @@
217
240
  parts.push(`<p class="td-goal"><strong>Value:</strong> ${escapeHtml(t.value_summary)}</p>`);
218
241
  }
219
242
 
243
+ // task 1003109: the ship-time VISUAL — the same claim as the value summary in
244
+ // another medium, so it renders directly beneath it and nowhere the summary
245
+ // isn't. Absent on almost every task, and that is not an omission: a task with
246
+ // no visual must never look incomplete, so nothing is drawn at all when there
247
+ // is none — no placeholder, no empty frame, no "no visual" line.
248
+ if (t.visual_url) parts.push(taskVisualHtml(t));
249
+
220
250
  if (t.description) {
221
251
  parts.push(`<div class="td-desc">${escapeHtml(t.description)}</div>`);
222
252
  }
@@ -43,6 +43,8 @@ async function recentShippedTasks(limit = 30, { days = null } = {}) {
43
43
  }
44
44
  const { rows } = await pool.query(
45
45
  `SELECT t.id, t.title, t.value_summary, t.version_id, t.status, t.shipped_at, t.credits_reward,
46
+ -- task 1003109: the SQL mirror of task-visuals.isPubliclyViewable — this feed is UNAUTHENTICATED, so a security_sensitive task's image is nulled out rather than published (nulling the URL keeps the flag itself off the public wire). Change this, change that predicate.
47
+ CASE WHEN t.security_sensitive THEN NULL ELSE t.visual_url END AS visual_url, CASE WHEN t.security_sensitive THEN NULL ELSE t.visual_alt END AS visual_alt,
46
48
  ${CREDITS_AWARDED_SUBQUERY} AS credits_awarded,
47
49
  t.est_minutes, t.actual_minutes,
48
50
  b.github_login, b.display_name
@@ -123,7 +125,7 @@ async function estimationDriftSummary() {
123
125
 
124
126
  async function recentShippedTasksByBuilder(builderId, limit = 10) {
125
127
  const { rows } = await pool.query(
126
- `SELECT t.id, t.title, t.value_summary, t.version_id, t.status, t.shipped_at, t.credits_reward,
128
+ `SELECT t.id, t.title, t.value_summary, t.visual_url, t.visual_alt, t.version_id, t.status, t.shipped_at, t.credits_reward,
127
129
  ${CREDITS_AWARDED_SUBQUERY} AS credits_awarded
128
130
  FROM tasks t
129
131
  WHERE t.shipped_by = $1
@@ -18,7 +18,8 @@
18
18
  "gate-approvals",
19
19
  "analytics",
20
20
  "lifecycle",
21
- "help-requests"
21
+ "help-requests",
22
+ "visuals"
22
23
  ],
23
24
  "pollers": [
24
25
  "publish-reconciler"