@bongos/core 1.20.66 → 1.20.67
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 +29 -24
- package/docs/copy-inventory.md +156 -154
- package/docs/copy-registry.json +196 -178
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-readings.json +28 -28
- package/modules/hall-ui/public/goals-lib.js +37 -5
- package/modules/hall-ui/public/goals-render.js +14 -5
- package/modules/hall-ui/public/task.js +25 -2
- package/modules/lifecycle/goal-rollup.js +13 -7
- package/modules/lifecycle/task-status.js +4 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/hall-preview/refresh-fixtures.js +5 -1
- package/src/module-api.js +1 -1
- package/tests/claim_from_task_and_home.mjs +22 -1
- package/tests/dep_blocked_ready_cards.mjs +70 -0
- package/tests/goal_sort_staleness.mjs +8 -3
- package/tests/goals_lib.mjs +58 -2
|
@@ -2789,5 +2789,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2789
2789
|
landed since 1.20.64 with no explicit bump. run 36852902728. (task 1002620)
|
|
2790
2790
|
1.20.66 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2791
2791
|
landed since 1.20.65 with no explicit bump. run 36854308972. (task 1002620)
|
|
2792
|
+
1.20.67 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2793
|
+
landed since 1.20.66 with no explicit bump. run 36860726629. (task 1002620)
|
|
2792
2794
|
---------------------------------------------------------------------------
|
|
2793
2795
|
```
|
package/docs/page-readings.json
CHANGED
|
@@ -1085,7 +1085,7 @@
|
|
|
1085
1085
|
"surface": "builders",
|
|
1086
1086
|
"title": "Goals",
|
|
1087
1087
|
"states_read": ["list","filters","cards","detail","out","uat","uat-panel"],
|
|
1088
|
-
"files_hash": "
|
|
1088
|
+
"files_hash": "bec6cb43b659cd69",
|
|
1089
1089
|
"reading_hash": "2104acb45018ce47",
|
|
1090
1090
|
"counts": {"lines":171,"placed":46,"shared":5,"unplaced":120},
|
|
1091
1091
|
"omitted": {"data":251},
|
|
@@ -1136,7 +1136,7 @@
|
|
|
1136
1136
|
{"key":"L0043","section":"Members0","text":"No members yet — be the first to join.","placement":"unplaced","state":"detail"},
|
|
1137
1137
|
{"key":"L0044","section":"Conflicts12","text":"Conflicts","placement":"unplaced","state":"detail"},
|
|
1138
1138
|
{"key":"L0045","section":"Conflicts12","text":"Open tasks whose declared files overlap — sequence these before two builders collide.","placement":"placed","file":"modules/hall-ui/public/goals.html","line":117,"string_id":"99c630009152","state":"detail"},
|
|
1139
|
-
{"key":"L0046","section":"Conflicts12","text":"View all {…} {…} →","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":
|
|
1139
|
+
{"key":"L0046","section":"Conflicts12","text":"View all {…} {…} →","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":617,"string_id":"9b92aa01516d","state":"detail"},
|
|
1140
1140
|
{"key":"L0047","section":"Task {…}","text":"Task dependencies","placement":"placed","file":"modules/hall-ui/public/goals.html","line":132,"string_id":"a0f384b022fd","state":"detail"},
|
|
1141
1141
|
{"key":"L0048","section":"Task {…}","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","state":"detail"},
|
|
1142
1142
|
{"key":"L0049","section":"Task {…}","text":"#1165 Blocker-solve","placement":"unplaced","state":"detail"},
|
|
@@ -1151,7 +1151,7 @@
|
|
|
1151
1151
|
{"key":"L0058","section":"Task {…}","text":"converge onto the kit…","placement":"unplaced","state":"detail"},
|
|
1152
1152
|
{"key":"L0059","section":"Task {…}","text":"#2172 Settings","placement":"unplaced","state":"detail"},
|
|
1153
1153
|
{"key":"L0060","section":"Task {…}","text":"regroup: 6 groups, st…","placement":"unplaced","state":"detail"},
|
|
1154
|
-
{"key":"L0061","section":"Task {…}","text":"Task","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
1154
|
+
{"key":"L0061","section":"Task {…}","text":"Task","placement":"placed","file":"modules/hall-ui/public/task.js","line":512,"string_id":"eff70e59d12e","state":"detail"},
|
|
1155
1155
|
{"key":"L0062","section":"Task {…}","text":"Status","placement":"unplaced","state":"detail"},
|
|
1156
1156
|
{"key":"L0063","section":"Task {…}","text":"Priority","placement":"unplaced","state":"detail"},
|
|
1157
1157
|
{"key":"L0064","section":"Task {…}","text":"Waits on","placement":"placed","file":"modules/hall-ui/public/goal-forest-render.js","line":146,"string_id":"8e4f0179e1d3","state":"detail"},
|
|
@@ -2808,25 +2808,25 @@
|
|
|
2808
2808
|
"surface": "builders",
|
|
2809
2809
|
"title": "Task",
|
|
2810
2810
|
"states_read": ["record","from-goal","missing"],
|
|
2811
|
-
"files_hash": "
|
|
2811
|
+
"files_hash": "e01a05d46624d7f0",
|
|
2812
2812
|
"reading_hash": "66f99a3663a75a77",
|
|
2813
2813
|
"counts": {"lines":117,"placed":29,"shared":5,"unplaced":83},
|
|
2814
2814
|
"omitted": {"data":67},
|
|
2815
2815
|
"notes": [],
|
|
2816
2816
|
"lines": [
|
|
2817
2817
|
{"key":"L0001","section":"skip link","text":"Skip to content","placement":"shared","file":"modules/hall-ui/public/shell.js","line":884,"string_id":"87cee78ca134"},
|
|
2818
|
-
{"key":"L0002","section":"top bar","text":"Task","placement":"shared","file":"modules/hall-ui/public/task.js","line":
|
|
2818
|
+
{"key":"L0002","section":"top bar","text":"Task","placement":"shared","file":"modules/hall-ui/public/task.js","line":512,"string_id":"eff70e59d12e"},
|
|
2819
2819
|
{"key":"L0003","section":"top bar","text":"Jump to…","placement":"shared","file":"modules/hall-ui/public/shell.js","line":495,"string_id":"eba610d25523"},
|
|
2820
2820
|
{"key":"L0004","section":"top bar","text":"Ctrl K","placement":"shared"},
|
|
2821
2821
|
{"key":"L0005","section":"page top","text":"Back to {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":226,"string_id":"f6bb75b08e98","state":"from-goal","occurrences":2},
|
|
2822
|
-
{"key":"L0006","section":"page top","text":"Task {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2823
|
-
{"key":"L0007","section":"page top","text":"Task","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2824
|
-
{"key":"L0008","section":"page top","text":"shipped","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":
|
|
2822
|
+
{"key":"L0006","section":"page top","text":"Task {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":512,"string_id":"eff70e59d12e"},
|
|
2823
|
+
{"key":"L0007","section":"page top","text":"Task","placement":"placed","file":"modules/hall-ui/public/task.js","line":512,"string_id":"eff70e59d12e"},
|
|
2824
|
+
{"key":"L0008","section":"page top","text":"shipped","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":828,"string_id":"bd3402efa59e"},
|
|
2825
2825
|
{"key":"L0009","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"· {…} · {…} · {…} to any rank","placement":"unplaced"},
|
|
2826
2826
|
{"key":"L0010","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Created {…}, 78d ago.","placement":"unplaced"},
|
|
2827
2827
|
{"key":"L0011","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Goal","placement":"placed","file":"modules/hall-ui/public/gate.js","line":288,"string_id":"ec88b946ad16"},
|
|
2828
2828
|
{"key":"L0012","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":8},
|
|
2829
|
-
{"key":"L0013","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Move to goal","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2829
|
+
{"key":"L0013","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Move to goal","placement":"placed","file":"modules/hall-ui/public/task.js","line":599,"string_id":"e52ae74f0052"},
|
|
2830
2830
|
{"key":"L0014","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Move","placement":"unplaced"},
|
|
2831
2831
|
{"key":"L0015","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Priority","placement":"unplaced","occurrences":2},
|
|
2832
2832
|
{"key":"L0016","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"— unset —","placement":"unplaced"},
|
|
@@ -2835,11 +2835,11 @@
|
|
|
2835
2835
|
{"key":"L0019","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"P3","placement":"unplaced"},
|
|
2836
2836
|
{"key":"L0020","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"P4","placement":"unplaced"},
|
|
2837
2837
|
{"key":"L0021","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"P5","placement":"unplaced"},
|
|
2838
|
-
{"key":"L0022","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Wait for task","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2839
|
-
{"key":"L0023","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"task id","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2840
|
-
{"key":"L0024","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Set priority","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2841
|
-
{"key":"L0025","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Add dependency","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2842
|
-
{"key":"L0026","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"A dependency keeps this task out of the claimable queue until that one ships. Remove one with the × beside it under “Blocked by”.","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2838
|
+
{"key":"L0022","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Wait for task","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"411b2ff58094"},
|
|
2839
|
+
{"key":"L0023","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"task id","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"3906fbc07bbe","attr":"placeholder"},
|
|
2840
|
+
{"key":"L0024","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Set priority","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"c11213056bab"},
|
|
2841
|
+
{"key":"L0025","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Add dependency","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"2b94171ce525"},
|
|
2842
|
+
{"key":"L0026","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"A dependency keeps this task out of the claimable queue until that one ships. Remove one with the × beside it under “Blocked by”.","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"518e5acc9184"},
|
|
2843
2843
|
{"key":"L0027","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Estimated","placement":"unplaced"},
|
|
2844
2844
|
{"key":"L0028","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"m","placement":"unplaced","occurrences":2},
|
|
2845
2845
|
{"key":"L0029","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Actual","placement":"unplaced"},
|
|
@@ -2853,7 +2853,7 @@
|
|
|
2853
2853
|
{"key":"L0037","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Credit","placement":"unplaced"},
|
|
2854
2854
|
{"key":"L0038","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"80 {…} landed","placement":"unplaced"},
|
|
2855
2855
|
{"key":"L0039","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"2 claims","placement":"unplaced"},
|
|
2856
|
-
{"key":"L0040","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Last claimed by","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2856
|
+
{"key":"L0040","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Last claimed by","placement":"placed","file":"modules/hall-ui/public/task.js","line":679,"string_id":"13bae4dfb28e"},
|
|
2857
2857
|
{"key":"L0041","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"on {…}, {…} released, outcome {…}.","placement":"unplaced"},
|
|
2858
2858
|
{"key":"L0042","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Value","placement":"unplaced"},
|
|
2859
2859
|
{"key":"L0043","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Description","placement":"unplaced"},
|
|
@@ -2910,26 +2910,26 @@
|
|
|
2910
2910
|
{"key":"L0094","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"on edited JS before ship.","placement":"unplaced"},
|
|
2911
2911
|
{"key":"L0095","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Touches","placement":"unplaced"},
|
|
2912
2912
|
{"key":"L0096","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Blocks","placement":"unplaced"},
|
|
2913
|
-
{"key":"L0097","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Shipped","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2914
|
-
{"key":"L0098","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Shipped {…}{…}, {…}.","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2915
|
-
{"key":"L0099","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"The adversarial panel. Each worker reports pass, concerns or fail on its own; the gating workers must all pass before confirmed becomes {…}, and one concerns falls the task through to the manual confirm path.","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2916
|
-
{"key":"L0100","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"verdict {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2913
|
+
{"key":"L0097","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Shipped","placement":"placed","file":"modules/hall-ui/public/task.js","line":714,"string_id":"0c0a50ba1666"},
|
|
2914
|
+
{"key":"L0098","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Shipped {…}{…}, {…}.","placement":"placed","file":"modules/hall-ui/public/task.js","line":714,"string_id":"0c0a50ba1666"},
|
|
2915
|
+
{"key":"L0099","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"The adversarial panel. Each worker reports pass, concerns or fail on its own; the gating workers must all pass before confirmed becomes {…}, and one concerns falls the task through to the manual confirm path.","placement":"placed","file":"modules/hall-ui/public/task.js","line":449,"string_id":"6e93ef49d63f"},
|
|
2916
|
+
{"key":"L0100","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"verdict {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":453,"string_id":"4de556377db2"},
|
|
2917
2917
|
{"key":"L0101","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"threshold 6 · panel {…} · {…}","placement":"unplaced"},
|
|
2918
|
-
{"key":"L0102","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Session log{…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2918
|
+
{"key":"L0102","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Session log{…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":505,"string_id":"55e0a34bafed"},
|
|
2919
2919
|
{"key":"L0103","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Session","placement":"unplaced"},
|
|
2920
2920
|
{"key":"L0104","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":", 37m","placement":"unplaced"},
|
|
2921
2921
|
{"key":"L0105","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Outcome","placement":"unplaced"},
|
|
2922
|
-
{"key":"L0106","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"shipped","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":
|
|
2923
|
-
{"key":"L0107","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"{…} awarded","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2922
|
+
{"key":"L0106","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"shipped","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":828,"string_id":"bd3402efa59e"},
|
|
2923
|
+
{"key":"L0107","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"{…} awarded","placement":"placed","file":"modules/hall-ui/public/task.js","line":496,"string_id":"788ff11f1135"},
|
|
2924
2924
|
{"key":"L0108","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Handoff","placement":"unplaced"},
|
|
2925
|
-
{"key":"L0109","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Read the handoff notes","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2926
|
-
{"key":"L0110","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Full log","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2927
|
-
{"key":"L0111","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Open in Sessions","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2925
|
+
{"key":"L0109","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Read the handoff notes","placement":"placed","file":"modules/hall-ui/public/task.js","line":499,"string_id":"c5f19a888a15"},
|
|
2926
|
+
{"key":"L0110","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Full log","placement":"placed","file":"modules/hall-ui/public/task.js","line":501,"string_id":"fc6fdfa639a7"},
|
|
2927
|
+
{"key":"L0111","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Open in Sessions","placement":"placed","file":"modules/hall-ui/public/task.js","line":501,"string_id":"5f3e0b179839"},
|
|
2928
2928
|
{"key":"L0112","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Actions","placement":"unplaced"},
|
|
2929
|
-
{"key":"L0113","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"This task has shipped and its credits have landed. Neither Ship it nor Confirm is available.","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2930
|
-
{"key":"L0114","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Copy prompt","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2929
|
+
{"key":"L0113","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"This task has shipped and its credits have landed. Neither Ship it nor Confirm is available.","placement":"placed","file":"modules/hall-ui/public/task.js","line":765,"string_id":"c9e50a67988a"},
|
|
2930
|
+
{"key":"L0114","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Copy prompt","placement":"placed","file":"modules/hall-ui/public/task.js","line":841,"string_id":"a873e7c29d80"},
|
|
2931
2931
|
{"key":"L0115","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Back to {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":226,"string_id":"f6bb75b08e98","state":"from-goal","occurrences":2},
|
|
2932
|
-
{"key":"L0116","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"A shipped task cannot be retired — it is immutable history now.","placement":"placed","file":"modules/hall-ui/public/task.js","line":
|
|
2932
|
+
{"key":"L0116","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"A shipped task cannot be retired — it is immutable history now.","placement":"placed","file":"modules/hall-ui/public/task.js","line":797,"string_id":"f3d6ed7c0d2c"},
|
|
2933
2933
|
{"key":"L0117","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":471,"string_id":"1883ad7f07fc"}
|
|
2934
2934
|
]
|
|
2935
2935
|
},
|
|
@@ -47,18 +47,50 @@
|
|
|
47
47
|
// goal's tasks into these buckets:
|
|
48
48
|
// done — settled (shipped/confirmed/completed)
|
|
49
49
|
// active — a live claim ("in progress")
|
|
50
|
-
// ready — claimable now (
|
|
51
|
-
// blocked —
|
|
52
|
-
// backlog —
|
|
50
|
+
// ready — claimable now (promoted AND every dependency shipped)
|
|
51
|
+
// blocked — unclaimed with ≥1 unshipped dependency, WHATEVER the row says
|
|
52
|
+
// backlog — not promoted, no unmet deps (just not started)
|
|
53
|
+
|
|
54
|
+
// hasUnmetDeps — the claim gate's own question, asked in the browser: does
|
|
55
|
+
// this task wait on an edge whose target has not shipped? `blocked_by[]`
|
|
56
|
+
// already carries the per-edge `satisfied` flag (attachTaskDepSummaries,
|
|
57
|
+
// satisfied === target.status === 'shipped'), which is the same rule
|
|
58
|
+
// `POST /claims` runs before it refuses with DEPS_NOT_SHIPPED (ADR 0015).
|
|
59
|
+
// Task→task edges only, like the array it reads — the goal/criterion arms of
|
|
60
|
+
// CLAIMABLE_SQL's guard have no client-side shape to read, so this is a
|
|
61
|
+
// subset: it never calls a claimable task blocked, it only stops short of
|
|
62
|
+
// catching every blocked one.
|
|
63
|
+
const hasUnmetDeps = (t) => ((t && t.blocked_by) || []).some((d) => !d.satisfied);
|
|
64
|
+
|
|
53
65
|
function readinessOf(t) {
|
|
54
66
|
const s = t.status;
|
|
55
67
|
if (DONE_STATUSES.has(s)) return 'done';
|
|
56
68
|
if (CUT_STATUSES.has(s)) return 'cut';
|
|
57
69
|
if (s === 'active') return 'active';
|
|
70
|
+
// task 1004487 — ahead of the `ready` arm, and no longer backlog-only. A
|
|
71
|
+
// dependency added after promotion leaves the row at `ready`: ADR 0015
|
|
72
|
+
// rules that the STATUS is deliberately never demoted and the readers are
|
|
73
|
+
// what change (core_264 did it for the claimable feed). Readiness is the
|
|
74
|
+
// derived question "can a builder claim this right now?", so a dep-blocked
|
|
75
|
+
// row belongs in this bucket however the status column reads.
|
|
76
|
+
if (hasUnmetDeps(t)) return 'blocked';
|
|
58
77
|
if (s === 'ready') return 'ready';
|
|
59
|
-
if (s === 'backlog' && (t.blocked_by || []).some((d) => !d.satisfied)) return 'blocked';
|
|
60
78
|
return 'backlog';
|
|
61
79
|
}
|
|
80
|
+
// taskStatusTagHtml — the lead tag a goal's task card shows when it is not
|
|
81
|
+
// offering a checkbox (task 1004487). A dep-blocked row reads "blocked" and
|
|
82
|
+
// names what it waits on, rather than echoing a `ready` status column that
|
|
83
|
+
// the claim gate will contradict — the same derivation `readinessOf` makes
|
|
84
|
+
// for the rollup counters, so the card and the goal's numbers agree. Every
|
|
85
|
+
// other row still shows its real status.
|
|
86
|
+
function taskStatusTagHtml(t) {
|
|
87
|
+
const esc = window.OTB.escapeHtml;
|
|
88
|
+
if (!hasUnmetDeps(t)) return `<span class="task__tag" title="Task status">${esc(t.status || '')}</span>`;
|
|
89
|
+
const waiting = (t.blocked_by || []).filter((d) => !d.satisfied)
|
|
90
|
+
.map((d) => `#${d.id} ${d.title || ''}`.trim()).join('\n');
|
|
91
|
+
return `<span class="task__tag" title="Waiting on ${esc(waiting)}">blocked</span>`;
|
|
92
|
+
}
|
|
93
|
+
|
|
62
94
|
const EMPTY_ROLLUP = { done: 0, active: 0, ready: 0, blocked: 0, backlog: 0, cut: 0, total: 0, nextReady: null };
|
|
63
95
|
|
|
64
96
|
// task 1002439 — a goal row's readiness rollup line (only the non-zero buckets).
|
|
@@ -139,7 +171,7 @@
|
|
|
139
171
|
window.OTBGoalsLib = {
|
|
140
172
|
DONE_STATUSES, CUT_STATUSES, isTodoStatus,
|
|
141
173
|
ring, memberName,
|
|
142
|
-
readinessOf, EMPTY_ROLLUP, goalRollLine,
|
|
174
|
+
hasUnmetDeps, taskStatusTagHtml, readinessOf, EMPTY_ROLLUP, goalRollLine,
|
|
143
175
|
matchesTodoSearch, msOf, taskActivityMs,
|
|
144
176
|
subtitleAttr, subtitleLine, applyGoalProse,
|
|
145
177
|
};
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
const { makePager, paginate, wirePager, pagerBarHtml, makeTabs, makeFilterBar,
|
|
28
28
|
emptyStateHtml, skeletonRows } = window.OTBKit;
|
|
29
29
|
const { DONE_STATUSES, CUT_STATUSES, isTodoStatus, ring, memberName,
|
|
30
|
-
readinessOf, EMPTY_ROLLUP, goalRollLine, matchesTodoSearch,
|
|
30
|
+
hasUnmetDeps, taskStatusTagHtml, readinessOf, EMPTY_ROLLUP, goalRollLine, matchesTodoSearch,
|
|
31
31
|
msOf, taskActivityMs, subtitleAttr, subtitleLine, applyGoalProse } = window.OTBGoalsLib;
|
|
32
32
|
const forestRender = window.OTBGoalForestRender(ctx);
|
|
33
33
|
// task 1004402 — criterion-uat-ui.js. If that script failed to load, the page
|
|
@@ -347,7 +347,11 @@
|
|
|
347
347
|
// work is the case that needed it most.
|
|
348
348
|
const ageChip = window.OTBKit.ageChipHtml(t.created_at);
|
|
349
349
|
const claim = state.claimsByTask.get(id);
|
|
350
|
-
|
|
350
|
+
// task 1004487 — `ready` is not enough: a dependency added after promotion
|
|
351
|
+
// leaves the row at `ready` and POST /claims still refuses it. Predicate
|
|
352
|
+
// and lead tag are both goals-lib.js's, because both are pure.
|
|
353
|
+
const isReadyNow = t.status === 'ready' && !hasUnmetDeps(t);
|
|
354
|
+
const isReady = isReadyNow && !!state.me;
|
|
351
355
|
const mineActive = t.status === 'active' && claim && sameId(claim.builder_id, selfId());
|
|
352
356
|
const releaseKey = mineActive ? String(claim.claim_id) : null;
|
|
353
357
|
const selected = isReady ? goalSel.claim.has(id) : (mineActive && goalSel.release.has(releaseKey));
|
|
@@ -358,7 +362,7 @@
|
|
|
358
362
|
? `<input type="checkbox" class="task__select" data-task-id="${escapeHtml(id)}" aria-label="Select task ${escapeHtml(id)} for bulk claim"${selected ? ' checked' : ''}>`
|
|
359
363
|
: (mineActive
|
|
360
364
|
? `<input type="checkbox" class="wip__select" data-claim-id="${escapeHtml(releaseKey)}" data-task-id="${escapeHtml(id)}" aria-label="Select task ${escapeHtml(id)} for bulk release"${selected ? ' checked' : ''}>`
|
|
361
|
-
:
|
|
365
|
+
: taskStatusTagHtml(t));
|
|
362
366
|
// Actions: ready → Claim + copy; mine-active → Release; other's active →
|
|
363
367
|
// "claimed · @who"; anything else → nothing (open via the title / card).
|
|
364
368
|
let actions = '';
|
|
@@ -404,7 +408,9 @@
|
|
|
404
408
|
const goalSel = { claim: new Set(), release: new Map() };
|
|
405
409
|
function goalSelTotal() { return goalSel.claim.size + goalSel.release.size; }
|
|
406
410
|
function pruneGoalSelection() {
|
|
407
|
-
|
|
411
|
+
// The same gate the card renders (task 1004487): a tick left on a
|
|
412
|
+
// dep-blocked `ready` row rides into a bulk claim the server refuses.
|
|
413
|
+
const readyIds = new Set(state.work.todo.filter((t) => t.status === 'ready' && !hasUnmetDeps(t)).map((t) => String(t.id)));
|
|
408
414
|
for (const id of Array.from(goalSel.claim)) if (!readyIds.has(id)) goalSel.claim.delete(id);
|
|
409
415
|
const mineClaimIds = new Set(
|
|
410
416
|
state.work.todo
|
|
@@ -1092,8 +1098,11 @@
|
|
|
1092
1098
|
}
|
|
1093
1099
|
async function loadGoalTasks(goalId) {
|
|
1094
1100
|
try {
|
|
1101
|
+
// ?include=deps (task 1004487) — the cards need `blocked_by[]` to tell a
|
|
1102
|
+
// claimable `ready` row from one whose dependencies have not shipped. Two
|
|
1103
|
+
// aggregate queries over ONE goal's tasks, so the cost stays bounded.
|
|
1095
1104
|
const [resp] = await Promise.all([
|
|
1096
|
-
getJSON(`${API}/tasks?goal_id=${encodeURIComponent(goalId)}`),
|
|
1105
|
+
getJSON(`${API}/tasks?goal_id=${encodeURIComponent(goalId)}&include=deps`),
|
|
1097
1106
|
loadActiveClaims(), // fills state.claimsByTask (best-effort)
|
|
1098
1107
|
]);
|
|
1099
1108
|
if (!sameId(state.current, goalId)) return; // navigated away while loading
|
|
@@ -305,8 +305,17 @@
|
|
|
305
305
|
function lifecycleRail(t, credits) {
|
|
306
306
|
const at = RAIL_AT[t.status] ?? 0;
|
|
307
307
|
const landed = t.status === 'shipped' && credits != null;
|
|
308
|
+
// task 1004487 — the first beat's caption is what tells a reader WHICH of
|
|
309
|
+
// the three unclaimed states this is, so it has to read the dependency
|
|
310
|
+
// graph too: a dep added after promotion leaves the row at `ready` while
|
|
311
|
+
// the claim gate refuses it, and "waiting for a builder" would then be the
|
|
312
|
+
// one sentence on the page that is wrong.
|
|
313
|
+
const waitingOnDep = (t.dependencies || []).some((d) => d.status !== 'shipped');
|
|
314
|
+
const claimCaption = (t.status === 'ready' && waitingOnDep)
|
|
315
|
+
? CLAIM_CAPTION.blocked
|
|
316
|
+
: (CLAIM_CAPTION[t.status] || 'one builder, one worktree');
|
|
308
317
|
const beats = [
|
|
309
|
-
['Claim',
|
|
318
|
+
['Claim', claimCaption],
|
|
310
319
|
['Build', 'the code and the docs'],
|
|
311
320
|
['Grade', 'the panel reads the diff'],
|
|
312
321
|
['Ship', 'merged, then deployed'],
|
|
@@ -727,8 +736,22 @@
|
|
|
727
736
|
// owed rebase, a module/touches conflict, …) is still possible and still
|
|
728
737
|
// surfaces, because claimAction.claim() carries the same friendlyClaimError
|
|
729
738
|
// switch the board uses, not a second guess at it here.
|
|
730
|
-
|
|
739
|
+
//
|
|
740
|
+
// task 1004487 — the status is NOT the whole answer, and the paragraph
|
|
741
|
+
// above assumed it was. A dependency added after this task was promoted
|
|
742
|
+
// leaves the row at `ready` and never at `blocked`: ADR 0015 rules that the
|
|
743
|
+
// status stays put and the READERS change, which is what core_264 did for
|
|
744
|
+
// the claimable feed. So the button also asks the dependency question the
|
|
745
|
+
// feed asks, from the `dependencies[]` the record already carries (an edge
|
|
746
|
+
// is met iff its target shipped — the DEPS_NOT_SHIPPED rule verbatim), and
|
|
747
|
+
// when the answer is no it says so in words rather than offering a control
|
|
748
|
+
// that cannot work, exactly as the retire refusals below do.
|
|
749
|
+
const unmetDeps = deps.filter((d) => d.status !== 'shipped');
|
|
750
|
+
if (t.status === 'ready' && unmetDeps.length === 0) {
|
|
731
751
|
lifecycle = `<button class="td-btn td-btn--primary" type="button" id="claim-btn">Claim it</button>`;
|
|
752
|
+
} else if (t.status === 'ready') {
|
|
753
|
+
const names = unmetDeps.map((d) => `${d.id}`).join(', ');
|
|
754
|
+
lifecycle = `<p class="rec-actnote">This task is waiting on ${unmetDeps.length === 1 ? 'task' : 'tasks'} ${escapeHtml(names)}. It cannot be claimed until ${unmetDeps.length === 1 ? 'that one ships' : 'those ship'} — see Blocked by above.</p>`;
|
|
732
755
|
} else if (t.status === 'confirmed' && (isSupervisor || ownsLastClaim)) {
|
|
733
756
|
lifecycle = `<button class="td-btn td-btn--primary" type="button" id="ship-btn">Ship it</button>`;
|
|
734
757
|
} else if (t.status === 'completed') {
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
// THE BUCKETS ARE THE CLIENT'S, VERBATIM. goals-lib.js `readinessOf` is the
|
|
20
20
|
// definition of record and this SQL mirrors it exactly — done (shipped |
|
|
21
21
|
// confirmed | completed) · cut (abandoned | dropped, excluded from the total the
|
|
22
|
-
// way migration 017 keeps them out of version_progress) · active ·
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
22
|
+
// way migration 017 keeps them out of version_progress) · active · blocked
|
|
23
|
+
// (unclaimed with >=1 unshipped dependency, whatever the status column says —
|
|
24
|
+
// task 1004487) · ready · backlog. A divergence here would show a person
|
|
25
|
+
// different numbers depending on which surface they opened, so
|
|
26
|
+
// tests/goal_sort_staleness.mjs pins the two against each other.
|
|
26
27
|
//
|
|
27
28
|
// LAST ACTIVITY is `max(updated_at, shipped_at)` — the same "any task activity
|
|
28
29
|
// counts" rule the client uses (task 1002965, the owner's call), now expressed
|
|
@@ -43,8 +44,8 @@ const { DONE_STATUSES, CUT_STATUSES } = require('./task-status');
|
|
|
43
44
|
// Returns [{ goal_id, done, active, ready, blocked, backlog, cut, total,
|
|
44
45
|
// last_activity_at, disciplines[], next_ready_id, next_ready_title }].
|
|
45
46
|
//
|
|
46
|
-
// `blocked` needs the dependency edges, so the CTE resolves each
|
|
47
|
-
// unshipped-dependency
|
|
47
|
+
// `blocked` needs the dependency edges, so the CTE resolves each task's
|
|
48
|
+
// unshipped-dependency flag once rather than per row. deps.pool honors the #538
|
|
48
49
|
// test seam (NODE_ENV==='test' only), like every other helper in this module.
|
|
49
50
|
// VISIBILITY. A rollup is a COUNT, and counts on a private goal are not the
|
|
50
51
|
// secret — its prose is (ADR 0112's leak rule: a non-member must not learn a
|
|
@@ -78,8 +79,13 @@ async function goalRollups({ versionId = null, viewerId = null, viewerIsArchon =
|
|
|
78
79
|
WHEN s.status = ANY($2::text[]) THEN 'done'
|
|
79
80
|
WHEN s.status = ANY($3::text[]) THEN 'cut'
|
|
80
81
|
WHEN s.status = 'active' THEN 'active'
|
|
82
|
+
-- task 1004487 — ahead of the 'ready' arm, and no longer
|
|
83
|
+
-- backlog-only: a dependency added after promotion leaves the
|
|
84
|
+
-- row at 'ready' (ADR 0015 demotes the readers, never the
|
|
85
|
+
-- status), so counting it ready advertises work POST /claims
|
|
86
|
+
-- refuses — and next_ready below reads this very bucket.
|
|
87
|
+
WHEN s.has_unshipped_dep THEN 'blocked'
|
|
81
88
|
WHEN s.status = 'ready' THEN 'ready'
|
|
82
|
-
WHEN s.status = 'backlog' AND s.has_unshipped_dep THEN 'blocked'
|
|
83
89
|
ELSE 'backlog'
|
|
84
90
|
END AS bucket
|
|
85
91
|
FROM scoped s
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
// tests/goal_sort_staleness.mjs pins). "Done" there means "no longer in the
|
|
28
28
|
// queue", which deliberately includes the mid-ship states. It is a progress
|
|
29
29
|
// ring, not a ledger, and must never be used to decide whether a goal may
|
|
30
|
-
// close.
|
|
30
|
+
// close. Note too that the bucketing is NOT a pure function of the status
|
|
31
|
+
// column: `blocked` there means "has an unshipped dependency", which a
|
|
32
|
+
// `ready` row can be (ADR 0015 / task 1004487), so neither set below is
|
|
33
|
+
// enough on its own to bucket a task.
|
|
31
34
|
//
|
|
32
35
|
// NO DEPENDENCIES, deliberately. This file requires nothing — not even
|
|
33
36
|
// `module-api` — so a CLI (`scripts/gds/sequence.js`) can read the vocabulary
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.67",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.20.
|
|
9
|
+
"version": "1.20.67",
|
|
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.20.
|
|
3
|
+
"version": "1.20.67",
|
|
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
|
@@ -8525,5 +8525,11 @@
|
|
|
8525
8525
|
"id": "1004485",
|
|
8526
8526
|
"text": "Goals can now be moved from one version to another, taking their tasks and done-when criteria with them and keeping every task number. Used first to move the two goals that did not make V2 into V3."
|
|
8527
8527
|
}
|
|
8528
|
+
],
|
|
8529
|
+
"1.20.67": [
|
|
8530
|
+
{
|
|
8531
|
+
"id": "1004487",
|
|
8532
|
+
"text": "The hall no longer offers a Claim button on work the system will refuse. If a task gets a new prerequisite after it was opened up for claiming, its page and its goal card now say it is waiting on that other task — and name whi"
|
|
8533
|
+
}
|
|
8528
8534
|
]
|
|
8529
8535
|
}
|
|
@@ -58,7 +58,11 @@ const SNAPSHOTS = [
|
|
|
58
58
|
['goals/25/conflicts', 'goals__conflicts'],
|
|
59
59
|
['goals/25/requests', 'goals__requests'],
|
|
60
60
|
['tasks/claimable', 'tasks__claimable'],
|
|
61
|
-
|
|
61
|
+
// &include=deps (task 1004487) — the goal page's task cards read `blocked_by[]`
|
|
62
|
+
// to tell a claimable `ready` row from one whose dependencies have not shipped.
|
|
63
|
+
// Without it every row refreshes with no dependency data and the harness can
|
|
64
|
+
// only ever demo the claimable half of that fork.
|
|
65
|
+
['tasks?status=ready&include=deps', 'tasks', trimList('tasks', 60)],
|
|
62
66
|
['tasks/2166', 'tasks__detail'],
|
|
63
67
|
['tasks/peer-votes/feed?limit=25', 'tasks__peer-votes__feed'],
|
|
64
68
|
['analytics/builder/95?days=90', 'analytics__builder'],
|
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.20.
|
|
78
|
+
const CORE_VERSION = '1.20.67'; // 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');
|
|
@@ -38,11 +38,32 @@ await test('task.js constructs the shared claim action', () => {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
await test('task.js renders a Claim button only when the task is ready', () => {
|
|
41
|
-
const m = taskJs.match(/if \(t\.status === 'ready'\) \{\s*lifecycle = `([^`]*)`/);
|
|
41
|
+
const m = taskJs.match(/if \(t\.status === 'ready' && unmetDeps\.length === 0\) \{\s*lifecycle = `([^`]*)`/);
|
|
42
42
|
assert.ok(m, 'no ready-status branch found in the lifecycle chain');
|
|
43
43
|
assert.match(m[1], /id="claim-btn"/);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
// Task 1004487 — `ready` alone was never the claim gate's question. A dependency
|
|
47
|
+
// added after promotion leaves the row at `ready` (ADR 0015: the status is
|
|
48
|
+
// deliberately never demoted), and POST /claims refuses it with
|
|
49
|
+
// DEPS_NOT_SHIPPED. core_264 taught the claimable FEED that; this page was the
|
|
50
|
+
// surface it did not reach.
|
|
51
|
+
await test('a dep-blocked ready task gets NO Claim button, and is told why', () => {
|
|
52
|
+
assert.match(taskJs, /const unmetDeps = deps\.filter\(\(d\) => d\.status !== 'shipped'\);/,
|
|
53
|
+
'the unmet-dependency predicate must mirror the gate: met iff the target shipped');
|
|
54
|
+
const refusal = taskJs.match(/\} else if \(t\.status === 'ready'\) \{\s*([\s\S]{0,600}?)\n {4}\} else if/);
|
|
55
|
+
assert.ok(refusal, 'no dep-blocked ready branch after the claimable one');
|
|
56
|
+
assert.doesNotMatch(refusal[1], /claim-btn/, 'a task the gate will refuse must not be offered the button');
|
|
57
|
+
assert.match(refusal[1], /rec-actnote/, 'the refusal is pre-answered in words, like the retire refusals');
|
|
58
|
+
assert.match(refusal[1], /waiting on/, 'and it names what it is waiting on');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await test("the lifecycle rail's first beat does not call a dep-blocked task 'waiting for a builder'", () => {
|
|
62
|
+
assert.match(taskJs, /const waitingOnDep = \(t\.dependencies \|\| \[\]\)\.some\(\(d\) => d\.status !== 'shipped'\);/);
|
|
63
|
+
assert.match(taskJs, /t\.status === 'ready' && waitingOnDep\)\s*\n?\s*\? CLAIM_CAPTION\.blocked/,
|
|
64
|
+
"a dep-blocked ready row borrows the 'blocked' caption — waiting on another task");
|
|
65
|
+
});
|
|
66
|
+
|
|
46
67
|
await test('the Claim button click routes through claimAction.claim, not a raw POST', () => {
|
|
47
68
|
const m = taskJs.match(/const claimBtn = \$\('#claim-btn'\);[\s\S]*?\n {4}\}/);
|
|
48
69
|
assert.ok(m, 'claim-btn wiring block not found');
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// tests/dep_blocked_ready_cards.mjs — task 1004487.
|
|
2
|
+
//
|
|
3
|
+
// THE BUG. ADR 0015 rules that a dependency added AFTER a task was promoted
|
|
4
|
+
// leaves the row at `ready`: the status answers "was this promoted?", not "is
|
|
5
|
+
// this workable right now?", so nothing demotes it. `core_264` (task 1004190)
|
|
6
|
+
// taught the claimable FEED to ask the dependency graph instead of the status
|
|
7
|
+
// column. The Goals page's task cards were a surface it did not reach — they
|
|
8
|
+
// read `t.status === 'ready'` alone, so a dep-blocked row got a bulk-claim
|
|
9
|
+
// checkbox and a Claim button for work `POST /claims` refuses with
|
|
10
|
+
// DEPS_NOT_SHIPPED.
|
|
11
|
+
//
|
|
12
|
+
// The predicate itself (`hasUnmetDeps` / `readinessOf`) is proven BEHAVIOURALLY
|
|
13
|
+
// in tests/goals_lib.mjs, and the server-side mirror in
|
|
14
|
+
// tests/goal_sort_staleness.mjs. What is left to pin, and what this file pins,
|
|
15
|
+
// is that the card actually CALLS it — and that the data it needs is asked for,
|
|
16
|
+
// because the predicate is silent (correctly) when `blocked_by[]` is absent, so
|
|
17
|
+
// a dropped `?include=deps` would reopen the bug with every test still green.
|
|
18
|
+
//
|
|
19
|
+
// goals-render.js is a browser IIFE reaching for `window` at eval time, so it is
|
|
20
|
+
// read as text — the idiom tests/claim_action.mjs and claim_from_task_and_home.mjs
|
|
21
|
+
// already use for these files.
|
|
22
|
+
//
|
|
23
|
+
// Run: node tests/dep_blocked_ready_cards.mjs
|
|
24
|
+
|
|
25
|
+
import { strict as assert } from 'node:assert';
|
|
26
|
+
import { readFileSync } from 'node:fs';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
import { dirname, join } from 'node:path';
|
|
29
|
+
import { makeRunner } from './helpers.mjs';
|
|
30
|
+
|
|
31
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const repoRoot = join(__dirname, '..');
|
|
33
|
+
const read = (...p) => readFileSync(join(repoRoot, ...p), 'utf8');
|
|
34
|
+
const { test, summary } = makeRunner();
|
|
35
|
+
|
|
36
|
+
const renderJs = read('modules', 'hall-ui', 'public', 'goals-render.js');
|
|
37
|
+
|
|
38
|
+
await test('the goal page ASKS for the dependency data its cards now read', () => {
|
|
39
|
+
// Without include=deps every task arrives with no blocked_by[], hasUnmetDeps
|
|
40
|
+
// answers false for all of them, and the gate below is a no-op that still passes.
|
|
41
|
+
assert.match(renderJs, /\$\{API\}\/tasks\?goal_id=\$\{encodeURIComponent\(goalId\)\}&include=deps/,
|
|
42
|
+
'loadGoalTasks must request ?include=deps');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await test('a dep-blocked ready card is not offered the claim affordances', () => {
|
|
46
|
+
assert.match(renderJs, /const isReadyNow = t\.status === 'ready' && !hasUnmetDeps\(t\);/,
|
|
47
|
+
"the card's claimable predicate must ask the dependency question, not the status alone");
|
|
48
|
+
// The checkbox and the Claim button both hang off isReady, so gating that one
|
|
49
|
+
// name is what removes both.
|
|
50
|
+
assert.match(renderJs, /const isReady = isReadyNow && !!state\.me;/);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await test('the bulk selection prunes a tick that is no longer claimable', () => {
|
|
54
|
+
// A tick that survives the prune rides into a bulk claim the server refuses,
|
|
55
|
+
// which is the same lie one layer down.
|
|
56
|
+
assert.match(renderJs, /state\.work\.todo\.filter\(\(t\) => t\.status === 'ready' && !hasUnmetDeps\(t\)\)/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await test('the card takes its lead tag from the lib, never a status echo of its own', () => {
|
|
60
|
+
// Hiding the checkbox without changing the word would leave the row reading
|
|
61
|
+
// "ready" with no affordance and no reason — worse than the bug. The tag is
|
|
62
|
+
// pure, so it lives in goals-lib.js and is proven BEHAVIOURALLY in
|
|
63
|
+
// tests/goals_lib.mjs; what this pins is that the card calls it and that the
|
|
64
|
+
// hand-rolled status span it replaced has not grown back beside it.
|
|
65
|
+
assert.match(renderJs, /: taskStatusTagHtml\(t\)\);/);
|
|
66
|
+
assert.doesNotMatch(renderJs, /<span class="task__tag" title="Task status">/,
|
|
67
|
+
'goals-render.js must not re-grow its own status tag');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
summary();
|
|
@@ -171,9 +171,14 @@ await test('the SQL rollup buckets MIRROR goals-lib readinessOf exactly', () =>
|
|
|
171
171
|
assert.match(lib, /DONE_STATUSES = new Set\(\['completed', 'confirmed', 'shipped'\]\)/);
|
|
172
172
|
assert.match(vocab, /const CUT_STATUSES = \['abandoned', 'dropped'\]/);
|
|
173
173
|
assert.match(lib, /CUT_STATUSES = new Set\(\['abandoned', 'dropped'\]\)/);
|
|
174
|
-
// blocked =
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
// blocked = >=1 unshipped dependency on an unclaimed task, both sides — and
|
|
175
|
+
// on BOTH sides the arm sits AHEAD of 'ready' (task 1004487), because a dep
|
|
176
|
+
// added after promotion leaves the row at 'ready' and a status-first
|
|
177
|
+
// bucketing would then count un-claimable work as ready. The ORDER is the
|
|
178
|
+
// assertion, not just the presence of the clause.
|
|
179
|
+
assert.match(rollup, /WHEN s\.has_unshipped_dep\s+THEN 'blocked'\s*\n\s*WHEN s\.status = 'ready'\s+THEN 'ready'/);
|
|
180
|
+
assert.match(lib, /const hasUnmetDeps = \(t\) => \(\(t && t\.blocked_by\) \|\| \[\]\)\.some\(\(d\) => !d\.satisfied\);/);
|
|
181
|
+
assert.match(lib, /if \(hasUnmetDeps\(t\)\) return 'blocked';\s*\n\s*if \(s === 'ready'\) return 'ready';/);
|
|
177
182
|
// cut is excluded from the total on both sides (migration 017's rule)
|
|
178
183
|
assert.match(rollup, /FILTER \(WHERE b\.bucket <> 'cut'\)::int\s+AS total/);
|
|
179
184
|
assert.match(read('modules/hall-ui/public/goals-page.js'), /total: row\.total \|\| 0/);
|