@bongos/core 1.19.1044 → 1.19.1045
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 +35 -25
- package/docs/copy-inventory.md +129 -138
- package/docs/copy-registry.json +155 -269
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/claim-action.js +250 -0
- package/modules/hall-ui/public/goals-page.js +28 -44
- package/modules/hall-ui/public/goals-render.js +7 -28
- package/modules/hall-ui/public/goals.html +5 -2
- package/modules/hall-ui/public/work.html +4 -1
- package/modules/hall-ui/public/work.js +35 -123
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/tests/claim_action.mjs +417 -0
- package/tests/goal_category_ui.mjs +3 -0
- package/tests/goal_criterion_confirm_ui.mjs +3 -0
- package/tests/goal_forest_page.mjs +3 -0
- package/tests/goal_invite_consent.mjs +3 -1
- package/tests/goal_mine_lens.mjs +3 -0
- package/tests/hall_mine_lens.mjs +3 -0
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
// file's exact variants (date-only fmtDate; a 2200ms clearing toast).
|
|
37
37
|
// fmtAgo / toAuth / getJSON / postJSON stay local below.
|
|
38
38
|
const { escapeHtml, $, fmtNum, errCode, errDetails } = window.OTB; // task 1002462: envelope readers
|
|
39
|
+
// The hall's ONE claim/release refusal copy (task 1003458). This board's own
|
|
40
|
+
// copy is gone: a second surface that wants a claim button consumes this
|
|
41
|
+
// instead of lifting the switch, which is how the two drifted apart before.
|
|
42
|
+
const { friendlyClaimError, makeClaimAction } = window.OTBClaimAction;
|
|
39
43
|
// Shared task-domain helpers (task 1002463). These lived in two copies here and
|
|
40
44
|
// in the other board and had already drifted — goals-page.js had silently lost
|
|
41
45
|
// friendlyBatchReason's MODULE_CONFLICT case. Destructured so every call site
|
|
@@ -180,6 +184,18 @@
|
|
|
180
184
|
return data;
|
|
181
185
|
}
|
|
182
186
|
|
|
187
|
+
// The hall's ONE claim/release action (task 1003458). Everything this board
|
|
188
|
+
// used to hand-roll four times — the POST, the refusal sentence, the
|
|
189
|
+
// stale-view refresh, the all-or-nothing batch report — lives in
|
|
190
|
+
// claim-action.js now; doClaim/doRelease/doBulkClaim/doBulkRelease below keep
|
|
191
|
+
// only what is genuinely this page: button text, the re-entrancy guard, the
|
|
192
|
+
// confirm() wording and the selection. friendlyBatchReason is injected rather
|
|
193
|
+
// than imported there, so claim-action.js stays dependency-free.
|
|
194
|
+
// refreshLive is a hoisted declaration; toast and API are already bound above.
|
|
195
|
+
const claimAction = makeClaimAction({
|
|
196
|
+
postJSON, toast, API, refresh: refreshLive, describeBatchReason: friendlyBatchReason,
|
|
197
|
+
});
|
|
198
|
+
|
|
183
199
|
// ---- paginated lists (task 1931/1955 → kit, task 2170) --------------------
|
|
184
200
|
// All four Work-page lists paginate through the shared OTBKit pager (the
|
|
185
201
|
// canonical copy of the helper that used to live here). localStorage keys are
|
|
@@ -627,51 +643,6 @@
|
|
|
627
643
|
}
|
|
628
644
|
}
|
|
629
645
|
}
|
|
630
|
-
|
|
631
|
-
// ---- claim / release actions ---------------------------------------------
|
|
632
|
-
|
|
633
|
-
// Map the server's typed error code to a builder-readable line. The codes are
|
|
634
|
-
// exactly those POST /claims and POST /claims/:id/resolve can return.
|
|
635
|
-
function friendlyActionError(status, data) {
|
|
636
|
-
// task 1002462: read the ADR 0116 envelope. `data.error` is an OBJECT, so the
|
|
637
|
-
// old `const code = data && data.error` compared a string to an object and
|
|
638
|
-
// EVERY case below fell through to the generic default — a wrong-but-plausible
|
|
639
|
-
// toast on every claim/release refusal.
|
|
640
|
-
const det = errDetails(data);
|
|
641
|
-
switch (errCode(data)) {
|
|
642
|
-
case 'ALREADY_CLAIMED':
|
|
643
|
-
return 'Someone else claimed that task first. The board will refresh.';
|
|
644
|
-
case 'TASK_NOT_READY':
|
|
645
|
-
return 'That task is no longer ready to be claimed.';
|
|
646
|
-
case 'TOUCHES_CONFLICT': {
|
|
647
|
-
const titles = Array.isArray(det.conflicts) ? det.conflicts.map((c) => c.title).filter(Boolean) : [];
|
|
648
|
-
return titles.length
|
|
649
|
-
? `That task overlaps an active claim: ${titles.join('; ')}. Wait for it to ship.`
|
|
650
|
-
: 'That task overlaps another active claim. Wait for it to ship.';
|
|
651
|
-
}
|
|
652
|
-
case 'DEPS_NOT_SHIPPED':
|
|
653
|
-
return 'That task waits on unshipped dependencies. Pick another for now.';
|
|
654
|
-
case 'rank_too_low_for_task':
|
|
655
|
-
case 'XENOS_RESTRICTED':
|
|
656
|
-
case 'INSUFFICIENT_RANK':
|
|
657
|
-
return 'Your rank is not yet high enough for that task.';
|
|
658
|
-
case 'newcomer_must_ship_first':
|
|
659
|
-
return det.what_to_do || 'Ship your current claim before opening another.';
|
|
660
|
-
case 'NOT_YOUR_CLAIM':
|
|
661
|
-
return 'That claim is not yours to release.';
|
|
662
|
-
case 'ALREADY_RESOLVED':
|
|
663
|
-
return 'That claim was already resolved. The board will refresh.';
|
|
664
|
-
case 'BUILDER_INACTIVE':
|
|
665
|
-
return 'Your account is inactive. Contact an Archon.';
|
|
666
|
-
default:
|
|
667
|
-
return `The board could not complete that (${status}). Try again.`;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Maps a single batch-item failure reason (POST /claims/batch,
|
|
672
|
-
// /claims/batch/validate) to a builder-readable fragment. Distinct from
|
|
673
|
-
// friendlyActionError above because a batch item's shape differs (reason,
|
|
674
|
-
// not error; extra fields like conflictsWithTaskId/module_key).
|
|
675
646
|
async function copyText(text) {
|
|
676
647
|
try { await navigator.clipboard.writeText(text); return true; }
|
|
677
648
|
catch (_) {
|
|
@@ -735,18 +706,15 @@
|
|
|
735
706
|
async function scheduleValidate(key, ids) {
|
|
736
707
|
if (validateDebounce) clearTimeout(validateDebounce);
|
|
737
708
|
validateDebounce = setTimeout(async () => {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
validateCache.parallelSafe = false;
|
|
748
|
-
validateCache.byId = new Map();
|
|
749
|
-
}
|
|
709
|
+
// The dry run lives in claim-action.js with the rest of the claim path; it
|
|
710
|
+
// is best-effort there, so a failure lands as parallelSafe:false with an
|
|
711
|
+
// empty map rather than a throw — Parallel stays disabled with a generic
|
|
712
|
+
// title instead of copying a prompt for tasks nobody verified. The
|
|
713
|
+
// debounce, the cache key and the re-render stay here: page furniture.
|
|
714
|
+
const out = await claimAction.validateMany(ids);
|
|
715
|
+
validateCache.key = key;
|
|
716
|
+
validateCache.parallelSafe = out.parallelSafe;
|
|
717
|
+
validateCache.byId = out.byId;
|
|
750
718
|
renderBulkToolbar();
|
|
751
719
|
}, 350);
|
|
752
720
|
}
|
|
@@ -911,33 +879,8 @@
|
|
|
911
879
|
const btn = $('#bulk-claim-btn');
|
|
912
880
|
if (btn) { btn.disabled = true; btn.textContent = 'Claiming…'; }
|
|
913
881
|
try {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
toast(`You claimed #${ids[0]}. Open a terminal to start work.`);
|
|
917
|
-
} else {
|
|
918
|
-
await postJSON(`${API}/claims/batch`, { task_ids: ids, bind_session: false });
|
|
919
|
-
toast(`Claimed ${ids.length} tasks. Open a terminal to start work.`);
|
|
920
|
-
}
|
|
921
|
-
selection.claim.clear();
|
|
922
|
-
await refreshLive();
|
|
923
|
-
} catch (err) {
|
|
924
|
-
if (err && err.message === 'redirecting to auth') return;
|
|
925
|
-
// task 1002462: same envelope defect — this tested `err.data.error === 'batch_failed'`
|
|
926
|
-
// against an object, so an all-or-nothing batch refusal rendered as the generic toast
|
|
927
|
-
// and the per-task reasons were dropped on the floor.
|
|
928
|
-
if (err.status === 409 && errCode(err.data) === 'batch_failed') {
|
|
929
|
-
// One read covers both envelopes — errDetails falls back to the body (task 1003657).
|
|
930
|
-
const bdet = errDetails(err.data);
|
|
931
|
-
const failures = Array.isArray(bdet.failures) ? bdet.failures : [];
|
|
932
|
-
const lines = failures.map((f) => `#${f.task_id} (${friendlyBatchReason(f)})`);
|
|
933
|
-
toast(lines.length
|
|
934
|
-
? `The batch could not be claimed — ${lines.join('; ')}.`
|
|
935
|
-
: 'The batch could not be claimed. It was left untouched (all-or-nothing).');
|
|
936
|
-
await refreshLive().catch(() => {});
|
|
937
|
-
} else {
|
|
938
|
-
toast(friendlyActionError(err.status, err.data));
|
|
939
|
-
if (err.status === 409) await refreshLive().catch(() => {});
|
|
940
|
-
}
|
|
882
|
+
const out = await claimAction.claimMany(ids);
|
|
883
|
+
if (out.ok) selection.claim.clear();
|
|
941
884
|
} finally {
|
|
942
885
|
renderBulkToolbar();
|
|
943
886
|
}
|
|
@@ -952,22 +895,8 @@
|
|
|
952
895
|
const btn = $('#bulk-release-btn');
|
|
953
896
|
if (btn) { btn.disabled = true; btn.textContent = 'Releasing…'; }
|
|
954
897
|
try {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
toast(`Released #${entries[0][1]}. It returns to the queue.`);
|
|
958
|
-
} else {
|
|
959
|
-
const result = await postJSON(`${API}/claims/batch/release`, { claim_ids: entries.map(([cid]) => Number(cid)) });
|
|
960
|
-
const released = Array.isArray(result.released) ? result.released.length : 0;
|
|
961
|
-
const failed = Array.isArray(result.failures) ? result.failures.length : 0;
|
|
962
|
-
toast(failed
|
|
963
|
-
? `Released ${released} of ${entries.length} — ${failed} could not be released.`
|
|
964
|
-
: `Released ${released} tasks. They return to the queue.`);
|
|
965
|
-
}
|
|
966
|
-
selection.release.clear();
|
|
967
|
-
await refreshLive();
|
|
968
|
-
} catch (err) {
|
|
969
|
-
if (err && err.message === 'redirecting to auth') return;
|
|
970
|
-
toast(friendlyActionError(err.status, err.data));
|
|
898
|
+
const out = await claimAction.releaseMany(entries);
|
|
899
|
+
if (out.ok) selection.release.clear();
|
|
971
900
|
} finally {
|
|
972
901
|
renderBulkToolbar();
|
|
973
902
|
}
|
|
@@ -979,18 +908,10 @@
|
|
|
979
908
|
pending.add(key);
|
|
980
909
|
if (btn) { btn.disabled = true; btn.textContent = 'Claiming…'; }
|
|
981
910
|
try {
|
|
982
|
-
|
|
983
|
-
//
|
|
984
|
-
// it
|
|
985
|
-
|
|
986
|
-
toast(`You claimed #${taskId}. Open a terminal to start work.`);
|
|
987
|
-
await refreshLive();
|
|
988
|
-
} catch (err) {
|
|
989
|
-
if (err && err.message === 'redirecting to auth') return;
|
|
990
|
-
toast(friendlyActionError(err.status, err.data));
|
|
991
|
-
// A conflict/already-claimed means our view is stale — pull fresh truth.
|
|
992
|
-
if (err.status === 409) await refreshLive().catch(() => {});
|
|
993
|
-
if (btn) { btn.disabled = false; btn.textContent = 'Claim'; }
|
|
911
|
+
const out = await claimAction.claim(taskId);
|
|
912
|
+
// Leave the button disabled while the page is navigating to sign-in —
|
|
913
|
+
// re-enabling it invites a click that cannot land.
|
|
914
|
+
if (!out.ok && !out.redirected && btn) { btn.disabled = false; btn.textContent = 'Claim'; }
|
|
994
915
|
} finally {
|
|
995
916
|
pending.delete(key);
|
|
996
917
|
}
|
|
@@ -1003,17 +924,8 @@
|
|
|
1003
924
|
pending.add(key);
|
|
1004
925
|
if (btn) { btn.disabled = true; btn.textContent = 'Releasing…'; }
|
|
1005
926
|
try {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
// server-side (NOT_YOUR_CLAIM).
|
|
1009
|
-
await postJSON(`${API}/claims/${encodeURIComponent(claimId)}/resolve`, { outcome: 'abandoned' });
|
|
1010
|
-
toast(`Released #${taskId}. It returns to the queue.`);
|
|
1011
|
-
await refreshLive();
|
|
1012
|
-
} catch (err) {
|
|
1013
|
-
if (err && err.message === 'redirecting to auth') return;
|
|
1014
|
-
toast(friendlyActionError(err.status, err.data));
|
|
1015
|
-
if (err.status === 409) await refreshLive().catch(() => {});
|
|
1016
|
-
if (btn) { btn.disabled = false; btn.textContent = 'Release'; }
|
|
927
|
+
const out = await claimAction.release(claimId, taskId);
|
|
928
|
+
if (!out.ok && !out.redirected && btn) { btn.disabled = false; btn.textContent = 'Release'; }
|
|
1017
929
|
} finally {
|
|
1018
930
|
pending.delete(key);
|
|
1019
931
|
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1045",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.1045",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1045",
|
|
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
|
@@ -7565,5 +7565,11 @@
|
|
|
7565
7565
|
"id": "1003518",
|
|
7566
7566
|
"text": "A blocker that was linked to the wrong task — a duplicate, or a mistake — can now be unlinked, and the task goes back to being claimable if nothing else was holding it. Before this the only way out was to mark the blocker reso"
|
|
7567
7567
|
}
|
|
7568
|
+
],
|
|
7569
|
+
"1.19.1045": [
|
|
7570
|
+
{
|
|
7571
|
+
"id": "1003458",
|
|
7572
|
+
"text": "The Tasks board and the Goals page now claim and release work through one shared piece of code instead of two copies that had already drifted apart — the same refusal was worded differently on each page, and one had lost its \""
|
|
7573
|
+
}
|
|
7568
7574
|
]
|
|
7569
7575
|
}
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.1045'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|