@bongos/core 1.21.2 → 1.21.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.21.2",
6
- "core_contract": "1.21.2",
7
- "source_commit": "7358a336e3a7237e1c57e4d74715c2c9b57890fc",
5
+ "core_version": "1.21.3",
6
+ "core_contract": "1.21.3",
7
+ "source_commit": "a694645f58d7360adc29badc79e5cefbd91e9958",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-10-01T23:55:32.380Z",
9
+ "built_at": "2026-10-02T00:11:07.226Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 576,
13
13
  "agent_docs_stubbed": 27,
14
- "functional_verbatim": 2847,
14
+ "functional_verbatim": 2848,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 3451,
20
- "tree_sha256": "0a292d64139ff96efb59093e745ea66c86e8017c1348e3b99769931dc1eaf149",
19
+ "file_count": 3452,
20
+ "tree_sha256": "b1bdc1e227ffd858e81feb70ce0a0e84792b730b569f5f132acb40360e1c065d",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2307,12 +2307,12 @@
2307
2307
  {
2308
2308
  "path": "docs/copy-inventory.md",
2309
2309
  "mode": "0000644",
2310
- "sha256": "71034150ee8f494e34b89723465ba2468cbb3801f72a9a79e99747f34092d6de"
2310
+ "sha256": "10381700ed5d3a63cf56babc5f4843b82ea19240569e51dc2f4904e792d0f2d2"
2311
2311
  },
2312
2312
  {
2313
2313
  "path": "docs/copy-registry.json",
2314
2314
  "mode": "0000644",
2315
- "sha256": "2b3b70c007c81217cce5b453ef2559cccd33548b6050c9c79449fa6d8c3ebe3d"
2315
+ "sha256": "73db034f2d5cffd41f54bde2869e5a5188e355ad821c5402c2a864d4f3da1e52"
2316
2316
  },
2317
2317
  {
2318
2318
  "path": "docs/design/apex-pass-2-direction.md",
@@ -2817,7 +2817,7 @@
2817
2817
  {
2818
2818
  "path": "docs/module-api-changelog.md",
2819
2819
  "mode": "0000644",
2820
- "sha256": "2b7f36fa0b0a9f2af39e8fb4a113d6f3605bfaa143dfd53f374bb7f1a49dd3f9"
2820
+ "sha256": "382700e91796438ead85187899b7f09a43964b68035bfc0ada301d049cb7d8e1"
2821
2821
  },
2822
2822
  {
2823
2823
  "path": "docs/modules-contract.md",
@@ -2907,7 +2907,7 @@
2907
2907
  {
2908
2908
  "path": "docs/page-readings.json",
2909
2909
  "mode": "0000644",
2910
- "sha256": "8b881eb8a8e0ce9fb6dc54b790ed31b48b846b526534824eb0622b7a81f992bd"
2910
+ "sha256": "c7a34b3c1d5a248b7c386e8135ae44330114969eb67cbea54513581af4e6f023"
2911
2911
  },
2912
2912
  {
2913
2913
  "path": "docs/project-context.template.md",
@@ -4024,6 +4024,11 @@
4024
4024
  "mode": "0000644",
4025
4025
  "sha256": "106be8e1efc153460d1dffc809598498f5f91063beb7799e36f025b9aceb7723"
4026
4026
  },
4027
+ {
4028
+ "path": "migrations/core_271_bongos_shipper_badge_name.sql",
4029
+ "mode": "0000644",
4030
+ "sha256": "7b05f548a3498aa472267ca10b26236cc81fa52df0334be9ec89c9d620866e2f"
4031
+ },
4027
4032
  {
4028
4033
  "path": "modules/agents/CLAUDE.md",
4029
4034
  "mode": "0000644",
@@ -5927,7 +5932,7 @@
5927
5932
  {
5928
5933
  "path": "modules/hall-ui/public/roster.js",
5929
5934
  "mode": "0000644",
5930
- "sha256": "03db368054e51de1dd38bdda0948ee7d21db6786e084a5672c597ccbe261e145"
5935
+ "sha256": "6039efe77849f1284f13226b8e9b0cd5f307372df15f52e481261d507a5d0e83"
5931
5936
  },
5932
5937
  {
5933
5938
  "path": "modules/hall-ui/public/roster.states.json",
@@ -9277,7 +9282,7 @@
9277
9282
  {
9278
9283
  "path": "modules/status-ui/public/status.js",
9279
9284
  "mode": "0000644",
9280
- "sha256": "91b56c4dc279a1b0e45012c3dc182ad314a5541bbb0e485d862caeed24fa4408"
9285
+ "sha256": "1e470dc9924196deaa2d92c314086a8e9a77fb446e015c74a3927a3de0f70712"
9281
9286
  },
9282
9287
  {
9283
9288
  "path": "modules/status-ui/public/style.css",
@@ -9597,12 +9602,12 @@
9597
9602
  {
9598
9603
  "path": "package-lock.json",
9599
9604
  "mode": "0000644",
9600
- "sha256": "64680039bac4428f2fcf0e53dbf0a6767fc54bc386cf01a00749e110cc8858e4"
9605
+ "sha256": "220d50d4e763e778d8027917440a7dfc58f691660e5de8c98d88d4f9b2f9becf"
9601
9606
  },
9602
9607
  {
9603
9608
  "path": "package.json",
9604
9609
  "mode": "0000644",
9605
- "sha256": "b0f8001ac06dea3d46f7371bcd7e42d71cf70848df93e7f71b24d8aa011be6d2"
9610
+ "sha256": "d6def8e4ddd25e3d57f226dcd345360204f41ebc8188744a9804faf2854cfe96"
9606
9611
  },
9607
9612
  {
9608
9613
  "path": "public-docs/index.html",
@@ -9622,7 +9627,7 @@
9622
9627
  {
9623
9628
  "path": "release-notes.json",
9624
9629
  "mode": "0000644",
9625
- "sha256": "c8b779db96a599cb704f431a21a22171a9778e8eccc3b255711adbdb1e1242d9"
9630
+ "sha256": "26fccda86a4b69f5b14d2f12bc1ac63a79659544fe7dcb46d8a964600e3e67d7"
9626
9631
  },
9627
9632
  {
9628
9633
  "path": "scripts/bongos-mcp.js",
@@ -11837,7 +11842,7 @@
11837
11842
  {
11838
11843
  "path": "src/module-api.js",
11839
11844
  "mode": "0000644",
11840
- "sha256": "76ad2edc89c2366ce96b533d8d7b2e72c7c4730916288b4db5917a9c0c2d2378"
11845
+ "sha256": "4d274e5ab27d9b6ffc5eeb602286391e9db864e757ca2e5f6cd564133a2b7440"
11841
11846
  },
11842
11847
  {
11843
11848
  "path": "src/module-loader/catalog.js",
@@ -12857,7 +12862,7 @@
12857
12862
  {
12858
12863
  "path": "tests/craft_achievements.mjs",
12859
12864
  "mode": "0000644",
12860
- "sha256": "9b61f183c64bce5f61a2d38a1648059b28d4b45b8fb2644e209b880718767f95"
12865
+ "sha256": "0fede138a51800506d1edba81eb46fc04eb49509ebe942d9834247d12d606d81"
12861
12866
  },
12862
12867
  {
12863
12868
  "path": "tests/craft_mode.mjs",
@@ -737,7 +737,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
737
737
  | `de1713edd4aa` | certain | Next page | `modules/hall-ui/public/hall-kit.js:74` |
738
738
  | `659d8256139c` | certain | Next up — ready to claim | `modules/hall-ui/public/hall-widgets.js:103` |
739
739
  | `f52ba261d930` | certain | Next › | `modules/hall-ui/public/hall-kit.js:74` |
740
- | `bef2cdebc5c6` | certain | No AI sessions | `modules/hall-ui/public/roster.js:232` |
740
+ | `bef2cdebc5c6` | certain | No AI sessions | `modules/hall-ui/public/roster.js:231` |
741
741
  | `41ec1cf65d6f` | certain | No GitHub account yet? Make a free one at | `modules/hall-ui/public/hall-render.js:297` |
742
742
  | `c255749ec989` | certain | No achievements earned yet. | `modules/hall-ui/public/builders.js:470` |
743
743
  | `c255749ec989` | certain | No achievements earned yet. | `modules/hall-ui/public/profile.js:199` |
@@ -1841,7 +1841,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1841
1841
  | `a072071b893c` | certain | All time | `modules/status-ui/public/index.html:313` |
1842
1842
  | `1fb3f6a598cd` | certain | Architects of {{worldName}} | `modules/status-ui/public/index.html:200` |
1843
1843
  | `4b16eed738cf` | certain | Attributed to builders: {…} · Shared keep (unattributed): {…} ({…}%). | `modules/status-ui/public/status.js:718` |
1844
- | `20a83501bba6` | certain | Avg time per work (min) | `modules/status-ui/public/status.js:1257` |
1844
+ | `20a83501bba6` | certain | Avg time per work (min) | `modules/status-ui/public/status.js:1258` |
1845
1845
  | `c8959bdc0e59` | certain | Breakdown filtered to {…}: {…} of {…} ({…}% of all spend). | `modules/status-ui/public/status.js:659` |
1846
1846
  | `1fe8937399f5` | certain | By order of the Senate | `modules/status-ui/public/index.html:354` |
1847
1847
  | `21f644463d57` | certain | Citizens who have set the most stone. | `modules/status-ui/public/index.html:201` |
@@ -1867,7 +1867,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1867
1867
  | `dc9eab0a02e2` | certain | Newest entries set down upon the ledger. | `modules/status-ui/public/index.html:305` |
1868
1868
  | `9ef3aaa7b441` | certain | Next page | `modules/status-ui/public/index.html:323` |
1869
1869
  | `37b171a6195a` | certain | No architects yet — be the first to lay a stone. | `modules/status-ui/public/index.html:205` |
1870
- | `7816fbe0add8` | certain | No builders yet — be the first. | `modules/status-ui/public/status.js:1149` |
1870
+ | `7816fbe0add8` | certain | No builders yet — be the first. | `modules/status-ui/public/status.js:1150` |
1871
1871
  | `0b097f051d6f` | certain | No endeavors underway just now. | `modules/status-ui/public/status.js:516` |
1872
1872
  | `ed9b06f79234` | certain | No projects on the map yet — check back soon. | `modules/status-ui/public/explore.html:108` |
1873
1873
  | `4b1a11bfdf10` | certain | No works yet inscribed in this window. | `modules/status-ui/public/status.js:947` |
@@ -1899,7 +1899,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1899
1899
  | `d1add9aa4db0` | certain | The standing of each pillar — whether it holds or has fallen. | `modules/status-ui/public/index.html:188` |
1900
1900
  | `c1ab82f376ec` | certain | The work, in brief | `modules/status-ui/public/status.js:594` |
1901
1901
  | `6cc811f31b3a` | certain | Works in Progress | `modules/status-ui/public/index.html:231` |
1902
- | `4abea0f631eb` | certain | Works shipped / day | `modules/status-ui/public/status.js:1256` |
1902
+ | `4abea0f631eb` | certain | Works shipped / day | `modules/status-ui/public/status.js:1257` |
1903
1903
  | `b2f58817e34e` | certain | World snapshots over time | `modules/status-ui/public/index.html:343` |
1904
1904
  | `ebdc5ecf315c` | certain | avg time / work | `modules/status-ui/public/index.html:220` |
1905
1905
  | `774e102fc137` | certain | city's standing | `modules/status-ui/public/index.html:163` |
@@ -1912,7 +1912,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1912
1912
  | `89ef611c7d37` | certain | page 1 of 1 | `modules/status-ui/public/index.html:322` |
1913
1913
  | `4a00bdbfd9be` | certain | page {…} of {…} | `modules/status-ui/public/status.js:1012` |
1914
1914
  | `1d0fbe3e5927` | certain | passed the inspector, last 7 days | `modules/status-ui/public/index.html:178` |
1915
- | `c1e3c2212207` | certain | tablet last consulted at {…} | `modules/status-ui/public/status.js:1201` |
1915
+ | `c1e3c2212207` | certain | tablet last consulted at {…} | `modules/status-ui/public/status.js:1202` |
1916
1916
  | `fefb0130c7ae` | certain | value wrought | `modules/status-ui/public/index.html:255` |
1917
1917
  | `b97f429c33fe` | certain | works shipped (90d) | `modules/status-ui/public/index.html:218` |
1918
1918
  | `71a303e1ba09` | certain | works wrought, last 7 days | `modules/status-ui/public/index.html:167` |
@@ -8338,7 +8338,7 @@
8338
8338
  "surface": "builders-hall",
8339
8339
  "text": "No AI sessions",
8340
8340
  "file": "modules/hall-ui/public/roster.js",
8341
- "line": 232,
8341
+ "line": 231,
8342
8342
  "origin": "js-markup",
8343
8343
  "confidence": "certain"
8344
8344
  },
@@ -24502,7 +24502,7 @@
24502
24502
  "surface": "status-dashboard",
24503
24503
  "text": "Avg time per work (min)",
24504
24504
  "file": "modules/status-ui/public/status.js",
24505
- "line": 1257,
24505
+ "line": 1258,
24506
24506
  "origin": "js-markup",
24507
24507
  "confidence": "certain"
24508
24508
  },
@@ -24736,7 +24736,7 @@
24736
24736
  "surface": "status-dashboard",
24737
24737
  "text": "No builders yet — be the first.",
24738
24738
  "file": "modules/status-ui/public/status.js",
24739
- "line": 1149,
24739
+ "line": 1150,
24740
24740
  "origin": "js-text-assign",
24741
24741
  "confidence": "certain"
24742
24742
  },
@@ -25024,7 +25024,7 @@
25024
25024
  "surface": "status-dashboard",
25025
25025
  "text": "Works shipped / day",
25026
25026
  "file": "modules/status-ui/public/status.js",
25027
- "line": 1256,
25027
+ "line": 1257,
25028
25028
  "origin": "js-markup",
25029
25029
  "confidence": "certain"
25030
25030
  },
@@ -25141,7 +25141,7 @@
25141
25141
  "surface": "status-dashboard",
25142
25142
  "text": "tablet last consulted at {…}",
25143
25143
  "file": "modules/status-ui/public/status.js",
25144
- "line": 1201,
25144
+ "line": 1202,
25145
25145
  "origin": "js-text-assign",
25146
25146
  "confidence": "certain"
25147
25147
  },
@@ -2831,5 +2831,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2831
2831
  landed since 1.21.0 with no explicit bump. run 36940540656. (task 1002620)
2832
2832
  1.21.2 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2833
2833
  landed since 1.21.1 with no explicit bump. run 36943290103. (task 1002620)
2834
+ 1.21.3 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2835
+ landed since 1.21.2 with no explicit bump. run 36944637655. (task 1002620)
2834
2836
  ---------------------------------------------------------------------------
2835
2837
  ```
@@ -2491,7 +2491,7 @@
2491
2491
  "surface": "builders",
2492
2492
  "title": "Builders",
2493
2493
  "states_read": ["roster","artists","search","out"],
2494
- "files_hash": "a9ce996d0130159f",
2494
+ "files_hash": "5da729281e697115",
2495
2495
  "reading_hash": "1cf32981ae9e428e",
2496
2496
  "counts": {"lines":36,"placed":8,"shared":5,"unplaced":23},
2497
2497
  "omitted": {"data":58},
@@ -5035,7 +5035,7 @@
5035
5035
  "surface": "status",
5036
5036
  "title": "{{worldName}} — the public ledger",
5037
5037
  "states_read": ["default"],
5038
- "files_hash": "c63f0262868bc3e4",
5038
+ "files_hash": "d825f08b6d62d51e",
5039
5039
  "reading_hash": "2853e52b66ba076c",
5040
5040
  "counts": {"lines":87,"placed":49,"shared":14,"unplaced":24},
5041
5041
  "omitted": {"data":89},
@@ -5044,7 +5044,7 @@
5044
5044
  {"key":"L0001","section":"top bar","text":"The Public Ledger of the City's Making","placement":"shared","file":"modules/status-ui/public/index.html","line":125,"string_id":"acdf97805931"},
5045
5045
  {"key":"L0002","section":"top bar","text":"A shipping container has washed ashore upon the coast south of Athens. About it a new civilization rises. Set out below: the city's affairs, openly recorded.","placement":"shared","file":"modules/status-ui/public/index.html","line":126,"string_id":"f4a664041da5"},
5046
5046
  {"key":"L0003","section":"top bar","text":"Cross into the city →","placement":"shared","file":"modules/status-ui/public/index.html","line":128,"string_id":"3aa071ef1142"},
5047
- {"key":"L0004","section":"top bar","text":"tablet last consulted at {…}","placement":"shared","file":"modules/status-ui/public/status.js","line":1201,"string_id":"c1e3c2212207"},
5047
+ {"key":"L0004","section":"top bar","text":"tablet last consulted at {…}","placement":"shared","file":"modules/status-ui/public/status.js","line":1202,"string_id":"c1e3c2212207"},
5048
5048
  {"key":"L0005","section":"navigation","text":"Pulse","placement":"shared"},
5049
5049
  {"key":"L0006","section":"navigation","text":"Watch","placement":"shared"},
5050
5050
  {"key":"L0007","section":"navigation","text":"Architects","placement":"shared"},
@@ -0,0 +1,14 @@
1
+ -- core_271_bongos_shipper_badge_name.sql — the shipper badge carries the platform's
2
+ -- name (task 1004532).
3
+ --
4
+ -- The badge for shipping work on the platform itself was renamed once already, from
5
+ -- the first name to "GDS Shipper" (migration 019), and the platform has since been
6
+ -- renamed Bongos (goal 1000073). The roster and the status page now show each
7
+ -- badge's catalog NAME on hover, so the old name would be spelled out to every
8
+ -- reader. The id `gds-shipper` stays: builder_achievements rows key on it, and an
9
+ -- id is not a word a reader sees.
10
+
11
+ UPDATE achievements
12
+ SET name = 'Bongos Shipper',
13
+ description = 'Ship a task whose touches[] include src/bongos/ or scripts/gds/.'
14
+ WHERE id = 'gds-shipper';
@@ -14,7 +14,7 @@
14
14
  // marked `private` still appears on the roster of a project they build on
15
15
  // (owner, 2026-08-30; ADR 0225, task 1003443).
16
16
  //
17
- // The laurels (ACH_EMOJI + achRowHtml) and the sort lenses MOVED here from
17
+ // The laurels (ACH + achRowHtml) and the sort lenses MOVED here from
18
18
  // hall-render.js rather than being copied — the leaderboard row was their only
19
19
  // caller, so the home page is left carrying no dead renderer.
20
20
  (function () {
@@ -43,37 +43,36 @@
43
43
  return '<span class="rank-badge ' + variant + '" aria-label="' + esc(aria) + '">' + esc(rankLabel(rank)) + '</span>';
44
44
  }
45
45
 
46
- var ACH_EMOJI = {
47
- 'first-ship': '\u{1F6A2}',
48
- 'first-claim': '\u{1FA99}',
49
- 'first-parallel-claim': '⚓',
50
- 'streak-3': '\u{1F525}',
51
- 'streak-7': '\u{1F30B}',
46
+ // Each laurel's emoji AND its catalog name (the achievements table's `name`,
47
+ // migrations 006/019/031/core_270), so a hover and a screen reader say what the
48
+ // badge is called (task 1004532) — never the id, and never words derived from it,
49
+ // which disagree with the catalog the moment a badge is renamed.
50
+ // tests/craft_achievements.mjs holds every row to the catalog.
51
+ var ACH = {
52
+ 'first-ship': ['\u{1F6A2}', 'First Ship'],
53
+ 'first-claim': ['\u{1FA99}', 'First Claim'],
54
+ 'first-parallel-claim': ['⚓', 'First Parallel Claim'],
55
+ 'streak-3': ['\u{1F525}', 'Streak: 3 Nights'],
56
+ 'streak-7': ['\u{1F30B}', 'Streak: 7 Nights'],
52
57
  // task 1004438: renamed Art Pipeline Engineer; the palette is the artist's now
53
- 'art-pipeline-shipper': '\u{1F6E0}\u{FE0F}',
54
- 'first-thought-ignited': '\u{1F4A1}',
55
- 'first-full-idea': '\u{1F4DC}',
56
- 'first-board-ratification': '\u{1F3DB}\u{FE0F}',
57
- 'thought-built-ten': '\u{1F31F}',
58
- 'first-page-approved': '\u{1F3A8}',
59
- 'ten-pages-approved': '\u{1F5BC}\u{FE0F}',
60
- 'whole-surface-tweaked': '\u{1F5FA}\u{FE0F}',
61
- 'gds-shipper': '⚙️',
62
- 'breaking-the-100c-bar': '\u{1F4AF}',
58
+ 'art-pipeline-shipper': ['\u{1F6E0}\u{FE0F}', 'Art Pipeline Engineer'],
59
+ 'subtractor': ['\u{1FA93}', 'Subtractor'],
60
+ 'gds-shipper': ['⚙️', 'Bongos Shipper'],
61
+ 'breaking-the-100c-bar': ['\u{1F4AF}', 'Breaking the 100c Bar'],
62
+ 'first-thought-ignited': ['\u{1F4A1}', 'First Thought Ignited'],
63
+ 'first-full-idea': ['\u{1F4DC}', 'First Full Idea'],
64
+ 'first-board-ratification': ['\u{1F3DB}\u{FE0F}', 'Ratified by the Board'],
65
+ 'thought-built-ten': ['\u{1F31F}', 'A Thought That Built Ten'],
66
+ 'first-page-approved': ['\u{1F3A8}', 'First Page Approved'],
67
+ 'ten-pages-approved': ['\u{1F5BC}\u{FE0F}', 'Ten Pages Approved'],
68
+ 'whole-surface-tweaked': ['\u{1F5FA}\u{FE0F}', 'A Whole Surface'],
63
69
  };
64
- // An achievement id as a sentence: "first-page-approved" → "First page approved".
65
- function achWords(id) {
66
- var s = String(id || '').replace(/-/g, ' ');
67
- return s.charAt(0).toUpperCase() + s.slice(1);
68
- }
69
70
  function achRowHtml(ids) {
70
71
  if (!Array.isArray(ids) || !ids.length) return '';
71
72
  var inner = ids.map(function (id) {
72
- var emoji = ACH_EMOJI[id];
73
- // The title is the achievement's name as words ("First page approved"), not
74
- // its id slug (task 1004438): it is what a hover shows and a screen reader says.
75
- var words = achWords(id);
76
- return emoji ? '<span class="lb-ach" title="' + esc(words) + '" aria-label="' + esc(words) + '">' + emoji + '</span>' : '';
73
+ var a = ACH[id];
74
+ // The title is the badge's catalog name: what a hover shows and a screen reader says.
75
+ return a ? '<span class="lb-ach" title="' + esc(a[1]) + '" aria-label="' + esc(a[1]) + '">' + a[0] + '</span>' : '';
77
76
  }).join('');
78
77
  return inner ? '<span class="lb-ach-row" aria-label="achievements">' + inner + '</span>' : '';
79
78
  }
@@ -1077,27 +1077,30 @@ function updateHeadlineShippedFromItems() {
1077
1077
  // ---------- leaderboard ----------
1078
1078
 
1079
1079
  // The catalog is small and changes only by migration (the latest: the seven
1080
- // craft achievements of task 1004438, migrations/core_270), so hardcoding the
1081
- // emoji map here is cheaper than another API round-trip per page load. Mirrors
1082
- // modules/hall-ui/public/roster.js ACH_EMOJI — tests/craft_achievements.mjs
1083
- // holds both maps to the migration's ids and icons.
1084
- const ACH_EMOJI = {
1085
- 'first-ship': '🚢',
1086
- 'first-claim': '🪙',
1087
- 'first-parallel-claim': '⚓',
1088
- 'streak-3': '🔥',
1089
- 'streak-7': '🌋',
1080
+ // craft achievements of task 1004438, migrations/core_270), so hardcoding it here
1081
+ // is cheaper than another API round-trip per page load. Each laurel carries its
1082
+ // emoji AND its catalog name (task 1004532), so a hover and a screen reader say
1083
+ // what the badge is called — never the id, and never words derived from it.
1084
+ // Mirrors modules/hall-ui/public/roster.js ACH — tests/craft_achievements.mjs
1085
+ // holds both maps to the catalog's ids, icons and names.
1086
+ const ACH = {
1087
+ 'first-ship': ['🚢', 'First Ship'],
1088
+ 'first-claim': ['🪙', 'First Claim'],
1089
+ 'first-parallel-claim': ['⚓', 'First Parallel Claim'],
1090
+ 'streak-3': ['🔥', 'Streak: 3 Nights'],
1091
+ 'streak-7': ['🌋', 'Streak: 7 Nights'],
1090
1092
  // task 1004438: renamed Art Pipeline Engineer; the palette is the artist's now
1091
- 'art-pipeline-shipper': '🛠️',
1092
- 'first-thought-ignited': '💡',
1093
- 'first-full-idea': '📜',
1094
- 'first-board-ratification': '🏛️',
1095
- 'thought-built-ten': '🌟',
1096
- 'first-page-approved': '🎨',
1097
- 'ten-pages-approved': '🖼️',
1098
- 'whole-surface-tweaked': '🗺️',
1099
- 'gds-shipper': '⚙️',
1100
- 'breaking-the-100c-bar': '💯',
1093
+ 'art-pipeline-shipper': ['🛠️', 'Art Pipeline Engineer'],
1094
+ 'subtractor': ['🪓', 'Subtractor'],
1095
+ 'gds-shipper': ['⚙️', 'Bongos Shipper'],
1096
+ 'breaking-the-100c-bar': ['💯', 'Breaking the 100c Bar'],
1097
+ 'first-thought-ignited': ['💡', 'First Thought Ignited'],
1098
+ 'first-full-idea': ['📜', 'First Full Idea'],
1099
+ 'first-board-ratification': ['🏛️', 'Ratified by the Board'],
1100
+ 'thought-built-ten': ['🌟', 'A Thought That Built Ten'],
1101
+ 'first-page-approved': ['🎨', 'First Page Approved'],
1102
+ 'ten-pages-approved': ['🖼️', 'Ten Pages Approved'],
1103
+ 'whole-surface-tweaked': ['🗺️', 'A Whole Surface'],
1101
1104
  };
1102
1105
 
1103
1106
  function renderAchievementRow(ids) {
@@ -1106,15 +1109,13 @@ function renderAchievementRow(ids) {
1106
1109
  row.className = 'leader__achievements';
1107
1110
  row.setAttribute('aria-label', 'achievements');
1108
1111
  for (const id of ids) {
1109
- const emoji = ACH_EMOJI[id];
1110
- if (!emoji) continue;
1112
+ const a = ACH[id];
1113
+ if (!a) continue;
1111
1114
  const span = document.createElement('span');
1112
1115
  span.className = 'leader__ach';
1113
- // the name as words ("First page approved"), not the id slug (task 1004438)
1114
- const words = String(id).replace(/-/g, ' ');
1115
- span.title = words.charAt(0).toUpperCase() + words.slice(1);
1116
- span.setAttribute('aria-label', span.title);
1117
- span.textContent = emoji;
1116
+ span.title = a[1];
1117
+ span.setAttribute('aria-label', a[1]);
1118
+ span.textContent = a[0];
1118
1119
  row.appendChild(span);
1119
1120
  }
1120
1121
  return row.children.length > 0 ? row : null;
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.2",
3
+ "version": "1.21.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.2",
9
+ "version": "1.21.3",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.2",
3
+ "version": "1.21.3",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -8704,5 +8704,11 @@
8704
8704
  "id": "1004438",
8705
8705
  "text": "Artists and ideators now earn achievements for their own work, not only for shipping code. Ideators: First Thought Ignited, First Full Idea, Ratified by the Board, and A Thought That Built Ten. Artists: First Page Approved, Te"
8706
8706
  }
8707
+ ],
8708
+ "1.21.3": [
8709
+ {
8710
+ "id": "1004532",
8711
+ "text": "Hovering over a badge on the builders list or the public status page now shows its real name, such as \"Art Pipeline Engineer\" or \"A Thought That Built Ten\", instead of words made from its internal code. Screen readers read the"
8712
+ }
8707
8713
  ]
8708
8714
  }
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.21.2'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.3'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -319,31 +319,70 @@ await test('the backfill keys each badge on the same ledger fact and stamps the
319
319
 
320
320
  // ---- 5. the pictures ------------------------------------------------------------------
321
321
 
322
- await test('the roster and the status page draw every craft badge with the migration\'s icon', () => {
323
- const roster = read('modules/hall-ui/public/roster.js');
324
- const status = read('modules/status-ui/public/status.js');
322
+ // The catalog as the database holds it: every migration's achievements rows and
323
+ // renames, applied in filename order (the order the migrator runs them).
324
+ function dbCatalog() {
325
+ const dir = path.join(ROOT, 'migrations');
326
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith('.sql')).sort();
327
+ const econ = path.join(ROOT, 'modules/economy/migrations');
328
+ const all = [...files.map((f) => path.join(dir, f)), ...fs.readdirSync(econ).filter((f) => f.endsWith('.sql')).sort().map((f) => path.join(econ, f))];
329
+ const cat = new Map();
330
+ for (const file of all) {
331
+ const sql = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
332
+ for (const block of sql.split(/INSERT INTO achievements\b/).slice(1)) {
333
+ const values = block.split(/ON CONFLICT|;\s*\n/)[0];
334
+ for (const m of values.matchAll(/\('([a-z0-9-]+)',\s*'([^']+)',\s*'(?:[^']|'')*',\s*'([^']+)'/g)) {
335
+ cat.set(m[1], { name: m[2], icon: m[3] });
336
+ }
337
+ }
338
+ for (const m of sql.matchAll(/UPDATE achievements\s+SET ([\s\S]*?)\s+WHERE id = '([a-z0-9-]+)';/g)) {
339
+ const [, sets, where] = m;
340
+ const row = { ...(cat.get(where) || {}) };
341
+ const name = sets.match(/name\s*=\s*'([^']+)'/);
342
+ const icon = sets.match(/icon\s*=\s*'([^']+)'/);
343
+ const id = sets.match(/\bid\s*=\s*'([a-z0-9-]+)'/);
344
+ if (name) row.name = name[1];
345
+ if (icon) row.icon = icon[1];
346
+ if (id) { cat.delete(where); cat.set(id[1], row); } else cat.set(where, row);
347
+ }
348
+ }
349
+ return cat;
350
+ }
351
+
352
+ function mapOf(src, constName) {
353
+ const body = src.slice(src.indexOf(`${constName} = {`), src.indexOf('};', src.indexOf(`${constName} = {`)));
325
354
  const unescape = (s) => s.replace(/\\u\{([0-9A-F]+)\}/gi, (_, h) => String.fromCodePoint(parseInt(h, 16)));
326
- for (const a of CATALOG.values()) {
327
- const r = roster.match(new RegExp(`'${a.id}': '([^']+)'`));
328
- const s = status.match(new RegExp(`'${a.id}': '([^']+)'`));
329
- assert.ok(r && unescape(r[1]) === a.icon, `roster draws ${a.id} as ${a.icon}`);
330
- assert.ok(s && s[1] === a.icon, `status draws ${a.id} as ${a.icon}`);
355
+ const out = new Map();
356
+ for (const m of body.matchAll(/'([a-z0-9-]+)': \['([^']+)', '([^']+)'\]/g)) out.set(m[1], { icon: unescape(m[2]), name: m[3] });
357
+ return out;
358
+ }
359
+
360
+ await test('the roster and the status page draw every badge with its catalog icon AND its catalog name', () => {
361
+ const cat = dbCatalog();
362
+ assert.equal(cat.get('art-pipeline-shipper').name, 'Art Pipeline Engineer', 'the catalog parse applies the rename');
363
+ assert.equal(cat.get('gds-shipper').name, 'Bongos Shipper', 'and the id rename (019) and the platform rename (core_271)');
364
+ for (const [file, constName] of [['modules/hall-ui/public/roster.js', 'var ACH'], ['modules/status-ui/public/status.js', 'const ACH']]) {
365
+ const map = mapOf(read(file), constName);
366
+ assert.deepEqual([...map.keys()].sort(), [...cat.keys()].sort(), `${file} names every badge in the catalog`);
367
+ for (const [id, row] of cat) {
368
+ assert.equal(map.get(id).name, row.name, `${file}: ${id} is named "${row.name}"`);
369
+ // Icons are pinned for the badges core_270 drew or redrew; the older laurels'
370
+ // list glyphs predate this (first-ship has always been a ship here, a box on
371
+ // the shelf) and are not this task's to change.
372
+ if (CRAFT.includes(id) || id === 'art-pipeline-shipper') {
373
+ assert.equal(map.get(id).icon, row.icon, `${file}: ${id} is drawn as ${row.icon}`);
374
+ }
375
+ }
331
376
  }
332
- assert.ok(unescape(roster.match(/'art-pipeline-shipper': '([^']+)'/)[1]) === '🛠️');
333
- assert.equal(status.match(/'art-pipeline-shipper': '([^']+)'/)[1], '🛠️');
334
377
  });
335
378
 
336
- await test('a badge is named in words for a hover and a screen reader, never its id slug', () => {
379
+ await test('a hover and a screen reader say the badge\'s name, never its id or words made from it', () => {
337
380
  const roster = read('modules/hall-ui/public/roster.js');
338
- const fn = roster.slice(roster.indexOf('function achWords'), roster.indexOf('function achRowHtml'));
339
- const achWords = new Function(`${fn}; return achWords;`)();
340
- assert.equal(achWords('first-page-approved'), 'First page approved');
341
- assert.equal(achWords('thought-built-ten'), 'Thought built ten');
342
- assert.match(roster, /title="' \+ esc\(words\) \+ '" aria-label="' \+ esc\(words\) \+ '"/);
343
- assert.doesNotMatch(roster, /title="' \+ esc\(id\)/);
381
+ assert.match(roster, /title="' \+ esc\(a\[1\]\) \+ '" aria-label="' \+ esc\(a\[1\]\) \+ '"/);
382
+ assert.doesNotMatch(roster, /achWords|esc\(id\)/);
344
383
  const status = read('modules/status-ui/public/status.js');
345
- assert.doesNotMatch(status, /span\.title = id;/);
346
- assert.match(status, /span\.setAttribute\('aria-label', span\.title\)/);
384
+ assert.match(status, /span\.title = a\[1\];\s*span\.setAttribute\('aria-label', a\[1\]\);/);
385
+ assert.doesNotMatch(status, /span\.title = id|replace\(\/-\/g, ' '\)/);
347
386
  });
348
387
 
349
388
  summary();