@bongos/core 1.19.990 → 1.19.991
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 +32 -22
- package/docs/copy-inventory.md +13 -13
- package/docs/copy-registry.json +13 -13
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/goal-forest-lib.js +1 -47
- package/modules/hall-ui/public/goal-forest-render.js +2 -1
- package/modules/hall-ui/public/goal-map-lib.js +2 -9
- package/modules/hall-ui/public/goal-map.html +2 -0
- package/modules/hall-ui/public/goal-map.js +28 -5
- package/modules/hall-ui/public/goals.html +2 -0
- package/modules/hall-ui/public/text-fit.js +58 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/goal_forest_lib.mjs +0 -61
- package/tests/goal_forest_page.mjs +2 -0
- package/tests/goal_map_page.mjs +31 -0
- package/tests/text_fit.mjs +91 -0
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.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.991",
|
|
6
|
+
"core_contract": "1.19.991",
|
|
7
|
+
"source_commit": "e80119e1a3eaf21835fda089e6279bdff4043c04",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-23T20:27:54.477Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 518,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2398,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2942,
|
|
20
|
+
"tree_sha256": "2e7708f19e4b524ede30ec609d2809a2d7e6bc77953d5d3106cbd01170134253",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2177,12 +2177,12 @@
|
|
|
2177
2177
|
{
|
|
2178
2178
|
"path": "docs/copy-inventory.md",
|
|
2179
2179
|
"mode": "0000644",
|
|
2180
|
-
"sha256": "
|
|
2180
|
+
"sha256": "8a156552c9834ecaf51ba06b9e09ba6e5cf44ca486694570426eb9fc0248dab1"
|
|
2181
2181
|
},
|
|
2182
2182
|
{
|
|
2183
2183
|
"path": "docs/copy-registry.json",
|
|
2184
2184
|
"mode": "0000644",
|
|
2185
|
-
"sha256": "
|
|
2185
|
+
"sha256": "6e50226135d6bab2ca2538531c6680ea6ccbf5820240af0daba240623d2313d2"
|
|
2186
2186
|
},
|
|
2187
2187
|
{
|
|
2188
2188
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2712,7 +2712,7 @@
|
|
|
2712
2712
|
{
|
|
2713
2713
|
"path": "docs/module-api-changelog.md",
|
|
2714
2714
|
"mode": "0000644",
|
|
2715
|
-
"sha256": "
|
|
2715
|
+
"sha256": "4f19e3d159ebd6ef4268a44b5805c0ad09cedcfb482d4e36e486f892e1e8df6e"
|
|
2716
2716
|
},
|
|
2717
2717
|
{
|
|
2718
2718
|
"path": "docs/modules-contract.md",
|
|
@@ -4922,12 +4922,12 @@
|
|
|
4922
4922
|
{
|
|
4923
4923
|
"path": "modules/hall-ui/public/goal-forest-lib.js",
|
|
4924
4924
|
"mode": "0000644",
|
|
4925
|
-
"sha256": "
|
|
4925
|
+
"sha256": "f4eef6f9ec782aa76369b98cbbd95152ab0e11858a4bb9d2c2f9839a1d71a08c"
|
|
4926
4926
|
},
|
|
4927
4927
|
{
|
|
4928
4928
|
"path": "modules/hall-ui/public/goal-forest-render.js",
|
|
4929
4929
|
"mode": "0000644",
|
|
4930
|
-
"sha256": "
|
|
4930
|
+
"sha256": "9edf4cf99fe2726e75b4ac21b6f41a5e51ecc1b933659ac0f9c7d98c3de217b1"
|
|
4931
4931
|
},
|
|
4932
4932
|
{
|
|
4933
4933
|
"path": "modules/hall-ui/public/goal-inbox.js",
|
|
@@ -4937,7 +4937,7 @@
|
|
|
4937
4937
|
{
|
|
4938
4938
|
"path": "modules/hall-ui/public/goal-map-lib.js",
|
|
4939
4939
|
"mode": "0000644",
|
|
4940
|
-
"sha256": "
|
|
4940
|
+
"sha256": "6f08884ef3afabb1ffc455995a0bd99ac8f7084cbb86eda39594eaa09326cba6"
|
|
4941
4941
|
},
|
|
4942
4942
|
{
|
|
4943
4943
|
"path": "modules/hall-ui/public/goal-map.css",
|
|
@@ -4947,12 +4947,12 @@
|
|
|
4947
4947
|
{
|
|
4948
4948
|
"path": "modules/hall-ui/public/goal-map.html",
|
|
4949
4949
|
"mode": "0000644",
|
|
4950
|
-
"sha256": "
|
|
4950
|
+
"sha256": "faa6152938c0c90cef5e4dc1f179bab967e01acb05c015b0fe93fa7c782941da"
|
|
4951
4951
|
},
|
|
4952
4952
|
{
|
|
4953
4953
|
"path": "modules/hall-ui/public/goal-map.js",
|
|
4954
4954
|
"mode": "0000644",
|
|
4955
|
-
"sha256": "
|
|
4955
|
+
"sha256": "b24a1f2d78a727d928668a431f2d8af8a1c42c8bfba0986cb4026ecd5397dd8a"
|
|
4956
4956
|
},
|
|
4957
4957
|
{
|
|
4958
4958
|
"path": "modules/hall-ui/public/goal-map.states.json",
|
|
@@ -4982,7 +4982,7 @@
|
|
|
4982
4982
|
{
|
|
4983
4983
|
"path": "modules/hall-ui/public/goals.html",
|
|
4984
4984
|
"mode": "0000644",
|
|
4985
|
-
"sha256": "
|
|
4985
|
+
"sha256": "597efb7e4a968892fdb1eaa8a113631aeb445c1a772520e81b80c53db572d3e3"
|
|
4986
4986
|
},
|
|
4987
4987
|
{
|
|
4988
4988
|
"path": "modules/hall-ui/public/goals.js",
|
|
@@ -5439,6 +5439,11 @@
|
|
|
5439
5439
|
"mode": "0000644",
|
|
5440
5440
|
"sha256": "1be94e892012740aae899aa28425f4be9c94a5ae777c8c77e5e1b787791f5ec9"
|
|
5441
5441
|
},
|
|
5442
|
+
{
|
|
5443
|
+
"path": "modules/hall-ui/public/text-fit.js",
|
|
5444
|
+
"mode": "0000644",
|
|
5445
|
+
"sha256": "c63dd0fb358b8c5e649460c9fc1581573a0369cbe022fc9e1db02f69da6e92f7"
|
|
5446
|
+
},
|
|
5442
5447
|
{
|
|
5443
5448
|
"path": "modules/hall-ui/public/thinking.css",
|
|
5444
5449
|
"mode": "0000644",
|
|
@@ -8162,12 +8167,12 @@
|
|
|
8162
8167
|
{
|
|
8163
8168
|
"path": "package-lock.json",
|
|
8164
8169
|
"mode": "0000644",
|
|
8165
|
-
"sha256": "
|
|
8170
|
+
"sha256": "8693404033a617a03140037d079df84b7e71b967394f7f4069b0681667761be8"
|
|
8166
8171
|
},
|
|
8167
8172
|
{
|
|
8168
8173
|
"path": "package.json",
|
|
8169
8174
|
"mode": "0000644",
|
|
8170
|
-
"sha256": "
|
|
8175
|
+
"sha256": "4b6ac3b9b46c3053276678a73c6aaef328f9a832523b8efc82386645aabc6345"
|
|
8171
8176
|
},
|
|
8172
8177
|
{
|
|
8173
8178
|
"path": "public-docs/index.html",
|
|
@@ -10207,7 +10212,7 @@
|
|
|
10207
10212
|
{
|
|
10208
10213
|
"path": "src/module-api.js",
|
|
10209
10214
|
"mode": "0000644",
|
|
10210
|
-
"sha256": "
|
|
10215
|
+
"sha256": "fa9ef668d1c3a3762533b0b74415725117d45c789d3f8b3b2c6cda3ab8344b47"
|
|
10211
10216
|
},
|
|
10212
10217
|
{
|
|
10213
10218
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11692,12 +11697,12 @@
|
|
|
11692
11697
|
{
|
|
11693
11698
|
"path": "tests/goal_forest_lib.mjs",
|
|
11694
11699
|
"mode": "0000644",
|
|
11695
|
-
"sha256": "
|
|
11700
|
+
"sha256": "3b309358c195469bee0ab1ce95242914fbc0b1b3def0bb7de3d51dc09dd29c6a"
|
|
11696
11701
|
},
|
|
11697
11702
|
{
|
|
11698
11703
|
"path": "tests/goal_forest_page.mjs",
|
|
11699
11704
|
"mode": "0000644",
|
|
11700
|
-
"sha256": "
|
|
11705
|
+
"sha256": "fe78639b77de1a85351ae954b59572f22b9dbce4f6575894f9f019daa0e66f3a"
|
|
11701
11706
|
},
|
|
11702
11707
|
{
|
|
11703
11708
|
"path": "tests/goal_graph.mjs",
|
|
@@ -11717,7 +11722,7 @@
|
|
|
11717
11722
|
{
|
|
11718
11723
|
"path": "tests/goal_map_page.mjs",
|
|
11719
11724
|
"mode": "0000644",
|
|
11720
|
-
"sha256": "
|
|
11725
|
+
"sha256": "d51008e89e01a195663e4e983b598272decc1cc2f5020cb7ec8c133f98f373fe"
|
|
11721
11726
|
},
|
|
11722
11727
|
{
|
|
11723
11728
|
"path": "tests/goal_member_kinds.mjs",
|
|
@@ -14444,6 +14449,11 @@
|
|
|
14444
14449
|
"mode": "0000644",
|
|
14445
14450
|
"sha256": "bbb0b063bffc6e394114bc4895da5006d8797a0317ed38f1c7f9b989841d5eab"
|
|
14446
14451
|
},
|
|
14452
|
+
{
|
|
14453
|
+
"path": "tests/text_fit.mjs",
|
|
14454
|
+
"mode": "0000644",
|
|
14455
|
+
"sha256": "cf2bab75f91ee81bd554e83b85de7d80c64a3a44a596ef8abc37f646041f584b"
|
|
14456
|
+
},
|
|
14447
14457
|
{
|
|
14448
14458
|
"path": "tests/tool-scan-fixtures/README.md",
|
|
14449
14459
|
"mode": "0000644",
|
package/docs/copy-inventory.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
| surface | lives at | files | strings |
|
|
12
12
|
|---|---|---|---|
|
|
13
|
-
| `builders-hall` | `modules/hall-ui/public` |
|
|
13
|
+
| `builders-hall` | `modules/hall-ui/public` | 97 | 1442 |
|
|
14
14
|
| `status-dashboard` | `modules/status-ui/public` | 4 | 100 |
|
|
15
15
|
| `landing` | `modules/public-landing/public` | 5 | 391 |
|
|
16
16
|
|
|
@@ -164,7 +164,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
164
164
|
| `86ea8d57d34e` | certain | Allowlisted goals | `modules/hall-ui/public/gate.js:286` |
|
|
165
165
|
| `e99cc5d37404` | certain | Already a member? | `modules/hall-ui/public/hall-render.js:367` |
|
|
166
166
|
| `da5368d10f85` | certain | Amendment {…} passed{…}, and the constitution below is not the one it decided: | `modules/hall-ui/public/government.js:633` |
|
|
167
|
-
| `20365fc74500` | certain | An arrow runs from the goal that has to ship first to the goal that waits on it, and a thicker line means more | `modules/hall-ui/public/goal-map.js:
|
|
167
|
+
| `20365fc74500` | certain | An arrow runs from the goal that has to ship first to the goal that waits on it, and a thicker line means more | `modules/hall-ui/public/goal-map.js:115` |
|
|
168
168
|
| `2ca420e19dbb` | certain | An earlier attempt did not land. | `modules/hall-ui/public/deploy.js:193` |
|
|
169
169
|
| `69f0ca4a4d84` | certain | Analytics scope | `modules/hall-ui/public/hall-render.js:1236` |
|
|
170
170
|
| `14bdceaac078` | certain | Answer the five questions and this spark becomes a Full Idea. | `modules/hall-ui/public/ideas.js:901` |
|
|
@@ -368,7 +368,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
368
368
|
| `278e8281c82f` | certain | Could not vote: {…} | `modules/hall-ui/public/board-room.js:391` |
|
|
369
369
|
| `a48e9f098392` | certain | Could not {…} just now. | `modules/hall-ui/public/goals-render.js:1348` |
|
|
370
370
|
| `cb09ee26014f` | certain | Could not {…}: {…} | `modules/hall-ui/public/government.js:314` |
|
|
371
|
-
| `f53f7aaa189f` | certain | Couldn’t load this goal’s task dependencies right now. | `modules/hall-ui/public/goal-forest-render.js:
|
|
371
|
+
| `f53f7aaa189f` | certain | Couldn’t load this goal’s task dependencies right now. | `modules/hall-ui/public/goal-forest-render.js:111` |
|
|
372
372
|
| `737bef533845` | certain | Couldn’t refresh the queue — try reloading the page. | `modules/hall-ui/public/blockers.js:285` |
|
|
373
373
|
| `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:194` |
|
|
374
374
|
| `38827a954f3b` | certain | Create a custom rank first. | `modules/hall-ui/public/government.js:459` |
|
|
@@ -486,7 +486,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
486
486
|
| `3807521c5db7` | certain | Goal map | `modules/hall-ui/public/goal-map.html:19` |
|
|
487
487
|
| `3807521c5db7` | certain | Goal map | `modules/hall-ui/public/goal-map.html:19` |
|
|
488
488
|
| `3807521c5db7` | certain | Goal map | `modules/hall-ui/public/goal-map.html:28` |
|
|
489
|
-
| `342253f7829f` | certain | Goal map — scrolls sideways inside this box | `modules/hall-ui/public/goal-map.js:
|
|
489
|
+
| `342253f7829f` | certain | Goal map — scrolls sideways inside this box | `modules/hall-ui/public/goal-map.js:115` |
|
|
490
490
|
| `6d04fe4e0ee5` | certain | Goal map — {{worldName}} | `modules/hall-ui/public/goal-map.html:6` |
|
|
491
491
|
| `6d04fe4e0ee5` | certain | Goal map — {{worldName}} | `modules/hall-ui/public/goal-map.html:6` |
|
|
492
492
|
| `e2a9e33abe60` | certain | Goal tasks | `modules/hall-ui/public/goals.html:151` |
|
|
@@ -650,7 +650,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
650
650
|
| `ae84ebe1d735` | certain | Name cannot be blank. | `modules/hall-ui/public/settings.js:761` |
|
|
651
651
|
| `31e91ee6ce03` | certain | Name must be {…} characters or fewer. | `modules/hall-ui/public/settings.js:766` |
|
|
652
652
|
| `5e8199266a8a` | certain | Name unvouched | `modules/hall-ui/public/watch.js:374` |
|
|
653
|
-
| `83197947ee8d` | certain | Needed by | `modules/hall-ui/public/goal-forest-render.js:
|
|
653
|
+
| `83197947ee8d` | certain | Needed by | `modules/hall-ui/public/goal-forest-render.js:146` |
|
|
654
654
|
| `3970d299c95a` | certain | Needs work | `modules/hall-ui/public/studio.js:162` |
|
|
655
655
|
| `7def635421ea` | certain | Needs your attention | `modules/hall-ui/public/goal-inbox.js:559` |
|
|
656
656
|
| `87f09d983f6c` | certain | New category label | `modules/hall-ui/public/project-settings.js:138` |
|
|
@@ -688,7 +688,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
688
688
|
| `bef9fe43ba84` | certain | No sessions recorded yet. They land here as builders ship and end sessions. | `modules/hall-ui/public/sessions.js:146` |
|
|
689
689
|
| `084748fd85b4` | certain | No shipped work is recorded between core {…} and {…}. | `modules/hall-ui/public/deploy.js:340` |
|
|
690
690
|
| `4485497d87bb` | certain | No specialities yet. Create one below — it takes a name and a craft, and you can train it afterwards. | `modules/hall-ui/public/settings-specialities.js:169` |
|
|
691
|
-
| `545dc44ad427` | certain | No task in this goal depends on another task in this goal. | `modules/hall-ui/public/goal-forest-render.js:
|
|
691
|
+
| `545dc44ad427` | certain | No task in this goal depends on another task in this goal. | `modules/hall-ui/public/goal-forest-render.js:126` |
|
|
692
692
|
| `712b64d8273f` | certain | No tasks match these filters. Try widening them. | `modules/hall-ui/public/work.js:417` |
|
|
693
693
|
| `78f78d1d1e1f` | certain | No version is in flight right now. | `modules/hall-ui/public/roadmap.js:409` |
|
|
694
694
|
| `c9800f8b6bc8` | certain | No versions declared yet. | `modules/hall-ui/public/roadmap.js:427` |
|
|
@@ -975,7 +975,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
975
975
|
| `4894b1d9b0de` | certain | System oversight: people, operations and security. | `modules/hall-ui/public/watch.js:1335` |
|
|
976
976
|
| `e604fc3ace79` | certain | Task # | `modules/hall-ui/public/blockers.js:139` |
|
|
977
977
|
| `a0f384b022fd` | certain | Task dependencies | `modules/hall-ui/public/goals.html:140` |
|
|
978
|
-
| `91576eb7c880` | certain | Task dependency forest — scrolls sideways inside this box | `modules/hall-ui/public/goal-forest-render.js:
|
|
978
|
+
| `91576eb7c880` | certain | Task dependency forest — scrolls sideways inside this box | `modules/hall-ui/public/goal-forest-render.js:146` |
|
|
979
979
|
| `dfc967a63d57` | certain | Task lifecycle from backlog to shipped, showing states and transitions and the edge where credits are awarded. | `modules/hall-ui/public/diagrams.html:51` |
|
|
980
980
|
| `10cc12f37636` | certain | Task sections | `modules/hall-ui/public/work.html:53` |
|
|
981
981
|
| `0e636ea41b29` | certain | Task status | `modules/hall-ui/public/goals-render.js:354` |
|
|
@@ -1125,7 +1125,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1125
1125
|
| `b1e63e33f739` | certain | Vulnerability reports, engagement reports, override requests, and the audit log. | `modules/hall-ui/public/watch.html:122` |
|
|
1126
1126
|
| `411b2ff58094` | certain | Wait for task | `modules/hall-ui/public/task.js:611` |
|
|
1127
1127
|
| `f2e019bfdbe8` | certain | Waiting to be developed | `modules/hall-ui/public/ideas.html:41` |
|
|
1128
|
-
| `8e4f0179e1d3` | certain | Waits on | `modules/hall-ui/public/goal-forest-render.js:
|
|
1128
|
+
| `8e4f0179e1d3` | certain | Waits on | `modules/hall-ui/public/goal-forest-render.js:146` |
|
|
1129
1129
|
| `3a9cd9a27a84` | certain | Walk the full ladder → | `modules/hall-ui/public/hall-render.js:84` |
|
|
1130
1130
|
| `18f2f54de2a8` | certain | Watch sections | `modules/hall-ui/public/watch.html:54` |
|
|
1131
1131
|
| `c1fcfa9b48c2` | certain | Watch — {{worldName}} | `modules/hall-ui/public/watch.html:6` |
|
|
@@ -1441,7 +1441,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1441
1441
|
| `3efa67dc6e07` | certain | {…} file{…} | `modules/hall-ui/public/gate.js:151` |
|
|
1442
1442
|
| `00c50715f271` | certain | {…} fundamentals · {…} you can change here | `modules/hall-ui/public/project-settings.js:228` |
|
|
1443
1443
|
| `511a4b6073cf` | certain | {…} more PR{…} still being checked. Refresh shortly. | `modules/hall-ui/public/gate.js:225` |
|
|
1444
|
-
| `73c1372b43d9` | certain | {…} of {…} | `modules/hall-ui/public/goal-map.js:
|
|
1444
|
+
| `73c1372b43d9` | certain | {…} of {…} | `modules/hall-ui/public/goal-map.js:170` |
|
|
1445
1445
|
| `cc5106af4b35` | certain | {…} of {…} criteria satisfied | `modules/hall-ui/public/goals-lib.js:32` |
|
|
1446
1446
|
| `cc5106af4b35` | certain | {…} of {…} criteria satisfied | `modules/hall-ui/public/goals-render.js:705` |
|
|
1447
1447
|
| `cc5106af4b35` | certain | {…} of {…} criteria satisfied | `modules/hall-ui/public/goals.js:45` |
|
|
@@ -1459,9 +1459,9 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1459
1459
|
| `955499559f0a` | certain | {…} tokens | `modules/hall-ui/public/sessions.js:170` |
|
|
1460
1460
|
| `8e28c14b106d` | certain | {…} total | `modules/hall-ui/public/roadmap.js:120` |
|
|
1461
1461
|
| `1a8a6267e490` | certain | {…} unfinished task{…} — say what happens to each | `modules/hall-ui/public/goals-render.js:1036` |
|
|
1462
|
-
| `f78fd28045b8` | certain | {…} waits on {…} — {…} | `modules/hall-ui/public/goal-map.js:
|
|
1462
|
+
| `f78fd28045b8` | certain | {…} waits on {…} — {…} | `modules/hall-ui/public/goal-map.js:78` |
|
|
1463
1463
|
| `3ad8f32dd43b` | certain | {…} you have sent | `modules/hall-ui/public/profile-connections.js:137` |
|
|
1464
|
-
| `ae6bb71c72da` | certain | {…} {…} no task that waits on another goal’s work, | `modules/hall-ui/public/goal-map.js:
|
|
1464
|
+
| `ae6bb71c72da` | certain | {…} {…} no task that waits on another goal’s work, | `modules/hall-ui/public/goal-map.js:146` |
|
|
1465
1465
|
| `8186b562553a` | certain | {…} {…} waiting | `modules/hall-ui/public/work.js:410` |
|
|
1466
1466
|
| `0d4016a5531c` | certain | {…} {…}{…} | `modules/hall-ui/public/watch.js:474` |
|
|
1467
1467
|
| `0d4016a5531c` | certain | {…} {…}{…} | `modules/hall-ui/public/work.js:249` |
|
|
@@ -1471,11 +1471,11 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1471
1471
|
| `cab2a4296aec` | certain | {…} · {…} · {…}{…} | `modules/hall-ui/public/goal-inbox.js:420` |
|
|
1472
1472
|
| `085f93f3f775` | certain | {…} — see all standings | `modules/hall-ui/public/hall-render.js:80` |
|
|
1473
1473
|
| `f75ee96a7511` | certain | {…} — total payout from shipped work. | `modules/hall-ui/public/profile.js:94` |
|
|
1474
|
-
| `0d4e848206f3` | certain | {…} — {…} — {…} of {…} tasks done | `modules/hall-ui/public/goal-map.js:
|
|
1474
|
+
| `0d4e848206f3` | certain | {…} — {…} — {…} of {…} tasks done | `modules/hall-ui/public/goal-map.js:91` |
|
|
1475
1475
|
| `2a2728a4ec4f` | certain | {…}% of tasks complete | `modules/hall-ui/public/roadmap.js:63` |
|
|
1476
1476
|
| `15e545eb3ce2` | certain | {…}, no such task | `modules/hall-ui/public/task.js:963` |
|
|
1477
1477
|
| `c43050b62297` | certain | {…}/100 complete | `modules/hall-ui/public/idea.js:87` |
|
|
1478
|
-
| `eb85443721f7` | certain | {…}No category | `modules/hall-ui/public/goal-map.js:
|
|
1478
|
+
| `eb85443721f7` | certain | {…}No category | `modules/hall-ui/public/goal-map.js:137` |
|
|
1479
1479
|
| `cbc5a8c686b2` | certain | {…}We store only your GitHub login, avatar, and the work you ship. | `modules/hall-ui/public/hall-render.js:392` |
|
|
1480
1480
|
| `8e8c7da4f7f5` | certain | {…}criteria met · awaiting review | `modules/hall-ui/public/goals-render.js:163` |
|
|
1481
1481
|
| `1e275ee0299c` | certain | {…}s only | `modules/hall-ui/public/government.js:896` |
|
package/docs/copy-registry.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"surfaces": {
|
|
13
13
|
"builders-hall": {
|
|
14
14
|
"at": "modules/hall-ui/public",
|
|
15
|
-
"files":
|
|
15
|
+
"files": 97,
|
|
16
16
|
"strings": 1442
|
|
17
17
|
},
|
|
18
18
|
"status-dashboard": {
|
|
@@ -2645,7 +2645,7 @@
|
|
|
2645
2645
|
"surface": "builders-hall",
|
|
2646
2646
|
"text": "An arrow runs from the goal that has to ship first to the goal that waits on it, and a thicker line means more tasks are waiting. Every goal in this map, and every dependency, is also in the table below.",
|
|
2647
2647
|
"file": "modules/hall-ui/public/goal-map.js",
|
|
2648
|
-
"line":
|
|
2648
|
+
"line": 115,
|
|
2649
2649
|
"origin": "js-markup",
|
|
2650
2650
|
"confidence": "certain"
|
|
2651
2651
|
},
|
|
@@ -4481,7 +4481,7 @@
|
|
|
4481
4481
|
"surface": "builders-hall",
|
|
4482
4482
|
"text": "Couldn’t load this goal’s task dependencies right now.",
|
|
4483
4483
|
"file": "modules/hall-ui/public/goal-forest-render.js",
|
|
4484
|
-
"line":
|
|
4484
|
+
"line": 111,
|
|
4485
4485
|
"origin": "js-text-assign",
|
|
4486
4486
|
"confidence": "certain"
|
|
4487
4487
|
},
|
|
@@ -5543,7 +5543,7 @@
|
|
|
5543
5543
|
"surface": "builders-hall",
|
|
5544
5544
|
"text": "Goal map — scrolls sideways inside this box",
|
|
5545
5545
|
"file": "modules/hall-ui/public/goal-map.js",
|
|
5546
|
-
"line":
|
|
5546
|
+
"line": 115,
|
|
5547
5547
|
"origin": "js-markup",
|
|
5548
5548
|
"confidence": "certain"
|
|
5549
5549
|
},
|
|
@@ -7019,7 +7019,7 @@
|
|
|
7019
7019
|
"surface": "builders-hall",
|
|
7020
7020
|
"text": "Needed by",
|
|
7021
7021
|
"file": "modules/hall-ui/public/goal-forest-render.js",
|
|
7022
|
-
"line":
|
|
7022
|
+
"line": 146,
|
|
7023
7023
|
"origin": "js-markup",
|
|
7024
7024
|
"confidence": "certain"
|
|
7025
7025
|
},
|
|
@@ -7361,7 +7361,7 @@
|
|
|
7361
7361
|
"surface": "builders-hall",
|
|
7362
7362
|
"text": "No task in this goal depends on another task in this goal.",
|
|
7363
7363
|
"file": "modules/hall-ui/public/goal-forest-render.js",
|
|
7364
|
-
"line":
|
|
7364
|
+
"line": 126,
|
|
7365
7365
|
"origin": "js-text-assign",
|
|
7366
7366
|
"confidence": "certain"
|
|
7367
7367
|
},
|
|
@@ -9944,7 +9944,7 @@
|
|
|
9944
9944
|
"surface": "builders-hall",
|
|
9945
9945
|
"text": "Task dependency forest — scrolls sideways inside this box",
|
|
9946
9946
|
"file": "modules/hall-ui/public/goal-forest-render.js",
|
|
9947
|
-
"line":
|
|
9947
|
+
"line": 146,
|
|
9948
9948
|
"origin": "js-markup",
|
|
9949
9949
|
"confidence": "certain"
|
|
9950
9950
|
},
|
|
@@ -11294,7 +11294,7 @@
|
|
|
11294
11294
|
"surface": "builders-hall",
|
|
11295
11295
|
"text": "Waits on",
|
|
11296
11296
|
"file": "modules/hall-ui/public/goal-forest-render.js",
|
|
11297
|
-
"line":
|
|
11297
|
+
"line": 146,
|
|
11298
11298
|
"origin": "js-markup",
|
|
11299
11299
|
"confidence": "certain"
|
|
11300
11300
|
},
|
|
@@ -14138,7 +14138,7 @@
|
|
|
14138
14138
|
"surface": "builders-hall",
|
|
14139
14139
|
"text": "{…} of {…}",
|
|
14140
14140
|
"file": "modules/hall-ui/public/goal-map.js",
|
|
14141
|
-
"line":
|
|
14141
|
+
"line": 170,
|
|
14142
14142
|
"origin": "js-markup",
|
|
14143
14143
|
"confidence": "certain"
|
|
14144
14144
|
},
|
|
@@ -14300,7 +14300,7 @@
|
|
|
14300
14300
|
"surface": "builders-hall",
|
|
14301
14301
|
"text": "{…} waits on {…} — {…}",
|
|
14302
14302
|
"file": "modules/hall-ui/public/goal-map.js",
|
|
14303
|
-
"line":
|
|
14303
|
+
"line": 78,
|
|
14304
14304
|
"origin": "js-text-assign",
|
|
14305
14305
|
"confidence": "certain"
|
|
14306
14306
|
},
|
|
@@ -14318,7 +14318,7 @@
|
|
|
14318
14318
|
"surface": "builders-hall",
|
|
14319
14319
|
"text": "{…} {…} no task that waits on another goal’s work,",
|
|
14320
14320
|
"file": "modules/hall-ui/public/goal-map.js",
|
|
14321
|
-
"line":
|
|
14321
|
+
"line": 146,
|
|
14322
14322
|
"origin": "js-text-assign",
|
|
14323
14323
|
"confidence": "certain"
|
|
14324
14324
|
},
|
|
@@ -14408,7 +14408,7 @@
|
|
|
14408
14408
|
"surface": "builders-hall",
|
|
14409
14409
|
"text": "{…} — {…} — {…} of {…} tasks done",
|
|
14410
14410
|
"file": "modules/hall-ui/public/goal-map.js",
|
|
14411
|
-
"line":
|
|
14411
|
+
"line": 91,
|
|
14412
14412
|
"origin": "js-text-assign",
|
|
14413
14413
|
"confidence": "certain"
|
|
14414
14414
|
},
|
|
@@ -14444,7 +14444,7 @@
|
|
|
14444
14444
|
"surface": "builders-hall",
|
|
14445
14445
|
"text": "{…}No category",
|
|
14446
14446
|
"file": "modules/hall-ui/public/goal-map.js",
|
|
14447
|
-
"line":
|
|
14447
|
+
"line": 137,
|
|
14448
14448
|
"origin": "js-markup",
|
|
14449
14449
|
"confidence": "certain"
|
|
14450
14450
|
},
|
|
@@ -2454,5 +2454,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2454
2454
|
landed since 1.19.988 with no explicit bump. run 35911817390. (task 1002620)
|
|
2455
2455
|
1.19.990 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2456
2456
|
landed since 1.19.989 with no explicit bump. run 35912578496. (task 1002620)
|
|
2457
|
+
1.19.991 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2458
|
+
landed since 1.19.990 with no explicit bump. run 35916061384. (task 1002620)
|
|
2457
2459
|
---------------------------------------------------------------------------
|
|
2458
2460
|
```
|
|
@@ -41,52 +41,6 @@
|
|
|
41
41
|
function num(id) { return Number(id); }
|
|
42
42
|
function byNumericId(a, b) { return num(a) - num(b); }
|
|
43
43
|
|
|
44
|
-
// Titles are fitted to MEASURED width, not a character budget (task 1004200):
|
|
45
|
-
// proportional glyphs vary too much for any fixed count to fit a fixed box.
|
|
46
|
-
// `measure` is always the caller's, so this file stays DOM-free.
|
|
47
|
-
|
|
48
|
-
// The longest code-point prefix of `chars` that measures within maxWidth with
|
|
49
|
-
// `suffix` appended ('' if none does). Code points, so an emoji is never split.
|
|
50
|
-
function longestPrefix(chars, maxWidth, measure, suffix) {
|
|
51
|
-
var best = '';
|
|
52
|
-
var lo = 1;
|
|
53
|
-
var hi = chars.length;
|
|
54
|
-
while (lo <= hi) {
|
|
55
|
-
var mid = (lo + hi) >> 1;
|
|
56
|
-
var cand = chars.slice(0, mid).join('').replace(/\s+$/, '');
|
|
57
|
-
if (measure(cand + suffix) <= maxWidth) { best = cand; lo = mid + 1; } else { hi = mid - 1; }
|
|
58
|
-
}
|
|
59
|
-
return best;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// fitText — `text` whole if it fits, else its longest prefix that fits with an ellipsis.
|
|
63
|
-
function fitText(text, maxWidth, measure) {
|
|
64
|
-
var t = String(text == null ? '' : text);
|
|
65
|
-
if (measure(t) <= maxWidth) return t;
|
|
66
|
-
return longestPrefix(Array.from(t), maxWidth, measure, '…') + '…';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// fitLines — `text` wrapped at spaces into at most `maxLines` lines of maxWidth,
|
|
70
|
-
// the last ellipsised when the rest will not fit. A word wider than a whole
|
|
71
|
-
// line is broken where it runs out of room, the way the browser would.
|
|
72
|
-
function fitLines(text, maxWidth, maxLines, measure) {
|
|
73
|
-
var rest = String(text == null ? '' : text).replace(/\s+/g, ' ').trim();
|
|
74
|
-
var lines = [];
|
|
75
|
-
while (rest && lines.length < maxLines - 1 && measure(rest) > maxWidth) {
|
|
76
|
-
var words = rest.split(' ');
|
|
77
|
-
var line = '';
|
|
78
|
-
for (var i = 0; i < words.length; i++) {
|
|
79
|
-
var next = line ? line + ' ' + words[i] : words[i];
|
|
80
|
-
if (measure(next) > maxWidth) break;
|
|
81
|
-
line = next;
|
|
82
|
-
}
|
|
83
|
-
if (!line) line = longestPrefix(Array.from(words[0]), maxWidth, measure, '') || Array.from(words[0])[0];
|
|
84
|
-
lines.push(line);
|
|
85
|
-
rest = rest.slice(line.length).trim();
|
|
86
|
-
}
|
|
87
|
-
if (rest) lines.push(fitText(rest, maxWidth, measure));
|
|
88
|
-
return lines;
|
|
89
|
-
}
|
|
90
44
|
function plural(n, one, many) { return n + ' ' + (n === 1 ? one : (many || one + 's')); }
|
|
91
45
|
|
|
92
46
|
// analyse — nodes by id, who each task waits on and who needs it, and the
|
|
@@ -268,7 +222,7 @@
|
|
|
268
222
|
|
|
269
223
|
return {
|
|
270
224
|
analyse: analyse, layout: layout, tableRows: tableRows, describe: describe,
|
|
271
|
-
|
|
225
|
+
plural: plural, priorityLabel: priorityLabel,
|
|
272
226
|
TEXT_INSET: TEXT_INSET, TITLE_LINES: TITLE_LINES,
|
|
273
227
|
};
|
|
274
228
|
}));
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
const { API, state, sameId, getJSON } = ctx;
|
|
30
30
|
const { escapeHtml, $ } = window.OTB;
|
|
31
31
|
const lib = window.OTBGoalForestLib;
|
|
32
|
+
const textFit = window.OTBTextFit;
|
|
32
33
|
|
|
33
34
|
function setCount(sel, n) {
|
|
34
35
|
const el = $(sel);
|
|
@@ -58,7 +59,7 @@
|
|
|
58
59
|
function fitNames(names, maxWidth) {
|
|
59
60
|
names.forEach(({ els, full }) => {
|
|
60
61
|
const measure = (s) => { els[0].textContent = s; return els[0].getComputedTextLength(); };
|
|
61
|
-
const lines =
|
|
62
|
+
const lines = textFit.fitLines(full, maxWidth, els.length, measure);
|
|
62
63
|
els.forEach((el, i) => { el.textContent = lines[i] || ''; });
|
|
63
64
|
});
|
|
64
65
|
}
|
|
@@ -52,17 +52,10 @@
|
|
|
52
52
|
// The categorical colour channel repeats after this many; the WORD on every
|
|
53
53
|
// node is what distinguishes them, so a repeat costs nothing.
|
|
54
54
|
var CATEGORY_COLOURS = 8;
|
|
55
|
-
var TITLE_CHARS = 24;
|
|
56
|
-
var SUB_CHARS = 30;
|
|
57
55
|
|
|
58
56
|
function num(id) { return Number(id); }
|
|
59
57
|
function byNumericId(a, b) { return num(a) - num(b); }
|
|
60
58
|
|
|
61
|
-
function clip(text, max) {
|
|
62
|
-
var t = String(text == null ? '' : text);
|
|
63
|
-
return t.length > max ? t.slice(0, max - 1).replace(/\s+$/, '') + '…' : t;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
59
|
// A private goal the viewer cannot read arrives with title null. It stays a node
|
|
67
60
|
// (dropping it would falsely isolate its neighbours) and is named as what it is.
|
|
68
61
|
function displayTitle(node) { return node.title ? String(node.title) : 'A private goal'; }
|
|
@@ -350,7 +343,7 @@
|
|
|
350
343
|
|
|
351
344
|
return {
|
|
352
345
|
analyse: analyse, layout: layout, categories: categories, colourOf: colourOf, weightOf: weightOf,
|
|
353
|
-
tableRows: tableRows, describe: describe, displayTitle: displayTitle,
|
|
354
|
-
plural: plural,
|
|
346
|
+
tableRows: tableRows, describe: describe, displayTitle: displayTitle,
|
|
347
|
+
plural: plural, CATEGORY_COLOURS: CATEGORY_COLOURS,
|
|
355
348
|
};
|
|
356
349
|
}));
|
|
@@ -63,6 +63,8 @@
|
|
|
63
63
|
<!-- The model and layout are a pure lib so a test runs the real arithmetic; the
|
|
64
64
|
page script keeps only the drawing and the table. Lib BEFORE page. -->
|
|
65
65
|
<script src="/builders/goal-map-lib.js"></script>
|
|
66
|
+
<!-- task 1004204: measured-width text fitting, shared with the goal forest. -->
|
|
67
|
+
<script src="/builders/text-fit.js"></script>
|
|
66
68
|
<script src="/builders/goal-map.js"></script>
|
|
67
69
|
</body>
|
|
68
70
|
</html>
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
const SVGNS = 'http://www.w3.org/2000/svg';
|
|
28
28
|
const kit = window.OTBKit;
|
|
29
29
|
const lib = window.OTBGoalMapLib;
|
|
30
|
+
const textFit = window.OTBTextFit;
|
|
30
31
|
const { escapeHtml } = window.OTB;
|
|
31
32
|
const $ = (id) => document.getElementById(id);
|
|
32
33
|
|
|
@@ -47,7 +48,18 @@
|
|
|
47
48
|
|
|
48
49
|
// ---- the map -----------------------------------------------------------------
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
// Node text is fitted to MEASURED width, not a character count (task 1004204;
|
|
52
|
+
// the forest's fix, task 1004200). Measuring needs the svg in the document, so
|
|
53
|
+
// drawGraph collects each line into `lines` and renderGraph fits them after
|
|
54
|
+
// appending. The clipPath is the backstop for a map drawn while hidden.
|
|
55
|
+
const TEXT_X = 16;
|
|
56
|
+
function fitNodeText(lines, maxWidth) {
|
|
57
|
+
lines.forEach(({ el, full }) => {
|
|
58
|
+
el.textContent = textFit.fitText(full, maxWidth, (s) => { el.textContent = s; return el.getComputedTextLength(); });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function drawGraph(a, lay, cats, lines) {
|
|
51
63
|
const svg = svgEl('svg', {
|
|
52
64
|
class: 'gm-svg', viewBox: `0 0 ${lay.width} ${lay.height}`, width: lay.width, height: lay.height,
|
|
53
65
|
role: 'img', 'aria-labelledby': 'gm-cap', focusable: 'false',
|
|
@@ -55,6 +67,8 @@
|
|
|
55
67
|
const defs = svgEl('defs', {}, svg);
|
|
56
68
|
const marker = svgEl('marker', { id: 'gm-arrow', viewBox: '0 0 10 10', refX: 9, refY: 5, markerWidth: 7, markerHeight: 7, orient: 'auto' }, defs);
|
|
57
69
|
svgEl('path', { class: 'gm-arrowhead', d: 'M0 0 L10 5 L0 10 z' }, marker);
|
|
70
|
+
const clip = svgEl('clipPath', { id: 'gm-text-clip' }, defs);
|
|
71
|
+
svgEl('rect', { width: lay.nodeW, height: lay.nodeH }, clip);
|
|
58
72
|
|
|
59
73
|
const edges = svgEl('g', { class: 'gm-edges' }, svg);
|
|
60
74
|
lay.edges.forEach((e) => {
|
|
@@ -78,9 +92,12 @@
|
|
|
78
92
|
+ ` — waits on ${lib.plural(n.depends_on || 0, 'goal')}, needed by ${lib.plural(n.depended_on_by || 0, 'goal')}`;
|
|
79
93
|
svgEl('rect', { class: 'gm-card', width: p.w, height: p.h, rx: 8 }, g);
|
|
80
94
|
svgEl('rect', { class: 'gm-stripe', width: 6, height: p.h, rx: 3 }, g);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
[['gm-name', 22, lib.displayTitle(n)], ['gm-cat', 38, cat], ['gm-tasks', 53, `${n.task_done} of ${lib.plural(n.task_total, 'task')} done`]]
|
|
96
|
+
.forEach(([cls, y, full]) => {
|
|
97
|
+
const el = text(g, cls, TEXT_X, y, full);
|
|
98
|
+
el.setAttribute('clip-path', 'url(#gm-text-clip)');
|
|
99
|
+
lines.push({ el, full });
|
|
100
|
+
});
|
|
84
101
|
const trackW = p.w - 32;
|
|
85
102
|
svgEl('rect', { class: 'gm-weight-track', x: 16, y: 60, width: trackW, height: 3, rx: 1.5 }, g);
|
|
86
103
|
const frac = lib.weightOf(n.task_total, maxTotal);
|
|
@@ -103,7 +120,13 @@
|
|
|
103
120
|
</figcaption>
|
|
104
121
|
<div class="gm-scroll" id="gm-scroll" tabindex="0" role="region" aria-label="Goal map — scrolls sideways inside this box"></div>
|
|
105
122
|
</figure>`;
|
|
106
|
-
|
|
123
|
+
const lay = lib.layout(a);
|
|
124
|
+
const lines = [];
|
|
125
|
+
$('gm-scroll').appendChild(drawGraph(a, lay, cats, lines));
|
|
126
|
+
const fit = () => fitNodeText(lines, lay.nodeW - 2 * TEXT_X);
|
|
127
|
+
fit();
|
|
128
|
+
// A web font that lands after the first fit changes every width.
|
|
129
|
+
if (document.fonts && document.fonts.status !== 'loaded') document.fonts.ready.then(fit);
|
|
107
130
|
}
|
|
108
131
|
|
|
109
132
|
function renderLegend(cats) {
|
|
@@ -249,6 +249,8 @@
|
|
|
249
249
|
<!-- task 1003480: the per-goal dependency forest's pure model + layout —
|
|
250
250
|
goal-map-lib.js's sibling, loaded before the renderer that draws it. -->
|
|
251
251
|
<script src="/builders/goal-forest-lib.js"></script>
|
|
252
|
+
<!-- tasks 1004200/1004204: measured-width title fitting, shared with the goal map. -->
|
|
253
|
+
<script src="/builders/text-fit.js"></script>
|
|
252
254
|
<!-- task 1003480: the forest's own drawing module — goal-map.js's sibling,
|
|
253
255
|
carved out of goals-render.js to keep that file under the size ratchet.
|
|
254
256
|
Loaded before goals-render.js, which calls window.OTBGoalForestRender(ctx). -->
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// text-fit.js — fit a line of text to a MEASURED width (task 1004200, shared by
|
|
2
|
+
// task 1004204). Proportional glyphs vary too much for any character budget to
|
|
3
|
+
// fit a fixed box, so every SVG card title is cut by its measured width instead.
|
|
4
|
+
// `measure` is always the caller's (getComputedTextLength in the page, a fake in
|
|
5
|
+
// tests), so this file stays DOM-free. Loaded by both the goal forest and the
|
|
6
|
+
// goal map.
|
|
7
|
+
(function (root, factory) {
|
|
8
|
+
var mod = factory();
|
|
9
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = mod;
|
|
10
|
+
else root.OTBTextFit = mod;
|
|
11
|
+
}(typeof self !== 'undefined' ? self : this, function () {
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
// The longest code-point prefix of `chars` that measures within maxWidth with
|
|
15
|
+
// `suffix` appended ('' if none does). Code points, so an emoji is never split.
|
|
16
|
+
function longestPrefix(chars, maxWidth, measure, suffix) {
|
|
17
|
+
var best = '';
|
|
18
|
+
var lo = 1;
|
|
19
|
+
var hi = chars.length;
|
|
20
|
+
while (lo <= hi) {
|
|
21
|
+
var mid = (lo + hi) >> 1;
|
|
22
|
+
var cand = chars.slice(0, mid).join('').replace(/\s+$/, '');
|
|
23
|
+
if (measure(cand + suffix) <= maxWidth) { best = cand; lo = mid + 1; } else { hi = mid - 1; }
|
|
24
|
+
}
|
|
25
|
+
return best;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// fitText — `text` whole if it fits, else its longest prefix that fits with an ellipsis.
|
|
29
|
+
function fitText(text, maxWidth, measure) {
|
|
30
|
+
var t = String(text == null ? '' : text);
|
|
31
|
+
if (measure(t) <= maxWidth) return t;
|
|
32
|
+
return longestPrefix(Array.from(t), maxWidth, measure, '…') + '…';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// fitLines — `text` wrapped at spaces into at most `maxLines` lines of maxWidth,
|
|
36
|
+
// the last ellipsised when the rest will not fit. A word wider than a whole
|
|
37
|
+
// line is broken where it runs out of room, the way the browser would.
|
|
38
|
+
function fitLines(text, maxWidth, maxLines, measure) {
|
|
39
|
+
var rest = String(text == null ? '' : text).replace(/\s+/g, ' ').trim();
|
|
40
|
+
var lines = [];
|
|
41
|
+
while (rest && lines.length < maxLines - 1 && measure(rest) > maxWidth) {
|
|
42
|
+
var words = rest.split(' ');
|
|
43
|
+
var line = '';
|
|
44
|
+
for (var i = 0; i < words.length; i++) {
|
|
45
|
+
var next = line ? line + ' ' + words[i] : words[i];
|
|
46
|
+
if (measure(next) > maxWidth) break;
|
|
47
|
+
line = next;
|
|
48
|
+
}
|
|
49
|
+
if (!line) line = longestPrefix(Array.from(words[0]), maxWidth, measure, '') || Array.from(words[0])[0];
|
|
50
|
+
lines.push(line);
|
|
51
|
+
rest = rest.slice(line.length).trim();
|
|
52
|
+
}
|
|
53
|
+
if (rest) lines.push(fitText(rest, maxWidth, measure));
|
|
54
|
+
return lines;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { fitText: fitText, fitLines: fitLines };
|
|
58
|
+
}));
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.991",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.991",
|
|
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.19.
|
|
3
|
+
"version": "1.19.991",
|
|
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",
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.991'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -141,67 +141,6 @@ await test('describe states the numbers plainly, and an empty forest says so', (
|
|
|
141
141
|
assert.equal(L.describe(L.analyse({})), 'No task in this goal depends on another task in this goal.');
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
-
// ---- fitting a title to its card (task 1004200) ------------------------------------
|
|
145
|
-
|
|
146
|
-
// A proportional fake: wide capitals cost three times a narrow letter, so any
|
|
147
|
-
// character budget that fits one title overflows the other.
|
|
148
|
-
const WIDTHS = { W: 12, M: 11, i: 3, '…': 8, '🚀': 14 };
|
|
149
|
-
const measure = (s) => Array.from(s).reduce((w, c) => w + (WIDTHS[c] ?? 6), 0);
|
|
150
|
-
|
|
151
|
-
await test('fitText leaves a title that fits untouched', () => {
|
|
152
|
-
assert.equal(L.fitText('#7 Fix', 136, measure), '#7 Fix');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
await test('fitText cuts to the MEASURED width, so wide glyphs keep fewer characters than narrow ones', () => {
|
|
156
|
-
const wide = L.fitText('W'.repeat(40), 136, measure);
|
|
157
|
-
const narrow = L.fitText('i'.repeat(80), 136, measure);
|
|
158
|
-
for (const s of [wide, narrow]) {
|
|
159
|
-
assert.ok(measure(s) <= 136, `${s} measures ${measure(s)}, past the box`);
|
|
160
|
-
assert.ok(s.endsWith('…'), 'a cut title says it was cut');
|
|
161
|
-
}
|
|
162
|
-
assert.ok(Array.from(narrow).length > 3 * Array.from(wide).length, 'no fixed character count could produce both');
|
|
163
|
-
assert.equal(wide, 'W'.repeat(10) + '…', 'the LONGEST prefix that fits: 10 W + … = 128px, 11 would be 140px');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
await test('fitText never ends on a dangling space and never splits an emoji', () => {
|
|
167
|
-
const spaced = L.fitText('ab cd ef gh ij kl mn op qr', 62, measure);
|
|
168
|
-
assert.doesNotMatch(spaced, /\s…$/);
|
|
169
|
-
const emoji = L.fitText('🚀'.repeat(30), 60, measure);
|
|
170
|
-
assert.equal(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(emoji), false, 'no lone high surrogate');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
await test('fitText returns a bare ellipsis when not even one character fits', () => {
|
|
174
|
-
assert.equal(L.fitText('WWW', 10, measure), '…');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
await test('fitLines keeps a short title on one line, whole', () => {
|
|
178
|
-
assert.deepEqual(L.fitLines('#7 Fix', 136, 2, measure), ['#7 Fix']);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
await test('fitLines wraps at a space, and a second line that fits is left whole', () => {
|
|
182
|
-
assert.deepEqual(L.fitLines('aaaa bbbb cccc dddd', 60, 2, measure), ['aaaa bbbb', 'cccc dddd']);
|
|
183
|
-
assert.deepEqual(L.fitLines('a b\n c', 60, 2, measure), ['a b c'], 'runs of whitespace read as one space');
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
await test('fitLines ellipsises the LAST line when the title outruns every line, and no line overflows', () => {
|
|
187
|
-
const lines = L.fitLines('BV1.R26 — Real governance: the constitution becomes enforceable at last', 136, 2, measure);
|
|
188
|
-
assert.equal(lines.length, 2);
|
|
189
|
-
for (const l of lines) assert.ok(measure(l) <= 136, `"${l}" measures ${measure(l)}`);
|
|
190
|
-
assert.ok(lines[1].endsWith('…'));
|
|
191
|
-
assert.ok(!lines[0].endsWith('…'), 'only the last line is cut');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
await test('fitLines breaks a word wider than a whole line instead of overflowing, and always finishes', () => {
|
|
195
|
-
const lines = L.fitLines('W'.repeat(40), 136, 2, measure);
|
|
196
|
-
assert.deepEqual(lines, ['W'.repeat(11), 'W'.repeat(10) + '…']);
|
|
197
|
-
assert.ok(L.fitLines('WWW', 5, 2, measure).length <= 2, 'a box narrower than one glyph still terminates');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
await test('fitLines with one line is fitText', () => {
|
|
201
|
-
const t = 'Move the remaining hall pages onto the kit';
|
|
202
|
-
assert.deepEqual(L.fitLines(t, 90, 1, measure), [L.fitText(t, 90, measure)]);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
144
|
// ---- purity -----------------------------------------------------------------------
|
|
206
145
|
|
|
207
146
|
await test('the lib is pure: no DOM, no fetch, no cross-goal input', () => {
|
|
@@ -25,6 +25,7 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
25
25
|
const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
26
26
|
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
27
27
|
const LIB_JS = read('goal-forest-lib.js');
|
|
28
|
+
const FIT_JS = read('text-fit.js');
|
|
28
29
|
const FOREST_RENDER_JS = read('goal-forest-render.js');
|
|
29
30
|
const RENDER_JS = read('goals-render.js');
|
|
30
31
|
|
|
@@ -74,6 +75,7 @@ async function boot({ status = 200, fail = false, payload = { nodes: [], edges:
|
|
|
74
75
|
sandbox.globalThis = sandbox;
|
|
75
76
|
vm.createContext(sandbox);
|
|
76
77
|
vm.runInContext(LIB_JS, sandbox, { filename: 'goal-forest-lib.js' });
|
|
78
|
+
vm.runInContext(FIT_JS, sandbox, { filename: 'text-fit.js' });
|
|
77
79
|
vm.runInContext(FOREST_RENDER_JS, sandbox, { filename: 'goal-forest-render.js' });
|
|
78
80
|
vm.runInContext(RENDER_JS, sandbox, { filename: 'goals-render.js' });
|
|
79
81
|
|
package/tests/goal_map_page.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import fs from 'node:fs';
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import vm from 'node:vm';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { createRequire } from 'node:module';
|
|
23
24
|
import { makeRunner, makeEl, walk } from './helpers.mjs';
|
|
24
25
|
|
|
25
26
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -27,6 +28,7 @@ const HALL = path.join(ROOT, 'modules/hall-ui/public');
|
|
|
27
28
|
const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
|
|
28
29
|
const KIT_JS = read('hall-kit.js');
|
|
29
30
|
const LIB_JS = read('goal-map-lib.js');
|
|
31
|
+
const FIT_JS = read('text-fit.js');
|
|
30
32
|
const PAGE_JS = read('goal-map.js');
|
|
31
33
|
const PAGE_HTML = read('goal-map.html');
|
|
32
34
|
const PAGE_CSS = read('goal-map.css');
|
|
@@ -52,6 +54,8 @@ async function boot(payload, { status = 200, fail = false } = {}) {
|
|
|
52
54
|
const el = makeEl(tag);
|
|
53
55
|
const orig = el.setAttribute.bind(el);
|
|
54
56
|
el.setAttribute = (k, v) => { orig(k, v); if (k === 'class') el.className = String(v); if (k === 'id') el.id = String(v); };
|
|
57
|
+
// A proportional fake of SVGTextElement's measurement: a W is twice a letter.
|
|
58
|
+
if (tag === 'text') el.getComputedTextLength = () => Array.from(el.textContent).reduce((w, c) => w + (c === 'W' ? 12 : 6), 0);
|
|
55
59
|
return el;
|
|
56
60
|
},
|
|
57
61
|
};
|
|
@@ -77,6 +81,7 @@ async function boot(payload, { status = 200, fail = false } = {}) {
|
|
|
77
81
|
vm.createContext(sandbox);
|
|
78
82
|
vm.runInContext(KIT_JS, sandbox, { filename: 'hall-kit.js' });
|
|
79
83
|
vm.runInContext(LIB_JS, sandbox, { filename: 'goal-map-lib.js' });
|
|
84
|
+
vm.runInContext(FIT_JS, sandbox, { filename: 'text-fit.js' });
|
|
80
85
|
vm.runInContext(PAGE_JS, sandbox, { filename: 'goal-map.js' });
|
|
81
86
|
await settle();
|
|
82
87
|
const $ = (id) => documentObj.getElementById(id);
|
|
@@ -131,6 +136,31 @@ await test('CATEGORY IS A WORD on every drawn node, not only a colour — and "N
|
|
|
131
136
|
assert.ok(h.$('gm-legend').textContent.includes('Modules'), 'and the legend spells each category out');
|
|
132
137
|
});
|
|
133
138
|
|
|
139
|
+
await test('node text is fitted to its card by MEASURED width, clipped as a backstop, and kept whole in the tooltip and table (task 1004204)', async () => {
|
|
140
|
+
const wide = 'W'.repeat(60);
|
|
141
|
+
const long = 'Agents module — framework, runtime, and the whole event router rewrite';
|
|
142
|
+
const h = await boot({
|
|
143
|
+
nodes: [node(1, { title: wide, depended_on_by: 1 }), node(2, { title: 'Fix', depends_on: 1, category: { slug: 'x', label: long } })],
|
|
144
|
+
edges: [{ from: '2', to: '1', count: 1 }],
|
|
145
|
+
counts: {},
|
|
146
|
+
});
|
|
147
|
+
const lib = createRequire(import.meta.url)(path.join(HALL, 'goal-map-lib.js'));
|
|
148
|
+
const inner = lib.layout(lib.analyse({ nodes: [node(1), node(2)], edges: [] })).nodeW - 32;
|
|
149
|
+
const texts = h.svg().querySelectorAll('.gm-node').flatMap((g) => g.querySelectorAll('text'));
|
|
150
|
+
assert.equal(texts.length, 6, 'name, category and task count on each of two nodes');
|
|
151
|
+
for (const t of texts) {
|
|
152
|
+
assert.ok(t.getComputedTextLength() <= inner, `"${t.textContent}" measures ${t.getComputedTextLength()}px, past the card's ${inner}px`);
|
|
153
|
+
assert.equal(t.getAttribute('clip-path'), 'url(#gm-text-clip)');
|
|
154
|
+
}
|
|
155
|
+
assert.ok(h.svg().querySelector('#gm-text-clip'), 'the clip the lines point at exists');
|
|
156
|
+
const names = h.svg().querySelectorAll('.gm-name').map((t) => t.textContent);
|
|
157
|
+
assert.ok(names.includes('Fix'), 'a title that fits is left whole');
|
|
158
|
+
assert.ok(names.some((n) => /^W+…$/.test(n)), 'a wide title is cut by width and says so');
|
|
159
|
+
assert.ok(h.svg().querySelectorAll('.gm-cat').some((t) => t.textContent.endsWith('…')), 'the category line is fitted too');
|
|
160
|
+
assert.ok(h.svg().querySelectorAll('title').some((x) => x.textContent.includes(wide)), 'the full title is still readable on hover');
|
|
161
|
+
assert.ok(h.$('gm-table').textContent.includes(wide), 'and in the table');
|
|
162
|
+
});
|
|
163
|
+
|
|
134
164
|
// ---- the split is stated, the alone goals are a list ----------------------------------
|
|
135
165
|
|
|
136
166
|
await test('the stand-alone goals are a LIST, never dots, and the split is stated in the page head', async () => {
|
|
@@ -225,6 +255,7 @@ await test('the page has its h1 in static markup (so every state has one) and lo
|
|
|
225
255
|
assert.match(PAGE_HTML, /<h1 class="page-head__h">Goal map<\/h1>/);
|
|
226
256
|
assert.ok(PAGE_HTML.indexOf('goal-map-lib.js') > -1 && PAGE_HTML.indexOf('goal-map-lib.js') < PAGE_HTML.indexOf('goal-map.js'));
|
|
227
257
|
assert.ok(PAGE_HTML.indexOf('hall-kit.js') < PAGE_HTML.indexOf('goal-map.js'));
|
|
258
|
+
assert.ok(PAGE_HTML.indexOf('text-fit.js') > -1 && PAGE_HTML.indexOf('text-fit.js') < PAGE_HTML.indexOf('goal-map.js'));
|
|
228
259
|
});
|
|
229
260
|
|
|
230
261
|
await test('it takes NO nav slot (the sky’s rule) — the Goals page is its door', () => {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// tests/text_fit.mjs — measured-width text fitting, EXECUTED (task 1004200; moved
|
|
2
|
+
// out of goal-forest-lib.js into its own file by task 1004204 so the goal map
|
|
3
|
+
// and the goal forest share one copy).
|
|
4
|
+
//
|
|
5
|
+
// text-fit.js is pure — the caller hands it `measure` — so these run the real
|
|
6
|
+
// code against a proportional fake where no fixed character count can fit.
|
|
7
|
+
//
|
|
8
|
+
// Run: node tests/text_fit.mjs
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { makeRunner } from './helpers.mjs';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const FILE = path.join(ROOT, 'modules/hall-ui/public/text-fit.js');
|
|
19
|
+
const L = require(FILE);
|
|
20
|
+
|
|
21
|
+
const { test, summary } = makeRunner();
|
|
22
|
+
|
|
23
|
+
// ---- fitting a line to its box (task 1004200) ---------------------------------------
|
|
24
|
+
|
|
25
|
+
// A proportional fake: wide capitals cost three times a narrow letter, so any
|
|
26
|
+
// character budget that fits one title overflows the other.
|
|
27
|
+
const WIDTHS = { W: 12, M: 11, i: 3, '…': 8, '🚀': 14 };
|
|
28
|
+
const measure = (s) => Array.from(s).reduce((w, c) => w + (WIDTHS[c] ?? 6), 0);
|
|
29
|
+
|
|
30
|
+
await test('fitText leaves a title that fits untouched', () => {
|
|
31
|
+
assert.equal(L.fitText('#7 Fix', 136, measure), '#7 Fix');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await test('fitText cuts to the MEASURED width, so wide glyphs keep fewer characters than narrow ones', () => {
|
|
35
|
+
const wide = L.fitText('W'.repeat(40), 136, measure);
|
|
36
|
+
const narrow = L.fitText('i'.repeat(80), 136, measure);
|
|
37
|
+
for (const s of [wide, narrow]) {
|
|
38
|
+
assert.ok(measure(s) <= 136, `${s} measures ${measure(s)}, past the box`);
|
|
39
|
+
assert.ok(s.endsWith('…'), 'a cut title says it was cut');
|
|
40
|
+
}
|
|
41
|
+
assert.ok(Array.from(narrow).length > 3 * Array.from(wide).length, 'no fixed character count could produce both');
|
|
42
|
+
assert.equal(wide, 'W'.repeat(10) + '…', 'the LONGEST prefix that fits: 10 W + … = 128px, 11 would be 140px');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await test('fitText never ends on a dangling space and never splits an emoji', () => {
|
|
46
|
+
const spaced = L.fitText('ab cd ef gh ij kl mn op qr', 62, measure);
|
|
47
|
+
assert.doesNotMatch(spaced, /\s…$/);
|
|
48
|
+
const emoji = L.fitText('🚀'.repeat(30), 60, measure);
|
|
49
|
+
assert.equal(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(emoji), false, 'no lone high surrogate');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await test('fitText returns a bare ellipsis when not even one character fits', () => {
|
|
53
|
+
assert.equal(L.fitText('WWW', 10, measure), '…');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await test('fitLines keeps a short title on one line, whole', () => {
|
|
57
|
+
assert.deepEqual(L.fitLines('#7 Fix', 136, 2, measure), ['#7 Fix']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await test('fitLines wraps at a space, and a second line that fits is left whole', () => {
|
|
61
|
+
assert.deepEqual(L.fitLines('aaaa bbbb cccc dddd', 60, 2, measure), ['aaaa bbbb', 'cccc dddd']);
|
|
62
|
+
assert.deepEqual(L.fitLines('a b\n c', 60, 2, measure), ['a b c'], 'runs of whitespace read as one space');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await test('fitLines ellipsises the LAST line when the title outruns every line, and no line overflows', () => {
|
|
66
|
+
const lines = L.fitLines('BV1.R26 — Real governance: the constitution becomes enforceable at last', 136, 2, measure);
|
|
67
|
+
assert.equal(lines.length, 2);
|
|
68
|
+
for (const l of lines) assert.ok(measure(l) <= 136, `"${l}" measures ${measure(l)}`);
|
|
69
|
+
assert.ok(lines[1].endsWith('…'));
|
|
70
|
+
assert.ok(!lines[0].endsWith('…'), 'only the last line is cut');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await test('fitLines breaks a word wider than a whole line instead of overflowing, and always finishes', () => {
|
|
74
|
+
const lines = L.fitLines('W'.repeat(40), 136, 2, measure);
|
|
75
|
+
assert.deepEqual(lines, ['W'.repeat(11), 'W'.repeat(10) + '…']);
|
|
76
|
+
assert.ok(L.fitLines('WWW', 5, 2, measure).length <= 2, 'a box narrower than one glyph still terminates');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await test('fitLines with one line is fitText', () => {
|
|
80
|
+
const t = 'Move the remaining hall pages onto the kit';
|
|
81
|
+
assert.deepEqual(L.fitLines(t, 90, 1, measure), [L.fitText(t, 90, measure)]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ---- purity -----------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
await test('the helper is pure: no DOM, no fetch — the caller measures', () => {
|
|
87
|
+
const src = fs.readFileSync(FILE, 'utf8').replace(/^\s*\/\/.*$/gm, '');
|
|
88
|
+
assert.equal(/\bdocument\b|\bfetch\b|\bwindow\b|getComputedTextLength/.test(src.replace(/root\.OTBTextFit/, '')), false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
summary();
|