@bongos/core 1.17.1 → 1.17.3
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 +521 -281
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +2 -0
- package/.claude/skills/builder-exit/SKILL.md +2 -2
- package/.claude/skills/builder-redteam/SKILL.md +3 -3
- package/.claude/skills/builder-setup/SKILL.md +2 -2
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/dev/SKILL.md +1 -1
- package/.claude/skills/idea-triage/SKILL.md +2 -2
- package/.claude/skills/ideate/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/new-project/SKILL.md +3 -0
- package/.claude/skills/paint/SKILL.md +1 -1
- package/GOVERNANCE.md +1 -1
- package/README.md +1 -1
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +4 -2
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0001-stack-choice-single-vps.md +1 -1
- package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
- package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
- package/docs/adr/0020-security-model-public-stub.md +1 -1
- package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
- package/docs/adr/0030-builders-hall-live-updates.md +1 -1
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
- package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
- package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
- package/docs/adr/0057-container-cost-ledger.md +3 -3
- package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
- package/docs/adr/0096-require-reward-before-workable.md +9 -9
- package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
- package/docs/adr/0100-two-repo-instance-model.md +1 -1
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
- package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
- package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
- package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
- package/docs/adr/README.md +9 -6
- package/docs/api/openapi.json +65 -26
- package/docs/api-reference.md +3 -2
- package/docs/architecture.md +2 -2
- package/docs/branding-contract.md +5 -4
- package/docs/canonical-permissions.md +1 -1
- package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
- package/docs/file-map.md +18 -17
- package/docs/handoff-template.md +2 -2
- package/docs/modules-contract.md +6 -1
- package/docs/onboarding/diagrams/README.md +7 -7
- package/docs/onboarding/drachmae-and-karma.md +18 -18
- package/docs/onboarding/primer.md +9 -9
- package/docs/recipes/audit-replay.md +2 -2
- package/docs/recipes/builder-box-lifecycle.md +2 -2
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/ops-gotchas.md +1 -1
- package/docs/recipes/private-npm-distribution.md +17 -0
- package/docs/recipes/staging-environment.md +1 -1
- package/docs/recipes/upgrading-the-core.md +55 -0
- package/docs/research/cursor-competitive-analysis.md +1 -1
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
- package/modules/discord/module.json +2 -1
- package/modules/economy/achievements.js +9 -1
- package/modules/economy/credits.js +10 -1
- package/modules/hall-ui/public/builders.js +137 -620
- package/modules/hall-ui/public/goal-inbox.js +232 -0
- package/modules/hall-ui/public/goals-page.js +4 -1
- package/modules/hall-ui/public/goals.html +8 -0
- package/modules/hall-ui/public/hall-kit.js +474 -0
- package/modules/hall-ui/public/hall-widgets.js +14 -5
- package/modules/hall-ui/public/index.html +3 -0
- package/modules/hall-ui/public/instances-panel.js +24 -3
- package/modules/hall-ui/public/new-project-wizard.js +7 -5
- package/modules/hall-ui/public/style.css +168 -0
- package/modules/hall-ui/public/work.css +194 -0
- package/modules/hall-ui/public/work.html +46 -374
- package/modules/hall-ui/public/work.js +137 -102
- package/modules/lifecycle/criterion-suggest.js +80 -0
- package/modules/lifecycle/db.js +42 -5
- package/modules/lifecycle/routes/goals.js +26 -0
- package/modules/lifecycle/routes/tasks.js +21 -0
- package/modules/provisioning/CLAUDE.md +15 -0
- package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
- package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
- package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
- package/modules/provisioning/module.json +17 -0
- package/modules/provisioning/onboard-plan.js +37 -0
- package/modules/provisioning/provisioning.js +539 -0
- package/modules/provisioning/rate-limit.js +35 -0
- package/modules/provisioning/routes/provisioning.js +607 -0
- package/modules/provisioning/tests/provisioning.mjs +232 -0
- package/modules/provisioning/ui/.gitkeep +0 -0
- package/modules/security/secret-scrub.js +10 -0
- package/package-lock.json +2 -2
- package/package.json +7 -2
- package/scripts/discord/README.md +1 -1
- package/scripts/gds/claim.js +98 -48
- package/scripts/gds/claude-materialize.js +30 -9
- package/scripts/gds/cutover-codemod.js +241 -0
- package/scripts/gds/fitness.js +54 -0
- package/scripts/gds/init.js +59 -3
- package/scripts/gds/lint-infra-exec.js +70 -0
- package/scripts/gds/lint-workflow-exec.js +63 -0
- package/scripts/gds/mirror-lineage-check.js +14 -5
- package/scripts/gds/mirror-redact.js +8 -0
- package/scripts/gds/onboard.js +4 -0
- package/scripts/gds/package-core.js +18 -3
- package/scripts/gds/publish-manifest.js +60 -2
- package/scripts/gds/run-routine.js +51 -13
- package/scripts/gds/run-unit-tests.js +4 -0
- package/scripts/gds/update-channel.js +186 -0
- package/scripts/gds/upgrade.js +116 -9
- package/scripts/hall-preview/README.md +103 -0
- package/scripts/hall-preview/fixtures/access-requests.json +1 -0
- package/scripts/hall-preview/fixtures/achievements.json +76 -0
- package/scripts/hall-preview/fixtures/analytics__builder.json +251 -0
- package/scripts/hall-preview/fixtures/audit-log.json +1 -0
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +1 -0
- package/scripts/hall-preview/fixtures/builders__active-claims.json +1 -0
- package/scripts/hall-preview/fixtures/builders__directory.json +81 -0
- package/scripts/hall-preview/fixtures/builders__roster.json +1 -0
- package/scripts/hall-preview/fixtures/goals.json +502 -0
- package/scripts/hall-preview/fixtures/goals__25.json +96 -0
- package/scripts/hall-preview/fixtures/goals__26.json +67 -0
- package/scripts/hall-preview/fixtures/goals__3.json +103 -0
- package/scripts/hall-preview/fixtures/goals__35.json +49 -0
- package/scripts/hall-preview/fixtures/goals__conflicts.json +154 -0
- package/scripts/hall-preview/fixtures/goals__detail.json +96 -0
- package/scripts/hall-preview/fixtures/goals__inbox.json +5 -0
- package/scripts/hall-preview/fixtures/goals__requests.json +3 -0
- package/scripts/hall-preview/fixtures/grades__by-builder.json +1 -0
- package/scripts/hall-preview/fixtures/inbox.json +330 -0
- package/scripts/hall-preview/fixtures/instance.json +124 -0
- package/scripts/hall-preview/fixtures/leaderboard.json +184 -0
- package/scripts/hall-preview/fixtures/me.json +476 -0
- package/scripts/hall-preview/fixtures/me__recent-ships.json +304 -0
- package/scripts/hall-preview/fixtures/override-requests.json +1 -0
- package/scripts/hall-preview/fixtures/provisioning__fleet.json +1 -0
- package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +1 -0
- package/scripts/hall-preview/fixtures/provisioning__instances.json +1 -0
- package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +516 -0
- package/scripts/hall-preview/fixtures/public__bounty-table.json +37 -0
- package/scripts/hall-preview/fixtures/public__cost-summary.json +742 -0
- package/scripts/hall-preview/fixtures/public__estimation-drift.json +41 -0
- package/scripts/hall-preview/fixtures/public__grades.json +50 -0
- package/scripts/hall-preview/fixtures/public__progress.json +124 -0
- package/scripts/hall-preview/fixtures/public__recent-shipped.json +426 -0
- package/scripts/hall-preview/fixtures/public__repo-health.json +160 -0
- package/scripts/hall-preview/fixtures/public__repo-info.json +5 -0
- package/scripts/hall-preview/fixtures/security__docs.json +1 -0
- package/scripts/hall-preview/fixtures/security__reports.json +1 -0
- package/scripts/hall-preview/fixtures/sessions__search.json +1 -0
- package/scripts/hall-preview/fixtures/tasks.json +2420 -0
- package/scripts/hall-preview/fixtures/tasks__claimable.json +2555 -0
- package/scripts/hall-preview/fixtures/tasks__detail.json +111 -0
- package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +330 -0
- package/scripts/hall-preview/fixtures/versions.json +89 -0
- package/scripts/hall-preview/refresh-fixtures.js +77 -0
- package/scripts/hall-preview/server.js +178 -0
- package/scripts/migrate.sh +29 -4
- package/src/bongos/auth.js +14 -1
- package/src/bongos/optimizer.js +37 -2
- package/src/bongos/serve-internal.js +10 -1
- package/src/branding.js +1 -0
- package/src/module-api.js +12 -1
- package/src/module-loader/loader.js +36 -4
- package/src/module-loader/manifest-schema.js +16 -1
- package/tests/branding.mjs +11 -0
- package/tests/claim_error_surface.mjs +107 -0
- package/tests/claude_materialize.mjs +20 -0
- package/tests/consumer_layout_boot.mjs +302 -0
- package/tests/criterion_suggest.mjs +106 -0
- package/tests/cutover_codemod.mjs +184 -0
- package/tests/device_flow_start.mjs +25 -2
- package/tests/hook_core_resolve.mjs +125 -0
- package/tests/infra_exec_paths.mjs +104 -0
- package/tests/init.mjs +47 -0
- package/tests/migrate_root_resolution.mjs +97 -0
- package/tests/mirror_lineage_proof.mjs +14 -0
- package/tests/module_loader.mjs +42 -0
- package/tests/module_manifest.mjs +19 -0
- package/tests/onboard.mjs +13 -0
- package/tests/package_core.mjs +8 -0
- package/tests/provisioning_github_app_routes.mjs +15 -0
- package/tests/publish_manifest.mjs +52 -1
- package/tests/run_routine.mjs +26 -1
- package/tests/run_routine_two_root.mjs +77 -0
- package/tests/scrubber_corpus.mjs +17 -0
- package/tests/update_channel.mjs +186 -0
- package/tests/upgrade.mjs +131 -0
- package/tests/workflow_exec_paths.mjs +98 -0
- package/docs/design/container-exterior-tiles.md +0 -49
- package/docs/design/container-exterior.md +0 -81
- package/docs/design/container-interior.md +0 -77
- package/docs/design/world-map-scroll.md +0 -41
- package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
- package/docs/recipes/cloudbongos-staging-gate.md +0 -139
- package/docs/recipes/cloudbongos-standup.md +0 -111
- package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
- package/docs/recipes/parallel-load-test.md +0 -32
- package/docs/recipes/pixel-art-pipeline.md +0 -411
- package/docs/recipes/staging-data.md +0 -50
- package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
- package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
- package/migrations/instance/110_medusa_planning_seed.sql +0 -70
- package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
- package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
- package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
- package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
- package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
- package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
- package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
- package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
- package/scripts/gds/art-stage.js +0 -152
- package/server.js +0 -212
- package/tests/art_key.mjs +0 -121
- package/tests/day_cycle.mjs +0 -197
- package/tests/snapshot_picker.mjs +0 -233
- package/tests/sprite_facing.mjs +0 -35
- package/tests/terrain_picker.mjs +0 -298
- package/tests/turn_in_place.mjs +0 -280
- package/tests/walkability.mjs +0 -188
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// goal-inbox.js — the Home "Needs your attention" card (task 2169), grown from
|
|
2
|
+
// the two-sided goal-membership inbox (task 1954, ADR 0112).
|
|
3
|
+
//
|
|
4
|
+
// This card is the SINGLE landing place for actionable items on Home: it owns
|
|
5
|
+
// the section (id 'attention-scroll'), renders the goal-membership slice
|
|
6
|
+
// itself, and hosts the idea-triage slice (#attention-ideas, rendered by
|
|
7
|
+
// builders.js). New actionable item classes join as line-item slices here —
|
|
8
|
+
// never as new Home cards (the anti-accretion rule).
|
|
9
|
+
//
|
|
10
|
+
// The goal slice surfaces a builder's private-goals queue:
|
|
11
|
+
// • "Invitations to you" — pending invites addressed to me (Accept / Decline).
|
|
12
|
+
// • "Requests to approve" — pending join-requests on goals I own or manage
|
|
13
|
+
// (Approve / Deny).
|
|
14
|
+
// • a quiet "your pending requests" line for my own outstanding asks.
|
|
15
|
+
// Reads GET /goals/inbox (task 1952) which already scopes each bucket to the caller.
|
|
16
|
+
// Registers into the window.OTB hall-widget registry (dom-utils.js) exactly like
|
|
17
|
+
// goals.js — same IIFE guard, same BongosClient result-mode getJSON, view:'home'.
|
|
18
|
+
//
|
|
19
|
+
// The whole card is HIDDEN when the inbox is empty, so a quiet home page pays nothing
|
|
20
|
+
// for it. On an older server without the route (404) — or a signed-out visitor (401) —
|
|
21
|
+
// it renders nothing (feature-detect), never an error card.
|
|
22
|
+
(function () {
|
|
23
|
+
'use strict';
|
|
24
|
+
if (!window.OTB || typeof window.OTB.contributeHallWidget !== 'function') return;
|
|
25
|
+
const { escapeHtml, toast, rankLabel } = window.OTB;
|
|
26
|
+
const API = '/api/bongos';
|
|
27
|
+
// The generated typed client (served as window.BongosClient), result-mode so the
|
|
28
|
+
// status-inspecting control flow below (401/404 → render nothing) reads 1:1.
|
|
29
|
+
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
30
|
+
|
|
31
|
+
async function getJSON(path) {
|
|
32
|
+
const res = await api.request('GET', path);
|
|
33
|
+
if (res.status === 401) { const e = new Error('auth-needed'); e.code = 'auth-needed'; throw e; }
|
|
34
|
+
if (res.status === 404) { const e = new Error('not-found'); e.code = 'not-found'; throw e; }
|
|
35
|
+
if (!res.ok) throw new Error(`${path} → ${res.status}`);
|
|
36
|
+
return res.data;
|
|
37
|
+
}
|
|
38
|
+
async function postJSON(path, body) {
|
|
39
|
+
const res = await api.request('POST', path, { body: body || {} });
|
|
40
|
+
if (!res.ok) { const e = new Error(`${path} → ${res.status}`); e.status = res.status; e.data = res.data; throw e; }
|
|
41
|
+
return res.data;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let host = null;
|
|
45
|
+
let busy = false;
|
|
46
|
+
|
|
47
|
+
// The goal-detail hash route on the Goals page (matches goals.js's row link).
|
|
48
|
+
const goalHref = (goalId) => `/goals#/goal/${encodeURIComponent(goalId)}`;
|
|
49
|
+
const noteHtml = (note) => (note ? `<p class="ginbox__note">“${escapeHtml(note)}”</p>` : '');
|
|
50
|
+
|
|
51
|
+
function invitationRow(inv) {
|
|
52
|
+
const who = inv.invited_by_name || inv.invited_by_login || 'someone';
|
|
53
|
+
return `<li class="ginbox__item" data-goal="${escapeHtml(String(inv.goal_id))}">
|
|
54
|
+
<div class="ginbox__body">
|
|
55
|
+
<a class="ginbox__goal" href="${goalHref(inv.goal_id)}" title="Open ${escapeHtml(inv.goal_title || 'goal')}">${escapeHtml(inv.goal_title || 'a goal')}</a>
|
|
56
|
+
<span class="ginbox__sub">invited by ${escapeHtml(who)}</span>
|
|
57
|
+
${noteHtml(inv.note)}
|
|
58
|
+
</div>
|
|
59
|
+
<div class="ginbox__actions">
|
|
60
|
+
<button class="btn-ghost" type="button" data-accept-inv="${escapeHtml(String(inv.id))}">Accept</button>
|
|
61
|
+
<button class="btn-ghost" type="button" data-decline-inv="${escapeHtml(String(inv.id))}">Decline</button>
|
|
62
|
+
</div>
|
|
63
|
+
</li>`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function requestRow(r) {
|
|
67
|
+
const who = r.display_name || r.github_login || 'a builder';
|
|
68
|
+
const rank = r.rank ? ` · ${escapeHtml(rankLabel(r.rank, { empty: '' }))}` : '';
|
|
69
|
+
return `<li class="ginbox__item" data-goal="${escapeHtml(String(r.goal_id))}">
|
|
70
|
+
<div class="ginbox__body">
|
|
71
|
+
<a class="ginbox__goal" href="${goalHref(r.goal_id)}" title="Open ${escapeHtml(r.goal_title || 'goal')}">${escapeHtml(r.goal_title || 'a goal')}</a>
|
|
72
|
+
<span class="ginbox__sub">${escapeHtml(who)}${rank} asks to join</span>
|
|
73
|
+
${noteHtml(r.note)}
|
|
74
|
+
</div>
|
|
75
|
+
<div class="ginbox__actions">
|
|
76
|
+
<button class="btn-ghost" type="button" data-approve-req="${escapeHtml(String(r.id))}">Approve</button>
|
|
77
|
+
<button class="btn-ghost" type="button" data-deny-req="${escapeHtml(String(r.id))}">Deny</button>
|
|
78
|
+
</div>
|
|
79
|
+
</li>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function pendingLine(myPending) {
|
|
83
|
+
if (!myPending.length) return '';
|
|
84
|
+
const links = myPending
|
|
85
|
+
.map((r) => `<a href="${goalHref(r.goal_id)}">${escapeHtml(r.goal_title || 'a goal')}</a>`)
|
|
86
|
+
.join(', ');
|
|
87
|
+
const noun = myPending.length === 1 ? 'request' : 'requests';
|
|
88
|
+
return `<p class="ginbox__pending card-foot">Your pending join ${noun}: ${links} — awaiting approval.</p>`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// task 2169: this render fills ONLY the goals slice (#attention-goals) — the
|
|
92
|
+
// ideas slice (#attention-ideas) belongs to builders.js's triage renderer.
|
|
93
|
+
// Card visibility + the headline count derive from BOTH via OTBAttention.
|
|
94
|
+
const MAX_GOAL_ROWS = 5;
|
|
95
|
+
function render(inbox) {
|
|
96
|
+
const box = host && host.querySelector('#attention-goals');
|
|
97
|
+
if (!box) return;
|
|
98
|
+
const invitations = inbox.invitations || [];
|
|
99
|
+
const joinRequests = inbox.joinRequests || [];
|
|
100
|
+
const myPending = inbox.myPendingRequests || [];
|
|
101
|
+
if (!invitations.length && !joinRequests.length && !myPending.length) {
|
|
102
|
+
box.innerHTML = '';
|
|
103
|
+
window.OTBAttention.refresh();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Cap the card at a handful of rows — it is a summary, not the queue.
|
|
107
|
+
let budget = MAX_GOAL_ROWS;
|
|
108
|
+
const inv = invitations.slice(0, budget); budget -= inv.length;
|
|
109
|
+
const reqs = joinRequests.slice(0, Math.max(0, budget));
|
|
110
|
+
const overflow = (invitations.length - inv.length) + (joinRequests.length - reqs.length);
|
|
111
|
+
let html = '';
|
|
112
|
+
if (inv.length) {
|
|
113
|
+
html += '<h3 class="ginbox__sec-h">Goal invitations to you</h3>';
|
|
114
|
+
html += `<ul class="ginbox">${inv.map(invitationRow).join('')}</ul>`;
|
|
115
|
+
}
|
|
116
|
+
if (reqs.length) {
|
|
117
|
+
html += '<h3 class="ginbox__sec-h">Join-requests to approve</h3>';
|
|
118
|
+
html += `<ul class="ginbox">${reqs.map(requestRow).join('')}</ul>`;
|
|
119
|
+
}
|
|
120
|
+
if (overflow > 0) {
|
|
121
|
+
html += `<p class="ginbox__pending">…and ${overflow} more waiting — <a href="/goals">open your goals</a>.</p>`;
|
|
122
|
+
}
|
|
123
|
+
html += pendingLine(myPending);
|
|
124
|
+
box.innerHTML = html;
|
|
125
|
+
window.OTBAttention.refresh();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function load() {
|
|
129
|
+
if (!host) return;
|
|
130
|
+
try {
|
|
131
|
+
const inbox = await getJSON(`${API}/goals/inbox`);
|
|
132
|
+
render(inbox);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
// Signed out (401) or an older server without the route (404) → render
|
|
135
|
+
// nothing in the goals slice; the ideas slice may still have content.
|
|
136
|
+
const box = host.querySelector('#attention-goals');
|
|
137
|
+
if (box) box.innerHTML = '';
|
|
138
|
+
window.OTBAttention.refresh();
|
|
139
|
+
if (err.code !== 'auth-needed' && err.code !== 'not-found') {
|
|
140
|
+
console.error('[hall] goal inbox load failed', err);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// One action, then refresh the widget (the row it acted on disappears, and the
|
|
146
|
+
// count-pill/live counter re-derive from the fresh inbox). Disable while in flight
|
|
147
|
+
// so a double-click can't fire the resolve twice (the server 409s a re-resolve, but
|
|
148
|
+
// the UI shouldn't invite it).
|
|
149
|
+
async function act(fn, okMsg) {
|
|
150
|
+
if (busy) return;
|
|
151
|
+
busy = true;
|
|
152
|
+
host.querySelectorAll('button').forEach((b) => { b.disabled = true; });
|
|
153
|
+
try {
|
|
154
|
+
await fn();
|
|
155
|
+
toast(okMsg);
|
|
156
|
+
await load();
|
|
157
|
+
} catch (err) {
|
|
158
|
+
toast((err && err.data && err.data.message) || 'Could not do that just now — refresh and retry.');
|
|
159
|
+
host.querySelectorAll('button').forEach((b) => { b.disabled = false; });
|
|
160
|
+
} finally {
|
|
161
|
+
busy = false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function onClick(e) {
|
|
166
|
+
const item = e.target.closest('.ginbox__item');
|
|
167
|
+
const goalId = item && item.dataset.goal;
|
|
168
|
+
if (!goalId) return;
|
|
169
|
+
|
|
170
|
+
const accept = e.target.closest('[data-accept-inv]');
|
|
171
|
+
if (accept) {
|
|
172
|
+
act(() => postJSON(`${API}/goals/${encodeURIComponent(goalId)}/invitations/${encodeURIComponent(accept.dataset.acceptInv)}/respond`, { action: 'accept' }),
|
|
173
|
+
'Invitation accepted — you’re now a member.');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const decline = e.target.closest('[data-decline-inv]');
|
|
177
|
+
if (decline) {
|
|
178
|
+
act(() => postJSON(`${API}/goals/${encodeURIComponent(goalId)}/invitations/${encodeURIComponent(decline.dataset.declineInv)}/respond`, { action: 'decline' }),
|
|
179
|
+
'Invitation declined.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const approve = e.target.closest('[data-approve-req]');
|
|
183
|
+
if (approve) {
|
|
184
|
+
act(() => postJSON(`${API}/goals/${encodeURIComponent(goalId)}/requests/${encodeURIComponent(approve.dataset.approveReq)}/respond`, { action: 'approve' }),
|
|
185
|
+
'Approved — they’re now a member.');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const deny = e.target.closest('[data-deny-req]');
|
|
189
|
+
if (deny) {
|
|
190
|
+
act(() => postJSON(`${API}/goals/${encodeURIComponent(goalId)}/requests/${encodeURIComponent(deny.dataset.denyReq)}/respond`, { action: 'deny' }),
|
|
191
|
+
'Request denied.');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// task 2169: this widget IS the "Needs your attention" card — ONE capped
|
|
196
|
+
// summary hosting every actionable item class: goal invitations +
|
|
197
|
+
// join-requests (this file) and ideas awaiting triage (builders.js renders
|
|
198
|
+
// into #attention-ideas). Future actionable item classes add a slice div
|
|
199
|
+
// here + call OTBAttention.refresh() — never a new Home card.
|
|
200
|
+
function mount(hostEl) {
|
|
201
|
+
host = hostEl;
|
|
202
|
+
host.hidden = true; // stays hidden until a slice finds something actionable
|
|
203
|
+
host.innerHTML =
|
|
204
|
+
'<h2 id="attention-h" class="scroll__h">Needs your attention<span class="count-pill" id="attention-count" hidden></span></h2>' +
|
|
205
|
+
'<div id="attention-goals"></div>' +
|
|
206
|
+
'<div id="attention-ideas"></div>';
|
|
207
|
+
host.addEventListener('click', onClick);
|
|
208
|
+
load();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// The card's shared visibility contract: any slice renderer calls refresh()
|
|
212
|
+
// after painting; the card shows iff any slice has actionable rows, and the
|
|
213
|
+
// headline pill is the cross-slice total.
|
|
214
|
+
window.OTBAttention = {
|
|
215
|
+
refresh() {
|
|
216
|
+
const sec = document.getElementById('attention-scroll');
|
|
217
|
+
if (!sec) return;
|
|
218
|
+
const n = sec.querySelectorAll('.ginbox__item, .triage-inbox__item').length;
|
|
219
|
+
const pending = sec.querySelector('.ginbox__pending');
|
|
220
|
+
const pill = document.getElementById('attention-count');
|
|
221
|
+
if (pill) { pill.hidden = !n; pill.textContent = String(n); }
|
|
222
|
+
sec.hidden = n === 0 && !pending;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// order 20 → the top of the Home main zone: personal + actionable beats
|
|
227
|
+
// informational. hiddenByDefault so a quiet inbox costs nothing.
|
|
228
|
+
window.OTB.contributeHallWidget({
|
|
229
|
+
id: 'attention-scroll', label: 'Needs your attention', order: 20, mount,
|
|
230
|
+
labelledby: 'attention-h', view: 'home', zone: 'home-main', hiddenByDefault: true,
|
|
231
|
+
});
|
|
232
|
+
})();
|
|
@@ -280,6 +280,7 @@
|
|
|
280
280
|
<span class="goal-card__title">${escapeHtml(g.title || 'Untitled goal')}</span>
|
|
281
281
|
</div>
|
|
282
282
|
<div class="goal-card__meta">
|
|
283
|
+
<span class="goal-card__id" title="Goal id — use this to reference the goal (e.g. 'goal ${escapeHtml(String(g.id))}'). Distinct from the priority position on the left.">#${escapeHtml(String(g.id))}</span>
|
|
283
284
|
<span>${escapeHtml(g.version_id || '')}</span>
|
|
284
285
|
${statusBadge(g.status)}
|
|
285
286
|
${blockedPill}
|
|
@@ -857,7 +858,9 @@
|
|
|
857
858
|
const iAmLead = members.some((m) => sameId(m.builder_id, selfId()) && m.role === 'lead');
|
|
858
859
|
|
|
859
860
|
const h = $('#goal-detail-h');
|
|
860
|
-
|
|
861
|
+
// task 2163 — surface the numeric goal id alongside the title (escaped title,
|
|
862
|
+
// so switching from textContent to innerHTML stays injection-safe).
|
|
863
|
+
if (h) h.innerHTML = `<span class="goal-detail__id" title="Goal id">#${escapeHtml(String(g.id))}</span> ${escapeHtml(g.title || 'Untitled goal')}`;
|
|
861
864
|
const desc = $('#goal-detail-desc');
|
|
862
865
|
if (desc) desc.textContent = g.description || '';
|
|
863
866
|
const meta = $('#goal-detail-meta');
|
|
@@ -38,6 +38,14 @@
|
|
|
38
38
|
.goal-deps--blocked { color: var(--warn); font-weight: 600; }
|
|
39
39
|
.goal-card__title { font-weight: 650; font-size: 14px; flex: 1; min-width: 0; }
|
|
40
40
|
.goal-card__meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; font-size: 12px; color: var(--ink-faint); }
|
|
41
|
+
/* task 2163 — the real goal id, shown as a monospace #NNN token so it reads as
|
|
42
|
+
an identifier and never gets confused with the priority ordinal on the left. */
|
|
43
|
+
.goal-card__id, .goal-detail__id {
|
|
44
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
45
|
+
font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink-faint);
|
|
46
|
+
}
|
|
47
|
+
.goal-card__id { padding: 0 5px; border: 1px solid var(--rule-soft); border-radius: 5px; }
|
|
48
|
+
.goal-detail__id { font-size: 0.72em; vertical-align: 0.12em; margin-right: 4px; }
|
|
41
49
|
.goal-card__foot { display: flex; gap: 14px; font-size: 12.5px; color: var(--ink-soft); }
|
|
42
50
|
.goal-card__foot strong { font-variant-numeric: tabular-nums; }
|
|
43
51
|
|