@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
@@ -1026,8 +1026,8 @@
1026
1026
  renderArtKeyCard(res.data);
1027
1027
  wireArtKeyCard();
1028
1028
  $('#art-key').hidden = false;
1029
- // Landed here from the hall's "Add your key" action / the CLI nudge link.
1030
- if (location.hash === '#art-key') $('#art-key').scrollIntoView({ behavior: 'smooth' });
1029
+ // The hall's "Add your key" action / CLI nudge deep link (#art-key) is
1030
+ // landed by load()'s final applyHash() once every section has revealed.
1031
1031
  } catch (err) {
1032
1032
  console.error('[settings] failed to load art key', err);
1033
1033
  $('#art-key').hidden = false;
@@ -1068,49 +1068,100 @@
1068
1068
  }
1069
1069
  }
1070
1070
 
1071
- // ---- section rail (grouped sub-nav, task 1723) -----------------------------
1071
+ // ---- section rail (grouped sub-nav, task 1723 → kit, task 2172) -----------
1072
1072
  //
1073
- // The sections sit in six groups behind a left rail (#settings-rail). CSS on
1074
- // the page shows only the active group (.settings-groups[data-active]); a
1075
- // section's own [hidden] attribute still wins, so the per-section reveal
1076
- // logic above is unchanged. Deep links like #art-key activate their group
1077
- // first, so the existing scrollIntoView lands on a visible section.
1078
-
1079
- const RAIL_GROUPS = ['account', 'devbox', 'access', 'preferences', 'sound', 'extensions'];
1073
+ // The sections sit in six groups behind a left rail (#settings-rail), built by
1074
+ // OTBKit.makeSectionRail in switch mode — the idiom this page originated. CSS
1075
+ // shows only the active group (.settings-groups[data-active]); a section's own
1076
+ // [hidden] attribute still wins, so the per-section reveal logic above is
1077
+ // unchanged. Deep links activate their group first, so scrollIntoView lands on
1078
+ // a visible section.
1079
+
1080
+ // The Dev box group (box panel, desktop app, pairing) exists only where the
1081
+ // dev-box module runs — a vanilla instance has no boxes to manage, so the
1082
+ // whole group (rail entry included) stays out rather than showing an empty
1083
+ // shell. RAIL_GROUPS derives from the items so a #dev-box anchor no-ops too.
1084
+ const RAIL_ITEMS = [
1085
+ { id: 'account', label: 'Account' },
1086
+ { id: 'access', label: 'Access & keys' },
1087
+ ...(window.OTB.moduleOn('dev-box') ? [{ id: 'devbox', label: 'Dev box' }] : []),
1088
+ { id: 'preferences', label: 'Preferences' },
1089
+ { id: 'sound', label: 'Sound' },
1090
+ ];
1091
+ const RAIL_GROUPS = RAIL_ITEMS.map((i) => i.id).concat('extensions');
1092
+ let rail = null;
1093
+
1094
+ // task 2172 — stable per-panel anchors (/settings#cli, /settings#dev-box, …)
1095
+ // so skills and docs can deep-link a panel without depending on internal
1096
+ // section ids. #art-key predates the map and IS its section id (the hall's
1097
+ // "Add your key" action + the CLI nudge target it) — kept verbatim.
1098
+ const PANEL_ANCHORS = {
1099
+ 'connections': 'connections-scroll',
1100
+ 'display-name': 'display-name-scroll',
1101
+ 'disciplines': 'craft-scroll',
1102
+ 'cli': 'cli-scroll',
1103
+ 'art-key': 'art-key',
1104
+ 'dev-box': 'box-scroll',
1105
+ 'dev-box-app': 'boxapp-scroll',
1106
+ 'pairing': 'pairing-scroll',
1107
+ 'wandering': 'wandering-scroll',
1108
+ 'rendering': 'render-scroll',
1109
+ 'voices': 'voices-scroll',
1110
+ 'sounds': 'sounds-scroll',
1111
+ 'event-sounds': 'event-sounds-scroll',
1112
+ };
1080
1113
 
1081
1114
  function activateGroup(group, opts) {
1082
1115
  if (!RAIL_GROUPS.includes(group)) return;
1083
1116
  const groups = document.getElementById('settings-groups');
1084
1117
  if (!groups) return;
1085
1118
  groups.dataset.active = group;
1086
- document.querySelectorAll('#settings-rail .rail-link').forEach((a) => {
1087
- a.classList.toggle('rail-link--active', a.dataset.group === group);
1088
- });
1119
+ rail?.activate(group);
1089
1120
  if (!opts || opts.scroll !== false) window.scrollTo({ top: 0 });
1090
1121
  }
1091
1122
 
1092
- // Map a #hash to its group: either a group name itself (#access), or the id
1093
- // of an element inside a grouped section (#art-key → access).
1094
- function groupForHash(hash) {
1123
+ // Resolve a #hash to { group, el }: a group name itself (#access), a panel
1124
+ // anchor (#cli), or the id of an element inside a grouped section (#art-key).
1125
+ function resolveHash(hash) {
1095
1126
  const id = String(hash || '').replace(/^#/, '');
1096
1127
  if (!id) return null;
1097
- if (RAIL_GROUPS.includes(id)) return id;
1098
- const el = document.getElementById(id);
1128
+ if (RAIL_GROUPS.includes(id)) return { group: id, el: null };
1129
+ const el = document.getElementById(PANEL_ANCHORS[id] || id);
1099
1130
  const grouped = el && el.closest ? el.closest('[data-group]') : null;
1100
- return grouped ? grouped.getAttribute('data-group') : null;
1131
+ return grouped ? { group: grouped.getAttribute('data-group'), el } : null;
1132
+ }
1133
+
1134
+ // Activate the hash's group; for a panel anchor also scroll the panel into
1135
+ // view (rail highlight = the group). Runs on load, after data lands (late-
1136
+ // revealed sections), and on hashchange.
1137
+ function applyHash(opts) {
1138
+ const t = resolveHash(window.location.hash);
1139
+ if (!t) return;
1140
+ activateGroup(t.group, { scroll: false });
1141
+ if (t.el && (!opts || opts.scroll !== false)) {
1142
+ t.el.scrollIntoView({ behavior: 'smooth', block: 'start' });
1143
+ } else if (!t.el) {
1144
+ window.scrollTo({ top: 0 });
1145
+ }
1101
1146
  }
1102
1147
 
1103
1148
  function wireRail() {
1104
- const rail = document.getElementById('settings-rail');
1105
- if (!rail) return;
1106
- rail.addEventListener('click', (e) => {
1107
- const a = e.target.closest ? e.target.closest('.rail-link') : null;
1108
- if (!a || !a.dataset.group) return;
1109
- e.preventDefault();
1110
- activateGroup(a.dataset.group);
1149
+ const mount = document.getElementById('settings-rail');
1150
+ if (!mount || !window.OTBKit) return;
1151
+ rail = window.OTBKit.makeSectionRail({
1152
+ mount,
1153
+ items: RAIL_ITEMS,
1154
+ mode: 'switch',
1155
+ onSwitch: (key) => {
1156
+ activateGroup(key);
1157
+ // Reflect the group in the URL so the view is shareable; replaceState
1158
+ // keeps Back for leaving the page, not retracing rail clicks.
1159
+ history.replaceState(null, '', window.location.pathname + window.location.search + `#${key}`);
1160
+ },
1111
1161
  });
1112
- const fromHash = groupForHash(window.location.hash);
1113
- if (fromHash) activateGroup(fromHash, { scroll: false });
1162
+ rail.activate('account');
1163
+ applyHash({ scroll: false });
1164
+ window.addEventListener('hashchange', () => applyHash());
1114
1165
  }
1115
1166
 
1116
1167
  // Hide the rail + groups entirely while the not-signed-in gate is showing —
@@ -1166,10 +1217,14 @@
1166
1217
 
1167
1218
  // DEV BOX — full box management (online terminal + SSH + close), #807.
1168
1219
  setupBox();
1169
- // DEV BOX APP — static download links (#904); revealed once we know the
1170
- // viewer is a real builder (matches the page's hidden-until-loaded pattern).
1171
- $('#boxapp-scroll').hidden = false;
1172
- loadDevboxLatestVersion(); // #924: best-effort "latest version: X" courtesy line
1220
+ // DEV BOX APP + PAIRING — revealed once we know the viewer is a real builder,
1221
+ // and only where the dev-box module runs (the group's rail entry is gated
1222
+ // the same way above — task 2172).
1223
+ if (window.OTB.moduleOn('dev-box')) {
1224
+ $('#boxapp-scroll').hidden = false;
1225
+ $('#pairing-scroll').hidden = false;
1226
+ loadDevboxLatestVersion(); // #924: best-effort "latest version: X" courtesy line
1227
+ }
1173
1228
  // CONNECTIONS — Discord (hidden unless the server has Discord configured).
1174
1229
  setupConnections(me && me.discord ? me.discord : null);
1175
1230
  // CLI ACCESS — re-issue a CLI bearer from this browser session.
@@ -1177,10 +1232,13 @@
1177
1232
  // MODULE PANELS — render any Settings panels modules contributed via the
1178
1233
  // seam (BV1.R42). None today; ready for dev-box/discord/art once they leave core.
1179
1234
  mountContributedPanels();
1180
- // Reveal the Extensions rail entry only when a module actually contributed.
1235
+ // Reveal the Extensions rail entry only when a module actually contributed:
1236
+ // the kit rail re-renders from the item list, then re-marks the active group.
1181
1237
  const contributed = $('#contributed-panels');
1182
- const railExt = document.getElementById('rail-extensions');
1183
- if (railExt) railExt.hidden = !(contributed && contributed.children.length > 0);
1238
+ if (rail && contributed && contributed.children.length > 0) {
1239
+ rail.refresh([...RAIL_ITEMS, { id: 'extensions', label: 'Extensions' }]);
1240
+ rail.activate($('#settings-groups')?.dataset.active || 'account');
1241
+ }
1184
1242
 
1185
1243
  // Voices loads independently — its own endpoint + its own try/catch.
1186
1244
  await loadVoices();
@@ -1195,6 +1253,9 @@
1195
1253
  // Image key (per-builder Gemini key, task 1013) — own try/catch; `me` carries
1196
1254
  // the `needs` art_key item that drives the covered/action-needed banner.
1197
1255
  await loadArtKey(me);
1256
+ // Every section has now loaded/revealed — land any panel-anchor deep link
1257
+ // (/settings#cli, #dev-box, …) on its visible section.
1258
+ applyHash();
1198
1259
  }
1199
1260
 
1200
1261
  // #924: show the current published Dev Box app version next to the download
@@ -1914,3 +1914,34 @@ a.stat-tile:hover {
1914
1914
  .proj-head .scroll__h { margin: 0; }
1915
1915
  /* The over-budget "More" row — one quiet line, not a card. */
1916
1916
  .home-more { margin: 0; font-size: 13px; color: var(--ink-soft); }
1917
+
1918
+ /* task 2184: two-column main zone on wide screens — Home reads as a
1919
+ dashboard, not a stack. The attention inbox (and the More row) span both
1920
+ columns; the work + Projects cards sit side by side. Mirrors the
1921
+ .view-zone--band grid idiom (cards drop their own margins inside a grid). */
1922
+ #zone-home-main { display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; align-items: start; }
1923
+ #zone-home-main > .scroll { margin: 0; }
1924
+ @media (min-width: 1024px) {
1925
+ #zone-home-main { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1926
+ #zone-home-main > #attention-scroll,
1927
+ #zone-home-main > #home-more-row,
1928
+ #zone-home-main > #zone-home-overflow { grid-column: 1 / -1; }
1929
+ }
1930
+
1931
+ /* task 2184: the "Next up" slice in the Your-work card — the page's answer to
1932
+ "what's my next action". Quiet rows, accented title, one foot link. */
1933
+ .next-up { list-style: none; margin: 0 0 4px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
1934
+ .next-up__item {
1935
+ display: flex;
1936
+ flex-direction: column;
1937
+ gap: 2px;
1938
+ padding: 9px 12px;
1939
+ border: 1px solid var(--border);
1940
+ border-left: 3px solid color-mix(in srgb, var(--accent) 55%, var(--border));
1941
+ border-radius: var(--radius-sm);
1942
+ background: var(--surface-2);
1943
+ }
1944
+ .next-up__title { font-weight: 600; font-size: 13.5px; color: var(--ink); }
1945
+ .next-up__title:hover { color: var(--accent-text); }
1946
+ .next-up__meta { font-size: 12px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
1947
+ .next-up__foot { margin: 0 0 14px; font-size: 13px; }
@@ -0,0 +1,184 @@
1
+ /* watch.css — page-local styles for Watch (task 2173; extracted from
2
+ watch.html's old inline <style> per the hall placement rules). Built on the
3
+ shared design system's derived tokens; class names are load-bearing
4
+ (watch.js writes them). The attention row is the shared .stat-row/.stat-tile;
5
+ tabs/pager/empty states come from style.css's hall-kit section. */
6
+
7
+ /* task 2173 — the attention row sits above the tabs; a touch tighter than the
8
+ Home tiles (this is a dashboard header, not a landing zone). */
9
+ .watch-attention { margin: 0 0 14px; }
10
+ .watch-attention .stat-tile { min-height: 76px; padding: 12px 16px; }
11
+ .watch-attention .stat-tile__num { font-size: 23px; }
12
+
13
+ /* task 2173 density pass — panels stack inside tab panes. */
14
+ [id^="pane-"] .scroll { padding: 16px 18px; margin: 0 0 14px; }
15
+
16
+ .watch-stats {
17
+ display: grid;
18
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
19
+ gap: 12px;
20
+ margin-top: 6px;
21
+ }
22
+ .watch-stat {
23
+ background: var(--surface);
24
+ border: 1px solid var(--border);
25
+ border-radius: var(--radius-sm);
26
+ padding: 12px 14px;
27
+ text-align: center;
28
+ }
29
+ .watch-stat__num {
30
+ font-family: var(--font-display);
31
+ font-size: 1.5rem;
32
+ font-weight: 650;
33
+ color: var(--ink);
34
+ line-height: 1.2;
35
+ font-variant-numeric: tabular-nums;
36
+ }
37
+ .watch-stat__label {
38
+ font-size: 11px;
39
+ font-weight: 600;
40
+ color: var(--ink-faint);
41
+ text-transform: uppercase;
42
+ letter-spacing: 0.06em;
43
+ margin-top: 4px;
44
+ }
45
+
46
+ .watch-table-wrap { overflow-x: auto; margin-top: 6px; }
47
+ table.watch {
48
+ width: 100%;
49
+ border-collapse: collapse;
50
+ font-size: 13px;
51
+ color: var(--ink);
52
+ }
53
+ table.watch th, table.watch td {
54
+ padding: 6px 10px;
55
+ text-align: left;
56
+ border-bottom: 1px solid var(--border);
57
+ vertical-align: top;
58
+ }
59
+ table.watch th {
60
+ font-size: 11px;
61
+ font-weight: 600;
62
+ text-transform: uppercase;
63
+ letter-spacing: 0.06em;
64
+ color: var(--ink-faint);
65
+ border-bottom: 1px solid var(--border-strong);
66
+ white-space: nowrap;
67
+ }
68
+ table.watch td.num { text-align: right; font-variant-numeric: tabular-nums; }
69
+ table.watch td.warn { color: var(--danger); font-weight: 600; }
70
+ table.watch tr:hover td { background: var(--surface-2); }
71
+ /* the kit ledger (audit log) shares the same numeric-cell affordances */
72
+ .ledger td.num { text-align: right; font-variant-numeric: tabular-nums; }
73
+ .ledger td.num.warn { color: var(--danger); font-weight: 600; }
74
+ .watch-mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 0.84rem; color: var(--info); }
75
+ .watch-empty { color: var(--ink-faint); font-size: 13px; padding: 10px 0; }
76
+ .watch-note { color: var(--ink-faint); font-size: 12.5px; margin-top: 10px; }
77
+ .watch-rank { font-size: 12px; color: var(--ink-faint); }
78
+ .watch-doc {
79
+ white-space: pre-wrap;
80
+ word-break: break-word;
81
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
82
+ font-size: 0.8rem;
83
+ line-height: 1.5;
84
+ max-height: 60vh;
85
+ overflow-y: auto;
86
+ padding: 12px;
87
+ margin-top: 8px;
88
+ background: var(--surface-2);
89
+ border: 1px solid var(--border);
90
+ border-radius: var(--radius-sm);
91
+ }
92
+ .watch-sub-h, .watch-subh {
93
+ font-family: var(--font-display);
94
+ font-size: 14px;
95
+ font-weight: 600;
96
+ color: var(--ink);
97
+ margin: 16px 0 4px;
98
+ }
99
+ .watch-sub-h:first-of-type { margin-top: 8px; }
100
+
101
+ /* Severity / status badges. */
102
+ .badge {
103
+ display: inline-block;
104
+ font-size: 11px;
105
+ font-weight: 600;
106
+ letter-spacing: 0.04em;
107
+ text-transform: uppercase;
108
+ padding: 2px 8px;
109
+ border-radius: 999px;
110
+ border: 1px solid var(--border-strong);
111
+ color: var(--ink-soft);
112
+ white-space: nowrap;
113
+ }
114
+ .badge--sev-critical { background: var(--danger-weak); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 35%, transparent); }
115
+ .badge--sev-high { background: var(--warn-weak); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 35%, transparent); }
116
+ .badge--sev-medium { background: var(--info-weak); color: var(--info); border-color: color-mix(in srgb, var(--info) 30%, transparent); }
117
+ .badge--sev-low { background: var(--surface-2); color: var(--ink-soft); }
118
+ .badge--status { background: var(--surface-2); color: var(--ink-soft); }
119
+
120
+ /* Row action buttons + selects. */
121
+ .watch-actions { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
122
+ .watch-btn {
123
+ font-family: var(--font-body);
124
+ font-size: 12.5px;
125
+ font-weight: 500;
126
+ padding: 4px 12px;
127
+ border: 1px solid var(--border-strong);
128
+ background: var(--surface);
129
+ color: var(--ink-soft);
130
+ border-radius: var(--radius-sm);
131
+ cursor: pointer;
132
+ }
133
+ .watch-btn:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }
134
+ .watch-btn:disabled { opacity: 0.45; cursor: default; }
135
+ .watch-btn--danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 40%, var(--border-strong)); }
136
+ .watch-select {
137
+ font-family: var(--font-body);
138
+ font-size: 13px;
139
+ padding: 4px 6px;
140
+ border: 1px solid var(--border-strong);
141
+ background: var(--surface);
142
+ color: var(--ink);
143
+ border-radius: var(--radius-sm);
144
+ }
145
+
146
+ .watch-req {
147
+ border-bottom: 1px solid var(--border);
148
+ padding: 10px 0;
149
+ display: flex; gap: 14px; flex-wrap: wrap;
150
+ align-items: flex-start; justify-content: space-between;
151
+ }
152
+ .watch-req__who { font-weight: 600; color: var(--ink); }
153
+ .watch-req__meta { color: var(--ink-faint); font-size: 12.5px; margin-top: 2px; }
154
+ .watch-req__note { color: var(--ink); font-size: 13.5px; margin-top: 6px; max-width: 56ch; }
155
+
156
+ .watch-unavailable { color: var(--ink-faint); font-size: 13px; padding: 8px 0; }
157
+
158
+ .watch-trend { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
159
+ .watch-trend__row {
160
+ display: grid; grid-template-columns: 84px 1fr 64px;
161
+ gap: 10px; align-items: center;
162
+ font-size: 13px; color: var(--ink);
163
+ }
164
+ .watch-trend__bar-track {
165
+ background: var(--surface-2); border-radius: 3px; height: 12px; overflow: hidden;
166
+ border: 1px solid var(--border);
167
+ }
168
+ .watch-trend__bar-fill { background: var(--accent); height: 100%; display: block; }
169
+ .watch-trend__val { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink-soft); }
170
+
171
+ .watch-list { list-style: none; padding: 0; margin: 8px 0 0; }
172
+ .watch-list li {
173
+ display: flex; justify-content: space-between; gap: 12px;
174
+ padding: 5px 0; border-bottom: 1px solid var(--border);
175
+ font-size: 13px;
176
+ }
177
+ .watch-list li .k { color: var(--ink); }
178
+ .watch-list li .v { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
179
+
180
+ .watch-foot-link {
181
+ display: inline-block; margin-top: 12px;
182
+ font-size: 13px; font-weight: 500;
183
+ color: var(--accent-text);
184
+ }