@bongos/core 1.19.795 → 1.19.796
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 +16 -16
- package/docs/copy-inventory.md +12 -10
- package/docs/copy-registry.json +30 -12
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/collab-lib.js +64 -1
- package/modules/hall-ui/public/collab.css +56 -0
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/collab.js +108 -17
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/hall_collab_world.mjs +114 -3
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
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.796",
|
|
6
|
+
"core_contract": "1.19.796",
|
|
7
|
+
"source_commit": "eb84110e187e21a515d265977ce83f905d8ca85a",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-17T01:04:56.932Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 486,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2693,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "19d44026eb8aaa1ed4e9b87d3ff059e80fe495cbb5ba3e19bf74a36fe69632d0",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -1997,12 +1997,12 @@
|
|
|
1997
1997
|
{
|
|
1998
1998
|
"path": "docs/copy-inventory.md",
|
|
1999
1999
|
"mode": "0000644",
|
|
2000
|
-
"sha256": "
|
|
2000
|
+
"sha256": "ab5057dfc9880545e8d2ac6ac44eb86af18f13a7efe1a071c9a3b9910a2ec5b6"
|
|
2001
2001
|
},
|
|
2002
2002
|
{
|
|
2003
2003
|
"path": "docs/copy-registry.json",
|
|
2004
2004
|
"mode": "0000644",
|
|
2005
|
-
"sha256": "
|
|
2005
|
+
"sha256": "8306b8b8748398081ddece28d92a5dd78b29facc39b55b394bb1b0877249264e"
|
|
2006
2006
|
},
|
|
2007
2007
|
{
|
|
2008
2008
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2527,7 +2527,7 @@
|
|
|
2527
2527
|
{
|
|
2528
2528
|
"path": "docs/module-api-changelog.md",
|
|
2529
2529
|
"mode": "0000644",
|
|
2530
|
-
"sha256": "
|
|
2530
|
+
"sha256": "393caf0055b05621fc26bb45600687b9f02f66d3efcb6774661001d93a6a1847"
|
|
2531
2531
|
},
|
|
2532
2532
|
{
|
|
2533
2533
|
"path": "docs/modules-contract.md",
|
|
@@ -4677,22 +4677,22 @@
|
|
|
4677
4677
|
{
|
|
4678
4678
|
"path": "modules/hall-ui/public/collab-lib.js",
|
|
4679
4679
|
"mode": "0000644",
|
|
4680
|
-
"sha256": "
|
|
4680
|
+
"sha256": "b5bb0d5f86879fe7fb154aaa819f2c5bceda50138f6c7cf9150bce2d0b51a832"
|
|
4681
4681
|
},
|
|
4682
4682
|
{
|
|
4683
4683
|
"path": "modules/hall-ui/public/collab.css",
|
|
4684
4684
|
"mode": "0000644",
|
|
4685
|
-
"sha256": "
|
|
4685
|
+
"sha256": "12c967c9bbed5fa4d1cb58b57f6f70eb90fee9626c7cad8e30e3a36b1f8c8db8"
|
|
4686
4686
|
},
|
|
4687
4687
|
{
|
|
4688
4688
|
"path": "modules/hall-ui/public/collab.html",
|
|
4689
4689
|
"mode": "0000644",
|
|
4690
|
-
"sha256": "
|
|
4690
|
+
"sha256": "664bbcc84bd92ed3762f5c74f265ac86f320995f02d0093c12758db6e8af35ed"
|
|
4691
4691
|
},
|
|
4692
4692
|
{
|
|
4693
4693
|
"path": "modules/hall-ui/public/collab.js",
|
|
4694
4694
|
"mode": "0000644",
|
|
4695
|
-
"sha256": "
|
|
4695
|
+
"sha256": "101a428f4b1bc76117eec665710265b201c32dbce76e2ddfd20e7e82b036faf7"
|
|
4696
4696
|
},
|
|
4697
4697
|
{
|
|
4698
4698
|
"path": "modules/hall-ui/public/collab.states.json",
|
|
@@ -7702,12 +7702,12 @@
|
|
|
7702
7702
|
{
|
|
7703
7703
|
"path": "package-lock.json",
|
|
7704
7704
|
"mode": "0000644",
|
|
7705
|
-
"sha256": "
|
|
7705
|
+
"sha256": "f93bb0db7f1d64f92d574b1c67095cb6e23f4101da20886678ac869477005630"
|
|
7706
7706
|
},
|
|
7707
7707
|
{
|
|
7708
7708
|
"path": "package.json",
|
|
7709
7709
|
"mode": "0000644",
|
|
7710
|
-
"sha256": "
|
|
7710
|
+
"sha256": "ddba830385dd1e004134ca3b317969fb69aa5772b607e2d650fae222915ec51e"
|
|
7711
7711
|
},
|
|
7712
7712
|
{
|
|
7713
7713
|
"path": "public-docs/index.html",
|
|
@@ -9587,7 +9587,7 @@
|
|
|
9587
9587
|
{
|
|
9588
9588
|
"path": "src/module-api.js",
|
|
9589
9589
|
"mode": "0000644",
|
|
9590
|
-
"sha256": "
|
|
9590
|
+
"sha256": "a7ef0a9cd6da08076cf17a8de8eae34b588fe51f05f0be618e9fd4e37951e214"
|
|
9591
9591
|
},
|
|
9592
9592
|
{
|
|
9593
9593
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11287,7 +11287,7 @@
|
|
|
11287
11287
|
{
|
|
11288
11288
|
"path": "tests/hall_collab_world.mjs",
|
|
11289
11289
|
"mode": "0000644",
|
|
11290
|
-
"sha256": "
|
|
11290
|
+
"sha256": "d005de58e0eabd019f616ee6c77123c5880b67034ce33dc1b01d687a2b284b10"
|
|
11291
11291
|
},
|
|
11292
11292
|
{
|
|
11293
11293
|
"path": "tests/hall_error_envelope.mjs",
|
package/docs/copy-inventory.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
> Deterministic on a given tree — regenerate and commit rather than hand-editing.
|
|
5
5
|
> Machine-readable twin: [`copy-registry.json`](copy-registry.json).
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**1795 strings** across 3 surfaces · 1551 distinct pieces of copy · 1795 certain, 0 heuristic · 169 shown in more than one place.
|
|
8
8
|
|
|
9
9
|
## Surfaces
|
|
10
10
|
|
|
11
11
|
| surface | lives at | files | strings |
|
|
12
12
|
|---|---|---|---|
|
|
13
|
-
| `builders-hall` | `modules/hall-ui/public` | 85 |
|
|
13
|
+
| `builders-hall` | `modules/hall-ui/public` | 85 | 1305 |
|
|
14
14
|
| `status-dashboard` | `modules/status-ui/public` | 4 | 100 |
|
|
15
15
|
| `landing` | `modules/public-landing/public` | 5 | 390 |
|
|
16
16
|
|
|
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
78
78
|
| `996edffc1966` | 2x | Access requests | modules/hall-ui/public/watch.html:46, modules/hall-ui/public/watch.html:61 |
|
|
79
79
|
| … | | _129 more in the JSON registry_ | |
|
|
80
80
|
|
|
81
|
-
## `builders-hall` —
|
|
81
|
+
## `builders-hall` — 1305 strings
|
|
82
82
|
|
|
83
83
|
| id | conf | text | where |
|
|
84
84
|
|---|---|---|---|
|
|
@@ -134,7 +134,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
134
134
|
| `a3185a43c230` | certain | A sound plays at each turn of your work — a gentle chime when Claude finishes, a flourish when a task ships or | `modules/hall-ui/public/settings.html:333` |
|
|
135
135
|
| `83b1274f9088` | certain | A tally of recorded work sessions, and the latest five. The full record lives on the Sessions page. | `modules/hall-ui/public/watch.html:103` |
|
|
136
136
|
| `daf73127afff` | certain | A word on why | `modules/hall-ui/public/hall-render.js:294` |
|
|
137
|
-
| `1b326b0be1a4` | certain | A {…} — you are a {…}. Mingle pairs across crafts. | `modules/hall-ui/public/collab.js:
|
|
137
|
+
| `1b326b0be1a4` | certain | A {…} — you are a {…}. Mingle pairs across crafts. | `modules/hall-ui/public/collab.js:365` |
|
|
138
138
|
| `996edffc1966` | certain | Access requests | `modules/hall-ui/public/watch.html:46` |
|
|
139
139
|
| `996edffc1966` | certain | Access requests | `modules/hall-ui/public/watch.html:61` |
|
|
140
140
|
| `303f8d90266f` | certain | Achievements could not be loaded. | `modules/hall-ui/public/builders.js:1158` |
|
|
@@ -173,7 +173,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
173
173
|
| `5fcfdf8975cf` | certain | As configuration: | `modules/hall-ui/public/government.js:829` |
|
|
174
174
|
| `01a0f29ff406` | certain | As first filed | `modules/hall-ui/public/idea.js:252` |
|
|
175
175
|
| `688cc6dd38ee` | certain | Asked of you | `modules/hall-ui/public/collab.html:41` |
|
|
176
|
-
| `b2656d0b2402` | certain | Asked {…} | `modules/hall-ui/public/collab.js:
|
|
176
|
+
| `b2656d0b2402` | certain | Asked {…} | `modules/hall-ui/public/collab.js:53` |
|
|
177
177
|
| `b2656d0b2402` | certain | Asked {…} | `modules/hall-ui/public/watch.js:488` |
|
|
178
178
|
| `8c8ec2597036` | certain | Assigned {…} to builder {…}. | `modules/hall-ui/public/government.js:463` |
|
|
179
179
|
| `46714066f2da` | certain | Audit log | `modules/hall-ui/public/watch.html:148` |
|
|
@@ -272,6 +272,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
272
272
|
| `12ee050d0180` | certain | Copy desk | `modules/hall-ui/public/copy-desk.html:27` |
|
|
273
273
|
| `1214ea8e0d1b` | certain | Copy desk — {{worldName}} | `modules/hall-ui/public/copy-desk.html:6` |
|
|
274
274
|
| `1214ea8e0d1b` | certain | Copy desk — {{worldName}} | `modules/hall-ui/public/copy-desk.html:6` |
|
|
275
|
+
| `30764a4f0e28` | certain | Copy for Session Start | `modules/hall-ui/public/collab.js:91` |
|
|
275
276
|
| `7cabea700b57` | certain | Copy not supported here — select the value and copy by hand. | `modules/hall-ui/public/box-panel.js:415` |
|
|
276
277
|
| `ebc8a6c16a22` | certain | Copy parallel prompt | `modules/hall-ui/public/work.html:146` |
|
|
277
278
|
| `a873e7c29d80` | certain | Copy prompt | `modules/hall-ui/public/task.js:614` |
|
|
@@ -304,7 +305,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
304
305
|
| `90691bceaf68` | certain | Could not load | `modules/hall-ui/public/task.js:707` |
|
|
305
306
|
| `589a4cfc5442` | certain | Could not load idea {…}. | `modules/hall-ui/public/ideas.js:644` |
|
|
306
307
|
| `192c893fc64e` | certain | Could not load modules. | `modules/hall-ui/public/modules.js:318` |
|
|
307
|
-
| `8a4b590fa1a0` | certain | Could not load requests. | `modules/hall-ui/public/collab.js:
|
|
308
|
+
| `8a4b590fa1a0` | certain | Could not load requests. | `modules/hall-ui/public/collab.js:458` |
|
|
308
309
|
| `7775fec0fa85` | certain | Could not load sessions. | `modules/hall-ui/public/sessions.js:275` |
|
|
309
310
|
| `7775fec0fa85` | certain | Could not load sessions. | `modules/hall-ui/public/sessions.js:313` |
|
|
310
311
|
| `b2147cc90e36` | certain | Could not load the idea template. Reload, or file from the terminal with capture.js. | `modules/hall-ui/public/ideas.js:626` |
|
|
@@ -659,7 +660,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
659
660
|
| `c72e0bdb834a` | certain | Not declared in module.json — this is who ships the module. | `modules/hall-ui/public/modules.js:62` |
|
|
660
661
|
| `361b353ded5e` | certain | Not enough shipped work yet to chart. | `modules/hall-ui/public/hall-widgets.js:184` |
|
|
661
662
|
| `90230572d017` | certain | Not found | `modules/hall-ui/public/task.js:695` |
|
|
662
|
-
| `8226c979db4d` | certain | Not now | `modules/hall-ui/public/collab.js:
|
|
663
|
+
| `8226c979db4d` | certain | Not now | `modules/hall-ui/public/collab.js:365` |
|
|
663
664
|
| `5ffcdc1a0176` | certain | Not scored — this builder’s own profile waived every signal, or the session predates this measure. | `modules/hall-ui/public/sessions.js:67` |
|
|
664
665
|
| `61250b49a90b` | certain | Not scored. | `modules/hall-ui/public/idea.js:84` |
|
|
665
666
|
| `8d6afe6def47` | certain | Not signed in | `modules/hall-ui/public/settings.html:40` |
|
|
@@ -765,6 +766,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
765
766
|
| `b03e17693dcd` | certain | Read it as a table | `modules/hall-ui/public/drachmae.js:148` |
|
|
766
767
|
| `b03e17693dcd` | certain | Read it as a table | `modules/hall-ui/public/watch.js:1167` |
|
|
767
768
|
| `b6fcfdd652a7` | certain | Read it here | `modules/hall-ui/public/work.html:45` |
|
|
769
|
+
| `053df132e22c` | certain | Read the full ask | `modules/hall-ui/public/collab.js:91` |
|
|
768
770
|
| `c5f19a888a15` | certain | Read the handoff notes | `modules/hall-ui/public/task.js:438` |
|
|
769
771
|
| `f9e998f4af3d` | certain | Read to the end and the button below unlocks. It is an honour system — nothing here checks your comprehension. | `modules/hall-ui/public/primer.html:100` |
|
|
770
772
|
| `26e6c9879035` | certain | Reading the diagrams | `modules/hall-ui/public/diagrams.html:39` |
|
|
@@ -828,7 +830,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
828
830
|
| `e8e4e49fe8f4` | certain | Saves when you press Save; shows everywhere at once. | `modules/hall-ui/public/settings.html:88` |
|
|
829
831
|
| `42cbc20a6ec8` | certain | Saves when you press Save; your next | `modules/hall-ui/public/settings.html:136` |
|
|
830
832
|
| `fc9d783ed0f5` | certain | Say why it is better. Whoever reviews this decides it from your reason, without asking you. | `modules/hall-ui/public/copy-desk.js:446` |
|
|
831
|
-
| `12c1ec21a2c6` | certain | Say yes | `modules/hall-ui/public/collab.js:
|
|
833
|
+
| `12c1ec21a2c6` | certain | Say yes | `modules/hall-ui/public/collab.js:365` |
|
|
832
834
|
| `46711f7bc3df` | certain | Search every string the product shows a person, by what it says or the file it lives in. | `modules/hall-ui/public/copy-desk.html:65` |
|
|
833
835
|
| `5d2ba8e72fa1` | certain | Search failed. | `modules/hall-ui/public/copy-desk.js:343` |
|
|
834
836
|
| `373a7639bcb9` | certain | Sections in this primer | `modules/hall-ui/public/primer.html:52` |
|
|
@@ -863,7 +865,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
863
865
|
| `ac44b08aec92` | certain | Ship it | `modules/hall-ui/public/task.js:600` |
|
|
864
866
|
| `b94a6b5b3d8c` | certain | Shipped with the core | `modules/hall-ui/public/modules.html:56` |
|
|
865
867
|
| `0c0a50ba1666` | certain | Shipped {…}{…}, {…}. | `modules/hall-ui/public/task.js:581` |
|
|
866
|
-
| `6dde12eb0c78` | certain | Show all {…} | `modules/hall-ui/public/collab.js:
|
|
868
|
+
| `6dde12eb0c78` | certain | Show all {…} | `modules/hall-ui/public/collab.js:245` |
|
|
867
869
|
| `14d32a35c15f` | certain | Show my cross-project record | `modules/hall-ui/public/settings.js:516` |
|
|
868
870
|
| `dc0f9fe50a65` | certain | Show the setup guide again | `modules/hall-ui/public/index.html:64` |
|
|
869
871
|
| `2af4c0f2b4b9` | certain | Showing {…} of {…}. | `modules/hall-ui/public/ideas.js:547` |
|
|
@@ -885,7 +887,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
885
887
|
| `87cee78ca134` | certain | Skip to content | `modules/hall-ui/public/shell.js:588` |
|
|
886
888
|
| `688ca7b6e30f` | certain | Slop — reads as machine-written | `modules/hall-ui/public/copy-desk.html:90` |
|
|
887
889
|
| `82df09a08476` | certain | So the Archon knows who you are | `modules/hall-ui/public/hall-render.js:294` |
|
|
888
|
-
| `7da6a848cd26` | certain | Someone helped | `modules/hall-ui/public/collab.js:
|
|
890
|
+
| `7da6a848cd26` | certain | Someone helped | `modules/hall-ui/public/collab.js:195` |
|
|
889
891
|
| `867df58c9aaa` | certain | Something else | `modules/hall-ui/public/copy-desk.html:95` |
|
|
890
892
|
| `c377d8ed744b` | certain | Something went wrong. Try again. | `modules/hall-ui/public/profile-connections.js:275` |
|
|
891
893
|
| `e0767e80206c` | certain | Sound for {…} | `modules/hall-ui/public/settings.js:311` |
|
package/docs/copy-registry.json
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"generator": "scripts/gds/copy-inventory.js",
|
|
4
4
|
"goal": 1000074,
|
|
5
5
|
"counts": {
|
|
6
|
-
"strings":
|
|
7
|
-
"unique_ids":
|
|
8
|
-
"certain":
|
|
6
|
+
"strings": 1795,
|
|
7
|
+
"unique_ids": 1551,
|
|
8
|
+
"certain": 1795,
|
|
9
9
|
"heuristic": 0,
|
|
10
10
|
"duplicate_ids": 169
|
|
11
11
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"builders-hall": {
|
|
14
14
|
"at": "modules/hall-ui/public",
|
|
15
15
|
"files": 85,
|
|
16
|
-
"strings":
|
|
16
|
+
"strings": 1305
|
|
17
17
|
},
|
|
18
18
|
"status-dashboard": {
|
|
19
19
|
"at": "modules/status-ui/public",
|
|
@@ -549,7 +549,7 @@
|
|
|
549
549
|
"text": "Asked {…}",
|
|
550
550
|
"occurrences": 2,
|
|
551
551
|
"at": [
|
|
552
|
-
"modules/hall-ui/public/collab.js:
|
|
552
|
+
"modules/hall-ui/public/collab.js:53",
|
|
553
553
|
"modules/hall-ui/public/watch.js:488"
|
|
554
554
|
]
|
|
555
555
|
},
|
|
@@ -2278,7 +2278,7 @@
|
|
|
2278
2278
|
"surface": "builders-hall",
|
|
2279
2279
|
"text": "A {…} — you are a {…}. Mingle pairs across crafts.",
|
|
2280
2280
|
"file": "modules/hall-ui/public/collab.js",
|
|
2281
|
-
"line":
|
|
2281
|
+
"line": 365,
|
|
2282
2282
|
"origin": "js-markup",
|
|
2283
2283
|
"confidence": "certain"
|
|
2284
2284
|
},
|
|
@@ -2629,7 +2629,7 @@
|
|
|
2629
2629
|
"surface": "builders-hall",
|
|
2630
2630
|
"text": "Asked {…}",
|
|
2631
2631
|
"file": "modules/hall-ui/public/collab.js",
|
|
2632
|
-
"line":
|
|
2632
|
+
"line": 53,
|
|
2633
2633
|
"origin": "js-markup",
|
|
2634
2634
|
"confidence": "certain"
|
|
2635
2635
|
},
|
|
@@ -3515,6 +3515,15 @@
|
|
|
3515
3515
|
"origin": "html-title",
|
|
3516
3516
|
"confidence": "certain"
|
|
3517
3517
|
},
|
|
3518
|
+
{
|
|
3519
|
+
"id": "30764a4f0e28",
|
|
3520
|
+
"surface": "builders-hall",
|
|
3521
|
+
"text": "Copy for Session Start",
|
|
3522
|
+
"file": "modules/hall-ui/public/collab.js",
|
|
3523
|
+
"line": 91,
|
|
3524
|
+
"origin": "js-markup",
|
|
3525
|
+
"confidence": "certain"
|
|
3526
|
+
},
|
|
3518
3527
|
{
|
|
3519
3528
|
"id": "7cabea700b57",
|
|
3520
3529
|
"surface": "builders-hall",
|
|
@@ -3808,7 +3817,7 @@
|
|
|
3808
3817
|
"surface": "builders-hall",
|
|
3809
3818
|
"text": "Could not load requests.",
|
|
3810
3819
|
"file": "modules/hall-ui/public/collab.js",
|
|
3811
|
-
"line":
|
|
3820
|
+
"line": 458,
|
|
3812
3821
|
"origin": "js-text-assign",
|
|
3813
3822
|
"confidence": "certain"
|
|
3814
3823
|
},
|
|
@@ -7003,7 +7012,7 @@
|
|
|
7003
7012
|
"surface": "builders-hall",
|
|
7004
7013
|
"text": "Not now",
|
|
7005
7014
|
"file": "modules/hall-ui/public/collab.js",
|
|
7006
|
-
"line":
|
|
7015
|
+
"line": 365,
|
|
7007
7016
|
"origin": "js-markup",
|
|
7008
7017
|
"confidence": "certain"
|
|
7009
7018
|
},
|
|
@@ -7952,6 +7961,15 @@
|
|
|
7952
7961
|
"origin": "html-text",
|
|
7953
7962
|
"confidence": "certain"
|
|
7954
7963
|
},
|
|
7964
|
+
{
|
|
7965
|
+
"id": "053df132e22c",
|
|
7966
|
+
"surface": "builders-hall",
|
|
7967
|
+
"text": "Read the full ask",
|
|
7968
|
+
"file": "modules/hall-ui/public/collab.js",
|
|
7969
|
+
"line": 91,
|
|
7970
|
+
"origin": "js-markup",
|
|
7971
|
+
"confidence": "certain"
|
|
7972
|
+
},
|
|
7955
7973
|
{
|
|
7956
7974
|
"id": "c5f19a888a15",
|
|
7957
7975
|
"surface": "builders-hall",
|
|
@@ -8524,7 +8542,7 @@
|
|
|
8524
8542
|
"surface": "builders-hall",
|
|
8525
8543
|
"text": "Say yes",
|
|
8526
8544
|
"file": "modules/hall-ui/public/collab.js",
|
|
8527
|
-
"line":
|
|
8545
|
+
"line": 365,
|
|
8528
8546
|
"origin": "js-markup",
|
|
8529
8547
|
"confidence": "certain"
|
|
8530
8548
|
},
|
|
@@ -8839,7 +8857,7 @@
|
|
|
8839
8857
|
"surface": "builders-hall",
|
|
8840
8858
|
"text": "Show all {…}",
|
|
8841
8859
|
"file": "modules/hall-ui/public/collab.js",
|
|
8842
|
-
"line":
|
|
8860
|
+
"line": 245,
|
|
8843
8861
|
"origin": "js-text-assign",
|
|
8844
8862
|
"confidence": "certain"
|
|
8845
8863
|
},
|
|
@@ -9037,7 +9055,7 @@
|
|
|
9037
9055
|
"surface": "builders-hall",
|
|
9038
9056
|
"text": "Someone helped",
|
|
9039
9057
|
"file": "modules/hall-ui/public/collab.js",
|
|
9040
|
-
"line":
|
|
9058
|
+
"line": 195,
|
|
9041
9059
|
"origin": "js-markup",
|
|
9042
9060
|
"confidence": "certain"
|
|
9043
9061
|
},
|
|
@@ -2064,5 +2064,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2064
2064
|
landed since 1.19.793 with no explicit bump. run 35167814500. (task 1002620)
|
|
2065
2065
|
1.19.795 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2066
2066
|
landed since 1.19.794 with no explicit bump. run 35168408728. (task 1002620)
|
|
2067
|
+
1.19.796 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2068
|
+
landed since 1.19.795 with no explicit bump. run 35169093667. (task 1002620)
|
|
2067
2069
|
---------------------------------------------------------------------------
|
|
2068
2070
|
```
|
|
@@ -120,6 +120,69 @@
|
|
|
120
120
|
return { title: m[1], rest: m[2].trim() };
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// THE CARD FACE IS TWO SENTENCES (owner direction, task 1003960). The
|
|
124
|
+
// ask-for-help skill asks a session to write up to 4000 characters — what is
|
|
125
|
+
// stuck, why this person, the steps, how to verify, what has been tried — and
|
|
126
|
+
// printing all of it on the face buried every row under whichever ask happened
|
|
127
|
+
// to be longest. So the face carries the ask's first sentence as the title and
|
|
128
|
+
// its second as the sub line, and everything else waits behind a door.
|
|
129
|
+
//
|
|
130
|
+
// WHAT IS BEHIND THE DOOR IS THE WHOLE ASK, not the leftover. A reader opening
|
|
131
|
+
// "the full ask" wants the document, and the copy control beside it hands that
|
|
132
|
+
// document to a fresh session verbatim — so `more` is the original text,
|
|
133
|
+
// untrimmed of its own opening, rather than a remainder that would copy as a
|
|
134
|
+
// sentence fragment with no beginning.
|
|
135
|
+
//
|
|
136
|
+
// A LINE BUDGET AS WELL AS A SENTENCE COUNT. Sentence-splitting alone is not
|
|
137
|
+
// enough: an ask written as one 4000-character paragraph with no full stop has
|
|
138
|
+
// exactly one sentence, and counting sentences would put all of it on the face.
|
|
139
|
+
// Each of the two lines is therefore also capped, and a line that had to be cut
|
|
140
|
+
// sends the whole ask behind the door.
|
|
141
|
+
var LEAD_MAX = 200;
|
|
142
|
+
|
|
143
|
+
// Trailing punctuation a cut can land on, hoisted so the literal is compiled
|
|
144
|
+
// once rather than per row per line.
|
|
145
|
+
var TRAILING_PUNCT = /[\s.,;:!?\u2013\u2014-]+$/;
|
|
146
|
+
|
|
147
|
+
// Cut at a word boundary, never mid-word, and mark the cut. The ellipsis is
|
|
148
|
+
// the only place this page abbreviates, and it is reached only when the asker
|
|
149
|
+
// wrote no sentence short enough to be a line — the full words are one click
|
|
150
|
+
// away either way.
|
|
151
|
+
function clipWords(s, max) {
|
|
152
|
+
var t = String(s == null ? '' : s);
|
|
153
|
+
if (t.length <= max) return { text: t, clipped: false };
|
|
154
|
+
var cut = t.slice(0, max);
|
|
155
|
+
var sp = cut.lastIndexOf(' ');
|
|
156
|
+
if (sp > max * 0.6) cut = cut.slice(0, sp);
|
|
157
|
+
return { text: cut.replace(TRAILING_PUNCT, '') + '\u2026', clipped: true };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// summaryOf(text) -> { title, lead, more }. `more` is '' when the ask already
|
|
161
|
+
// FITS on the face, which is not the same as "has two sentences or fewer". A
|
|
162
|
+
// door onto nothing is a lie about there being more to read; so is no door
|
|
163
|
+
// over words the reader cannot see.
|
|
164
|
+
//
|
|
165
|
+
// THE CASE THAT LOOKS LIKE A BUG AND IS NOT (grader, task 1003960): two
|
|
166
|
+
// sentences whose SECOND runs past LEAD_MAX gets a door, even though the ask
|
|
167
|
+
// has only two sentences. That is correct and deliberate. The lead was cut, so
|
|
168
|
+
// part of that second sentence is not on the face; hiding the door there would
|
|
169
|
+
// abbreviate an ask with no way to read the rest, which is the one thing this
|
|
170
|
+
// whole change exists to prevent. The door tracks WHAT IS UNREAD, never the
|
|
171
|
+
// sentence count — which is why `hidden` is `l.clipped || second.rest` and not
|
|
172
|
+
// `second.rest` alone.
|
|
173
|
+
function summaryOf(text) {
|
|
174
|
+
var full = String(text == null ? '' : text).trim();
|
|
175
|
+
var first = titleOf(full);
|
|
176
|
+
var t = clipWords(first.title, TITLE_MAX);
|
|
177
|
+
// No sentence short enough to be a headline: the face carries a lead-in only
|
|
178
|
+
// and the ask goes behind the door whole.
|
|
179
|
+
if (t.clipped) return { title: t.text, lead: '', more: full };
|
|
180
|
+
var second = first.rest ? titleOf(first.rest) : { title: '', rest: '' };
|
|
181
|
+
var l = clipWords(second.title, LEAD_MAX);
|
|
182
|
+
var hidden = l.clipped || !!second.rest;
|
|
183
|
+
return { title: t.text, lead: l.text, more: hidden ? full : '' };
|
|
184
|
+
}
|
|
185
|
+
|
|
123
186
|
// The "asked of you" list reads in runs: what is asked of you BY NAME first
|
|
124
187
|
// (a personal ask is the one you owe an answer), then one run per craft.
|
|
125
188
|
function groupOf(req) {
|
|
@@ -223,7 +286,7 @@
|
|
|
223
286
|
BANDS: BANDS, ageBand: ageBand, byAgeOldestFirst: byAgeOldestFirst, byNewestFirst: byNewestFirst,
|
|
224
287
|
askerOf: askerOf, contextOf: contextOf,
|
|
225
288
|
directionLine: directionLine, boardLine: boardLine,
|
|
226
|
-
titleOf: titleOf, groupOf: groupOf,
|
|
289
|
+
titleOf: titleOf, summaryOf: summaryOf, clipWords: clipWords, LEAD_MAX: LEAD_MAX, groupOf: groupOf,
|
|
227
290
|
httpUrl: httpUrl, initialOf: initialOf, faceOf: faceOf, askers: askers, splitShown: splitShown,
|
|
228
291
|
};
|
|
229
292
|
})();
|
|
@@ -120,6 +120,62 @@
|
|
|
120
120
|
.cb-band { grid-template-columns: 1fr; }
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/* ---- the door onto the whole ask -------------------------------------------
|
|
124
|
+
Owner direction, task 1003960: a card shows two sentences and no more. What
|
|
125
|
+
the asker actually wrote — up to four thousand characters of context, steps
|
|
126
|
+
and what they already ruled out — waits behind this pair of controls, which
|
|
127
|
+
sit together under the two visible lines.
|
|
128
|
+
|
|
129
|
+
TWO CONTROLS, TWO WEIGHTS. The door is a text action in the accent ink, like
|
|
130
|
+
every other door on this page; the copy control is a pill, because it does
|
|
131
|
+
something rather than going somewhere, and because a reader scanning the board
|
|
132
|
+
for "take this into a session" should find a button shape. It keeps its own
|
|
133
|
+
capitals — .btn-ghost lowercases its label, and this one is a named action the
|
|
134
|
+
owner wrote, so it does not borrow that class. */
|
|
135
|
+
.cb-ask { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 7px 0 0; }
|
|
136
|
+
.cb-ask__door {
|
|
137
|
+
font: inherit;
|
|
138
|
+
font-size: 12.5px;
|
|
139
|
+
color: var(--accent-ink);
|
|
140
|
+
background: none;
|
|
141
|
+
border: 0;
|
|
142
|
+
padding: 0;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
}
|
|
145
|
+
.cb-ask__door:hover { text-decoration: underline; }
|
|
146
|
+
.cb-ask__copy {
|
|
147
|
+
font: inherit;
|
|
148
|
+
font-size: 12px;
|
|
149
|
+
line-height: 1;
|
|
150
|
+
color: var(--ink-soft);
|
|
151
|
+
background: none;
|
|
152
|
+
border: 1px solid var(--rule);
|
|
153
|
+
border-radius: var(--radius-pill);
|
|
154
|
+
padding: 5px 11px;
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
}
|
|
157
|
+
.cb-ask__copy:hover { border-color: var(--ink-faint); color: var(--ink); }
|
|
158
|
+
|
|
159
|
+
/* The ask itself, opened in place. pre-wrap because an ask is written as a
|
|
160
|
+
briefing with its own paragraphs and steps, and collapsing them here would
|
|
161
|
+
make the page's own rendering less readable than the text a reader copies out
|
|
162
|
+
of it. Capped to the measure so it never becomes the wall of prose this
|
|
163
|
+
change exists to end. */
|
|
164
|
+
.cb-ask__full {
|
|
165
|
+
margin: 9px 0 0;
|
|
166
|
+
padding: 11px 13px;
|
|
167
|
+
background: var(--glass);
|
|
168
|
+
border-radius: var(--radius-sm);
|
|
169
|
+
white-space: pre-wrap;
|
|
170
|
+
font-size: 13px;
|
|
171
|
+
line-height: 1.6;
|
|
172
|
+
color: var(--ink-soft);
|
|
173
|
+
/* --measure is 48ch, and ch scales with the ELEMENT's font-size — spending it
|
|
174
|
+
at 13px would cap a briefing at 380px, well under a readable line. So this
|
|
175
|
+
one block states its own measure, computed at its own size. */
|
|
176
|
+
max-width: 62ch;
|
|
177
|
+
}
|
|
178
|
+
|
|
123
179
|
/* ---- empties and the foot -------------------------------------------------- */
|
|
124
180
|
.cb-empty p { font-size: 14px; }
|
|
125
181
|
.cb-foot { margin: var(--sp-sm) 0 0; font-size: 12.5px; color: var(--ink-faint); max-width: var(--measure); }
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
<script src="/builders/shell.js?v=2026-07-02-shell1"></script>
|
|
99
99
|
<script src="/builders/palette.js" defer></script>
|
|
100
100
|
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
101
|
-
<script src="/builders/collab-lib.js?v=2026-09-16-
|
|
102
|
-
<script src="/builders/collab.js?v=2026-09-16-
|
|
101
|
+
<script src="/builders/collab-lib.js?v=2026-09-16-twosentence"></script>
|
|
102
|
+
<script src="/builders/collab.js?v=2026-09-16-twosentence"></script>
|
|
103
103
|
</body>
|
|
104
104
|
</html>
|
|
@@ -13,11 +13,15 @@
|
|
|
13
13
|
//
|
|
14
14
|
// A row is the family's .ov-row--lead: the person's FACE first (collab-lib
|
|
15
15
|
// faceOf: the asker on what was asked of you and on the board, the addressee on
|
|
16
|
-
// your own asks), the ask's first
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
16
|
+
// your own asks), then the ask's first TWO sentences and no more (summaryOf),
|
|
17
|
+
// then the direction as a fact with a door to their profile, the task or goal as
|
|
18
|
+
// a chip, and the AGE as a state pill in the right column, so a glance down a
|
|
19
|
+
// list reads the ages in words and in the verification colours. Every rule that
|
|
20
|
+
// can be pure lives in collab-lib.js where a test can execute it.
|
|
21
|
+
//
|
|
22
|
+
// Under those two sentences, when there is more ask than fits: a door onto the
|
|
23
|
+
// whole thing and a control that copies it verbatim for a fresh session (task
|
|
24
|
+
// 1003960).
|
|
21
25
|
//
|
|
22
26
|
// MATCHING IS THE SERVER'S JOB, not this file's. GET /help-requests/for-me derives
|
|
23
27
|
// the viewer's crafts from their own profile; sending a craft from here would be
|
|
@@ -55,10 +59,40 @@
|
|
|
55
59
|
return `<span class="ov-row__when">${escapeHtml(d.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' }))}</span>`;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
// A DOM id per row that is stable, unique and safe to put in an attribute. The
|
|
63
|
+
// same request appears in up to three blocks at once, so the block's key is
|
|
64
|
+
// part of the id — without it "all open asks" and "your asks" would hand the
|
|
65
|
+
// same id to two elements and every aria-controls on the page would resolve to
|
|
66
|
+
// whichever painted first.
|
|
67
|
+
function askId(key, id) {
|
|
68
|
+
return `cb-ask-${key}-${String(id).replace(/[^A-Za-z0-9_-]/g, '')}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// THE FACE IS TWO SENTENCES, AND THE ASK ITSELF IS BEHIND A DOOR (owner
|
|
72
|
+
// direction, task 1003960: "the text here does not need to be visible, all we
|
|
73
|
+
// need is two sentences"). An ask is written to be answerable by someone with
|
|
74
|
+
// none of your context — the skill asks for what is stuck, why this person, the
|
|
75
|
+
// steps, how to verify and what was already tried, up to 4000 characters — so
|
|
76
|
+
// the words are worth keeping and were only ever at the wrong altitude.
|
|
77
|
+
//
|
|
78
|
+
// THE COPY CONTROL IS THE POINT OF THE DOOR, not an extra. What is behind it is
|
|
79
|
+
// a handoff: the reader's next move is usually to open a fresh session and
|
|
80
|
+
// paste the whole ask in as its opening context, and a page that made them
|
|
81
|
+
// expand, select and drag over four thousand characters would be asking them to
|
|
82
|
+
// do by hand the one thing it knows they came to do. It sits beside the door in
|
|
83
|
+
// the CLOSED state on purpose — taking the ask away does not require reading it
|
|
84
|
+
// here first.
|
|
85
|
+
function askHtml(req, key) {
|
|
86
|
+
const s = L.summaryOf(req.what_is_stuck);
|
|
87
|
+
let html = `<span class="ov-row__title">${escapeHtml(s.title)}</span>`;
|
|
88
|
+
if (s.lead) html += `<p class="ov-row__sub">${escapeHtml(s.lead)}</p>`;
|
|
89
|
+
if (!s.more) return html;
|
|
90
|
+
const fid = askId(key, req.id);
|
|
91
|
+
return html + `<div class="cb-ask">
|
|
92
|
+
<button class="cb-ask__door" type="button" data-ask-toggle aria-expanded="false" aria-controls="${fid}">Read the full ask</button>
|
|
93
|
+
<button class="cb-ask__copy" type="button" data-ask-copy="${fid}">Copy for Session Start</button>
|
|
94
|
+
</div>
|
|
95
|
+
<p class="cb-ask__full" id="${fid}" hidden>${escapeHtml(s.more)}</p>`;
|
|
62
96
|
}
|
|
63
97
|
|
|
64
98
|
function contextChip(req) {
|
|
@@ -142,11 +176,11 @@
|
|
|
142
176
|
// chat mirror). What the page owes a helper is the request, its age, its
|
|
143
177
|
// context, and who to answer — and their face. A by-name ask is the personal
|
|
144
178
|
// call on this reader, so its row is the LIT one (.cb-row--live).
|
|
145
|
-
function forMeRow(req, nowMs, extra) {
|
|
179
|
+
function forMeRow(req, nowMs, extra, key) {
|
|
146
180
|
const live = req.needs_builder_id != null ? ' cb-row--live' : '';
|
|
147
181
|
return `<li class="ov-row ov-row--lead cb-row${live}" data-id="${escapeHtml(String(req.id))}"${extra ? ' data-extra hidden' : ''}>
|
|
148
182
|
${faceHtml(L.faceOf(req, 'in'))}
|
|
149
|
-
<div class="ov-row__main">${
|
|
183
|
+
<div class="ov-row__main">${askHtml(req, key)}
|
|
150
184
|
<div class="ov-row__facts">${directionFact(req, 'in')}${contextChip(req)}</div>
|
|
151
185
|
</div>
|
|
152
186
|
<div class="ov-row__right">${ageChip(req, nowMs)}${whenLine(req.created_at)}</div>
|
|
@@ -156,11 +190,11 @@
|
|
|
156
190
|
// Your own ask, led by who you asked, with the two settle actions. Only the
|
|
157
191
|
// asker can settle — the server enforces it in the UPDATE's WHERE, this is
|
|
158
192
|
// just the affordance.
|
|
159
|
-
function mineRow(req, nowMs, extra) {
|
|
193
|
+
function mineRow(req, nowMs, extra, key) {
|
|
160
194
|
const id = escapeHtml(String(req.id));
|
|
161
195
|
return `<li class="ov-row ov-row--lead cb-row" data-id="${id}"${extra ? ' data-extra hidden' : ''}>
|
|
162
196
|
${faceHtml(L.faceOf(req, 'out'))}
|
|
163
|
-
<div class="ov-row__main">${
|
|
197
|
+
<div class="ov-row__main">${askHtml(req, key)}
|
|
164
198
|
<div class="ov-row__facts">${directionFact(req, 'out')}${contextChip(req)}</div>
|
|
165
199
|
</div>
|
|
166
200
|
<div class="ov-row__right">${ageChip(req, nowMs)}
|
|
@@ -174,10 +208,10 @@
|
|
|
174
208
|
|
|
175
209
|
// A row on the board of everyone's asks: the asker's face, the third-person
|
|
176
210
|
// line, no actions — settling is the asker's, in their own block.
|
|
177
|
-
function allRow(req, nowMs, extra) {
|
|
211
|
+
function allRow(req, nowMs, extra, key) {
|
|
178
212
|
return `<li class="ov-row ov-row--lead cb-row" data-id="${escapeHtml(String(req.id))}"${extra ? ' data-extra hidden' : ''}>
|
|
179
213
|
${faceHtml(L.faceOf(req, 'in'))}
|
|
180
|
-
<div class="ov-row__main">${
|
|
214
|
+
<div class="ov-row__main">${askHtml(req, key)}
|
|
181
215
|
<div class="ov-row__facts">${boardFact(req)}${contextChip(req)}</div>
|
|
182
216
|
</div>
|
|
183
217
|
<div class="ov-row__right">${ageChip(req, nowMs)}${whenLine(req.created_at)}</div>
|
|
@@ -195,8 +229,8 @@
|
|
|
195
229
|
const { shown, hidden } = L.splitShown(list, SHOWN);
|
|
196
230
|
const host = $(`#cb-${key}`);
|
|
197
231
|
if (host) {
|
|
198
|
-
host.innerHTML = shown.map((r) => rowFn(r, nowMs, false)).join('')
|
|
199
|
-
+ hidden.map((r) => rowFn(r, nowMs, true)).join('');
|
|
232
|
+
host.innerHTML = shown.map((r) => rowFn(r, nowMs, false, key)).join('')
|
|
233
|
+
+ hidden.map((r) => rowFn(r, nowMs, true, key)).join('');
|
|
200
234
|
}
|
|
201
235
|
const n = $(`#cb-${key}-n`);
|
|
202
236
|
if (n) n.textContent = list.length ? String(list.length) : '';
|
|
@@ -213,6 +247,59 @@
|
|
|
213
247
|
}
|
|
214
248
|
}
|
|
215
249
|
|
|
250
|
+
// The per-row door. Independent of the block's "Show all N", which flips
|
|
251
|
+
// [data-extra] on the <li>s: this flips one <p> inside one row, so opening a
|
|
252
|
+
// row inside a condensed block and then expanding the block cannot leave either
|
|
253
|
+
// control describing a state the page is not in.
|
|
254
|
+
function toggleAsk(btn) {
|
|
255
|
+
const full = document.getElementById(btn.getAttribute('aria-controls'));
|
|
256
|
+
if (!full) return;
|
|
257
|
+
const open = btn.getAttribute('aria-expanded') === 'true';
|
|
258
|
+
full.hidden = open;
|
|
259
|
+
btn.setAttribute('aria-expanded', open ? 'false' : 'true');
|
|
260
|
+
btn.textContent = open ? 'Read the full ask' : 'Hide the full ask';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Clipboard, with the hall's own fallback (copy-on-click.js's rule): the async
|
|
264
|
+
// API is absent outside a secure context, and a copy control that silently did
|
|
265
|
+
// nothing on a plain-http preview would be worse than no control at all — so
|
|
266
|
+
// the caller is always told which way it went.
|
|
267
|
+
function writeClipboard(text) {
|
|
268
|
+
if (navigator.clipboard && navigator.clipboard.writeText && window.isSecureContext) {
|
|
269
|
+
return navigator.clipboard.writeText(text).then(() => true, () => legacyCopy(text));
|
|
270
|
+
}
|
|
271
|
+
return Promise.resolve(legacyCopy(text));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function legacyCopy(text) {
|
|
275
|
+
try {
|
|
276
|
+
const ta = document.createElement('textarea');
|
|
277
|
+
ta.value = text;
|
|
278
|
+
ta.setAttribute('readonly', '');
|
|
279
|
+
ta.style.position = 'fixed';
|
|
280
|
+
ta.style.top = '-1000px';
|
|
281
|
+
ta.style.opacity = '0';
|
|
282
|
+
document.body.appendChild(ta);
|
|
283
|
+
ta.select();
|
|
284
|
+
const ok = document.execCommand('copy');
|
|
285
|
+
document.body.removeChild(ta);
|
|
286
|
+
return ok;
|
|
287
|
+
} catch (_) { return false; }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// textContent, not innerText: the hidden <p> holds the ask verbatim including
|
|
291
|
+
// its line breaks, and innerText on a hidden element is not guaranteed to
|
|
292
|
+
// return them. What lands on the clipboard is exactly what the asker wrote.
|
|
293
|
+
function copyAsk(btn) {
|
|
294
|
+
const full = document.getElementById(btn.getAttribute('data-ask-copy'));
|
|
295
|
+
if (!full) return;
|
|
296
|
+
const text = full.textContent || '';
|
|
297
|
+
if (!text) return;
|
|
298
|
+
writeClipboard(text).then((ok) => {
|
|
299
|
+
toast(ok ? 'Copied — paste it in to start a session on this.' : 'Could not copy — open the full ask and copy it by hand.');
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
216
303
|
function toggleMore(btn) {
|
|
217
304
|
const key = btn.id.replace(/^cb-|-more$/g, '');
|
|
218
305
|
const host = $(`#cb-${key}`);
|
|
@@ -350,6 +437,10 @@
|
|
|
350
437
|
const closest = (sel) => (ev.target && ev.target.closest ? ev.target.closest(sel) : null);
|
|
351
438
|
const more = closest('.cb-more');
|
|
352
439
|
if (more) { ev.preventDefault(); toggleMore(more); return; }
|
|
440
|
+
const door = closest('[data-ask-toggle]');
|
|
441
|
+
if (door) { ev.preventDefault(); toggleAsk(door); return; }
|
|
442
|
+
const copyBtn = closest('[data-ask-copy]');
|
|
443
|
+
if (copyBtn) { ev.preventDefault(); copyAsk(copyBtn); return; }
|
|
353
444
|
const mingleBtn = closest('[data-mingle]');
|
|
354
445
|
if (mingleBtn) {
|
|
355
446
|
ev.preventDefault();
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.796",
|
|
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.796",
|
|
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.796",
|
|
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.796'; // 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');
|
|
@@ -123,10 +123,121 @@ test('the first line is decided: the ask\'s first sentence is the title, the res
|
|
|
123
123
|
assert.equal(L.titleOf('docs/x.md is stale and y.js too').title, 'docs/x.md is stale and y.js too', 'a dot inside a path is not a sentence break');
|
|
124
124
|
const t = L.titleOf(' padded. and trailing ');
|
|
125
125
|
assert.equal(`${t.title} ${t.rest}`, 'padded. and trailing', 'nothing is clipped');
|
|
126
|
+
// titleOf is still the sentence splitter; what the page DOES with the tail
|
|
127
|
+
// changed in task 1003960 and is asserted in the next test.
|
|
128
|
+
assert.equal(typeof L.summaryOf, 'function', 'the page face is built on summaryOf now');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('a card face carries two sentences at most, and the whole ask waits behind a door', () => {
|
|
132
|
+
const L = lib();
|
|
133
|
+
|
|
134
|
+
// The common case: two short sentences fit, so there is nothing behind a door
|
|
135
|
+
// and the page must not offer one. A door onto nothing is a lie about there
|
|
136
|
+
// being more to read.
|
|
137
|
+
const fits = L.summaryOf('The pin will not move past 782. Can you look before I ship?');
|
|
138
|
+
assert.deepEqual(fits, {
|
|
139
|
+
title: 'The pin will not move past 782.',
|
|
140
|
+
lead: 'Can you look before I ship?',
|
|
141
|
+
more: '',
|
|
142
|
+
});
|
|
143
|
+
assert.equal(L.summaryOf('Can you look at the pin?').more, '', 'one sentence needs no door');
|
|
144
|
+
|
|
145
|
+
// The case the owner reported: a real ask, written the way the ask-for-help
|
|
146
|
+
// skill asks for it, is five parts long. Two reach the face; the rest does not.
|
|
147
|
+
const long = [
|
|
148
|
+
'The deploy pin is stuck at 1.19.782.',
|
|
149
|
+
'I need a Metic because the release gate is above my rank.',
|
|
150
|
+
'Run the release pipeline, then check /version reports 789.',
|
|
151
|
+
'Verify by loading the hall and reading the footer.',
|
|
152
|
+
'I already ruled out a stale CDN cache and a bad merge.',
|
|
153
|
+
].join(' ');
|
|
154
|
+
const s = L.summaryOf(long);
|
|
155
|
+
assert.equal(s.title, 'The deploy pin is stuck at 1.19.782.');
|
|
156
|
+
assert.equal(s.lead, 'I need a Metic because the release gate is above my rank.');
|
|
157
|
+
assert.ok(s.more, 'the rest goes behind the door');
|
|
158
|
+
assert.equal(s.more, long, 'and what is behind the door is the WHOLE ask, so a copy is not a fragment');
|
|
159
|
+
assert.ok(!s.title.includes('Verify by loading'), 'nothing past the second sentence is on the face');
|
|
160
|
+
|
|
161
|
+
// A sentence count alone is not enough: one 4000-character paragraph with no
|
|
162
|
+
// full stop is ONE sentence, and counting sentences would put all of it on the
|
|
163
|
+
// face. The line budget catches it, and the ask still goes behind the door whole.
|
|
164
|
+
const blob = 'x '.repeat(900).trim();
|
|
165
|
+
const b = L.summaryOf(blob);
|
|
166
|
+
assert.ok(b.title.length < 160, 'a blob is cut to a lead-in');
|
|
167
|
+
assert.ok(b.title.endsWith('\u2026'), 'and the cut is marked');
|
|
168
|
+
assert.equal(b.lead, '');
|
|
169
|
+
assert.equal(b.more, blob, 'the words are not lost, only moved');
|
|
170
|
+
|
|
171
|
+
// The cut never lands mid-word.
|
|
172
|
+
const words = 'antidisestablishmentarianism '.repeat(40).trim();
|
|
173
|
+
const w = L.summaryOf(words);
|
|
174
|
+
assert.ok(/(^|\s)antidisestablishmentarianism\u2026$/.test(w.title) || w.title.endsWith('antidisestablishmentarianism\u2026'),
|
|
175
|
+
'clipWords cuts at a space, never inside a word');
|
|
176
|
+
|
|
177
|
+
assert.deepEqual(L.summaryOf(''), { title: '', lead: '', more: '' }, 'an empty ask renders nothing, not a door');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('the door tracks what is UNREAD, not the sentence count', () => {
|
|
181
|
+
const L = lib();
|
|
182
|
+
|
|
183
|
+
// Raised by the grader on task 1003960 as a violation of "one or two sentences
|
|
184
|
+
// shows no expander". It is neither a violation nor a bug, and the rule it
|
|
185
|
+
// appeals to was the imprecise one: an ask earns a door when part of it is not
|
|
186
|
+
// on the face, and sentence two can be longer than the face on its own.
|
|
187
|
+
const twoButLong = 'The pin will not move. ' + 'I have tried '.repeat(30).trim() + '.';
|
|
188
|
+
const r = L.summaryOf(twoButLong);
|
|
189
|
+
assert.equal(r.title, 'The pin will not move.');
|
|
190
|
+
assert.ok(r.lead.endsWith('\u2026'), 'the second sentence did not fit, so it was cut');
|
|
191
|
+
assert.ok(r.lead.length < twoButLong.length, 'and part of it is therefore unread');
|
|
192
|
+
assert.equal(r.more, twoButLong,
|
|
193
|
+
'so it gets a door — hiding one here would abbreviate an ask with no way to read the rest');
|
|
194
|
+
|
|
195
|
+
// The mirror case, so the pair states the rule between them: two sentences
|
|
196
|
+
// that BOTH fit are fully on the face, so there is nothing to open.
|
|
197
|
+
assert.equal(L.summaryOf('The pin will not move. Can you look?').more, '',
|
|
198
|
+
'nothing unread means no door');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('the door and the copy control are on the row, and the copy control is reachable while it is shut', () => {
|
|
126
202
|
const js = read('collab.js');
|
|
127
|
-
assert.match(js, /L\.
|
|
128
|
-
assert.match(js, /class="ov-row__title">\$\{escapeHtml\(
|
|
129
|
-
assert.match(js, /class="ov-row__sub">\$\{escapeHtml\(
|
|
203
|
+
assert.match(js, /L\.summaryOf\(req\.what_is_stuck\)/, 'the face is built from summaryOf');
|
|
204
|
+
assert.match(js, /class="ov-row__title">\$\{escapeHtml\(s\.title\)\}/);
|
|
205
|
+
assert.match(js, /class="ov-row__sub">\$\{escapeHtml\(s\.lead\)\}/);
|
|
206
|
+
assert.match(js, /if \(!s\.more\) return html;/, 'no door when the ask already fits');
|
|
207
|
+
|
|
208
|
+
// The door announces its state and names what it controls, so it works from
|
|
209
|
+
// the keyboard and reads correctly to a screen reader.
|
|
210
|
+
assert.match(js, /data-ask-toggle aria-expanded="false" aria-controls="\$\{fid\}"/);
|
|
211
|
+
assert.match(js, /aria-expanded', open \? 'false' : 'true'/, 'and the state flips when it opens');
|
|
212
|
+
assert.match(js, /Read the full ask/);
|
|
213
|
+
assert.match(js, /Hide the full ask/);
|
|
214
|
+
|
|
215
|
+
// The copy control sits in the SAME block as the door, which is rendered
|
|
216
|
+
// whether or not the ask is open — the owner asked for it "in the closed
|
|
217
|
+
// version next to the link", and a control only reachable after expanding
|
|
218
|
+
// would make a reader open four thousand characters to take them away.
|
|
219
|
+
const bar = js.slice(js.indexOf('<div class="cb-ask">'), js.indexOf('</div>', js.indexOf('<div class="cb-ask">')));
|
|
220
|
+
assert.match(bar, /data-ask-toggle/, 'the door is in the bar');
|
|
221
|
+
assert.match(bar, /Copy for Session Start/, 'and so is the copy control, in the owner\'s words');
|
|
222
|
+
assert.ok(!/hidden/.test(bar), 'the bar itself is never hidden');
|
|
223
|
+
assert.match(js, /<p class="cb-ask__full" id="\$\{fid\}" hidden>/, 'only the ask is hidden');
|
|
224
|
+
|
|
225
|
+
// What lands on the clipboard is the ask verbatim, line breaks and all.
|
|
226
|
+
assert.match(js, /full\.textContent/, 'copied from textContent, so the line breaks survive');
|
|
227
|
+
assert.ok(!/full\.innerText/.test(js), 'innerText on a hidden element is not guaranteed to keep them');
|
|
228
|
+
assert.match(js, /document\.execCommand\('copy'\)/, 'with the hall\'s fallback for an insecure context');
|
|
229
|
+
|
|
230
|
+
// One request can appear in three blocks at once, so the id is block-scoped.
|
|
231
|
+
assert.match(js, /function askId\(key, id\)/);
|
|
232
|
+
assert.match(js, /cb-ask-\$\{key\}-/);
|
|
233
|
+
assert.match(js, /rowFn\(r, nowMs, false, key\)/, 'and paintBlock hands the key down');
|
|
234
|
+
|
|
235
|
+
const css = read('collab.css');
|
|
236
|
+
for (const cls of ['.cb-ask ', '.cb-ask__door', '.cb-ask__copy', '.cb-ask__full']) {
|
|
237
|
+
assert.ok(css.includes(cls), `collab.css dresses ${cls.trim()}`);
|
|
238
|
+
}
|
|
239
|
+
assert.match(css, /\.cb-ask__full[\s\S]*?white-space: pre-wrap/, 'the opened ask keeps its own paragraphs');
|
|
240
|
+
assert.ok(!/\.cb-ask__copy[^}]*text-transform/.test(css), 'the copy control keeps the capitals the owner wrote');
|
|
130
241
|
});
|
|
131
242
|
|
|
132
243
|
test('"asked of you" reads in runs — by name first, then each craft', () => {
|