@bongos/core 1.21.173 → 1.21.174
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 +38 -28
- package/docs/copy-inventory.md +4 -3
- package/docs/copy-registry.json +13 -4
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-readings.json +33 -32
- package/modules/copy-desk/routes/copy-desk.js +4 -4
- package/modules/hall-ui/public/city-decide.js +36 -2
- package/modules/hall-ui/public/city.css +8 -2
- package/modules/hall-ui/public/city.html +5 -5
- package/modules/lifecycle/artist-review-goal.js +103 -0
- package/modules/lifecycle/db-tasks.js +17 -1
- package/modules/lifecycle/db-versions.js +5 -1
- package/modules/lifecycle/page-tweak-claim.js +3 -3
- package/modules/lifecycle/page-tweak-write.js +12 -4
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +10 -0
- package/src/module-api.js +1 -1
- package/tests/artist_review_goal.mjs +119 -0
- package/tests/copy_desk_page_claim.mjs +6 -1
- package/tests/copy_desk_page_submit.mjs +1 -1
- package/tests/fixtures/page-ledger.mjs +13 -0
- package/tests/hall_city_rewording.mjs +11 -0
- package/tests/task_goal_required.mjs +12 -2
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.21.
|
|
6
|
-
"core_contract": "1.21.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.21.174",
|
|
6
|
+
"core_contract": "1.21.174",
|
|
7
|
+
"source_commit": "adfd7c27e23529361ee07bbaf4ef49077e4111d1",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-10-23T18:
|
|
9
|
+
"built_at": "2026-10-23T18:25:19.708Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 609,
|
|
13
13
|
"agent_docs_stubbed": 28,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 3196,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 3834,
|
|
20
|
+
"tree_sha256": "caab1743d1725c9a840734341335be349f8c978cf2d458f1ba9effab69975d30",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2412,12 +2412,12 @@
|
|
|
2412
2412
|
{
|
|
2413
2413
|
"path": "docs/copy-inventory.md",
|
|
2414
2414
|
"mode": "0000644",
|
|
2415
|
-
"sha256": "
|
|
2415
|
+
"sha256": "c75fc40e23e292f5f95de5218c44861ea53ef2d0f691afbd2e8c3383c3e77a09"
|
|
2416
2416
|
},
|
|
2417
2417
|
{
|
|
2418
2418
|
"path": "docs/copy-registry.json",
|
|
2419
2419
|
"mode": "0000644",
|
|
2420
|
-
"sha256": "
|
|
2420
|
+
"sha256": "16a9f7fc63e3c592067ed0286a9faaf52fc7c07fb88d0eb5b1e9ef966822b741"
|
|
2421
2421
|
},
|
|
2422
2422
|
{
|
|
2423
2423
|
"path": "docs/design/apex-pass-2-direction.md",
|
|
@@ -2927,7 +2927,7 @@
|
|
|
2927
2927
|
{
|
|
2928
2928
|
"path": "docs/module-api-changelog.md",
|
|
2929
2929
|
"mode": "0000644",
|
|
2930
|
-
"sha256": "
|
|
2930
|
+
"sha256": "8c7c1a18bb466eb86c4e6ed6d7b39b433111e99562b04f444671c455bb96ac62"
|
|
2931
2931
|
},
|
|
2932
2932
|
{
|
|
2933
2933
|
"path": "docs/modules-contract.md",
|
|
@@ -3017,7 +3017,7 @@
|
|
|
3017
3017
|
{
|
|
3018
3018
|
"path": "docs/page-readings.json",
|
|
3019
3019
|
"mode": "0000644",
|
|
3020
|
-
"sha256": "
|
|
3020
|
+
"sha256": "9ec7d9542b85b8232622692dbe007a852da947e6f674172b2aa51f961acf17d4"
|
|
3021
3021
|
},
|
|
3022
3022
|
{
|
|
3023
3023
|
"path": "docs/project-context.template.md",
|
|
@@ -4517,7 +4517,7 @@
|
|
|
4517
4517
|
{
|
|
4518
4518
|
"path": "modules/copy-desk/routes/copy-desk.js",
|
|
4519
4519
|
"mode": "0000644",
|
|
4520
|
-
"sha256": "
|
|
4520
|
+
"sha256": "c29c4015b09ed16ee0ae929077f077f4490b33e5a8055f9d331e097e1aabcf97"
|
|
4521
4521
|
},
|
|
4522
4522
|
{
|
|
4523
4523
|
"path": "modules/copy-desk/skills/tweak/SKILL.md",
|
|
@@ -5652,7 +5652,7 @@
|
|
|
5652
5652
|
{
|
|
5653
5653
|
"path": "modules/hall-ui/public/city-decide.js",
|
|
5654
5654
|
"mode": "0000644",
|
|
5655
|
-
"sha256": "
|
|
5655
|
+
"sha256": "de7085b284e4ed6bfd8f5703074934ec962697ae1d9cf9d7e31ec3ef81952fc2"
|
|
5656
5656
|
},
|
|
5657
5657
|
{
|
|
5658
5658
|
"path": "modules/hall-ui/public/city-draw.js",
|
|
@@ -5667,12 +5667,12 @@
|
|
|
5667
5667
|
{
|
|
5668
5668
|
"path": "modules/hall-ui/public/city.css",
|
|
5669
5669
|
"mode": "0000644",
|
|
5670
|
-
"sha256": "
|
|
5670
|
+
"sha256": "bc2b6e59862ada8af0fc125ccff4f875c916ca5624d46d5fb1493a8d849c154c"
|
|
5671
5671
|
},
|
|
5672
5672
|
{
|
|
5673
5673
|
"path": "modules/hall-ui/public/city.html",
|
|
5674
5674
|
"mode": "0000644",
|
|
5675
|
-
"sha256": "
|
|
5675
|
+
"sha256": "48d8eacaf02f0f6da2959da09f0d56245d718ef26efe2753012dee0f0b64bcb1"
|
|
5676
5676
|
},
|
|
5677
5677
|
{
|
|
5678
5678
|
"path": "modules/hall-ui/public/city.js",
|
|
@@ -7154,6 +7154,11 @@
|
|
|
7154
7154
|
"mode": "0000644",
|
|
7155
7155
|
"sha256": "e7c7db8a6faf50f3b75cb26ac33caa8357c462293e64318212ace69b0b654507"
|
|
7156
7156
|
},
|
|
7157
|
+
{
|
|
7158
|
+
"path": "modules/lifecycle/artist-review-goal.js",
|
|
7159
|
+
"mode": "0000644",
|
|
7160
|
+
"sha256": "e2e9318d76c337b6339b0685cd2585c9d58cd13c7ec1bac8186e614a60a31be4"
|
|
7161
|
+
},
|
|
7157
7162
|
{
|
|
7158
7163
|
"path": "modules/lifecycle/autopickup-files.js",
|
|
7159
7164
|
"mode": "0000644",
|
|
@@ -7292,12 +7297,12 @@
|
|
|
7292
7297
|
{
|
|
7293
7298
|
"path": "modules/lifecycle/db-tasks.js",
|
|
7294
7299
|
"mode": "0000644",
|
|
7295
|
-
"sha256": "
|
|
7300
|
+
"sha256": "29b4ced94d5fc5721dcb0c17ecbd9171b90e5444f0c526f3d8498f6e99b22fc6"
|
|
7296
7301
|
},
|
|
7297
7302
|
{
|
|
7298
7303
|
"path": "modules/lifecycle/db-versions.js",
|
|
7299
7304
|
"mode": "0000644",
|
|
7300
|
-
"sha256": "
|
|
7305
|
+
"sha256": "e5328df4be96e980fd22486f441f0f2a47abe5572a1b34ae47caed96965dadc9"
|
|
7301
7306
|
},
|
|
7302
7307
|
{
|
|
7303
7308
|
"path": "modules/lifecycle/db.js",
|
|
@@ -7577,7 +7582,7 @@
|
|
|
7577
7582
|
{
|
|
7578
7583
|
"path": "modules/lifecycle/page-tweak-claim.js",
|
|
7579
7584
|
"mode": "0000644",
|
|
7580
|
-
"sha256": "
|
|
7585
|
+
"sha256": "a3adde93c8bdef716f68aa09b9c60a81fa55f65520980d9686447db95647eecf"
|
|
7581
7586
|
},
|
|
7582
7587
|
{
|
|
7583
7588
|
"path": "modules/lifecycle/page-tweak-hold.js",
|
|
@@ -7592,7 +7597,7 @@
|
|
|
7592
7597
|
{
|
|
7593
7598
|
"path": "modules/lifecycle/page-tweak-write.js",
|
|
7594
7599
|
"mode": "0000644",
|
|
7595
|
-
"sha256": "
|
|
7600
|
+
"sha256": "c6b5cb868778d1043029c0f17640caa4e2124e13783319e33cbdb56b0135ba45"
|
|
7596
7601
|
},
|
|
7597
7602
|
{
|
|
7598
7603
|
"path": "modules/lifecycle/pg-constraint-fail.js",
|
|
@@ -10602,12 +10607,12 @@
|
|
|
10602
10607
|
{
|
|
10603
10608
|
"path": "package-lock.json",
|
|
10604
10609
|
"mode": "0000644",
|
|
10605
|
-
"sha256": "
|
|
10610
|
+
"sha256": "0a8aad85242c815483d937f464a70e0635a6f82f3e0bbd1d53e0007c4f391599"
|
|
10606
10611
|
},
|
|
10607
10612
|
{
|
|
10608
10613
|
"path": "package.json",
|
|
10609
10614
|
"mode": "0000644",
|
|
10610
|
-
"sha256": "
|
|
10615
|
+
"sha256": "d875bd84ad023a7e1a802a045ae88ef86e835f82ebd527db9ec1a82134a72e31"
|
|
10611
10616
|
},
|
|
10612
10617
|
{
|
|
10613
10618
|
"path": "public-docs/index.html",
|
|
@@ -10627,7 +10632,7 @@
|
|
|
10627
10632
|
{
|
|
10628
10633
|
"path": "release-notes.json",
|
|
10629
10634
|
"mode": "0000644",
|
|
10630
|
-
"sha256": "
|
|
10635
|
+
"sha256": "23c78c80dcbd8687b941dbae2186b92c9c5842b381dbbc7549cb8780b6cf7cb5"
|
|
10631
10636
|
},
|
|
10632
10637
|
{
|
|
10633
10638
|
"path": "scripts/cron/style_guide_audit.sh",
|
|
@@ -13017,7 +13022,7 @@
|
|
|
13017
13022
|
{
|
|
13018
13023
|
"path": "src/module-api.js",
|
|
13019
13024
|
"mode": "0000644",
|
|
13020
|
-
"sha256": "
|
|
13025
|
+
"sha256": "2a6426c050bd5aad4518c4ff3613fac041993deb0eebc6cab7d79184ff120159"
|
|
13021
13026
|
},
|
|
13022
13027
|
{
|
|
13023
13028
|
"path": "src/module-loader/catalog.js",
|
|
@@ -13374,6 +13379,11 @@
|
|
|
13374
13379
|
"mode": "0000644",
|
|
13375
13380
|
"sha256": "370b54d4e323e144ef8f9e382b873a4ff803b7727844c0e2d6b9e5cdfeb0fbe1"
|
|
13376
13381
|
},
|
|
13382
|
+
{
|
|
13383
|
+
"path": "tests/artist_review_goal.mjs",
|
|
13384
|
+
"mode": "0000644",
|
|
13385
|
+
"sha256": "ce9b9035338a7dbfb316277ac3d881bb7c36560f93d50dd811ea8f6b2ce2ec7c"
|
|
13386
|
+
},
|
|
13377
13387
|
{
|
|
13378
13388
|
"path": "tests/asset_version.mjs",
|
|
13379
13389
|
"mode": "0000644",
|
|
@@ -13992,7 +14002,7 @@
|
|
|
13992
14002
|
{
|
|
13993
14003
|
"path": "tests/copy_desk_page_claim.mjs",
|
|
13994
14004
|
"mode": "0000644",
|
|
13995
|
-
"sha256": "
|
|
14005
|
+
"sha256": "f8bf52b346895367eca47dc9b0d169439296f908c504e7b3dc25f3e76a62250b"
|
|
13996
14006
|
},
|
|
13997
14007
|
{
|
|
13998
14008
|
"path": "tests/copy_desk_page_docx.mjs",
|
|
@@ -14017,7 +14027,7 @@
|
|
|
14017
14027
|
{
|
|
14018
14028
|
"path": "tests/copy_desk_page_submit.mjs",
|
|
14019
14029
|
"mode": "0000644",
|
|
14020
|
-
"sha256": "
|
|
14030
|
+
"sha256": "c21159c1d8dcf82128120362232f72d4c7d6cf0bdc8726517788f5c6b18f5bd2"
|
|
14021
14031
|
},
|
|
14022
14032
|
{
|
|
14023
14033
|
"path": "tests/copy_guarded_lines.mjs",
|
|
@@ -14642,7 +14652,7 @@
|
|
|
14642
14652
|
{
|
|
14643
14653
|
"path": "tests/fixtures/page-ledger.mjs",
|
|
14644
14654
|
"mode": "0000644",
|
|
14645
|
-
"sha256": "
|
|
14655
|
+
"sha256": "b0a8f277883957ea314ad6efd16fc3109729726a75c28136bdd890c1570749b1"
|
|
14646
14656
|
},
|
|
14647
14657
|
{
|
|
14648
14658
|
"path": "tests/fixtures/session-506-digest.json",
|
|
@@ -15557,7 +15567,7 @@
|
|
|
15557
15567
|
{
|
|
15558
15568
|
"path": "tests/hall_city_rewording.mjs",
|
|
15559
15569
|
"mode": "0000644",
|
|
15560
|
-
"sha256": "
|
|
15570
|
+
"sha256": "88dc75df0b62ed23ff0aa110d6fa3eb362ccc05e8ac6c0999dcc064c315e6962"
|
|
15561
15571
|
},
|
|
15562
15572
|
{
|
|
15563
15573
|
"path": "tests/hall_city_switchboard.mjs",
|
|
@@ -18717,7 +18727,7 @@
|
|
|
18717
18727
|
{
|
|
18718
18728
|
"path": "tests/task_goal_required.mjs",
|
|
18719
18729
|
"mode": "0000644",
|
|
18720
|
-
"sha256": "
|
|
18730
|
+
"sha256": "5bf5deb47453f3ec5a64251137d731f5974c712ada11b9dbedde91469421c6a4"
|
|
18721
18731
|
},
|
|
18722
18732
|
{
|
|
18723
18733
|
"path": "tests/task_lib.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
|
+
**2797 strings** across 3 surfaces · 2378 distinct pieces of copy · 2797 certain, 0 heuristic · 243 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` | 150 |
|
|
13
|
+
| `builders-hall` | `modules/hall-ui/public` | 150 | 1975 |
|
|
14
14
|
| `status-dashboard` | `modules/status-ui/public` | 4 | 100 |
|
|
15
15
|
| `landing` | `modules/public-landing/public` | 9 | 722 |
|
|
16
16
|
|
|
@@ -78,7 +78,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
78
78
|
| `8226c979db4d` | 3x | Not now | modules/hall-ui/public/collab.js:668, modules/hall-ui/public/paintbrush.js:119, modules/hall-ui/public/paintbrush.js:183 |
|
|
79
79
|
| … | | _203 more in the JSON registry_ | |
|
|
80
80
|
|
|
81
|
-
## `builders-hall` —
|
|
81
|
+
## `builders-hall` — 1975 strings
|
|
82
82
|
|
|
83
83
|
| id | conf | text | where |
|
|
84
84
|
|---|---|---|---|
|
|
@@ -563,6 +563,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
563
563
|
| `d32c5e058d1e` | certain | From the system | `modules/hall-ui/public/hall-render.js:721` |
|
|
564
564
|
| `fc6fdfa639a7` | certain | Full log | `modules/hall-ui/public/task.js:464` |
|
|
565
565
|
| `11fc0483c149` | certain | Full screen — press Esc, or the button in the corner, to bring the bars back. | `modules/hall-ui/public/shell.js:595` |
|
|
566
|
+
| `7537fe1f3792` | certain | Full text | `modules/hall-ui/public/city-decide.js:253` |
|
|
566
567
|
| `ecae3916bf01` | certain | Gate — {{worldName}} | `modules/hall-ui/public/gate.html:6` |
|
|
567
568
|
| `ecae3916bf01` | certain | Gate — {{worldName}} | `modules/hall-ui/public/gate.html:6` |
|
|
568
569
|
| `c93912bbad76` | certain | Gathering the reviews and flagged copy that wait for you. | `modules/hall-ui/public/studio.html:172` |
|
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": 2797,
|
|
7
|
+
"unique_ids": 2378,
|
|
8
|
+
"certain": 2797,
|
|
9
9
|
"heuristic": 0,
|
|
10
10
|
"duplicate_ids": 243
|
|
11
11
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"builders-hall": {
|
|
14
14
|
"at": "modules/hall-ui/public",
|
|
15
15
|
"files": 150,
|
|
16
|
-
"strings":
|
|
16
|
+
"strings": 1975
|
|
17
17
|
},
|
|
18
18
|
"status-dashboard": {
|
|
19
19
|
"at": "modules/status-ui/public",
|
|
@@ -6975,6 +6975,15 @@
|
|
|
6975
6975
|
"origin": "js-text-assign",
|
|
6976
6976
|
"confidence": "certain"
|
|
6977
6977
|
},
|
|
6978
|
+
{
|
|
6979
|
+
"id": "7537fe1f3792",
|
|
6980
|
+
"surface": "builders-hall",
|
|
6981
|
+
"text": "Full text",
|
|
6982
|
+
"file": "modules/hall-ui/public/city-decide.js",
|
|
6983
|
+
"line": 253,
|
|
6984
|
+
"origin": "js-text-assign",
|
|
6985
|
+
"confidence": "certain"
|
|
6986
|
+
},
|
|
6978
6987
|
{
|
|
6979
6988
|
"id": "ecae3916bf01",
|
|
6980
6989
|
"surface": "builders-hall",
|
|
@@ -3183,5 +3183,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3183
3183
|
landed since 1.21.171 with no explicit bump. run 39998870016. (task 1002620)
|
|
3184
3184
|
1.21.173 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3185
3185
|
landed since 1.21.172 with no explicit bump. run 40000711332. (task 1002620)
|
|
3186
|
+
1.21.174 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3187
|
+
landed since 1.21.173 with no explicit bump. run 40003024305. (task 1002620)
|
|
3186
3188
|
---------------------------------------------------------------------------
|
|
3187
3189
|
```
|
package/docs/page-readings.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"adr": "0341 D3",
|
|
6
6
|
"reread": "node scripts/gds/page-reader.js --stale",
|
|
7
7
|
"how": ["Generated by node scripts/gds/page-reader.js (BV2.TW04, task 1004315; ADR 0341 D3). Do not hand-edit.","One entry per page of docs/page-inventory.json. Lines are in page order: the default state first, and a line only another state shows sits where that state shows it, with its state named.","placement: placed = a docs/copy-registry.json row renders it (file, line, string_id); shared = shell text every page of the surface carries (top bar, sidebar, footer), with its row when the registry has one; unplaced = the registry cannot place it (a rewrite becomes an engineer task, ADR 0341 D4).","text keeps {…} where the registry row has a hole, or where a value the page read from its API was filled in. A string that is only such data is counted under omitted.data, not listed.","reading_hash is over the non-shared lines (key, section, text); files_hash is over the page's inventory files. --check compares files_hash only and never opens a browser.","Rendered through the hall-preview harness (hall) and the ui-design kit stub (landing, status), at 1280x800 with the clock pinned, so a reading is the fixture world's page."],
|
|
8
|
-
"counts": {"pages":46,"lines":
|
|
8
|
+
"counts": {"pages":46,"lines":5405,"placed":1698,"shared":399,"unplaced":3308,"omitted_data":3498},
|
|
9
9
|
"pages": [
|
|
10
10
|
{
|
|
11
11
|
"id": "builders:atlas",
|
|
@@ -145,9 +145,9 @@
|
|
|
145
145
|
"surface": "builders",
|
|
146
146
|
"title": "The city",
|
|
147
147
|
"states_read": ["city","fullscreen","switched","focus","pins","living","card","card-ask"],
|
|
148
|
-
"files_hash": "
|
|
149
|
-
"reading_hash": "
|
|
150
|
-
"counts": {"lines":
|
|
148
|
+
"files_hash": "1e4c5ab40907996d",
|
|
149
|
+
"reading_hash": "77470c4eb3047a05",
|
|
150
|
+
"counts": {"lines":43,"placed":10,"shared":8,"unplaced":25},
|
|
151
151
|
"omitted": {"data":17},
|
|
152
152
|
"notes": [],
|
|
153
153
|
"lines": [
|
|
@@ -165,34 +165,35 @@
|
|
|
165
165
|
{"key":"L0012","section":"page top","text":"Total 1 /","placement":"unplaced","state":"card-ask"},
|
|
166
166
|
{"key":"L0013","section":"page top","text":"Yours 1","placement":"unplaced","state":"card-ask"},
|
|
167
167
|
{"key":"L0014","section":"{…}","text":"I'd {…} $2350. {…}","placement":"unplaced","state":"card-ask"},
|
|
168
|
-
{"key":"L0015","section":"{…}","text":"
|
|
169
|
-
{"key":"L0016","section":"{…}","text":"
|
|
170
|
-
{"key":"L0017","section":"{…}","text":"
|
|
171
|
-
{"key":"L0018","section":"{…}","text":"
|
|
172
|
-
{"key":"L0019","section":"{…}","text":"
|
|
173
|
-
{"key":"L0020","section":"
|
|
174
|
-
{"key":"L0021","section":"page top","text":"
|
|
175
|
-
{"key":"L0022","section":"
|
|
176
|
-
{"key":"L0023","section":"{…} of {…}","text":"
|
|
177
|
-
{"key":"L0024","section":"{…} of {…}","text":"
|
|
178
|
-
{"key":"L0025","section":"{…} of {…}","text":"
|
|
179
|
-
{"key":"L0026","section":"{…} of {…}","text":"
|
|
180
|
-
{"key":"L0027","section":"{…} of {…}","text":"
|
|
181
|
-
{"key":"L0028","section":"
|
|
182
|
-
{"key":"L0029","section":"
|
|
183
|
-
{"key":"L0030","section":"37 to do · 5 {…} working","text":"
|
|
184
|
-
{"key":"L0031","section":"37 to do · 5 {…} working","text":"
|
|
185
|
-
{"key":"L0032","section":"37 to do · 5 {…} working","text":"
|
|
186
|
-
{"key":"L0033","section":"37 to do · 5 {…} working","text":"
|
|
187
|
-
{"key":"L0034","section":"37 to do · 5 {…} working","text":"
|
|
188
|
-
{"key":"L0035","section":"
|
|
189
|
-
{"key":"L0036","section":"What are you doing now?","text":"
|
|
190
|
-
{"key":"L0037","section":"What are you doing now?","text":"
|
|
191
|
-
{"key":"L0038","section":"What are you doing now?","text":"
|
|
192
|
-
{"key":"L0039","section":"What are you doing now?","text":"
|
|
193
|
-
{"key":"L0040","section":"What are you doing now?","text":"
|
|
194
|
-
{"key":"L0041","section":"What are you doing now?","text":"
|
|
195
|
-
{"key":"L0042","section":"What are you doing now?","text":"
|
|
168
|
+
{"key":"L0015","section":"{…}","text":"Full text","placement":"placed","file":"modules/hall-ui/public/city-decide.js","line":253,"string_id":"7537fe1f3792","state":"card-ask"},
|
|
169
|
+
{"key":"L0016","section":"{…}","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","state":"card-ask","occurrences":2},
|
|
170
|
+
{"key":"L0017","section":"{…}","text":"my pick","placement":"unplaced","state":"card-ask"},
|
|
171
|
+
{"key":"L0018","section":"{…}","text":"{…} it","placement":"unplaced","state":"card-ask"},
|
|
172
|
+
{"key":"L0019","section":"{…}","text":"esc","placement":"unplaced","state":"card-ask"},
|
|
173
|
+
{"key":"L0020","section":"{…}","text":"{…} 37 in a row","placement":"unplaced","state":"card-ask"},
|
|
174
|
+
{"key":"L0021","section":"page top","text":"Total 9 /","placement":"unplaced","state":"card"},
|
|
175
|
+
{"key":"L0022","section":"page top","text":"Yours 9","placement":"unplaced","state":"card"},
|
|
176
|
+
{"key":"L0023","section":"{…} of {…}","text":"{…} of {…}","placement":"placed","file":"modules/hall-ui/public/sky-panel.js","line":120,"string_id":"73c1372b43d9","state":"card","occurrences":2},
|
|
177
|
+
{"key":"L0024","section":"{…} of {…}","text":"It holds work until someone decides.","placement":"unplaced","state":"card"},
|
|
178
|
+
{"key":"L0025","section":"{…} of {…}","text":"Tab · ↑ ↓ items ← → buildings","placement":"unplaced","state":"card"},
|
|
179
|
+
{"key":"L0026","section":"{…} of {…}","text":"{…} all 9","placement":"unplaced","state":"card"},
|
|
180
|
+
{"key":"L0027","section":"{…} of {…}","text":"esc","placement":"unplaced","state":"card"},
|
|
181
|
+
{"key":"L0028","section":"{…} of {…}","text":"{…} 37 in a row","placement":"unplaced","state":"card"},
|
|
182
|
+
{"key":"L0029","section":"page top","text":"{…} 37 in a row","placement":"unplaced"},
|
|
183
|
+
{"key":"L0030","section":"37 to do · 5 {…} working","text":"37 to do · 5 {…} working","placement":"unplaced"},
|
|
184
|
+
{"key":"L0031","section":"37 to do · 5 {…} working","text":"Archived:","placement":"unplaced"},
|
|
185
|
+
{"key":"L0032","section":"37 to do · 5 {…} working","text":"Street","placement":"unplaced"},
|
|
186
|
+
{"key":"L0033","section":"37 to do · 5 {…} working","text":"Building Level Views","placement":"placed","file":"modules/hall-ui/public/city.html","line":103,"string_id":"5df2aa54f042"},
|
|
187
|
+
{"key":"L0034","section":"37 to do · 5 {…} working","text":"Full screen — press Esc, or the button in the corner, to bring the bars back.","placement":"placed","file":"modules/hall-ui/public/shell.js","line":595,"string_id":"11fc0483c149","state":"fullscreen"},
|
|
188
|
+
{"key":"L0035","section":"37 to do · 5 {…} working","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":674,"string_id":"fd1903586e8e"},
|
|
189
|
+
{"key":"L0036","section":"What are you doing now?","text":"What are you doing now?","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":675,"string_id":"60853319f290"},
|
|
190
|
+
{"key":"L0037","section":"What are you doing now?","text":"Pick one. You can switch any time from the top bar.","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":676,"string_id":"4f87d5b17b54"},
|
|
191
|
+
{"key":"L0038","section":"What are you doing now?","text":"Building","placement":"unplaced"},
|
|
192
|
+
{"key":"L0039","section":"What are you doing now?","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":3},
|
|
193
|
+
{"key":"L0040","section":"What are you doing now?","text":"Artistry","placement":"unplaced"},
|
|
194
|
+
{"key":"L0041","section":"What are you doing now?","text":"Ideating","placement":"unplaced"},
|
|
195
|
+
{"key":"L0042","section":"What are you doing now?","text":"Turn thoughts into {…}.","placement":"unplaced"},
|
|
196
|
+
{"key":"L0043","section":"What are you doing now?","text":"Ask me again next session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":679,"string_id":"fa86bf403775"}
|
|
196
197
|
]
|
|
197
198
|
},
|
|
198
199
|
{
|
|
@@ -690,10 +690,10 @@ module.exports = function buildCopyDeskRouter() {
|
|
|
690
690
|
const body = proposals.proposalTaskBody(patch);
|
|
691
691
|
const task = await lifecycle.createTask({
|
|
692
692
|
versionId: building.id,
|
|
693
|
-
//
|
|
694
|
-
// goal
|
|
695
|
-
//
|
|
696
|
-
|
|
693
|
+
// task 1004819: a copy proposal is studio work, so it lands in the version's
|
|
694
|
+
// artist review goal (created on first use; the catch-all only when the
|
|
695
|
+
// version cannot take one) — see createTask.
|
|
696
|
+
artistReview: true,
|
|
697
697
|
title: proposals.proposalTaskTitle(patch),
|
|
698
698
|
description: flagRef
|
|
699
699
|
? `${body}\n\nRaised from copy-desk flag ${flagRef}.`
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
if (choice && choice.options && choice.options.length) {
|
|
47
47
|
var bare = a.key === 'decide' || a.key === 'answer';
|
|
48
48
|
choice.options.forEach(function (o) {
|
|
49
|
-
opts.push({ key: a.key + ':' + str(o.key), label: bare ?
|
|
49
|
+
opts.push({ key: a.key + ':' + str(o.key), label: (function (w) { return bare ? w : str(a.label) + ': ' + w; }(str(o.input && o.input.label) || str(o.label))), result: '', act: a.key, rec: false, plan: plan(o.input), off: false, why: '' });
|
|
50
50
|
});
|
|
51
51
|
} else {
|
|
52
52
|
opts.push({ key: a.key, label: str(a.label), result: '', act: a.key, rec: false, plan: plan({}), off: false, why: '' });
|
|
@@ -221,10 +221,44 @@
|
|
|
221
221
|
return b;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
function matterOf(docket, id) {
|
|
225
|
+
var ms = docket && Array.isArray(docket.matters) ? docket.matters : [];
|
|
226
|
+
for (var i = 0; i < ms.length; i++) if (ms[i] && ms[i].id === id) return ms[i];
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
// The matter's whole text, when it says more than the card already shows.
|
|
230
|
+
function fullText(matter, shown) {
|
|
231
|
+
if (!matter) return '';
|
|
232
|
+
var parts = [];
|
|
233
|
+
var title = str(matter.title).trim();
|
|
234
|
+
if (title && title !== str(shown.question).trim()) parts.push(title);
|
|
235
|
+
var detail = str((matter.ask && matter.ask.detail) || matter.detail).trim();
|
|
236
|
+
if (detail && detail !== title) parts.push(detail);
|
|
237
|
+
return parts.join('\n\n');
|
|
238
|
+
}
|
|
239
|
+
|
|
224
240
|
// The deck's card, answerable; a typed answer gets its own box and send.
|
|
225
241
|
function drawCard(doc, K, cv, on, o) {
|
|
226
242
|
var shown = answerable(cv);
|
|
243
|
+
var matter = matterOf(on.docket && on.docket(), cv.id);
|
|
244
|
+
// the question in full: a matter with no stored card is titled by its own words, cut to fit the deck
|
|
245
|
+
if (matter && /…$/.test(str(shown.question)) && matter.title) shown.question = str(matter.title).trim();
|
|
227
246
|
var art = K.drawAsk(doc, shown, on, o);
|
|
247
|
+
// and everything else it says, one tap away rather than cut
|
|
248
|
+
var full = fullText(matter, shown);
|
|
249
|
+
if (full) {
|
|
250
|
+
var more = doc.createElement('details');
|
|
251
|
+
more.className = 'ask-full';
|
|
252
|
+
var sum = doc.createElement('summary');
|
|
253
|
+
sum.textContent = 'Full text';
|
|
254
|
+
more.appendChild(sum);
|
|
255
|
+
var body = doc.createElement('p');
|
|
256
|
+
body.className = 'ask-full__text';
|
|
257
|
+
body.textContent = full;
|
|
258
|
+
more.appendChild(body);
|
|
259
|
+
var opts = art.querySelector ? art.querySelector('.ask__opts') : null;
|
|
260
|
+
if (opts && typeof art.insertBefore === 'function') art.insertBefore(more, opts); else art.appendChild(more);
|
|
261
|
+
}
|
|
228
262
|
// the crab: open this one in Claude to help decide it
|
|
229
263
|
var q = art.querySelector ? art.querySelector('.ask__q') : null;
|
|
230
264
|
if (q && q.parentNode && typeof q.parentNode.insertBefore === 'function') {
|
|
@@ -638,5 +672,5 @@
|
|
|
638
672
|
return { open: open, close: close, render: render, isOpen: function () { return st.open; } };
|
|
639
673
|
}
|
|
640
674
|
|
|
641
|
-
window.GovCityDecide = { make: make, answerable: answerable, drawCard: drawCard, flash: flash, armKey: armKey, rewordingIds: rewordingIds, rewordingCount: rewordingCount, withoutReworded: withoutReworded, paintArmed: paintArmed, promptOf: promptOf, CLAUDE_URL: CLAUDE_URL };
|
|
675
|
+
window.GovCityDecide = { make: make, answerable: answerable, drawCard: drawCard, flash: flash, fullText: fullText, armKey: armKey, rewordingIds: rewordingIds, rewordingCount: rewordingCount, withoutReworded: withoutReworded, paintArmed: paintArmed, promptOf: promptOf, CLAUDE_URL: CLAUDE_URL };
|
|
642
676
|
}());
|
|
@@ -394,8 +394,8 @@ html[data-fullscreen="on"] .city-top { right: 184px; }
|
|
|
394
394
|
.swb__row { display: flex; align-items: center; gap: 10px; min-height: 40px; border-top: 1px solid var(--rule-soft); padding-top: 6px; }
|
|
395
395
|
.swb__row:first-of-type { border-top: 0; padding-top: 0; }
|
|
396
396
|
.swb__label { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
397
|
-
.swb__name { font-size: 14px; color: var(--ink); overflow
|
|
398
|
-
.swb__fact { font-size: 12px; color: var(--ink-soft); overflow
|
|
397
|
+
.swb__name { font-size: 14px; line-height: 1.35; color: var(--ink); overflow-wrap: anywhere; }
|
|
398
|
+
.swb__fact { font-size: 12px; color: var(--ink-soft); overflow-wrap: anywhere; }
|
|
399
399
|
.swb__toggle { flex: none; position: relative; display: inline-flex; align-items: center; width: 64px; height: 30px; padding: 0 8px; box-sizing: border-box; border-radius: 999px; border: 1px solid var(--gov-chip-edge); background: var(--gov-chip-pick); color: var(--ink-soft); font-family: var(--font-body); font-size: 11.5px; cursor: pointer; transition: background 160ms ease; }
|
|
400
400
|
.swb__knob { position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%; background: var(--ink-soft); transition: left 160ms ease, background 160ms ease; }
|
|
401
401
|
.swb__state { margin-left: auto; }
|
|
@@ -451,3 +451,9 @@ html[data-fullscreen="on"] .city-top { right: 184px; }
|
|
|
451
451
|
.ask-reword__go:focus-visible, .ask-reword__no:focus-visible, .ask-reword__note:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
452
452
|
/* only drawn when something is out for a rewrite; a quiet footnote to the counts */
|
|
453
453
|
.city-card__rewording, .decide__rewording { display: block; margin-top: 1px; font-size: 10.5px; color: var(--ink-faint); text-align: right; }
|
|
454
|
+
|
|
455
|
+
/* a card's whole text, one tap away instead of cut (city-decide.js) */
|
|
456
|
+
.ask-full { font-size: 12.5px; color: var(--ink-soft); }
|
|
457
|
+
.ask-full summary { display: inline-flex; align-items: center; min-height: 24px; cursor: pointer; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--gov-chip-edge-pick); }
|
|
458
|
+
.ask-full__text { margin: 6px 0 0; max-height: 240px; overflow-y: auto; white-space: pre-wrap; line-height: 1.5; overflow-wrap: anywhere; }
|
|
459
|
+
.ask-full summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
(tests/hall_tokens.mjs: no hall page hardcodes a web-font stylesheet).
|
|
13
13
|
The prototype carried its own Google Fonts link for the same two faces. -->
|
|
14
14
|
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
15
|
-
<link rel="stylesheet" href="/builders/city.css?v=2026-10-23-
|
|
15
|
+
<link rel="stylesheet" href="/builders/city.css?v=2026-10-23-decide12">
|
|
16
16
|
<!-- the ask card in the glass language, shared with the street's deck (task 1004702) -->
|
|
17
17
|
<link rel="stylesheet" href="/builders/street-deck.css?v=2026-10-17-tidy">
|
|
18
18
|
</head>
|
|
@@ -115,14 +115,14 @@
|
|
|
115
115
|
<!-- a building's card answers its first ask in place through the deck's own
|
|
116
116
|
route (task 1004702): the desk rows (street-desk.js over the street's kit)
|
|
117
117
|
and the deck's card and sender (street-deck.js) -->
|
|
118
|
-
<script src="/builders/hall-kit.js?v=2026-10-23-
|
|
118
|
+
<script src="/builders/hall-kit.js?v=2026-10-23-decide12"></script>
|
|
119
119
|
<script src="/builders/street-draw.js?v=2026-10-09-living3"></script>
|
|
120
120
|
<script src="/builders/street-card.js?v=2026-10-04-record1"></script>
|
|
121
121
|
<script src="/builders/street-inside.js?v=2026-10-09-living3"></script>
|
|
122
122
|
<script src="/builders/street-desk.js?v=2026-10-04-record1"></script>
|
|
123
123
|
<script src="/builders/street-deck.js?v=2026-10-13-glass1"></script>
|
|
124
|
-
<script src="/builders/city-decide.js?v=2026-10-23-
|
|
125
|
-
<script src="/builders/city-switchboard.js?v=2026-10-23-
|
|
126
|
-
<script src="/builders/city.js?v=2026-10-23-
|
|
124
|
+
<script src="/builders/city-decide.js?v=2026-10-23-decide12"></script>
|
|
125
|
+
<script src="/builders/city-switchboard.js?v=2026-10-23-board5"></script>
|
|
126
|
+
<script src="/builders/city.js?v=2026-10-23-decide12"></script>
|
|
127
127
|
</body>
|
|
128
128
|
</html>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// modules/lifecycle/artist-review-goal.js — every version's ARTIST REVIEW goal
|
|
2
|
+
// (task 1004819). The home for copywriting and Artist Studio work: page tweak
|
|
3
|
+
// rounds, the engineer tasks a round leaves behind, and copy proposals.
|
|
4
|
+
//
|
|
5
|
+
// Owner direction (2026-10-23): all of that work sits under one ongoing goal, and
|
|
6
|
+
// every version carries one, with two standing done-when criteria (below). Before
|
|
7
|
+
// this the studio's create paths passed allowCatchAll, and a version with no
|
|
8
|
+
// "<version> — general" goal (BONGOS-V2) filed every round with no goal at all.
|
|
9
|
+
//
|
|
10
|
+
// ensureMaintenanceGoal's shape (db-goals.js), with two differences worth knowing:
|
|
11
|
+
// - it is matched by TITLE, not a flag. Nothing decides anything from this goal
|
|
12
|
+
// being the artist one except where new studio work lands, so a column (and a
|
|
13
|
+
// migration) would buy nothing a hand-titled goal could abuse;
|
|
14
|
+
// - it is NOT exempt from the version-close count. Its criteria are meant to
|
|
15
|
+
// hold the version open until every page has been reviewed.
|
|
16
|
+
// A 'planning' version takes one too, so createVersion can give a new version
|
|
17
|
+
// its goal at birth; a closed version never does.
|
|
18
|
+
//
|
|
19
|
+
// Runs on the caller's client (deps.client) when given, so the goal, its criteria
|
|
20
|
+
// and the task that needed them commit together or not at all; otherwise in a
|
|
21
|
+
// transaction of its own.
|
|
22
|
+
|
|
23
|
+
const api = require('../../src/module-api');
|
|
24
|
+
|
|
25
|
+
const { pool, withTx } = api;
|
|
26
|
+
|
|
27
|
+
const OPEN_TO_WORK = new Set(['planning', 'building']);
|
|
28
|
+
|
|
29
|
+
const ARTIST_REVIEW_CRITERIA = Object.freeze([
|
|
30
|
+
Object.freeze({
|
|
31
|
+
criterion_id: 'artist-every-page-reviewed',
|
|
32
|
+
criterion_md: 'Every page in the page inventory has had at least one artist review (a shipped tweak round) of its words before the version closes.',
|
|
33
|
+
}),
|
|
34
|
+
Object.freeze({
|
|
35
|
+
criterion_id: 'artist-pages-simple-to-use',
|
|
36
|
+
criterion_md: 'Every page is simple for a user to read and use: plain words, one clear next step, nothing a newcomer has to decode. An artist signs this off per page.',
|
|
37
|
+
}),
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
function artistReviewGoalTitle(versionId) {
|
|
41
|
+
return `${versionId} — artist review`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function ensureArtistReviewGoal(versionId, deps = {}) {
|
|
45
|
+
if (versionId == null) return null;
|
|
46
|
+
if (deps.client) return findOrCreate(versionId, deps.client);
|
|
47
|
+
// No caller transaction (a copy proposal): open one, or the advisory lock below
|
|
48
|
+
// would release the moment it is taken and two writers could both insert.
|
|
49
|
+
const txPool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
|
|
50
|
+
return withTx((client) => findOrCreate(versionId, client), { pool: txPool });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function findOrCreate(versionId, exec) {
|
|
54
|
+
const title = artistReviewGoalTitle(versionId);
|
|
55
|
+
|
|
56
|
+
const { rows: vRows } = await exec.query(`SELECT status FROM versions WHERE id = $1`, [versionId]);
|
|
57
|
+
if (!vRows[0] || !OPEN_TO_WORK.has(vRows[0].status)) return null;
|
|
58
|
+
|
|
59
|
+
// No unique constraint on (version_id, title), so two studio writes in the same
|
|
60
|
+
// second would both insert without this. Released at COMMIT/ROLLBACK.
|
|
61
|
+
await exec.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [title]);
|
|
62
|
+
|
|
63
|
+
const { rows: found } = await exec.query(
|
|
64
|
+
`SELECT id, version_id, title FROM goals WHERE version_id = $1 AND title = $2 ORDER BY id LIMIT 1`,
|
|
65
|
+
[versionId, title]
|
|
66
|
+
);
|
|
67
|
+
if (found[0]) {
|
|
68
|
+
// A goal of this title made by hand (POST /goals) before the first studio
|
|
69
|
+
// write still gets the two standing criteria; ones already there are kept.
|
|
70
|
+
await seedCriteria(exec, versionId, found[0].id);
|
|
71
|
+
return found[0];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const { rows: made } = await exec.query(
|
|
75
|
+
`INSERT INTO goals (version_id, title, description, scope_modules, status, created_by, sort_order)
|
|
76
|
+
SELECT $1, $2, $3, $4, 'open', NULL, COALESCE(MAX(sort_order) + 1, 0)
|
|
77
|
+
FROM goals WHERE version_id = $1
|
|
78
|
+
RETURNING id, version_id, title`,
|
|
79
|
+
[versionId, title,
|
|
80
|
+
'The ongoing home for copywriting and Artist Studio work in this version: page tweak rounds, ' +
|
|
81
|
+
'the engineer tasks they leave, and copy proposals. Every version carries one (task 1004819).',
|
|
82
|
+
[]]
|
|
83
|
+
);
|
|
84
|
+
const goal = made[0] || null;
|
|
85
|
+
if (!goal) return null;
|
|
86
|
+
await seedCriteria(exec, versionId, goal.id);
|
|
87
|
+
return goal;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function seedCriteria(exec, versionId, goalId) {
|
|
91
|
+
let order = 0;
|
|
92
|
+
for (const c of ARTIST_REVIEW_CRITERIA) {
|
|
93
|
+
order += 1;
|
|
94
|
+
await exec.query(
|
|
95
|
+
`INSERT INTO done_when_criteria (version_id, criterion_id, criterion_md, goal_id, sort_order)
|
|
96
|
+
SELECT $1, $2, $3, $4, COALESCE(MAX(sort_order), 0) + $5 FROM done_when_criteria WHERE version_id = $1
|
|
97
|
+
ON CONFLICT (version_id, criterion_id) DO NOTHING`,
|
|
98
|
+
[versionId, c.criterion_id, c.criterion_md, goalId, order]
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { ensureArtistReviewGoal, artistReviewGoalTitle, ARTIST_REVIEW_CRITERIA };
|
|
@@ -21,6 +21,7 @@ const { executorFor } = require('./db-executor.js');
|
|
|
21
21
|
const { isReservedCriterionSlug } = require('./criterion-ref.js');
|
|
22
22
|
const { resolveWriteDiscipline, WRITABLE_DISCIPLINES } = require('./task-classifier.js');
|
|
23
23
|
const { gateOnSpecTask } = require('./goal-spec-task.js');
|
|
24
|
+
const { ensureArtistReviewGoal } = require('./artist-review-goal.js');
|
|
24
25
|
|
|
25
26
|
// -------------------------------------------------------------------------
|
|
26
27
|
// Tasks
|
|
@@ -158,6 +159,11 @@ async function createTask({
|
|
|
158
159
|
// copy-desk. Grep for it before adding a third: the answer is almost always
|
|
159
160
|
// to name a goal instead.
|
|
160
161
|
allowCatchAll = false,
|
|
162
|
+
// task 1004819: copywriting / Artist Studio work. With no goalId it lands in
|
|
163
|
+
// the version's artist review goal (created on first use, artist-review-goal.js)
|
|
164
|
+
// rather than the catch-all; the catch-all is only the fallback for a version
|
|
165
|
+
// that cannot take one.
|
|
166
|
+
artistReview = false,
|
|
161
167
|
}, deps = {}) { // deps.client enlists this INSERT in the CALLER'S transaction (task 1003065, BV1.R01); the argument for it is at modules/ideas/inbox.js routeIdeaToGoal
|
|
162
168
|
// task 1677 / ADR 0096 (2026-06-28): creation is now the PRIMARY reward-assignment
|
|
163
169
|
// point — every task is born with a fair drachmae value, not just at promote/claim.
|
|
@@ -196,8 +202,18 @@ async function createTask({
|
|
|
196
202
|
// opt-ins can name a real goal. Owner's call, recorded here so the remaining
|
|
197
203
|
// gap is not mistaken for an oversight.
|
|
198
204
|
let goalIdEffective = goalId;
|
|
205
|
+
if (goalIdEffective == null && artistReview === true && !deps.client) {
|
|
206
|
+
// No caller transaction (a copy proposal): run the whole create in one, so the
|
|
207
|
+
// goal it may create and the task commit together or not at all.
|
|
208
|
+
return api.withTx((client) => createTask(arguments[0], { ...deps, client }),
|
|
209
|
+
{ pool: (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool });
|
|
210
|
+
}
|
|
211
|
+
if (goalIdEffective == null && artistReview === true) {
|
|
212
|
+
const artistGoal = await ensureArtistReviewGoal(versionId, { client: deps.client });
|
|
213
|
+
if (artistGoal) goalIdEffective = artistGoal.id;
|
|
214
|
+
}
|
|
199
215
|
if (goalIdEffective == null) {
|
|
200
|
-
if (allowCatchAll !== true) {
|
|
216
|
+
if (allowCatchAll !== true && artistReview !== true) {
|
|
201
217
|
throw new Error(
|
|
202
218
|
'createTask: goalId is required — every task belongs to a goal (BV1.R11). ' +
|
|
203
219
|
'A vector that genuinely cannot name one yet must pass allowCatchAll: true, ' +
|
|
@@ -20,6 +20,7 @@ const { executorFor, inTransaction } = require('./db-executor.js');
|
|
|
20
20
|
// (fitness pins the count). createVersion needs it to seed a version's FIRST goal
|
|
21
21
|
// in the same transaction — task 1003701.
|
|
22
22
|
const { createGoal } = require('./db-goals.js');
|
|
23
|
+
const { ensureArtistReviewGoal } = require('./artist-review-goal.js');
|
|
23
24
|
const { cancelPendingMembershipRequests } = require('./goal-close-sweep.js');
|
|
24
25
|
|
|
25
26
|
async function listVersions() {
|
|
@@ -214,7 +215,10 @@ async function createVersion({ id, name, status = 'planning', scopeDocPath = nul
|
|
|
214
215
|
if (cr[0]) createdCriteria.push(cr[0]);
|
|
215
216
|
i++;
|
|
216
217
|
}
|
|
217
|
-
|
|
218
|
+
// task 1004819: every version is born with its artist review goal and its two
|
|
219
|
+
// standing criteria, on the same client, so they roll back with the version.
|
|
220
|
+
const artistReviewGoal = await ensureArtistReviewGoal(id, { client });
|
|
221
|
+
return { version, goal: goal || null, criteria: createdCriteria, artistReviewGoal };
|
|
218
222
|
}, { pool: txPool });
|
|
219
223
|
}
|
|
220
224
|
|
|
@@ -156,9 +156,9 @@ async function webClaimPageTweak({ pageId, builderId, versionId = null, round },
|
|
|
156
156
|
const n = Math.max(rounds.length, ...rounds.map((t) => roundNumber(t.source_ref))) + 1;
|
|
157
157
|
const task = await createTask({
|
|
158
158
|
versionId,
|
|
159
|
-
// ADR 0341 D2: a round lands in the building version's
|
|
160
|
-
// exactly as a copy proposal does.
|
|
161
|
-
|
|
159
|
+
// ADR 0341 D2, task 1004819: a round lands in the building version's
|
|
160
|
+
// artist review goal, exactly as a copy proposal does.
|
|
161
|
+
artistReview: true,
|
|
162
162
|
title: round.title(n),
|
|
163
163
|
description: round.description(n),
|
|
164
164
|
status: 'ready',
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
'use strict';
|
|
36
36
|
|
|
37
37
|
const api = require('../../src/module-api');
|
|
38
|
+
const { ensureArtistReviewGoal } = require('./artist-review-goal.js');
|
|
38
39
|
const db = require('./db');
|
|
39
40
|
|
|
40
41
|
function activePoolFor(deps) {
|
|
@@ -139,14 +140,21 @@ async function submitPageTweak({ taskId, builderId, versionId = null, freeze },
|
|
|
139
140
|
);
|
|
140
141
|
for (const f of found) if (!existing.has(f.source_ref)) existing.set(f.source_ref, String(f.id));
|
|
141
142
|
}
|
|
143
|
+
// The version's artist review goal, resolved ONCE for the whole loop (task
|
|
144
|
+
// 1004819) rather than once per line: the version cannot change mid-submit.
|
|
145
|
+
const roundVersionId = versionId != null ? versionId : held.task.version_id;
|
|
146
|
+
const artistGoal = wanted.some((u) => !existing.has(u.sourceRef))
|
|
147
|
+
? await ensureArtistReviewGoal(roundVersionId, { client }) : null;
|
|
142
148
|
const filed = [];
|
|
143
149
|
for (const u of wanted) {
|
|
144
150
|
if (existing.has(u.sourceRef)) { filed.push({ id: existing.get(u.sourceRef), key: u.key, created: false }); continue; }
|
|
145
151
|
const t = await createTask({
|
|
146
|
-
versionId:
|
|
147
|
-
// The
|
|
148
|
-
//
|
|
149
|
-
|
|
152
|
+
versionId: roundVersionId,
|
|
153
|
+
// The version's artist review goal (task 1004819), exactly as the
|
|
154
|
+
// round itself and a copy proposal land; artistReview is the fallback
|
|
155
|
+
// should the goal above not resolve.
|
|
156
|
+
goalId: artistGoal ? artistGoal.id : null,
|
|
157
|
+
artistReview: true,
|
|
150
158
|
title: u.title,
|
|
151
159
|
description: u.description,
|
|
152
160
|
// BACKLOG (ADR 0341 D4): an engineer's triage decides when, and the
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.174",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.174",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.174",
|
|
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/release-notes.json
CHANGED
|
@@ -10120,5 +10120,15 @@
|
|
|
10120
10120
|
"id": "1003808",
|
|
10121
10121
|
"text": "The module store now ranks the best-scoring modules first, uses price only to break ties (free first), and lets buyers hide anything above a price they set."
|
|
10122
10122
|
}
|
|
10123
|
+
],
|
|
10124
|
+
"1.21.174": [
|
|
10125
|
+
{
|
|
10126
|
+
"id": "1004791",
|
|
10127
|
+
"text": "The city's cards no longer cut off their words. Long questions show in full, answer buttons say their whole answer, and a \"Full text\" link on each card opens everything the item says when there is more to read. Long goal names"
|
|
10128
|
+
},
|
|
10129
|
+
{
|
|
10130
|
+
"id": "1004819",
|
|
10131
|
+
"text": "Every version now has an artist review goal, holding all copywriting and Artist Studio work, with two standing checks: every page reviewed by an artist before the version closes, and every page simple to read and use. New page"
|
|
10132
|
+
}
|
|
10123
10133
|
]
|
|
10124
10134
|
}
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.21.
|
|
78
|
+
const CORE_VERSION = '1.21.174'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// tests/artist_review_goal.mjs
|
|
2
|
+
//
|
|
3
|
+
// Every version's ARTIST REVIEW goal (task 1004819): the find-or-create behind
|
|
4
|
+
// createTask's `artistReview` switch and createVersion. DB-free: a recording fake
|
|
5
|
+
// client stands in for Postgres, and the statement order is the contract — the
|
|
6
|
+
// version gate before anything else, the advisory lock before the read, and the
|
|
7
|
+
// two standing criteria written only when the goal is new.
|
|
8
|
+
//
|
|
9
|
+
// Run: node tests/artist_review_goal.mjs
|
|
10
|
+
|
|
11
|
+
import { strict as assert } from 'node:assert';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import { makeRunner } from './helpers.mjs';
|
|
14
|
+
|
|
15
|
+
process.env.NODE_ENV = 'test';
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const { ensureArtistReviewGoal, artistReviewGoalTitle, ARTIST_REVIEW_CRITERIA } = require('../modules/lifecycle/artist-review-goal.js');
|
|
19
|
+
const { createTask } = require('../modules/lifecycle/db.js');
|
|
20
|
+
|
|
21
|
+
const { test, summary } = makeRunner();
|
|
22
|
+
const norm = (s) => String(s).replace(/\s+/g, ' ').trim();
|
|
23
|
+
|
|
24
|
+
// One version, at most one goal: the fake answers like the tables would.
|
|
25
|
+
function fakeClient({ status = 'building', goal = null } = {}) {
|
|
26
|
+
const queries = [];
|
|
27
|
+
const state = { goal, criteria: [], tasks: [] };
|
|
28
|
+
const client = {
|
|
29
|
+
queries, state,
|
|
30
|
+
async query(sql, params = []) {
|
|
31
|
+
const q = norm(sql);
|
|
32
|
+
queries.push({ sql: q, params });
|
|
33
|
+
if (/^(BEGIN|COMMIT|ROLLBACK)$/.test(q)) return { rows: [] };
|
|
34
|
+
if (/^SELECT status FROM versions WHERE id = \$1/.test(q)) return { rows: status ? [{ status }] : [] };
|
|
35
|
+
if (/pg_advisory_xact_lock/.test(q)) return { rows: [] };
|
|
36
|
+
if (/^SELECT id, version_id, title FROM goals WHERE version_id = \$1 AND title = \$2/.test(q)) {
|
|
37
|
+
return { rows: state.goal && state.goal.title === params[1] ? [state.goal] : [] };
|
|
38
|
+
}
|
|
39
|
+
if (/^INSERT INTO goals/.test(q)) { state.goal = { id: 91, version_id: params[0], title: params[1] }; return { rows: [state.goal] }; }
|
|
40
|
+
if (/^INSERT INTO done_when_criteria/.test(q)) { state.criteria.push({ id: params[1], goal: params[3] }); return { rows: [] }; }
|
|
41
|
+
if (/^SELECT scope_modules FROM goals WHERE id = \$1/.test(q)) return { rows: [{ scope_modules: [] }] };
|
|
42
|
+
if (/^INSERT INTO tasks/.test(q)) { const t = { id: 1, goal_id: params[22] }; state.tasks.push(t); return { rows: [t] }; }
|
|
43
|
+
return { rows: [] };
|
|
44
|
+
},
|
|
45
|
+
release() {},
|
|
46
|
+
};
|
|
47
|
+
return client;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await test('the title is the version plus " — artist review" (em dash)', () => {
|
|
51
|
+
assert.equal(artistReviewGoalTitle('BONGOS-V3'), 'BONGOS-V3 — artist review');
|
|
52
|
+
assert.deepEqual(ARTIST_REVIEW_CRITERIA.map((c) => c.criterion_id), ['artist-every-page-reviewed', 'artist-pages-simple-to-use']);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await test('a new goal is made with its two criteria, after the version gate and the lock', async () => {
|
|
56
|
+
const c = fakeClient();
|
|
57
|
+
const g = await ensureArtistReviewGoal('BONGOS-V3', { client: c });
|
|
58
|
+
assert.equal(g.title, 'BONGOS-V3 — artist review');
|
|
59
|
+
const order = c.queries.map((q) => q.sql.split(' ').slice(0, 3).join(' '));
|
|
60
|
+
const at = (re) => c.queries.findIndex((q) => re.test(q.sql));
|
|
61
|
+
assert.ok(at(/FROM versions/) < at(/pg_advisory_xact_lock/), 'the version is checked first');
|
|
62
|
+
assert.ok(at(/pg_advisory_xact_lock/) < at(/FROM goals WHERE version_id/), 'the lock is taken before the read');
|
|
63
|
+
assert.equal(c.queries.find((q) => /pg_advisory_xact_lock/.test(q.sql)).params[0], 'BONGOS-V3 — artist review', 'locked on the title');
|
|
64
|
+
assert.deepEqual(c.state.criteria, [{ id: 'artist-every-page-reviewed', goal: 91 }, { id: 'artist-pages-simple-to-use', goal: 91 }]);
|
|
65
|
+
assert.ok(/ON CONFLICT \(version_id, criterion_id\) DO NOTHING/.test(c.queries.find((q) => /INSERT INTO done_when_criteria/.test(q.sql)).sql),
|
|
66
|
+
'a criterion already on the version is left alone', order);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await test('an existing goal is reused, never duplicated, and still carries the two criteria', async () => {
|
|
70
|
+
// A goal of this title made by hand before the first studio write is found,
|
|
71
|
+
// and its criteria are seeded (ON CONFLICT DO NOTHING keeps any already there).
|
|
72
|
+
const c = fakeClient({ goal: { id: 1000134, version_id: 'BONGOS-V2', title: 'BONGOS-V2 — artist review' } });
|
|
73
|
+
const g = await ensureArtistReviewGoal('BONGOS-V2', { client: c });
|
|
74
|
+
assert.equal(g.id, 1000134);
|
|
75
|
+
assert.equal(c.queries.some((q) => /^INSERT INTO goals/.test(q.sql)), false, 'no second goal');
|
|
76
|
+
assert.deepEqual(c.state.criteria, [{ id: 'artist-every-page-reviewed', goal: 1000134 }, { id: 'artist-pages-simple-to-use', goal: 1000134 }]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await test('a closed or missing version gets no goal, and nothing is locked or written', async () => {
|
|
80
|
+
for (const status of ['shipped', null]) {
|
|
81
|
+
const c = fakeClient({ status });
|
|
82
|
+
assert.equal(await ensureArtistReviewGoal('V0', { client: c }), null);
|
|
83
|
+
assert.equal(c.queries.length, 1, 'only the version read ran');
|
|
84
|
+
}
|
|
85
|
+
const planning = fakeClient({ status: 'planning' });
|
|
86
|
+
assert.ok(await ensureArtistReviewGoal('V4', { client: planning }), 'a planning version takes one, so a new version is born with it');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await test('with no caller transaction it opens its own, so the lock means something', async () => {
|
|
90
|
+
const c = fakeClient();
|
|
91
|
+
const pool = { connect: async () => c, query: (...a) => c.query(...a) };
|
|
92
|
+
await ensureArtistReviewGoal('BONGOS-V3', { pool });
|
|
93
|
+
const sqls = c.queries.map((q) => q.sql);
|
|
94
|
+
assert.equal(sqls[0], 'BEGIN');
|
|
95
|
+
assert.equal(sqls.at(-1), 'COMMIT');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await test('createTask with artistReview lands in the goal; without one the catch-all still answers', async () => {
|
|
99
|
+
const c = fakeClient();
|
|
100
|
+
await createTask({ versionId: 'BONGOS-V3', title: 'Tweak: Home (builders:home)', discipline: 'artist', artistReview: true }, { client: c });
|
|
101
|
+
assert.equal(c.state.tasks[0].goal_id, 91, 'the studio task names the artist review goal');
|
|
102
|
+
await assert.rejects(
|
|
103
|
+
() => createTask({ versionId: 'BONGOS-V3', title: 'plain', discipline: 'engineer' }, { client: fakeClient() }),
|
|
104
|
+
/goalId is required/, 'an ordinary goal-less create still throws');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await test('a create with no caller transaction (a copy proposal) puts goal and task in ONE transaction', async () => {
|
|
108
|
+
const c = fakeClient();
|
|
109
|
+
const pool = { connect: async () => c, query: () => { throw new Error('nothing may run outside the transaction'); } };
|
|
110
|
+
await createTask({ versionId: 'BONGOS-V3', title: 'Reword the Home heading', discipline: 'artist', artistReview: true }, { pool });
|
|
111
|
+
const sqls = c.queries.map((q) => q.sql);
|
|
112
|
+
const begins = sqls.filter((q) => q === 'BEGIN').length;
|
|
113
|
+
assert.equal(begins, 1, 'exactly one transaction');
|
|
114
|
+
assert.ok(sqls.indexOf('BEGIN') < sqls.findIndex((q) => /^INSERT INTO goals/.test(q)), 'the goal is made inside it');
|
|
115
|
+
assert.ok(sqls.findIndex((q) => /^INSERT INTO tasks/.test(q)) < sqls.lastIndexOf('COMMIT'), 'and the task too, before the one COMMIT');
|
|
116
|
+
assert.equal(c.state.tasks[0].goal_id, 91);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
summary();
|
|
@@ -86,8 +86,13 @@ try {
|
|
|
86
86
|
assert.equal(db.tasks.length, 1);
|
|
87
87
|
assert.deepEqual(
|
|
88
88
|
[t.source, t.source_ref, t.title, t.kind, t.discipline, t.credits_reward, t.version_id, t.goal_id, t.status],
|
|
89
|
-
['page-tweak', 'page-tweak/builders:studio/r1', 'Tweak: Studio (builders:studio)', 'feature', 'artist', 30, 'V9', '
|
|
89
|
+
['page-tweak', 'page-tweak/builders:studio/r1', 'Tweak: Studio (builders:studio)', 'feature', 'artist', 30, 'V9', '88', 'active']
|
|
90
90
|
);
|
|
91
|
+
// task 1004819: the round lands in the version's artist review goal, made on
|
|
92
|
+
// first use with its two standing criteria — never the catch-all.
|
|
93
|
+
assert.equal(db.artistGoal.title, 'V9 — artist review');
|
|
94
|
+
assert.deepEqual(db.criteria.map((x) => [x.criterion_id, x.goal_id]),
|
|
95
|
+
[['artist-every-page-reviewed', '88'], ['artist-pages-simple-to-use', '88']]);
|
|
91
96
|
assert.deepEqual(t.touches, F.STUDIO_FILES, 'touches are the inventory page files');
|
|
92
97
|
const c = db.claims[0];
|
|
93
98
|
assert.deepEqual([c.builder_id, c.worktree_name, c.creator_session_id], [ALICE, null, null], 'a web claim: no worktree, no session');
|
|
@@ -208,7 +208,7 @@ try {
|
|
|
208
208
|
`page-tweak-unplaced/${studioTask().id}/L0005`,
|
|
209
209
|
]);
|
|
210
210
|
for (const t of eng) {
|
|
211
|
-
assert.deepEqual([t.status, t.discipline, t.kind, t.version_id, t.goal_id, t.created_by], ['backlog', 'engineer', 'feature', 'V9', '
|
|
211
|
+
assert.deepEqual([t.status, t.discipline, t.kind, t.version_id, t.goal_id, t.created_by], ['backlog', 'engineer', 'feature', 'V9', '88', ALICE], 'an unplaced line lands in the artist review goal (task 1004819)');
|
|
212
212
|
assert.match(t.description, /builders:studio/);
|
|
213
213
|
}
|
|
214
214
|
const quiet = eng.find((t) => t.source_ref.endsWith('/L0005'));
|
|
@@ -36,6 +36,7 @@ export function installPageLedger(api, { builders }) {
|
|
|
36
36
|
// first commits or rolls back.
|
|
37
37
|
const locks = new Map();
|
|
38
38
|
async function lock(key, owner) {
|
|
39
|
+
if (locks.has(key) && locks.get(key).owner === owner) return; // re-entrant, as Postgres is for one session
|
|
39
40
|
while (locks.has(key)) await locks.get(key).done;
|
|
40
41
|
let release;
|
|
41
42
|
const done = new Promise((r) => { release = r; });
|
|
@@ -109,6 +110,18 @@ export function installPageLedger(api, { builders }) {
|
|
|
109
110
|
if (/FROM goals g JOIN versions v ON v\.id = g\.version_id WHERE g\.version_id = \$1 AND g\.title = \$2/.test(q)) {
|
|
110
111
|
return { rows: [{ id: '77', version_status: 'building' }] };
|
|
111
112
|
}
|
|
113
|
+
// task 1004819: studio work lands in the version's artist review goal, found
|
|
114
|
+
// (or made, with its two criteria) on the same transaction client.
|
|
115
|
+
if (/^SELECT status FROM versions WHERE id = \$1/.test(q)) return { rows: [{ status: 'building' }] };
|
|
116
|
+
if (/^SELECT id, version_id, title FROM goals WHERE version_id = \$1 AND title = \$2/.test(q)) {
|
|
117
|
+
assert.ok(owner, 'the artist review goal must be found on the transaction client');
|
|
118
|
+
return { rows: db.artistGoal ? [{ ...db.artistGoal }] : [] };
|
|
119
|
+
}
|
|
120
|
+
if (/^INSERT INTO goals \(version_id, title, description, scope_modules, status, created_by, sort_order\)/.test(q)) {
|
|
121
|
+
db.artistGoal = { id: '88', version_id: p[0], title: p[1] };
|
|
122
|
+
return { rows: [{ ...db.artistGoal }] };
|
|
123
|
+
}
|
|
124
|
+
if (/^INSERT INTO done_when_criteria/.test(q)) { (db.criteria = db.criteria || []).push({ version_id: p[0], criterion_id: p[1], goal_id: p[3] }); return { rows: [] }; }
|
|
112
125
|
if (/^SELECT scope_modules FROM goals WHERE id = \$1/.test(q)) return { rows: [{ scope_modules: [] }] };
|
|
113
126
|
if (/^INSERT INTO tasks \(version_id, title, description, status, touches,/.test(q)) {
|
|
114
127
|
assert.ok(owner, 'the round must be created inside the claim transaction');
|
|
@@ -49,3 +49,14 @@ test('the "?" asks before it sends: the send lives behind a confirm button', ()
|
|
|
49
49
|
assert.match(fn, /Send this to Claude to rewrite\?/);
|
|
50
50
|
assert.match(fn, /Nothing is decided or changed/);
|
|
51
51
|
});
|
|
52
|
+
|
|
53
|
+
test('a card never cuts what it says: the whole text is one tap away, and only when there is more', () => {
|
|
54
|
+
const G = boot();
|
|
55
|
+
const m = { id: 'override_requests:1', title: 'Override: a long title', detail: 'The whole story, word for word.' };
|
|
56
|
+
assert.equal(G.fullText(m, { question: 'Release it?' }), 'Override: a long title\n\nThe whole story, word for word.');
|
|
57
|
+
assert.equal(G.fullText(m, { question: 'Override: a long title' }), 'The whole story, word for word.', 'the question is not repeated');
|
|
58
|
+
assert.equal(G.fullText({ id: 'x:1', title: 'Same' }, { question: 'Same' }), '', 'nothing more to show, no opener');
|
|
59
|
+
assert.equal(G.fullText(null, { question: 'Q?' }), '');
|
|
60
|
+
const src = fs.readFileSync(path.join(PUB, 'city-decide.js'), 'utf8');
|
|
61
|
+
assert.match(src, /str\(o\.input && o\.input\.label\) \|\| str\(o\.label\)/, 'an answer uses its full words, not the desk chip cut to fit');
|
|
62
|
+
});
|
|
@@ -113,12 +113,11 @@ test('POST /tasks does NOT pass the escape hatch — this door always has a goal
|
|
|
113
113
|
|
|
114
114
|
const OPT_INS = [
|
|
115
115
|
['modules/onboarding/newcomer-restock.js', 'the beginner queue, stamped from templates'],
|
|
116
|
-
['modules/copy-desk/routes/copy-desk.js', 'copy-change proposals'],
|
|
117
116
|
['modules/lifecycle/kickoff-seed.js', "the founder's first board, on an instance with no goals at all"],
|
|
118
117
|
['modules/lifecycle/cascade.js', 'a follow-up off a parent task that is itself goal-less'],
|
|
119
118
|
];
|
|
120
119
|
|
|
121
|
-
test('exactly
|
|
120
|
+
test('exactly three vectors opt in, and each says why', () => {
|
|
122
121
|
for (const [file, why] of OPT_INS) {
|
|
123
122
|
const body = src(file);
|
|
124
123
|
assert.match(body, /allowCatchAll/, `${file} should opt in (${why})`);
|
|
@@ -127,6 +126,17 @@ test('exactly four vectors opt in, and each says why', () => {
|
|
|
127
126
|
}
|
|
128
127
|
});
|
|
129
128
|
|
|
129
|
+
// task 1004819: studio work names its home instead of the catch-all. Each of the
|
|
130
|
+
// three studio create vectors passes artistReview, which lands it in the
|
|
131
|
+
// version's artist review goal; none of them opts into the catch-all any more.
|
|
132
|
+
test('the three studio vectors land in the artist review goal, not the catch-all', () => {
|
|
133
|
+
for (const file of ['modules/copy-desk/routes/copy-desk.js', 'modules/lifecycle/page-tweak-claim.js', 'modules/lifecycle/page-tweak-write.js']) {
|
|
134
|
+
const body = src(file);
|
|
135
|
+
assert.match(body, /artistReview: true/, `${file} should name the artist review goal`);
|
|
136
|
+
assert.equal(/allowCatchAll/.test(body), false, `${file} must not opt into the catch-all`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
130
140
|
test('cascade opts in ONLY when inheritance actually failed', () => {
|
|
131
141
|
// The others opt in unconditionally because they have no goal to name at all.
|
|
132
142
|
// A cascade normally inherits its parent's goal and must keep doing so — the
|