@bongos/core 1.24.28 → 1.24.30
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 +100 -70
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +4 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/api/openapi.json +36 -1
- package/docs/api-reference.md +7 -1
- package/docs/copy-inventory.md +247 -280
- package/docs/copy-registry.json +381 -691
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +5 -10
- package/docs/page-readings.json +327 -393
- package/modules/builder-settings/interaction-prefs.js +2 -2
- package/modules/government/db.js +29 -0
- package/modules/government/docket.js +61 -4
- package/modules/government/routes/government.js +2 -0
- package/modules/hall-ui/public/builders.js +3 -79
- package/modules/hall-ui/public/hall-render.js +6 -81
- package/modules/hall-ui/public/hall-widgets.js +4 -45
- package/modules/hall-ui/public/home-city-draw.js +275 -0
- package/modules/hall-ui/public/home-city.css +66 -0
- package/modules/hall-ui/public/home-city.js +223 -0
- package/modules/hall-ui/public/index.html +5 -6
- package/modules/hall-ui/public/index.states.json +73 -8
- package/modules/hall-ui/public/my-agents.js +35 -2
- package/modules/hall-ui/public/palette.js +8 -0
- package/modules/hall-ui/public/settings-moved.js +41 -0
- package/modules/hall-ui/public/settings.css +4 -28
- package/modules/hall-ui/public/settings.html +19 -107
- package/modules/hall-ui/public/settings.js +20 -215
- package/modules/hall-ui/public/settings.states.json +2 -4
- package/modules/hall-ui/records/home-city.md +66 -0
- package/modules/hall-ui/records/my-agents.md +3 -2
- package/modules/hall-ui/records/panel-family.md +2 -0
- package/modules/lifecycle/home-city.js +340 -0
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/routes/home-city.js +29 -0
- package/modules/lifecycle/ship-card.js +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +16 -0
- package/src/module-api.js +1 -1
- package/tests/claim_from_task_and_home.mjs +8 -43
- package/tests/government_tidy_up.mjs +21 -24
- package/tests/hall_board_world.mjs +10 -12
- package/tests/hall_home_ships_bounded.mjs +4 -3
- package/tests/hall_my_agents.mjs +88 -2
- package/tests/hall_palette.mjs +12 -0
- package/tests/hall_settings_world.mjs +64 -100
- package/tests/helpers.mjs +6 -5
- package/tests/home_bounded_reads.mjs +16 -31
- package/tests/home_city.mjs +385 -0
- package/tests/home_city_widget.mjs +120 -0
- package/tests/interaction_posture.mjs +3 -1
- package/tests/leaderboard_roles.mjs +3 -2
- package/tests/speciality_offer_ui.mjs +6 -5
- package/tests/speciality_walkthrough_ui.mjs +7 -5
- package/modules/hall-ui/public/settings-interaction.js +0 -132
- package/modules/hall-ui/public/settings-law-locks.js +0 -53
- package/modules/hall-ui/public/settings-render.js +0 -138
|
@@ -174,7 +174,7 @@ function shouldInject(behaviorPrefs) {
|
|
|
174
174
|
return contractFor(behaviorPrefs) !== '';
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
// The full catalog the /settings
|
|
177
|
+
// The full catalog the My agents page renders (it left /settings, task 1005044).
|
|
178
178
|
function fieldCatalog() {
|
|
179
179
|
return FIELD_KEYS.map((f) => ({
|
|
180
180
|
field: f,
|
|
@@ -185,7 +185,7 @@ function fieldCatalog() {
|
|
|
185
185
|
}));
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
// Everything the /me payload + the
|
|
188
|
+
// Everything the /me payload + the My agents page need in one object.
|
|
189
189
|
function describeForApi(behaviorPrefs) {
|
|
190
190
|
return {
|
|
191
191
|
fields: fieldCatalog(),
|
package/modules/government/db.js
CHANGED
|
@@ -902,6 +902,34 @@ async function listAccessHolders() {
|
|
|
902
902
|
return rows;
|
|
903
903
|
}
|
|
904
904
|
|
|
905
|
+
// THE GOAL OF A DOCKET MATTER (task 1005056): which goal each goal-shaped matter
|
|
906
|
+
// belongs to, so Home's project city can put the docket's "yours" over the right
|
|
907
|
+
// tower. A blocker belongs to every goal whose tasks it holds (task_blockers →
|
|
908
|
+
// tasks.goal_id); a criterion and a goal join request carry their goal. The
|
|
909
|
+
// tables are core's; this is a read across the wall in SQL, never a require.
|
|
910
|
+
// → [{ matter_id: 'blockers:12', goal_id: '1000095' }, …]; ids are numeric or skipped.
|
|
911
|
+
async function goalsOfMatters({ blockers = [], criteria = [], joinRequests = [] } = {}) {
|
|
912
|
+
const ids = (list) => (Array.isArray(list) ? list : []).map(String).filter((v) => /^\d{1,18}$/.test(v));
|
|
913
|
+
const [b, c, j] = [ids(blockers), ids(criteria), ids(joinRequests)];
|
|
914
|
+
if (!b.length && !c.length && !j.length) return [];
|
|
915
|
+
const { rows } = await pool.query(
|
|
916
|
+
`SELECT 'blockers:' || tb.blocker_id AS matter_id, t.goal_id::text AS goal_id
|
|
917
|
+
FROM task_blockers tb
|
|
918
|
+
JOIN tasks t ON t.id = tb.task_id
|
|
919
|
+
WHERE tb.blocker_id = ANY($1::bigint[]) AND t.goal_id IS NOT NULL
|
|
920
|
+
UNION
|
|
921
|
+
SELECT 'criteria_uat:' || c.id, c.goal_id::text
|
|
922
|
+
FROM done_when_criteria c
|
|
923
|
+
WHERE c.id = ANY($2::bigint[]) AND c.goal_id IS NOT NULL
|
|
924
|
+
UNION
|
|
925
|
+
SELECT 'goal_join_requests:' || r.id, r.goal_id::text
|
|
926
|
+
FROM goal_membership_requests r
|
|
927
|
+
WHERE r.id = ANY($3::bigint[])`,
|
|
928
|
+
[b, c, j],
|
|
929
|
+
);
|
|
930
|
+
return rows;
|
|
931
|
+
}
|
|
932
|
+
|
|
905
933
|
// Record that a builder holds an access. Idempotent: an existing row is kept as it
|
|
906
934
|
// was (its original granted_at/by). → true when a row was added.
|
|
907
935
|
async function grantAccess(accessKey, builderId, grantedBy) {
|
|
@@ -1342,6 +1370,7 @@ module.exports = {
|
|
|
1342
1370
|
listLeads,
|
|
1343
1371
|
listActiveArchons,
|
|
1344
1372
|
listAccessHolders,
|
|
1373
|
+
goalsOfMatters,
|
|
1345
1374
|
grantAccess,
|
|
1346
1375
|
revokeAccess,
|
|
1347
1376
|
getActiveBuilder,
|
|
@@ -932,14 +932,69 @@ async function blockersByPerson(builder, { contributions = registeredContributio
|
|
|
932
932
|
return { available: false, reason: 'unavailable', people: [], nobody: [], yours: waiting.forViewer(null, builder.id) };
|
|
933
933
|
}
|
|
934
934
|
await attachAsks(matters, asksDb.asksFor, timeoutMs);
|
|
935
|
+
const seats = await seatsFor(matters);
|
|
936
|
+
if (!seats) return { available: false, reason: 'unavailable', people: [], nobody: [], yours: waiting.forViewer(null, builder.id) };
|
|
937
|
+
const grouped = waiting.groupByPerson(matters, seats);
|
|
938
|
+
return { available: true, ...grouped, yours: waiting.forViewer(grouped, builder.id) };
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// The seats a set of matters routes against (loadSeats), reading a board's holders
|
|
942
|
+
// only when some matter names a board seat. null when the seats are unreadable.
|
|
943
|
+
async function seatsFor(matters) {
|
|
935
944
|
const boardSeat = matters.some((m) => /^board:/.test(routing.seatOf(m)));
|
|
936
945
|
const government = boardSeat
|
|
937
946
|
? await loadGovernment({ decisions: await board.decisionRulesView().catch(() => null) })
|
|
938
947
|
: { holders: {} };
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
948
|
+
return loadSeats(government);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// WHAT IS YOURS, PER GOAL (task 1005056). Home's project city puts a salmon dot
|
|
952
|
+
// over a working area for each matter waiting on the viewer; this answers it the
|
|
953
|
+
// docket's way, so the city and the docket can never disagree. The goal-shaped
|
|
954
|
+
// sources are read exactly as buildDocket reads them (each under its own gate),
|
|
955
|
+
// carry their stored asks, and are routed against the seats with routing.js — a
|
|
956
|
+
// matter counts when its route is `yours`. Like docketFor, a viewer without the
|
|
957
|
+
// docket's own atom is routed nothing. Each matter is then placed on its goal(s)
|
|
958
|
+
// (db.goalsOfMatters). A source that fails is left out, as the docket leaves it
|
|
959
|
+
// unavailable; unreadable seats say `available: false` rather than "none".
|
|
960
|
+
// → { available: true, goals: { [goal_id]: count } } | { available: false, reason }
|
|
961
|
+
const GOAL_SOURCES = Object.freeze(['blockers', 'criteria_uat', 'goal_join_requests']);
|
|
962
|
+
async function yoursByGoal(builder, {
|
|
963
|
+
contributions = registeredContributions(), now = Date.now(), timeoutMs = SOURCE_TIMEOUT_MS,
|
|
964
|
+
permissions = null, readAsks = asksDb.asksFor, readSeats = seatsFor, goalsOf = db.goalsOfMatters,
|
|
965
|
+
} = {}) {
|
|
966
|
+
if (!builder || builder.id == null) return { available: true, goals: {} };
|
|
967
|
+
const held = new Set(permissions || await resolver.resolveBuilderPermissions(builder.id));
|
|
968
|
+
if (!held.has(GOVERNMENT_GATE.permission)) return { available: true, goals: {} };
|
|
969
|
+
const ctx = Object.freeze({ builderId: String(builder.id), rank: builder.rank || null, has: (p) => held.has(p) });
|
|
970
|
+
const matters = [];
|
|
971
|
+
await Promise.all(GOAL_SOURCES.map(async (source) => {
|
|
972
|
+
const c = contributions.sources.get(source);
|
|
973
|
+
if (!c || !gateAllows(c.gate, ctx)) return;
|
|
974
|
+
const decl = EXPECTED_SOURCES.find((d) => d.source === source);
|
|
975
|
+
try {
|
|
976
|
+
const raw = await withTimeout(Promise.resolve(c.read(ctx, { now })), timeoutMs);
|
|
977
|
+
matters.push(...(Array.isArray(raw) ? raw : []).map((m) => normalizeMatter(m, { source, building: c.building || decl.building })).filter(Boolean));
|
|
978
|
+
} catch (err) {
|
|
979
|
+
log.warn({ source, err: err && err.message }, 'yours by goal: a source could not be read');
|
|
980
|
+
}
|
|
981
|
+
}));
|
|
982
|
+
const needs = matters.filter((m) => m.state === 'needs');
|
|
983
|
+
if (!needs.length) return { available: true, goals: {} };
|
|
984
|
+
await attachAsks(needs, readAsks, timeoutMs);
|
|
985
|
+
const seats = await readSeats(needs);
|
|
986
|
+
if (!seats) return { available: false, reason: 'unavailable' };
|
|
987
|
+
routing.routeAll(needs, ctx.builderId, seats, now);
|
|
988
|
+
const mine = needs.filter((m) => m.route && m.route.yours);
|
|
989
|
+
if (!mine.length) return { available: true, goals: {} };
|
|
990
|
+
const tail = (m) => m.id.slice(m.source.length + 1);
|
|
991
|
+
const of = (source) => mine.filter((m) => m.source === source).map(tail);
|
|
992
|
+
const rows = await goalsOf({ blockers: of('blockers'), criteria: of('criteria_uat'), joinRequests: of('goal_join_requests') });
|
|
993
|
+
const goals = {};
|
|
994
|
+
for (const r of rows || []) {
|
|
995
|
+
if (r && r.goal_id != null) goals[String(r.goal_id)] = (goals[String(r.goal_id)] || 0) + 1;
|
|
996
|
+
}
|
|
997
|
+
return { available: true, goals };
|
|
943
998
|
}
|
|
944
999
|
|
|
945
1000
|
// One matter with its stored ask and the seats, for passing it on to `toSeat`
|
|
@@ -1024,5 +1079,7 @@ module.exports = {
|
|
|
1024
1079
|
findMatterFor,
|
|
1025
1080
|
loadSeats,
|
|
1026
1081
|
blockersByPerson,
|
|
1082
|
+
yoursByGoal,
|
|
1083
|
+
GOAL_SOURCES,
|
|
1027
1084
|
matterWithSeats,
|
|
1028
1085
|
};
|
|
@@ -171,6 +171,8 @@ const governmentPort = {
|
|
|
171
171
|
askRequiredMessage: asks.ASK_REQUIRED_MESSAGE,
|
|
172
172
|
saveAsk: (...a) => asksDb.saveAsk(...a),
|
|
173
173
|
asksFor: (...a) => asksDb.asksFor(...a),
|
|
174
|
+
// task 1005056: per goal, the docket matters routed to this viewer (Home's city)
|
|
175
|
+
yoursByGoal: (viewer) => docket.yoursByGoal(viewer),
|
|
174
176
|
};
|
|
175
177
|
|
|
176
178
|
// An ask arrives bare, or wrapped as { ask } (the shape POST /blockers takes it in).
|
|
@@ -185,9 +185,6 @@
|
|
|
185
185
|
// (same pattern as lastNeedsState). These are un-landed confirmed tasks that
|
|
186
186
|
// GATE new claims and whose reward is pending until they land on ship.
|
|
187
187
|
let lastRebaseState = null;
|
|
188
|
-
// The public version progress, which the Home version tab draws (task 1004774).
|
|
189
|
-
// Null until it lands.
|
|
190
|
-
let lastProgressState = null;
|
|
191
188
|
|
|
192
189
|
// Cached /me payload from the most recent loadAll() call. Used by the
|
|
193
190
|
// welcome-chip reopen path (#408) so we can re-render the welcome panel
|
|
@@ -874,62 +871,6 @@
|
|
|
874
871
|
if (btn) window.location.hash = `#/idea/${encodeURIComponent(btn.dataset.triageIdea)}`;
|
|
875
872
|
});
|
|
876
873
|
|
|
877
|
-
// ---- Home "Next up" slice (task 2184) -------------------------------------
|
|
878
|
-
|
|
879
|
-
// Fill the top of the "Your work" card with the first five rows of the
|
|
880
|
-
// claimable queue (server order: priority, then calibrated estimate) so Home
|
|
881
|
-
// answers "what's my next action". Best-effort: a primer-gated 403, an auth
|
|
882
|
-
// hiccup, or an empty queue hides the slice — Home never shows an error for
|
|
883
|
-
// it. Re-runs with every loadAll (SSE nudges keep it current).
|
|
884
|
-
async function loadNextUp() {
|
|
885
|
-
const wrap = document.getElementById('home-next');
|
|
886
|
-
const list = document.getElementById('home-next-list');
|
|
887
|
-
const subhead = document.getElementById('home-recent-subh');
|
|
888
|
-
if (!wrap || !list) return;
|
|
889
|
-
try {
|
|
890
|
-
// ?limit=2 — this card is TWO lines. It used to download the entire claimable
|
|
891
|
-
// feed, every row with its full description, and `.slice(0, 2)` it: the same
|
|
892
|
-
// shape goal 1000072's criterion names, and the one goal-rollup.js was carved to
|
|
893
|
-
// fix on the Goals page (5,229,455 bytes to produce forty counters). The server
|
|
894
|
-
// already orders by priority then calibrated estimate, so the first two rows ARE
|
|
895
|
-
// the top two. `claimable_total` comes back so the foot can say how many are
|
|
896
|
-
// waiting instead of leaving two rows to imply there are only two.
|
|
897
|
-
// task 1004774: five rows, level with the five Recently shipped rows beside it.
|
|
898
|
-
const resp = await getJSON(`${API}/tasks/claimable?limit=5`, { softAuth: true });
|
|
899
|
-
// capped as well as asked for: a server that ignores ?limit must not grow the card.
|
|
900
|
-
const rows = resp && Array.isArray(resp.claimable) ? resp.claimable.slice(0, 5) : [];
|
|
901
|
-
if (!rows.length) { wrap.hidden = true; if (subhead) subhead.hidden = true; return; }
|
|
902
|
-
const total = Number(resp.claimable_total);
|
|
903
|
-
const cur = (window.__BRANDING__ && window.__BRANDING__.currency && window.__BRANDING__.currency.label) || 'credits';
|
|
904
|
-
list.innerHTML = rows.map((t) => {
|
|
905
|
-
const credits = window.OTBBranding?.taskCreditValue?.(t) ?? t.credits_reward;
|
|
906
|
-
const mins = t.est_minutes ? ` · ~${escapeHtml(String(t.est_minutes))}m` : '';
|
|
907
|
-
// task 1003459 — this row now claims directly (the extracted claim
|
|
908
|
-
// action, task 1003458) rather than only linking to the record and
|
|
909
|
-
// making the builder find the door there.
|
|
910
|
-
return `<li class="next-up__item">
|
|
911
|
-
<a class="next-up__title" href="/task/${encodeURIComponent(t.id)}">${escapeHtml(t.title || `task ${t.id}`)}</a>
|
|
912
|
-
<span class="next-up__meta">#${escapeHtml(String(t.id))}${mins} · ${fmtNum(credits)} ${escapeHtml(cur)} est.</span>
|
|
913
|
-
<button class="task__claim next-up__claim" type="button" data-task-id="${escapeHtml(String(t.id))}" aria-label="Claim task ${escapeHtml(String(t.id))}">Claim</button>
|
|
914
|
-
</li>`;
|
|
915
|
-
}).join('');
|
|
916
|
-
// Name the size of the queue these two came off. Silent when the count is
|
|
917
|
-
// missing or not bigger than what is shown — "See all 2" under two rows is
|
|
918
|
-
// noise, and a made-up number is worse than none.
|
|
919
|
-
const foot = document.getElementById('home-next-foot');
|
|
920
|
-
if (foot) {
|
|
921
|
-
foot.textContent = Number.isFinite(total) && total > rows.length
|
|
922
|
-
? `See all ${fmtNum(total)} ready work →`
|
|
923
|
-
: 'See all ready work →';
|
|
924
|
-
}
|
|
925
|
-
wrap.hidden = false;
|
|
926
|
-
if (subhead) subhead.hidden = false;
|
|
927
|
-
} catch (_) {
|
|
928
|
-
wrap.hidden = true;
|
|
929
|
-
if (subhead) subhead.hidden = true;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
|
|
933
874
|
// ---- the Roll’s rows, for the Standing card -----------------------------
|
|
934
875
|
//
|
|
935
876
|
// task 1003441 moved the leaderboard TABLE to its own page (/roster,
|
|
@@ -1084,11 +1025,6 @@
|
|
|
1084
1025
|
// /me because it spans all builders' work), loaded fire-and-forget so a
|
|
1085
1026
|
// slow/failed feed can't hold up the rest of the hall.
|
|
1086
1027
|
loadAcclaimFeed();
|
|
1087
|
-
// task 2184: the Home "Next up" slice — top of the claimable queue, so the
|
|
1088
|
-
// page answers "what's my next action" without a trip to the Work board.
|
|
1089
|
-
// Fire-and-forget; hides itself when the feed is gated (primer 403) or empty.
|
|
1090
|
-
loadNextUp();
|
|
1091
|
-
|
|
1092
1028
|
// #408: build the sticky nav now that every section has settled its
|
|
1093
1029
|
// hidden state (welcome → karma → onboarding → security/roster).
|
|
1094
1030
|
// The renderNav call also wires the IntersectionObserver for active-
|
|
@@ -1108,21 +1044,15 @@
|
|
|
1108
1044
|
// down the page — log and show an inline placeholder.
|
|
1109
1045
|
// #726: the per-skill model picker (Voices) moved to /builders/settings,
|
|
1110
1046
|
// so the hall no longer fetches /me/skill-prefs here.
|
|
1111
|
-
const [achAll, ships, versionsResp, lb
|
|
1047
|
+
const [achAll, ships, versionsResp, lb] = await Promise.allSettled([
|
|
1112
1048
|
getJSON(`${API}/achievements`),
|
|
1113
1049
|
// task 1004854: Home's newest few + a count — never the whole history,
|
|
1114
1050
|
// which only the Accomplishments ledger fetches (hall-render.js loadShipLedger).
|
|
1115
1051
|
getJSON(`${API}/me/recent-ships?limit=${HOME_SHIPS_LIMIT}`),
|
|
1116
1052
|
getJSON(`${API}/versions`),
|
|
1117
1053
|
getJSON(`${API}/public/leaderboard`), // task 1004437: the list with each builder's roles
|
|
1118
|
-
getJSON(`${API}/public/progress`), // the Home version tab (task 1004774)
|
|
1119
1054
|
]);
|
|
1120
1055
|
|
|
1121
|
-
if (progress.status === 'fulfilled') {
|
|
1122
|
-
lastProgressState = progress.value;
|
|
1123
|
-
renderGreeting();
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
1056
|
if (achAll.status === 'fulfilled') {
|
|
1127
1057
|
// Show top 10 by sort_order — already ordered server-side. (Set is locked at 8
|
|
1128
1058
|
// for V2, so the slice is defensive.) Unlocked + locked-with-progress come
|
|
@@ -1227,11 +1157,6 @@
|
|
|
1227
1157
|
// task 1004833: the shared vote control reports each landed vote to the feed.
|
|
1228
1158
|
$('#acclaim-feed')?.addEventListener('otb-vote', onAcclaimVote);
|
|
1229
1159
|
|
|
1230
|
-
// task 1003459 — the Claim button on each Next-up row; the action + the
|
|
1231
|
-
// delegated listener live in hall-render.js (builders.js is at the
|
|
1232
|
-
// 1,500-line ratchet, hall-ui/CLAUDE.md's "growth goes in the lib/renderer").
|
|
1233
|
-
wireNextUpClaim();
|
|
1234
|
-
|
|
1235
1160
|
$('#f-ships-window')?.addEventListener('change', onShipsFilterChange);
|
|
1236
1161
|
$('#f-ships-version')?.addEventListener('change', onShipsFilterChange);
|
|
1237
1162
|
$('#f-ships-sort')?.addEventListener('change', renderRecentShips);
|
|
@@ -1346,14 +1271,13 @@
|
|
|
1346
1271
|
// reads what this page holds NOW rather than what it held at boot. Everything
|
|
1347
1272
|
// else is stable for the life of the page. This call sits AFTER every
|
|
1348
1273
|
// declaration it passes — moving it earlier is a temporal-dead-zone crash.
|
|
1349
|
-
const { acclaimEvidenceHtml, analyticsState, lbState, mountHallWidgets, renderCurveBody, renderGreeting, renderLanding, renderSessionRefused, renderNav, renderOnboarding, renderProfile, renderRecentShips, renderWelcomeCore, HOME_SHIPS_LIMIT, populateShipVersionFilter, setRecentShips, shipsPager, welcomeDismissalKey
|
|
1350
|
-
API, RANK_ORDER, api, brandWorld, curLabel, emptyRow, fmtDate, getJSON,
|
|
1274
|
+
const { acclaimEvidenceHtml, analyticsState, lbState, mountHallWidgets, renderCurveBody, renderGreeting, renderLanding, renderSessionRefused, renderNav, renderOnboarding, renderProfile, renderRecentShips, renderWelcomeCore, HOME_SHIPS_LIMIT, populateShipVersionFilter, setRecentShips, shipsPager, welcomeDismissalKey } = window.OTBHallRender({
|
|
1275
|
+
API, RANK_ORDER, api, brandWorld, curLabel, emptyRow, fmtDate, getJSON, mins, normalizeRank, pct, postJSON, rankLabel, rankWeight, signClasses, toast, usd,
|
|
1351
1276
|
get instanceRepo() { return instanceRepo; },
|
|
1352
1277
|
get lastCostState() { return lastCostState; },
|
|
1353
1278
|
get lastKarmaState() { return lastKarmaState; },
|
|
1354
1279
|
get lastMeState() { return lastMeState; },
|
|
1355
1280
|
get lastNeedsState() { return lastNeedsState; },
|
|
1356
|
-
get lastProgressState() { return lastProgressState; },
|
|
1357
1281
|
get lastRebaseState() { return lastRebaseState; },
|
|
1358
1282
|
get newcomerGated() { return newcomerGated; },
|
|
1359
1283
|
});
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
'use strict';
|
|
23
23
|
|
|
24
24
|
window.OTBHallRender = function createHallRender(ctx) {
|
|
25
|
-
const { API, RANK_ORDER, api, brandWorld, curLabel, emptyRow, fmtDate, getJSON,
|
|
25
|
+
const { API, RANK_ORDER, api, brandWorld, curLabel, emptyRow, fmtDate, getJSON, mins, normalizeRank, pct, postJSON, rankLabel, rankWeight, signClasses, toast, usd } = ctx;
|
|
26
26
|
const { escapeHtml, $, fmtNum, moduleOn } = window.OTB;
|
|
27
27
|
|
|
28
28
|
const RANK_UNLOCKS = {
|
|
@@ -603,14 +603,6 @@
|
|
|
603
603
|
const el = document.getElementById(id);
|
|
604
604
|
if (el) el.textContent = text;
|
|
605
605
|
};
|
|
606
|
-
setText('stat-credits', fmtNum(builder.total_credits));
|
|
607
|
-
setText('stat-karma', fmtNum(karmaTotal));
|
|
608
|
-
setText('stat-karma-sub', karmaTrendText);
|
|
609
|
-
const statRank = document.getElementById('stat-rank');
|
|
610
|
-
if (statRank) statRank.innerHTML = builder.rank ? renderRankBadge(normalizeRank(builder.rank), { tip: false }) : '—';
|
|
611
|
-
setText('stat-rank-sub', rollPlace ? [`#${rollPlace.place} on the leaderboard`, rolePlaceText(rollPlace)].filter(Boolean).join(' · ') : '');
|
|
612
|
-
const statsSection = document.getElementById('home-stats');
|
|
613
|
-
if (statsSection && !ctx.newcomerGated) statsSection.hidden = false;
|
|
614
606
|
|
|
615
607
|
// task 1003464: the Home card as the mock draws it — an identity LINE and a
|
|
616
608
|
// three-row fact ledger. The mock repeats Credits and the place even though
|
|
@@ -635,7 +627,7 @@
|
|
|
635
627
|
<dl class="home-facts home-facts--grid">
|
|
636
628
|
<div><dt>${escapeHtml(curLabel(true))}</dt><dd>${fmtNum(builder.total_credits)}</dd></div>
|
|
637
629
|
<div><dt>Karma</dt><dd>${fmtNum(karmaTotal)}</dd></div>
|
|
638
|
-
<div><dt>Shipped</dt><dd id="standing-shipped">${
|
|
630
|
+
<div><dt>Shipped</dt><dd id="standing-shipped">${shipState.seen ? fmtNum(shipState.total) : '—'}</dd></div>
|
|
639
631
|
<div><dt>Leaderboard place</dt><dd>${place}</dd></div>
|
|
640
632
|
</dl>
|
|
641
633
|
<dl class="home-facts">
|
|
@@ -716,39 +708,15 @@
|
|
|
716
708
|
}
|
|
717
709
|
|
|
718
710
|
// task 1003464: the greeting IS Home's one h1 (the mock opens with it).
|
|
719
|
-
// task 1004780: it has no sub-line any more
|
|
720
|
-
//
|
|
721
|
-
// repeating both was redundant (owner, 2026-10-23).
|
|
711
|
+
// task 1004780: it has no sub-line any more (owner, 2026-10-23); since task
|
|
712
|
+
// 1005054 the city under it (home-city.js) is how far the project has come.
|
|
722
713
|
const greetingWord = (hour) => (hour < 12 ? 'Good morning' : (hour < 18 ? 'Good afternoon' : 'Good evening'));
|
|
723
|
-
// The version the tab is about: the one being built with the most work in it.
|
|
724
|
-
function currentVersion(progress) {
|
|
725
|
-
const rows = progress && Array.isArray(progress.progress) ? progress.progress : [];
|
|
726
|
-
return rows.filter((r) => r && r.status === 'building')
|
|
727
|
-
.sort((a, b) => (Number(b.total_count) || 0) - (Number(a.total_count) || 0))[0] || null;
|
|
728
|
-
}
|
|
729
|
-
// task 1004774: the version tab under the Home banner, drawn as a bar.
|
|
730
|
-
// Hidden while no version is being built.
|
|
731
|
-
function renderVersionTab(v) {
|
|
732
|
-
const tab = document.getElementById('home-version');
|
|
733
|
-
if (!tab) return;
|
|
734
|
-
if (!v) { tab.hidden = true; return; }
|
|
735
|
-
const pct = Math.max(0, Math.min(100, Math.round(Number(v.percent_complete) || 0)));
|
|
736
|
-
const name = document.getElementById('home-version-name');
|
|
737
|
-
const fill = document.getElementById('home-version-fill');
|
|
738
|
-
const pctEl = document.getElementById('home-version-pct');
|
|
739
|
-
if (name) name.textContent = v.version_id;
|
|
740
|
-
if (fill) fill.style.width = `${pct}%`;
|
|
741
|
-
if (pctEl) pctEl.textContent = `${pct}% complete`;
|
|
742
|
-
tab.setAttribute('aria-label', `${v.version_id} is ${pct} percent complete`);
|
|
743
|
-
tab.hidden = false;
|
|
744
|
-
}
|
|
745
714
|
function renderGreeting() {
|
|
746
715
|
const h1 = document.getElementById('hall-h1');
|
|
747
716
|
const builder = ctx.lastMeState && ctx.lastMeState.builder;
|
|
748
717
|
if (h1 && builder) {
|
|
749
718
|
h1.textContent = `${greetingWord(new Date().getHours())}, ${builder.display_name || builder.github_login || 'builder'}`;
|
|
750
719
|
}
|
|
751
|
-
renderVersionTab(currentVersion(ctx.lastProgressState));
|
|
752
720
|
}
|
|
753
721
|
const ONBOARDING_STAGE_ORDER = [
|
|
754
722
|
'auth_complete',
|
|
@@ -1096,28 +1064,9 @@
|
|
|
1096
1064
|
const countEl = $('#ships-count');
|
|
1097
1065
|
const pagerEl = $('#ships-pager');
|
|
1098
1066
|
|
|
1099
|
-
// task
|
|
1100
|
-
// 5-row recent-work table are fed from the same feed. Guarded writes.
|
|
1101
|
-
const statShipped = document.getElementById('stat-shipped');
|
|
1102
|
-
if (statShipped) statShipped.textContent = fmtNum(shipState.total);
|
|
1103
|
-
// task 1004774: the Standing hover card carries the same count.
|
|
1067
|
+
// task 1004774: the Standing hover card carries the shipped count.
|
|
1104
1068
|
const standingShipped = document.getElementById('standing-shipped');
|
|
1105
1069
|
if (standingShipped) standingShipped.textContent = fmtNum(shipState.total);
|
|
1106
|
-
const homeRecent = document.querySelector('#home-recent-table tbody');
|
|
1107
|
-
if (homeRecent) {
|
|
1108
|
-
const latest = sortShips(shipState.recent, 'recent').slice(0, HOME_SHIPS_LIMIT);
|
|
1109
|
-
homeRecent.innerHTML = latest.length
|
|
1110
|
-
? latest.map((s) => `
|
|
1111
|
-
<tr>
|
|
1112
|
-
<td class="ledger__num">#${s.id}</td>
|
|
1113
|
-
<td>${escapeHtml(s.title)}</td>
|
|
1114
|
-
<td class="ledger__drachmae">${fmtNum(window.OTBBranding?.taskCreditValue?.(s) ?? s.credits_reward)}</td>
|
|
1115
|
-
<td class="ledger__date">${fmtDate(s.shipped_at)}</td>
|
|
1116
|
-
</tr>`).join('')
|
|
1117
|
-
: emptyRow(4, 'Nothing shipped yet. Claim a task on the Work board to get started.');
|
|
1118
|
-
}
|
|
1119
|
-
const homeRecentSection = document.getElementById('home-recent');
|
|
1120
|
-
if (homeRecentSection && !ctx.newcomerGated) homeRecentSection.hidden = false;
|
|
1121
1070
|
|
|
1122
1071
|
// The ledger waits for its own fetch (loadShipLedger); until then it keeps its placeholder.
|
|
1123
1072
|
const all = shipState.all;
|
|
@@ -1378,31 +1327,7 @@
|
|
|
1378
1327
|
return `#${p.role.place} as ${ROLE_AS[p.role.craft]}`;
|
|
1379
1328
|
}
|
|
1380
1329
|
|
|
1381
|
-
// task 1003459 — the Claim button on each Home "Next up" row, through the
|
|
1382
|
-
// hall's ONE claim action (task 1003458) rather than a third hand-rolled
|
|
1383
|
-
// POST /claims. Lives here, not builders.js, because that file is at the
|
|
1384
|
-
// 1,500-line ratchet (hall-ui/CLAUDE.md: "growth in the page half has to be
|
|
1385
|
-
// paid for; new pure helper → the lib/renderer"). refresh re-fetches the
|
|
1386
|
-
// two-row slice so a claimed task drops out of it immediately.
|
|
1387
|
-
function wireNextUpClaim() {
|
|
1388
|
-
const claimAction = window.OTBClaimAction.makeClaimAction({ postJSON, toast, API, refresh: loadNextUp });
|
|
1389
|
-
$('#home-next-list')?.addEventListener('click', (e) => {
|
|
1390
|
-
const btn = e.target.closest('.next-up__claim');
|
|
1391
|
-
if (!btn || btn.disabled) return;
|
|
1392
|
-
const taskId = btn.dataset.taskId;
|
|
1393
|
-
btn.disabled = true;
|
|
1394
|
-
btn.textContent = '…';
|
|
1395
|
-
claimAction.claim(taskId).then((out) => {
|
|
1396
|
-
// A redirect to sign-in is already navigating away; re-enabling here
|
|
1397
|
-
// would just invite a click that can't land.
|
|
1398
|
-
if (!out.ok && !out.redirected) {
|
|
1399
|
-
btn.disabled = false;
|
|
1400
|
-
btn.textContent = 'Claim';
|
|
1401
|
-
}
|
|
1402
|
-
});
|
|
1403
|
-
});
|
|
1404
|
-
}
|
|
1405
1330
|
|
|
1406
|
-
return { acclaimEvidenceHtml, analyticsState, lbState, mountHallWidgets, renderCurveBody, renderGreeting, renderLanding, renderSessionRefused, renderNav, renderOnboarding, renderProfile, renderRecentShips, renderWelcomeCore, HOME_SHIPS_LIMIT, loadShipLedger, populateShipVersionFilter, setRecentShips, shipState, shipsPager, welcomeDismissalKey
|
|
1331
|
+
return { acclaimEvidenceHtml, analyticsState, lbState, mountHallWidgets, renderCurveBody, renderGreeting, renderLanding, renderSessionRefused, renderNav, renderOnboarding, renderProfile, renderRecentShips, renderWelcomeCore, HOME_SHIPS_LIMIT, loadShipLedger, populateShipVersionFilter, setRecentShips, shipState, shipsPager, welcomeDismissalKey };
|
|
1407
1332
|
};
|
|
1408
1333
|
})();
|
|
@@ -62,29 +62,8 @@
|
|
|
62
62
|
'<p class="ledger__note" id="onboarding-status" hidden></p>',
|
|
63
63
|
{ view: 'home', zone: 'home-banner', labelledby: 'onboarding-h', hiddenByDefault: true });
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// shipped count once the ships feed settles (renderRecentShips).
|
|
68
|
-
// task 2169: each tile is a LINK — the stat row doubles as the fastest
|
|
69
|
-
// navigation on the page (Credits → the economy doc, Shipped/Karma → the
|
|
70
|
-
// Standing sections that explain them, Rank → the ladder).
|
|
71
|
-
w('home-stats', 'Overview', 15,
|
|
72
|
-
'<h2 id="home-stats-h" class="sr-only">Overview</h2>' +
|
|
73
|
-
'<div class="stat-row" id="home-stats-row">' +
|
|
74
|
-
'<a class="stat-tile" href="/drachmae"><span class="stat-tile__label" data-currency-label="caps">Credits</span><span class="stat-tile__num" id="stat-credits">—</span></a>' +
|
|
75
|
-
'<a class="stat-tile" href="#accomplishments-work"><span class="stat-tile__label">Shipped</span><span class="stat-tile__num" id="stat-shipped">—</span><span class="stat-tile__sub" id="stat-shipped-sub"></span></a>' +
|
|
76
|
-
'<a class="stat-tile" href="#accomplishments-karma"><span class="stat-tile__label">Karma</span><span class="stat-tile__num" id="stat-karma">—</span><span class="stat-tile__sub" id="stat-karma-sub"></span></a>' +
|
|
77
|
-
'<a class="stat-tile" href="/ranks"><span class="stat-tile__label">Rank</span><span class="stat-tile__num stat-tile__num--badge" id="stat-rank">—</span><span class="stat-tile__sub" id="stat-rank-sub"></span></a>' +
|
|
78
|
-
'</div>' +
|
|
79
|
-
// task 1004774: the version tab under the banner — how far the version being
|
|
80
|
-
// built has come. hall-render.js renderGreeting fills it from the same
|
|
81
|
-
// progress read as the greeting's sub-line; hidden until there is a version.
|
|
82
|
-
'<a class="version-tab" id="home-version" href="/roadmap" hidden>' +
|
|
83
|
-
'<span class="version-tab__name" id="home-version-name"></span>' +
|
|
84
|
-
'<span class="version-tab__bar" aria-hidden="true"><span class="version-tab__fill" id="home-version-fill"></span></span>' +
|
|
85
|
-
'<span class="version-tab__pct" id="home-version-pct"></span>' +
|
|
86
|
-
'</a>',
|
|
87
|
-
{ view: 'home', zone: 'home-stats', sectionClass: 'scroll scroll--bare', labelledby: 'home-stats-h', hiddenByDefault: true });
|
|
65
|
+
// (task 1005054: the quick-stats strip and the version tab left Home. The
|
|
66
|
+
// zone they held, home-stats, is the project city's — home-city.js.)
|
|
88
67
|
|
|
89
68
|
// (task 1004774: the Standing summary is no longer a Home card. It is the
|
|
90
69
|
// chip at the top right of the page head — index.html #hall-standing — whose
|
|
@@ -97,28 +76,8 @@
|
|
|
97
76
|
// goal-inbox.js owns (#attention-ideas), the single landing place for every
|
|
98
77
|
// actionable item. builders.js still gates the /inbox fetch to Metic+.)
|
|
99
78
|
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// claimable queue (builders.js loadNextUp fills it; hidden when the feed is
|
|
103
|
-
// gated or empty), then the 5-row recent-ships table below it.
|
|
104
|
-
w('home-recent', 'Your work', 28,
|
|
105
|
-
'<h2 id="home-recent-h" class="scroll__h">Your work</h2>' +
|
|
106
|
-
// task 1004774: Next up and Recently shipped sit side by side (stacking
|
|
107
|
-
// again on a phone); when Next up is hidden, shipped takes the full width.
|
|
108
|
-
'<div class="home-split">' +
|
|
109
|
-
'<div class="home-split__col" id="home-next" hidden>' +
|
|
110
|
-
'<h3 class="ginbox__sec-h">Next up — ready to claim</h3>' +
|
|
111
|
-
'<ul class="next-up" id="home-next-list"></ul>' +
|
|
112
|
-
'<p class="next-up__foot"><a href="/work" id="home-next-foot">See all ready work →</a></p>' +
|
|
113
|
-
'</div>' +
|
|
114
|
-
'<div class="home-split__col">' +
|
|
115
|
-
'<h3 class="ginbox__sec-h" id="home-recent-subh" hidden>Recently shipped</h3>' +
|
|
116
|
-
'<table class="ledger" id="home-recent-table"><thead><tr><th>№</th><th>Work</th><th data-currency-label="caps">Credits</th><th>Shipped</th></tr></thead>' +
|
|
117
|
-
'<tbody><tr><td colspan="4" class="ledger__empty"><span class="hall-loading" role="status">Loading…</span></td></tr></tbody></table>' +
|
|
118
|
-
'<p class="card-foot"><a href="#accomplishments-work">All your work →</a></p>' +
|
|
119
|
-
'</div>' +
|
|
120
|
-
'</div>',
|
|
121
|
-
{ view: 'home', zone: 'home-main', labelledby: 'home-recent-h', hiddenByDefault: true });
|
|
79
|
+
// (task 1005054: Your work — Next up and Recently shipped — left Home; ready
|
|
80
|
+
// work is the Work board's and your history is Accomplishments'.)
|
|
122
81
|
|
|
123
82
|
// 0f. The founding wall (task 1004424) — everyone who joined while the project was
|
|
124
83
|
// being founded, founder first, each with their mark. Hidden until the founding
|