@bongos/core 1.17.3 → 1.18.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.
Files changed (163) hide show
  1. package/.bongos-core.json +212 -332
  2. package/.devcontainer/healthcheck.sh +0 -0
  3. package/.devcontainer/init-firewall.sh +0 -0
  4. package/.devcontainer/post-create.sh +0 -0
  5. package/GOVERNANCE.md +1 -1
  6. package/README.md +26 -22
  7. package/bin/bongos.js +0 -0
  8. package/config/branding.neutral.json +5 -1
  9. package/docker-entrypoint.sh +0 -0
  10. package/docs/adr/0142-project-context-is-prose-only.md +41 -0
  11. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
  12. package/docs/adr/README.md +6 -0
  13. package/docs/api/openapi.json +1630 -632
  14. package/docs/api-reference.md +50 -15
  15. package/docs/file-map.md +0 -18
  16. package/docs/onboarding/diagrams/05-versioning.mmd +4 -6
  17. package/docs/onboarding/diagrams/assertions.json +5 -9
  18. package/docs/recipes/packaging-the-core.md +13 -0
  19. package/docs/recipes/upgrading-the-core.md +5 -2
  20. package/migrations/039_newcomer_friendly_column.sql +5 -0
  21. package/migrations/102_remove_box_first_provision_approval.sql +4 -0
  22. package/migrations/core_192_github_oauth_tokens.sql +33 -0
  23. package/modules/hall-ui/public/builders.js +37 -0
  24. package/modules/hall-ui/public/goals-page.js +98 -176
  25. package/modules/hall-ui/public/goals.css +183 -0
  26. package/modules/hall-ui/public/goals.html +35 -261
  27. package/modules/hall-ui/public/goals.js +7 -2
  28. package/modules/hall-ui/public/hall-widgets.js +12 -4
  29. package/modules/hall-ui/public/my-projects.html +78 -0
  30. package/modules/hall-ui/public/my-projects.js +142 -0
  31. package/modules/hall-ui/public/scouting.html +45 -0
  32. package/modules/hall-ui/public/scouting.js +73 -0
  33. package/modules/hall-ui/public/settings.css +355 -0
  34. package/modules/hall-ui/public/settings.html +86 -414
  35. package/modules/hall-ui/public/settings.js +96 -35
  36. package/modules/hall-ui/public/style.css +31 -0
  37. package/modules/hall-ui/public/watch.css +184 -0
  38. package/modules/hall-ui/public/watch.html +111 -285
  39. package/modules/hall-ui/public/watch.js +59 -21
  40. package/modules/status-ui/public/explore.html +54 -0
  41. package/modules/status-ui/public/explore.js +67 -0
  42. package/package-lock.json +2 -2
  43. package/package.json +1 -1
  44. package/public-landing/create.html +292 -19
  45. package/public-landing/index.html +11 -76
  46. package/scripts/cron/style_guide_audit.sh +0 -0
  47. package/scripts/gds/api.js +0 -0
  48. package/scripts/gds/architect-audit.js +0 -0
  49. package/scripts/gds/audit-authorship.sh +0 -0
  50. package/scripts/gds/audit-replay.sh +0 -0
  51. package/scripts/gds/box.js +0 -0
  52. package/scripts/gds/capture.js +0 -0
  53. package/scripts/gds/cli-lib.js +11 -5
  54. package/scripts/gds/conductor-dispatch.js +0 -0
  55. package/scripts/gds/coreB-carve-proof.sh +0 -0
  56. package/scripts/gds/db-backup-nightly.sh +0 -0
  57. package/scripts/gds/db-backup-offsite.sh +0 -0
  58. package/scripts/gds/doctor.js +0 -0
  59. package/scripts/gds/grade-smoke.js +0 -0
  60. package/scripts/gds/hooks/pre-push.js +0 -0
  61. package/scripts/gds/install-git-hooks.js +0 -0
  62. package/scripts/gds/lint-migrations.js +102 -0
  63. package/scripts/gds/login.js +42 -10
  64. package/scripts/gds/main-audit.js +0 -0
  65. package/scripts/gds/mirror-redact.js +5 -0
  66. package/scripts/gds/oauth-secret.js +0 -0
  67. package/scripts/gds/onboarding-analyze.js +0 -0
  68. package/scripts/gds/onboarding-ship.js +0 -0
  69. package/scripts/gds/package-core.js +263 -4
  70. package/scripts/gds/paste-token.js +0 -0
  71. package/scripts/gds/portability-smoke-art.sh +0 -0
  72. package/scripts/gds/portability-smoke-memory.sh +0 -0
  73. package/scripts/gds/provision.js +270 -11
  74. package/scripts/gds/publish-manifest.js +15 -0
  75. package/scripts/gds/push-main.js +0 -0
  76. package/scripts/gds/render-ideas.js +0 -0
  77. package/scripts/gds/run-unit-tests.js +38 -14
  78. package/scripts/gds/security-baseline-check.sh +0 -0
  79. package/scripts/gds/session.js +0 -0
  80. package/scripts/gds/setup.js +41 -0
  81. package/scripts/gds/ship.js +126 -32
  82. package/scripts/gds/smoke-batch-claim.sh +0 -0
  83. package/scripts/gds/smoke-batch-release.sh +0 -0
  84. package/scripts/gds/smoke-box.sh +0 -0
  85. package/scripts/gds/smoke-claim-consistency.sh +0 -0
  86. package/scripts/gds/smoke-dependencies.sh +0 -0
  87. package/scripts/gds/smoke-write.sh +0 -0
  88. package/scripts/gds/upgrade.js +147 -5
  89. package/scripts/hall-preview/server.js +18 -1
  90. package/scripts/migrate.sh +10 -0
  91. package/scripts/migrate_cost_ledger.sh +0 -0
  92. package/scripts/render-diagrams.sh +0 -0
  93. package/scripts/security/dep-audit.js +0 -0
  94. package/scripts/setup-builder.js +0 -0
  95. package/scripts/setup-builder.sh +0 -0
  96. package/scripts/snapshots/maybe-render-on-deploy.sh +0 -0
  97. package/src/bongos/auth.js +362 -5
  98. package/src/bongos/db.js +66 -0
  99. package/src/bongos/github-repos.js +93 -0
  100. package/src/bongos/routes/auth.js +93 -11
  101. package/src/bongos/routes/github.js +90 -0
  102. package/src/bongos/routes/instance.js +15 -3
  103. package/src/bongos/routes.js +2 -0
  104. package/src/branding.js +17 -2
  105. package/src/instance-config.js +16 -0
  106. package/src/module-api.js +48 -1
  107. package/tests/branding.mjs +29 -23
  108. package/tests/github_repos_helpers.mjs +91 -0
  109. package/tests/github_token_store.mjs +121 -0
  110. package/tests/idp_device_exchange.mjs +105 -0
  111. package/tests/idp_federation.mjs +100 -0
  112. package/tests/migrations_forward_safe.mjs +86 -0
  113. package/tests/module_api.mjs +1 -1
  114. package/tests/package_core.mjs +246 -1
  115. package/tests/provision.mjs +226 -0
  116. package/tests/pruned_lockfile_integrity.mjs +79 -0
  117. package/tests/ship_ci_deploy.mjs +60 -0
  118. package/tests/upgrade.mjs +254 -5
  119. package/.gitignore +0 -91
  120. package/scripts/hall-preview/fixtures/access-requests.json +0 -1
  121. package/scripts/hall-preview/fixtures/achievements.json +0 -76
  122. package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
  123. package/scripts/hall-preview/fixtures/audit-log.json +0 -1
  124. package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -1
  125. package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
  126. package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
  127. package/scripts/hall-preview/fixtures/builders__roster.json +0 -1
  128. package/scripts/hall-preview/fixtures/goals.json +0 -502
  129. package/scripts/hall-preview/fixtures/goals__25.json +0 -96
  130. package/scripts/hall-preview/fixtures/goals__26.json +0 -67
  131. package/scripts/hall-preview/fixtures/goals__3.json +0 -103
  132. package/scripts/hall-preview/fixtures/goals__35.json +0 -49
  133. package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
  134. package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
  135. package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
  136. package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
  137. package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -1
  138. package/scripts/hall-preview/fixtures/inbox.json +0 -330
  139. package/scripts/hall-preview/fixtures/instance.json +0 -124
  140. package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
  141. package/scripts/hall-preview/fixtures/me.json +0 -476
  142. package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
  143. package/scripts/hall-preview/fixtures/override-requests.json +0 -1
  144. package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
  145. package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
  146. package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
  147. package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
  148. package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
  149. package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
  150. package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
  151. package/scripts/hall-preview/fixtures/public__grades.json +0 -50
  152. package/scripts/hall-preview/fixtures/public__progress.json +0 -124
  153. package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
  154. package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
  155. package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
  156. package/scripts/hall-preview/fixtures/security__docs.json +0 -1
  157. package/scripts/hall-preview/fixtures/security__reports.json +0 -1
  158. package/scripts/hall-preview/fixtures/sessions__search.json +0 -1
  159. package/scripts/hall-preview/fixtures/tasks.json +0 -2420
  160. package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
  161. package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
  162. package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
  163. package/scripts/hall-preview/fixtures/versions.json +0 -89
@@ -0,0 +1,78 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>My Projects</title>
7
+ <style>
8
+ :root {
9
+ --bg: #fff7ed; --card: #ffffff; --ink: #2a1f17; --ink-soft: #6b5d52;
10
+ --rule: #fed7aa; --accent: #ea580c; --accent-deep: #c2410c;
11
+ }
12
+ * { box-sizing: border-box; }
13
+ body {
14
+ margin: 0; padding: 2.5rem 1.25rem 4rem; background: var(--bg); color: var(--ink);
15
+ font-family: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.5;
16
+ }
17
+ .wrap { max-width: 820px; margin: 0 auto; }
18
+ h1 { font-size: 1.9rem; margin: 0 0 .3rem; }
19
+ .sub { color: var(--ink-soft); margin: 0 0 2rem; }
20
+ h2 { font-size: 1.1rem; margin: 2rem 0 .75rem; border-bottom: 1px solid var(--rule); padding-bottom: .35rem; }
21
+ .row {
22
+ display: flex; align-items: center; gap: .75rem; background: var(--card);
23
+ border: 1px solid var(--rule); border-radius: 12px; padding: .8rem 1rem; margin-bottom: .6rem;
24
+ }
25
+ .row .meta { flex: 1; min-width: 0; }
26
+ .row .name { font-weight: 600; }
27
+ .row .origin { color: var(--accent); font-size: .82rem; word-break: break-all; }
28
+ .row .owner-pill { font-size: .72rem; background: var(--accent); color: #fff; border-radius: 999px; padding: .1rem .55rem; }
29
+ button {
30
+ font: inherit; cursor: pointer; border: 1px solid var(--rule); background: #fff; color: var(--ink);
31
+ border-radius: 9px; padding: .4rem .8rem;
32
+ }
33
+ button:hover { border-color: var(--accent); color: var(--accent-deep); }
34
+ .join { display: flex; gap: .5rem; margin-top: .75rem; }
35
+ .join input { flex: 1; font: inherit; padding: .5rem .7rem; border: 1px solid var(--rule); border-radius: 9px; }
36
+ .empty { color: var(--ink-soft); }
37
+ .note { margin-top: .5rem; color: var(--ink-soft); font-size: .9rem; min-height: 1.2em; }
38
+ a.home { color: var(--accent); text-decoration: none; font-size: .9rem; }
39
+ @media (prefers-color-scheme: dark) {
40
+ :root { --bg: #1a1410; --card: #241c16; --ink: #f5ece3; --ink-soft: #b9a998; --rule: #4a3728; }
41
+ button { background: #241c16; }
42
+ .join input { background: #241c16; color: inherit; }
43
+ }
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <div class="wrap">
48
+ <a class="home" href="/builders">← Back to the hall</a>
49
+ <h1>My Projects</h1>
50
+ <p class="sub">Every project you build on across the platform.</p>
51
+
52
+ <section>
53
+ <h2>Projects you own</h2>
54
+ <div id="owned"><p class="empty">—</p></div>
55
+ </section>
56
+
57
+ <section id="invites-section" hidden>
58
+ <h2>Invitations</h2>
59
+ <div id="invites"></div>
60
+ </section>
61
+
62
+ <section>
63
+ <h2>Projects you build on</h2>
64
+ <div id="building"><p class="empty">—</p></div>
65
+ </section>
66
+
67
+ <section>
68
+ <h2>Join a project</h2>
69
+ <div class="join">
70
+ <input id="join-origin" type="url" placeholder="https://project.cloudbongos.com" autocomplete="off">
71
+ <button id="join-btn" type="button">Request to join</button>
72
+ </div>
73
+ <p id="join-note" class="note"></p>
74
+ </section>
75
+ </div>
76
+ <script src="/my-projects.js"></script>
77
+ </body>
78
+ </html>
@@ -0,0 +1,142 @@
1
+ // my-projects.js — the signed-in builder's cross-project view (ADR 0141 §3, task
2
+ // #1002112). Fetches /api/gds/my-projects (session cookie, same-origin) and renders
3
+ // owned + building-on lists with self-service leave, plus a join-by-origin form.
4
+ // All network values are rendered via textContent — never innerHTML.
5
+ (function () {
6
+ 'use strict';
7
+ var ownedEl = document.getElementById('owned');
8
+ var buildingEl = document.getElementById('building');
9
+ var joinBtn = document.getElementById('join-btn');
10
+ var joinInput = document.getElementById('join-origin');
11
+ var joinNote = document.getElementById('join-note');
12
+
13
+ function el(tag, cls) { var e = document.createElement(tag); if (cls) e.className = cls; return e; }
14
+
15
+ function safeOriginHost(origin) {
16
+ try {
17
+ var u = new URL(String(origin));
18
+ return (u.protocol === 'https:' || u.protocol === 'http:') ? u.host : null;
19
+ } catch (_) { return null; }
20
+ }
21
+
22
+ function renderList(container, items, opts) {
23
+ container.textContent = '';
24
+ if (!items || items.length === 0) {
25
+ var p = el('p', 'empty');
26
+ p.textContent = opts.emptyText;
27
+ container.appendChild(p);
28
+ return;
29
+ }
30
+ items.forEach(function (m) {
31
+ var row = el('div', 'row');
32
+ var meta = el('div', 'meta');
33
+ var name = el('div', 'name');
34
+ name.textContent = m.name || m.client_id;
35
+ meta.appendChild(name);
36
+ var host = safeOriginHost(m.origin);
37
+ if (host) {
38
+ var o = el('div', 'origin');
39
+ o.textContent = host;
40
+ meta.appendChild(o);
41
+ }
42
+ row.appendChild(meta);
43
+ if (m.role === 'owner') {
44
+ var pill = el('span', 'owner-pill');
45
+ pill.textContent = 'owner';
46
+ row.appendChild(pill);
47
+ }
48
+ if (opts.leavable) {
49
+ var btn = el('button');
50
+ btn.type = 'button';
51
+ btn.textContent = 'Leave';
52
+ btn.addEventListener('click', function () { leave(m.client_id, row, btn); });
53
+ row.appendChild(btn);
54
+ }
55
+ container.appendChild(row);
56
+ });
57
+ }
58
+
59
+ function leave(clientId, row, btn) {
60
+ btn.disabled = true;
61
+ btn.textContent = 'Leaving…';
62
+ fetch('/api/gds/my-projects/' + encodeURIComponent(clientId) + '/leave', { method: 'POST' })
63
+ .then(function (r) { return r.ok ? r.json() : Promise.reject(); })
64
+ .then(function () { row.remove(); })
65
+ .catch(function () { btn.disabled = false; btn.textContent = 'Leave'; });
66
+ }
67
+
68
+ function resolveInvite(clientId, accept, row, btns) {
69
+ btns.forEach(function (b) { b.disabled = true; });
70
+ fetch('/api/gds/my-projects/invites/' + encodeURIComponent(clientId) + '/' + (accept ? 'accept' : 'decline'), { method: 'POST' })
71
+ .then(function (r) { return r.ok ? r.json() : Promise.reject(); })
72
+ .then(function () { row.remove(); if (accept) load(); loadInvites(); })
73
+ .catch(function () { btns.forEach(function (b) { b.disabled = false; }); });
74
+ }
75
+
76
+ function loadInvites() {
77
+ var section = document.getElementById('invites-section');
78
+ var container = document.getElementById('invites');
79
+ fetch('/api/gds/my-projects/invites', { headers: { Accept: 'application/json' } })
80
+ .then(function (r) { return r.ok ? r.json() : Promise.reject(); })
81
+ .then(function (data) {
82
+ var invites = (data && data.invites) || [];
83
+ if (invites.length === 0) { section.hidden = true; return; }
84
+ container.textContent = '';
85
+ invites.forEach(function (inv) {
86
+ var row = el('div', 'row');
87
+ var meta = el('div', 'meta');
88
+ var name = el('div', 'name');
89
+ name.textContent = inv.name || inv.client_id;
90
+ meta.appendChild(name);
91
+ var host = safeOriginHost(inv.origin);
92
+ if (host) { var o = el('div', 'origin'); o.textContent = host; meta.appendChild(o); }
93
+ row.appendChild(meta);
94
+ var accept = el('button'); accept.type = 'button'; accept.textContent = 'Accept';
95
+ var decline = el('button'); decline.type = 'button'; decline.textContent = 'Decline';
96
+ accept.addEventListener('click', function () { resolveInvite(inv.client_id, true, row, [accept, decline]); });
97
+ decline.addEventListener('click', function () { resolveInvite(inv.client_id, false, row, [accept, decline]); });
98
+ row.appendChild(accept); row.appendChild(decline);
99
+ container.appendChild(row);
100
+ });
101
+ section.hidden = false;
102
+ })
103
+ .catch(function () { section.hidden = true; });
104
+ }
105
+
106
+ function load() {
107
+ fetch('/api/gds/my-projects', { headers: { Accept: 'application/json' } })
108
+ .then(function (r) { return r.ok ? r.json() : Promise.reject(); })
109
+ .then(function (data) {
110
+ renderList(ownedEl, data.owned, { emptyText: 'You don’t own any projects yet.', leavable: false });
111
+ renderList(buildingEl, data.building, { emptyText: 'You’re not building on any other projects yet.', leavable: true });
112
+ })
113
+ .catch(function () {
114
+ ownedEl.textContent = ''; buildingEl.textContent = '';
115
+ var p = el('p', 'empty'); p.textContent = 'Couldn’t load your projects right now.';
116
+ buildingEl.appendChild(p);
117
+ });
118
+ }
119
+
120
+ joinBtn.addEventListener('click', function () {
121
+ var origin = (joinInput.value || '').trim();
122
+ if (!origin) { joinNote.textContent = 'Enter a project URL.'; return; }
123
+ joinBtn.disabled = true;
124
+ joinNote.textContent = 'Requesting…';
125
+ fetch('/api/gds/my-projects/join', {
126
+ method: 'POST',
127
+ headers: { 'Content-Type': 'application/json' },
128
+ body: JSON.stringify({ origin: origin }),
129
+ })
130
+ .then(function (r) { return r.json().then(function (b) { return { ok: r.ok, body: b }; }); })
131
+ .then(function (res) {
132
+ joinBtn.disabled = false;
133
+ if (res.ok && res.body && res.body.message) { joinNote.textContent = res.body.message; joinInput.value = ''; load(); }
134
+ else if (res.body && res.body.error && res.body.error.code === 'unknown_project') { joinNote.textContent = 'That project isn’t on the platform.'; }
135
+ else { joinNote.textContent = 'Could not request access right now.'; }
136
+ })
137
+ .catch(function () { joinBtn.disabled = false; joinNote.textContent = 'Could not request access right now.'; });
138
+ });
139
+
140
+ load();
141
+ loadInvites();
142
+ }());
@@ -0,0 +1,45 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Scouting — cross-project builders</title>
7
+ <style>
8
+ :root {
9
+ --bg: #fff7ed; --card: #ffffff; --ink: #2a1f17; --ink-soft: #6b5d52;
10
+ --rule: #fed7aa; --accent: #ea580c;
11
+ }
12
+ * { box-sizing: border-box; }
13
+ body {
14
+ margin: 0; padding: 2.5rem 1.25rem 4rem; background: var(--bg); color: var(--ink);
15
+ font-family: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.5;
16
+ }
17
+ .wrap { max-width: 960px; margin: 0 auto; }
18
+ a.home { color: var(--accent); text-decoration: none; font-size: .9rem; }
19
+ h1 { font-size: 1.9rem; margin: .4rem 0 .3rem; }
20
+ .sub { color: var(--ink-soft); margin: 0 0 1.75rem; }
21
+ .badge { display: inline-block; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
22
+ background: var(--accent); color: #fff; border-radius: 999px; padding: .12rem .6rem; vertical-align: middle; }
23
+ table { width: 100%; border-collapse: collapse; background: var(--card); border: 1px solid var(--rule); border-radius: 12px; overflow: hidden; }
24
+ th, td { text-align: left; padding: .6rem .8rem; border-bottom: 1px solid var(--rule); font-size: .92rem; }
25
+ th { color: var(--ink-soft); font-weight: 600; }
26
+ td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
27
+ tr:last-child td { border-bottom: none; }
28
+ .who { font-weight: 600; }
29
+ .note { color: var(--ink-soft); padding: 2.5rem 1rem; text-align: center; }
30
+ .scroll { overflow-x: auto; }
31
+ @media (prefers-color-scheme: dark) {
32
+ :root { --bg: #1a1410; --card: #241c16; --ink: #f5ece3; --ink-soft: #b9a998; --rule: #4a3728; }
33
+ }
34
+ </style>
35
+ </head>
36
+ <body>
37
+ <div class="wrap">
38
+ <a class="home" href="/builders">← Back to the hall</a>
39
+ <h1>Scouting <span class="badge">Archon only</span></h1>
40
+ <p class="sub">Builders active across all hosted projects — an internal talent view. Descriptive only; it grants no authority anywhere.</p>
41
+ <div id="body" class="note">Loading…</div>
42
+ </div>
43
+ <script src="/scouting.js"></script>
44
+ </body>
45
+ </html>
@@ -0,0 +1,73 @@
1
+ // scouting.js — the internal Archon-only cross-project scouting view (ADR 0141 §4,
2
+ // task #1002114). Fetches /api/gds/scouting (Archon-gated server-side) and renders a
3
+ // ranked table. A non-Archon gets a 403 and sees the Archon-only notice — the data
4
+ // endpoint's rank gate is the real protection ("NOT shown to builders"). All network
5
+ // values are rendered via textContent — never innerHTML.
6
+ (function () {
7
+ 'use strict';
8
+ var body = document.getElementById('body');
9
+
10
+ function el(tag, cls) { var e = document.createElement(tag); if (cls) e.className = cls; return e; }
11
+ function td(text, cls) { var c = el('td', cls); c.textContent = text; return c; }
12
+
13
+ function fmtRecency(iso) {
14
+ if (!iso) return '—';
15
+ var t = Date.parse(iso);
16
+ if (isNaN(t)) return '—';
17
+ var days = Math.floor((Date.now() - t) / 86400000);
18
+ if (days <= 0) return 'today';
19
+ if (days === 1) return '1 day ago';
20
+ if (days < 30) return days + ' days ago';
21
+ return Math.floor(days / 30) + ' mo ago';
22
+ }
23
+
24
+ function render(builders) {
25
+ body.className = '';
26
+ body.textContent = '';
27
+ if (!builders || builders.length === 0) {
28
+ body.className = 'note';
29
+ body.textContent = 'No cross-project activity reported yet.';
30
+ return;
31
+ }
32
+ var scroll = el('div', 'scroll');
33
+ var table = el('table');
34
+ var thead = el('thead');
35
+ var hr = el('tr');
36
+ [['Builder', ''], ['Projects', 'num'], ['Credits', 'num'], ['Ships', 'num'], ['Karma', 'num'], ['Disciplines', ''], ['Last active', '']]
37
+ .forEach(function (h) { var th = el('th', h[1]); th.textContent = h[0]; hr.appendChild(th); });
38
+ thead.appendChild(hr); table.appendChild(thead);
39
+ var tbody = el('tbody');
40
+ builders.forEach(function (b) {
41
+ var tr = el('tr');
42
+ var who = el('td');
43
+ var name = el('span', 'who');
44
+ name.textContent = b.github_login || b.display_name || ('#' + b.github_id);
45
+ who.appendChild(name);
46
+ tr.appendChild(who);
47
+ tr.appendChild(td(String(b.project_count != null ? b.project_count : '—'), 'num'));
48
+ tr.appendChild(td(String(b.total_credits != null ? b.total_credits : 0), 'num'));
49
+ tr.appendChild(td(String(b.total_tasks_shipped != null ? b.total_tasks_shipped : 0), 'num'));
50
+ tr.appendChild(td(String(b.total_karma != null ? b.total_karma : 0), 'num'));
51
+ tr.appendChild(td(Array.isArray(b.disciplines) ? b.disciplines.join(', ') : '—'));
52
+ tr.appendChild(td(fmtRecency(b.last_active)));
53
+ tbody.appendChild(tr);
54
+ });
55
+ table.appendChild(tbody);
56
+ scroll.appendChild(table);
57
+ body.appendChild(scroll);
58
+ }
59
+
60
+ fetch('/api/gds/scouting', { headers: { Accept: 'application/json' } })
61
+ .then(function (r) {
62
+ if (r.status === 403 || r.status === 401) { throw { archon: true }; }
63
+ if (!r.ok) throw new Error('http ' + r.status);
64
+ return r.json();
65
+ })
66
+ .then(function (data) { render(data && data.builders); })
67
+ .catch(function (e) {
68
+ body.className = 'note';
69
+ body.textContent = (e && e.archon)
70
+ ? 'This view is Archon-only.'
71
+ : 'Couldn’t load the scouting view right now.';
72
+ });
73
+ }());
@@ -0,0 +1,355 @@
1
+ /* settings.css — page-local styles for Settings (task 2172; extracted from
2
+ settings.html's old inline <style> per the hall placement rules). Components
3
+ layered on the shared design system (style.css) — the rail (.section-rail /
4
+ .rail-link) and buttons derive from the shared tokens so instance theme
5
+ packs reskin this page too. */
6
+
7
+ /* ---- grouped layout: left sub-nav rail + one visible group (task 1723) ---- */
8
+ .settings-layout {
9
+ display: grid;
10
+ grid-template-columns: 180px minmax(0, 1fr);
11
+ gap: 20px;
12
+ align-items: start;
13
+ }
14
+ .settings-layout[hidden] { display: none !important; }
15
+ /* Only the active group's sections render. Each grouped element is a direct
16
+ child of .settings-groups carrying data-group; a section's own [hidden]
17
+ attribute always wins (the !important rule below), so the per-section
18
+ reveal logic in settings.js is unchanged. */
19
+ .settings-groups > [data-group] { display: none; }
20
+ .settings-groups[data-active="account"] > [data-group="account"],
21
+ .settings-groups[data-active="devbox"] > [data-group="devbox"],
22
+ .settings-groups[data-active="access"] > [data-group="access"],
23
+ .settings-groups[data-active="preferences"] > [data-group="preferences"],
24
+ .settings-groups[data-active="sound"] > [data-group="sound"],
25
+ .settings-groups[data-active="extensions"] > [data-group="extensions"] { display: block; }
26
+ .settings-groups > [data-group][hidden] { display: none !important; }
27
+
28
+ /* task 2172 density pass — standard panel anatomy (title → one-line purpose →
29
+ control → save-semantics note) in a tighter card: less padding + stacking
30
+ margin than the default .scroll, ledes clamped to one visual line's worth. */
31
+ .settings-groups .scroll { padding: 16px 18px; margin: 0 0 12px; }
32
+ .settings-groups .scroll__lede { font-size: 13px; margin-bottom: 10px; }
33
+
34
+ @media (max-width: 768px) {
35
+ /* Collapse to one column; the shared .section-rail ≤768px rules turn the
36
+ rail into a sticky horizontal chip strip just under the top bar. */
37
+ .settings-layout { grid-template-columns: minmax(0, 1fr); }
38
+ }
39
+
40
+ .settings-status {
41
+ font-size: 13px;
42
+ color: var(--ink-soft);
43
+ min-height: 1.2em;
44
+ margin-top: 8px;
45
+ }
46
+ .section-note {
47
+ color: var(--ink-faint);
48
+ font-size: 12.5px;
49
+ margin-top: 10px;
50
+ margin-bottom: 0;
51
+ }
52
+
53
+ /* ---- Voices (per-skill model picker) ---- */
54
+ .voices-scroll { /* keep many rows compact within a bounded, scrollable box */ }
55
+ .voices-wrap {
56
+ max-height: 460px;
57
+ overflow-y: auto;
58
+ margin-top: 6px;
59
+ border: 1px solid var(--border);
60
+ border-radius: var(--radius-sm);
61
+ }
62
+ table.voices {
63
+ width: 100%;
64
+ border-collapse: collapse;
65
+ font-size: 13px;
66
+ color: var(--ink);
67
+ }
68
+ table.voices th, table.voices td {
69
+ padding: 7px 12px;
70
+ text-align: left;
71
+ border-bottom: 1px solid var(--rule-soft);
72
+ vertical-align: middle;
73
+ }
74
+ table.voices th {
75
+ /* sticks inside .voices-wrap's own scroller, not the window — top:0 is
76
+ relative to the wrap, so no --topbar-h offset needed here */
77
+ position: sticky;
78
+ top: 0;
79
+ z-index: 1;
80
+ background: var(--surface);
81
+ font-size: 11px;
82
+ font-weight: 600;
83
+ letter-spacing: 0.06em;
84
+ text-transform: uppercase;
85
+ color: var(--ink-faint);
86
+ border-bottom: 1px solid var(--border-strong);
87
+ }
88
+ table.voices tr:last-child td { border-bottom: none; }
89
+ table.voices tbody tr:hover td { background: var(--surface-2); }
90
+ .voices__skill code {
91
+ font-size: 12.5px;
92
+ color: var(--info);
93
+ }
94
+ .voices__default {
95
+ font-size: 12.5px;
96
+ color: var(--ink-soft);
97
+ }
98
+ select.voices__select { min-width: 140px; }
99
+ .voices-empty, .craft-empty {
100
+ color: var(--ink-faint);
101
+ font-style: italic;
102
+ font-size: 13px;
103
+ padding: 10px 0;
104
+ }
105
+
106
+ /* ---- Sounds (per-sound on/off toggles) ---- */
107
+ .sound-rows { margin-top: 8px; }
108
+ .sound-row {
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: space-between;
112
+ gap: 16px;
113
+ padding: 9px 0;
114
+ border-bottom: 1px solid var(--rule-soft);
115
+ }
116
+ .sound-row:last-child { border-bottom: none; }
117
+ .sound-row__text { min-width: 0; }
118
+ .sound-row__label {
119
+ font-size: 13.5px;
120
+ color: var(--ink);
121
+ font-weight: 600;
122
+ }
123
+ .sound-row__desc {
124
+ font-size: 12.5px;
125
+ color: var(--ink-soft);
126
+ margin-top: 2px;
127
+ }
128
+ button.sound-toggle {
129
+ font-family: var(--font-body);
130
+ font-size: 12.5px;
131
+ font-weight: 600;
132
+ padding: 5px 16px;
133
+ min-width: 72px;
134
+ border-radius: 999px;
135
+ border: 1px solid var(--border-strong);
136
+ background: var(--surface);
137
+ color: var(--ink-soft);
138
+ cursor: pointer;
139
+ flex-shrink: 0;
140
+ }
141
+ button.sound-toggle[data-on="1"] {
142
+ background: var(--accent);
143
+ border-color: var(--accent);
144
+ color: #fff;
145
+ }
146
+ button.sound-toggle:disabled { opacity: 0.5; cursor: default; }
147
+ .sound-volume {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 14px;
151
+ padding: 4px 0 12px;
152
+ border-bottom: 1px solid var(--border);
153
+ margin-bottom: 4px;
154
+ }
155
+ .sound-volume__label {
156
+ font-size: 11px;
157
+ font-weight: 600;
158
+ text-transform: uppercase;
159
+ letter-spacing: 0.06em;
160
+ color: var(--ink-faint);
161
+ flex-shrink: 0;
162
+ }
163
+ .sound-volume input[type="range"] {
164
+ flex: 1;
165
+ accent-color: var(--accent);
166
+ cursor: pointer;
167
+ }
168
+ .sound-volume__pct {
169
+ font-size: 13px;
170
+ color: var(--ink);
171
+ min-width: 3.2em;
172
+ text-align: right;
173
+ font-variant-numeric: tabular-nums;
174
+ }
175
+
176
+ /* ---- Event sounds (per-event variant dropdowns, task 582) ---- */
177
+ .event-sound-rows { margin-top: 8px; }
178
+ .event-sound-row {
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: space-between;
182
+ gap: 16px;
183
+ padding: 9px 0;
184
+ border-bottom: 1px solid var(--rule-soft);
185
+ }
186
+ .event-sound-row:last-child { border-bottom: none; }
187
+ .event-sound-row__text { flex: 1; min-width: 0; }
188
+ .event-sound-row__label {
189
+ font-size: 13.5px;
190
+ color: var(--ink);
191
+ font-weight: 600;
192
+ }
193
+ .event-sound-row__desc {
194
+ font-size: 12.5px;
195
+ color: var(--ink-soft);
196
+ margin-top: 2px;
197
+ }
198
+ .event-sound-row__controls {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 8px;
202
+ flex-shrink: 0;
203
+ }
204
+ .event-sound-select { cursor: pointer; }
205
+ .event-sound-preview {
206
+ font-family: var(--font-body);
207
+ font-size: 12px;
208
+ font-weight: 600;
209
+ padding: 5px 12px;
210
+ border-radius: 999px;
211
+ border: 1px solid var(--border-strong);
212
+ background: transparent;
213
+ color: var(--ink-soft);
214
+ cursor: pointer;
215
+ }
216
+ .event-sound-preview:active { opacity: 0.7; }
217
+
218
+ /* ---- Disciplines (preference chips) ---- */
219
+ .craft-chips {
220
+ display: flex;
221
+ flex-wrap: wrap;
222
+ gap: 10px;
223
+ margin-top: 8px;
224
+ }
225
+ .craft-chip {
226
+ display: inline-flex;
227
+ align-items: center;
228
+ gap: 8px;
229
+ font-size: 13.5px;
230
+ color: var(--ink);
231
+ background: var(--surface);
232
+ border: 1px solid var(--border-strong);
233
+ border-radius: 999px;
234
+ padding: 6px 14px;
235
+ cursor: pointer;
236
+ user-select: none;
237
+ text-transform: capitalize;
238
+ }
239
+ .craft-chip:hover { background: var(--surface-2); }
240
+ .craft-chip input { cursor: pointer; }
241
+ .craft-chip[data-on="1"] {
242
+ background: var(--accent);
243
+ border-color: var(--accent);
244
+ color: #fff;
245
+ }
246
+ .craft-actions {
247
+ display: flex;
248
+ align-items: center;
249
+ gap: 14px;
250
+ margin-top: 14px;
251
+ flex-wrap: wrap;
252
+ }
253
+
254
+ /* ---- Buttons (Save / card actions) ---- */
255
+ button.craft-save, .settings-btn {
256
+ font-family: var(--font-body);
257
+ font-size: 13px;
258
+ font-weight: 600;
259
+ padding: 7px 16px;
260
+ border: 1px solid var(--accent);
261
+ background: var(--surface);
262
+ color: var(--accent-text);
263
+ border-radius: var(--radius-sm);
264
+ cursor: pointer;
265
+ display: inline-block;
266
+ text-decoration: none;
267
+ }
268
+ button.craft-save:hover:not(:disabled), .settings-btn:hover:not(:disabled) {
269
+ background: var(--accent);
270
+ color: #fff;
271
+ text-decoration: none;
272
+ }
273
+ button.craft-save:disabled, .settings-btn:disabled { opacity: 0.5; cursor: default; }
274
+ /* Discord brand button — keeps its blurple so it's recognisable. */
275
+ .settings-btn--discord {
276
+ background: #5865F2;
277
+ border-color: #4853c4;
278
+ color: #fff;
279
+ }
280
+ .settings-btn--discord:hover { background: #4853c4; color: #fff; }
281
+ .settings-btn--quiet {
282
+ border-color: var(--border-strong);
283
+ color: var(--ink-soft);
284
+ }
285
+ .settings-btn--quiet:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }
286
+
287
+ /* ---- Display name (single editable field, task 1669) ---- */
288
+ .display-name-row {
289
+ display: flex;
290
+ flex-wrap: wrap;
291
+ align-items: center;
292
+ gap: 12px;
293
+ margin-top: 8px;
294
+ }
295
+ input.display-name-input {
296
+ flex: 1 1 280px;
297
+ min-width: 0;
298
+ }
299
+
300
+ /* ---- Connections + CLI access cards ---- */
301
+ .settings-card {
302
+ margin-top: 8px;
303
+ display: flex;
304
+ flex-wrap: wrap;
305
+ align-items: center;
306
+ gap: 12px;
307
+ }
308
+ .settings-card__hint { color: var(--ink-soft); font-size: 13px; }
309
+ .settings-card__ok { font-weight: 600; color: var(--ok); }
310
+ .cli-result { flex-basis: 100%; margin-top: 4px; }
311
+ .cli-result pre {
312
+ background: var(--bg-deep);
313
+ color: var(--ink);
314
+ padding: 10px 12px;
315
+ border: 1px solid var(--border);
316
+ border-radius: var(--radius-sm);
317
+ overflow-x: auto;
318
+ font-size: 12.5px;
319
+ margin: 6px 0;
320
+ }
321
+
322
+ /* ---- Image key (task 1013) — the "we cover it" / "action needed" banner
323
+ uses the shared semantic status tokens. ---- */
324
+ .artkey-banner {
325
+ border-radius: var(--radius-sm);
326
+ padding: 10px 14px;
327
+ font-size: 13px;
328
+ line-height: 1.45;
329
+ border: 1px solid var(--border);
330
+ margin-bottom: 10px;
331
+ }
332
+ .artkey-banner--covered {
333
+ background: var(--ok-weak);
334
+ border-color: color-mix(in srgb, var(--ok) 45%, var(--border));
335
+ color: var(--ok);
336
+ }
337
+ .artkey-banner--action {
338
+ background: var(--warn-weak);
339
+ border-color: color-mix(in srgb, var(--warn) 45%, var(--border));
340
+ color: var(--warn);
341
+ }
342
+ .artkey-entry-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; flex-basis: 100%; }
343
+ .artkey-input {
344
+ flex: 1 1 280px;
345
+ min-width: 0;
346
+ padding: 7px 10px;
347
+ border: 1px solid var(--border-strong);
348
+ border-radius: var(--radius-sm);
349
+ background: var(--surface);
350
+ color: var(--ink);
351
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
352
+ font-size: 13px;
353
+ }
354
+ .artkey-actions { display: flex; gap: 10px; flex-wrap: wrap; }
355
+ #artkey-entry { flex-basis: 100%; }