@bongos/core 1.19.461 → 1.19.463

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 (51) hide show
  1. package/.bongos-core.json +145 -40
  2. package/.claude/skills/design/SKILL.md +2 -1
  3. package/docs/adr/0221-the-impeccable-method-is-one-skill-with-sub-command-playbooks.md +36 -0
  4. package/docs/adr/README.md +1 -0
  5. package/docs/copy-inventory.md +122 -125
  6. package/docs/copy-registry.json +306 -322
  7. package/docs/file-map.md +2 -2
  8. package/docs/module-api-changelog.md +4 -0
  9. package/modules/hall-ui/public/goals-lib.js +10 -6
  10. package/modules/hall-ui/public/goals-page.js +27 -12
  11. package/modules/hall-ui/public/goals-render.js +83 -52
  12. package/modules/hall-ui/public/goals.css +129 -120
  13. package/modules/hall-ui/public/goals.html +29 -11
  14. package/modules/hall-ui/public/goals.states.json +71 -3
  15. package/modules/hall-ui/public/people.css +27 -0
  16. package/modules/hall-ui/public/people.html +15 -7
  17. package/modules/hall-ui/public/people.js +111 -22
  18. package/modules/hall-ui/public/people.states.json +31 -1
  19. package/modules/hall-ui/public/scouting.html +30 -17
  20. package/modules/hall-ui/public/scouting.states.json +7 -2
  21. package/modules/hall-ui/public/sessions.css +18 -15
  22. package/modules/hall-ui/public/sessions.html +27 -22
  23. package/modules/hall-ui/public/sessions.js +32 -13
  24. package/modules/hall-ui/public/sessions.states.json +3 -2
  25. package/modules/ui-design/module.json +1 -1
  26. package/modules/ui-design/skills/README.md +4 -3
  27. package/modules/ui-design/skills/impeccable/SKILL.md +143 -0
  28. package/modules/ui-design/skills/impeccable/reference/adapt.md +25 -0
  29. package/modules/ui-design/skills/impeccable/reference/animate.md +27 -0
  30. package/modules/ui-design/skills/impeccable/reference/audit.md +19 -0
  31. package/modules/ui-design/skills/impeccable/reference/bolder.md +17 -0
  32. package/modules/ui-design/skills/impeccable/reference/clarify.md +25 -0
  33. package/modules/ui-design/skills/impeccable/reference/colorize.md +22 -0
  34. package/modules/ui-design/skills/impeccable/reference/critique.md +15 -0
  35. package/modules/ui-design/skills/impeccable/reference/distill.md +26 -0
  36. package/modules/ui-design/skills/impeccable/reference/document.md +25 -0
  37. package/modules/ui-design/skills/impeccable/reference/extract.md +20 -0
  38. package/modules/ui-design/skills/impeccable/reference/harden.md +21 -0
  39. package/modules/ui-design/skills/impeccable/reference/layout.md +20 -0
  40. package/modules/ui-design/skills/impeccable/reference/onboard.md +22 -0
  41. package/modules/ui-design/skills/impeccable/reference/optimize.md +22 -0
  42. package/modules/ui-design/skills/impeccable/reference/polish.md +22 -0
  43. package/modules/ui-design/skills/impeccable/reference/quieter.md +17 -0
  44. package/modules/ui-design/skills/impeccable/reference/shape.md +23 -0
  45. package/modules/ui-design/skills/impeccable/reference/typeset.md +24 -0
  46. package/package-lock.json +2 -2
  47. package/package.json +1 -1
  48. package/src/module-api.js +1 -1
  49. package/tests/hall_roster_world.mjs +287 -0
  50. package/tests/ui_design_skills.mjs +41 -0
  51. package/modules/hall-ui/public/scouting.js +0 -73
@@ -1,16 +1,29 @@
1
- // people.js — the People page: the /scouting cross-project builder directory
2
- // graduated into the hall (task 1002577; ADR 0141 §4 is the data's design of
3
- // record). Same data endpoint as scouting.js — /api/gds/scouting, gated
4
- // server-side on 'project.curate' OR being a project owner (R14, ADR 0210); the
5
- // page itself is gated in serve-internal.js on that same pair, so a visitor who
6
- // is neither never sees this shell. The hall chrome + kit primitives are what
7
- // "graduate" means: searchable, paged, shell-navigable.
1
+ // people.js — the People page: the cross-project builder directory (task
2
+ // 1002577; ADR 0141 §4 is the data's design of record), served at /people and
3
+ // at its legacy door /scouting — task 1003470 made the two ONE page, this
4
+ // script, because the same /api/gds/scouting rollup was being drawn three
5
+ // different ways (a kit ledger here, a hand-built table there, and the same
6
+ // columns in a different order). The endpoint is gated server-side on
7
+ // 'project.curate' OR being a project owner (R14, ADR 0210); both pages are
8
+ // gated in serve-internal.js on that same pair, so a visitor who is neither
9
+ // never sees this shell.
10
+ //
11
+ // What a directory row is (task 1003470): WHO — the avatar the API returns and
12
+ // both pages dropped, the display name, the login under it as the door (GitHub
13
+ // is the identity the platform account is keyed on, and the one page every
14
+ // listed builder has: the directory is cross-project, so no local profile id
15
+ // exists); then the four figures in tabular columns, the crafts as chips, and
16
+ // when they were last seen. Search, a sort and a craft lens are the kit's
17
+ // filter bar; the rows page through the kit's pager.
8
18
  // All network values render through OTBKit.esc — never raw innerHTML.
9
19
  (function () {
10
20
  'use strict';
11
21
  var kit = window.OTBKit;
22
+ var OTB = window.OTB;
12
23
  var esc = kit.esc;
13
24
  var body = document.getElementById('people-body');
25
+ var sub = document.getElementById('people-sub');
26
+ var countPill = document.getElementById('people-count');
14
27
 
15
28
  function fmtRecency(iso) {
16
29
  if (!iso) return '—';
@@ -22,38 +35,90 @@
22
35
  if (days < 30) return days + ' days ago';
23
36
  return Math.floor(days / 30) + ' mo ago';
24
37
  }
38
+ var fmtInt = function (n) { return OTB && OTB.fmtInt ? OTB.fmtInt(n) : String(n == null ? 0 : n); };
39
+ var rankLabel = function (r) { return OTB && OTB.rankLabel ? OTB.rankLabel(r) : r; };
40
+ var currencyLabel = function () {
41
+ var b = window.OTBBranding;
42
+ return (b && b.currencyLabel && b.currencyLabel({ caps: true })) || 'Credits';
43
+ };
25
44
 
26
45
  function builderName(b) {
27
- return b.github_login || b.display_name || ('#' + b.github_id);
46
+ return b.display_name || b.github_login || ('#' + b.github_id);
47
+ }
48
+ function initialOf(b) {
49
+ var n = String(builderName(b)).replace(/^[@#]/, '');
50
+ return (n.charAt(0) || '?').toUpperCase();
51
+ }
52
+
53
+ // The identity cell. The name is the shell's .lb-name (a door that reads as
54
+ // text until hovered) when a login exists to open; the login sits under it in
55
+ // mono so the door's destination is never a surprise.
56
+ function whoHtml(b) {
57
+ var name = esc(builderName(b));
58
+ var avatar = b.avatar_url
59
+ ? '<img class="dir-avatar" src="' + esc(b.avatar_url) + '" alt="" loading="lazy">'
60
+ : '<span class="dir-avatar dir-avatar--initial" aria-hidden="true">' + esc(initialOf(b)) + '</span>';
61
+ var nameHtml = b.github_login
62
+ ? '<a class="lb-name dir-name" href="https://github.com/' + encodeURIComponent(b.github_login) + '" rel="noopener">' + name + '</a>'
63
+ : '<span class="dir-name">' + name + '</span>';
64
+ var login = b.github_login ? '<span class="dir-login">@' + esc(b.github_login) + '</span>' : '';
65
+ return '<span class="dir-who">' + avatar + '<span class="dir-who__t">' + nameHtml + login + '</span></span>';
66
+ }
67
+ function craftsHtml(b) {
68
+ var list = Array.isArray(b.disciplines) ? b.disciplines : [];
69
+ if (!list.length) return '<span class="dir-none">—</span>';
70
+ return '<span class="dir-crafts">' + list.map(function (d) {
71
+ return '<span class="fact-chip">' + esc(String(d)) + '</span>';
72
+ }).join('') + '</span>';
28
73
  }
29
74
 
30
75
  var all = [];
31
76
  var query = '';
77
+ var craft = '';
78
+ var sortBy = '';
32
79
 
33
80
  function matches(b) {
81
+ if (craft && !(Array.isArray(b.disciplines) && b.disciplines.indexOf(craft) !== -1)) return false;
34
82
  if (!query) return true;
35
- var hay = (builderName(b) + ' ' + (b.display_name || '') + ' ' +
83
+ var hay = ((b.github_login || '') + ' ' + (b.display_name || '') + ' ' +
36
84
  (Array.isArray(b.disciplines) ? b.disciplines.join(' ') : '')).toLowerCase();
37
85
  return hay.indexOf(query) !== -1;
38
86
  }
87
+ // The server's order IS "track record" (ADR 0141 §4 ranks the rollup); the
88
+ // other lenses re-order the loaded set client-side, ties keeping that order.
89
+ var SORTS = {
90
+ '': null,
91
+ ships: function (b) { return -(b.total_tasks_shipped || 0); },
92
+ credits: function (b) { return -(b.total_credits || 0); },
93
+ karma: function (b) { return -(b.total_karma || 0); },
94
+ projects: function (b) { return -(b.project_count || 0); },
95
+ recent: function (b) { var t = Date.parse(b.last_active || ''); return isNaN(t) ? Infinity : -t; },
96
+ };
97
+ function ordered(rows) {
98
+ var key = SORTS[sortBy];
99
+ if (!key) return rows;
100
+ return rows.map(function (b, i) { return { b: b, i: i }; })
101
+ .sort(function (x, y) { return (key(x.b) - key(y.b)) || (x.i - y.i); })
102
+ .map(function (x) { return x.b; });
103
+ }
39
104
 
40
105
  var pager = kit.makePager('people', 'hall-people-per-page');
41
106
  var ledger = null;
42
107
 
43
108
  function rowHtml(b) {
44
109
  return '<tr>' +
45
- '<td><span class="who">' + esc(builderName(b)) + '</span></td>' +
46
- '<td class="num">' + esc(b.project_count != null ? b.project_count : '—') + '</td>' +
47
- '<td class="num">' + esc(b.total_credits != null ? b.total_credits : 0) + '</td>' +
48
- '<td class="num">' + esc(b.total_tasks_shipped != null ? b.total_tasks_shipped : 0) + '</td>' +
49
- '<td class="num">' + esc(b.total_karma != null ? b.total_karma : 0) + '</td>' +
50
- '<td>' + esc(Array.isArray(b.disciplines) ? b.disciplines.join(', ') : '—') + '</td>' +
51
- '<td>' + esc(fmtRecency(b.last_active)) + '</td>' +
110
+ '<td>' + whoHtml(b) + '</td>' +
111
+ '<td class="ledger__num">' + esc(fmtInt(b.project_count != null ? b.project_count : 0)) + '</td>' +
112
+ '<td class="ledger__num">' + esc(fmtInt(b.total_tasks_shipped != null ? b.total_tasks_shipped : 0)) + '</td>' +
113
+ '<td class="ledger__num">' + esc(fmtInt(b.total_credits != null ? b.total_credits : 0)) + '</td>' +
114
+ '<td class="ledger__num">' + esc(fmtInt(b.total_karma != null ? b.total_karma : 0)) + '</td>' +
115
+ '<td>' + craftsHtml(b) + '</td>' +
116
+ '<td class="ledger__date">' + esc(fmtRecency(b.last_active)) + '</td>' +
52
117
  '</tr>';
53
118
  }
54
119
 
55
120
  function rerender() {
56
- if (ledger) ledger.render(all.filter(matches));
121
+ if (ledger) ledger.render(ordered(all.filter(matches)));
57
122
  }
58
123
 
59
124
  function mountDirectory(builders) {
@@ -77,22 +142,44 @@
77
142
  + 'and none are listed without it. Their work still shows on each project’s own ledger.');
78
143
  return;
79
144
  }
145
+ if (countPill) { countPill.textContent = String(all.length); countPill.hidden = false; }
146
+ if (sub) {
147
+ sub.textContent = fmtInt(all.length) + ' builder' + (all.length === 1 ? '' : 's') + ' listed across hosted projects, ranked by track record. '
148
+ + 'Descriptive only — cross-project numbers never carry authority here.';
149
+ }
150
+ var crafts = [];
151
+ all.forEach(function (b) {
152
+ (Array.isArray(b.disciplines) ? b.disciplines : []).forEach(function (d) {
153
+ if (crafts.indexOf(d) === -1) crafts.push(d);
154
+ });
155
+ });
156
+ crafts.sort();
80
157
  kit.makeFilterBar({
81
158
  mount: document.getElementById('people-filters'),
82
- search: { id: 'people-q', label: 'Search', placeholder: 'name or discipline' },
159
+ search: { id: 'people-q', label: 'Search', placeholder: 'name or craft' },
160
+ primary: [
161
+ { id: 'people-sort', label: 'Sort', options: [
162
+ { value: '', label: 'track record' }, { value: 'ships', label: 'most ships' },
163
+ { value: 'credits', label: 'most ' + currencyLabel().toLowerCase() }, { value: 'karma', label: 'most karma' },
164
+ { value: 'projects', label: 'most projects' }, { value: 'recent', label: 'most recently active' }] },
165
+ { id: 'people-craft', label: 'Craft', options: [{ value: '', label: '— any —' }].concat(crafts) },
166
+ ],
83
167
  onChange: function (vals) {
84
168
  query = String(vals['people-q'] || '').trim().toLowerCase();
169
+ craft = String(vals['people-craft'] || '');
170
+ sortBy = String(vals['people-sort'] || '');
85
171
  pager.page = 1;
86
172
  rerender();
87
173
  },
88
174
  });
89
175
  body.textContent = '';
176
+ body.className = '';
90
177
  ledger = kit.makeLedger({
91
178
  mount: body,
92
- columns: ['Builder', { label: 'Projects', class: 'num' }, { label: 'Credits', class: 'num' },
93
- { label: 'Ships', class: 'num' }, { label: 'Karma', class: 'num' }, 'Disciplines', 'Last active'],
179
+ columns: ['Builder', { label: 'Projects', class: 'ledger__num' }, { label: 'Ships', class: 'ledger__num' },
180
+ { label: currencyLabel(), class: 'ledger__num' }, { label: 'Karma', class: 'ledger__num' }, 'Crafts', 'Last active'],
94
181
  rowHtml: rowHtml,
95
- // Reached only when the directory HAS builders and the search excluded them
182
+ // Reached only when the directory HAS builders and the lenses excluded them
96
183
  // all — the empty-API case returns above with its own explanation.
97
184
  emptyMsg: 'No listed builder matches that search.',
98
185
  pager: pager,
@@ -114,8 +201,10 @@
114
201
  })
115
202
  .then(function (data) { mountDirectory(data && data.builders); })
116
203
  .catch(function (e) {
204
+ // The floor is the pack's label for metic, never a hardcoded rank name —
205
+ // and the page used to say "Archon-only", which was wrong since ADR 0210.
117
206
  notice(e && e.gated
118
- ? 'The builder directory is Archon-only for now.'
207
+ ? 'The directory opens at the ' + rankLabel('metic') + ' rank, or to a project’s owner.'
119
208
  : 'Couldn’t load the directory right now.');
120
209
  });
121
210
  }());
@@ -1,5 +1,5 @@
1
1
  {
2
- "_": "People — the cross-project directory behind requireProjectCuratePage (metic+ via project.curate). Descriptive only; it grants no authority, and the page says so above the fold. Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once.",
2
+ "_": "People — the cross-project directory behind requireProjectCuratePage (metic+ via project.curate, or a project owner). Descriptive only; it grants no authority, and the page says so above the fold. Written with the re-skin (task 1003307); the redesign (task 1003470) added the identity cell, the sort and craft lenses, and the `search` state. /scouting is the legacy door to this same page (scouting.states.json measures it separately). Populated look: the hall-preview harness (scripts/hall-preview/fixtures/scouting.json carries five builders, three with avatars).",
3
3
  "page": "/builders/people",
4
4
  "surface": "hall-ui",
5
5
  "stub": {
@@ -14,6 +14,7 @@
14
14
  "/api/(gds|bongos)/inbox/",
15
15
  "/api/(gds|bongos)/versions",
16
16
  "/api/(gds|bongos)/builders",
17
+ "/api/(gds|bongos)/scouting",
17
18
  "/api/(gds|bongos)/public/"
18
19
  ],
19
20
  "ignoreConsole": [
@@ -43,6 +44,35 @@
43
44
  ]
44
45
  }
45
46
  },
47
+ "search": {
48
+ "auth": true,
49
+ "actions": [
50
+ [
51
+ "wait",
52
+ 700
53
+ ],
54
+ [
55
+ "fill",
56
+ "#people-q",
57
+ "hyp"
58
+ ],
59
+ [
60
+ "wait",
61
+ 200
62
+ ]
63
+ ],
64
+ "expect": {
65
+ "visible": [
66
+ "#people-scroll"
67
+ ],
68
+ "value": [
69
+ [
70
+ "#people-q",
71
+ "hyp"
72
+ ]
73
+ ]
74
+ }
75
+ },
46
76
  "out": {
47
77
  "auth": false
48
78
  }
@@ -3,17 +3,22 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Scouting — {{worldName}}</title>
7
- <meta name="description" content="Cross-project builder activity — an internal talent view for project curators.">
6
+ <title>People — {{worldName}}</title>
7
+ <meta name="description" content="The builder directory: everyone building across {{worldName}} projects, searchable and ranked by track record.">
8
8
  <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
11
  <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
12
- <style>
13
- #body .who { font-weight: 600; }
14
- </style>
12
+ <!-- /scouting is the LEGACY DOOR to the People directory (task 1002577
13
+ graduated it into the hall; task 1002710 walled it on the same
14
+ project.curate gate). Task 1003470 made it the SAME PAGE: the body below
15
+ is byte-identical to people.html and runs people.js — the one
16
+ /api/gds/scouting rollup used to be drawn three different ways, and a
17
+ door that renders a different page from the same data is the drift.
18
+ tests/hall_roster_world.mjs holds the two bodies equal. -->
19
+ <link rel="stylesheet" href="/builders/people.css">
15
20
  </head>
16
- <body data-page="scouting" data-page-title="Scouting">
21
+ <body data-page="people" data-page-title="People">
17
22
 
18
23
  <div class="app">
19
24
  <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
@@ -21,20 +26,24 @@
21
26
  <div class="app-body">
22
27
  <header class="app-topbar" id="app-topbar"></header>
23
28
  <div class="app-content">
24
- <!-- task 1003307: the board page head. Scouting shipped with NO <h1> at all
25
- (one-header-main-h1 measured 0 on every render), so the re-skin
26
- carries the structural fix rather than porting the hole. The heading
27
- is the LABEL voice, not a display line: on a working surface the
28
- page's own name is the least interesting thing on it. -->
29
+ <!-- The board page head (task 1003307): the label voice, because on a
30
+ working surface the page's own name is the least interesting thing on
31
+ it. The sub is rewritten by people.js once the directory has loaded. -->
29
32
  <div class="page-head page-head--board">
30
- <h1 class="page-head__h">Scouting</h1>
31
- <p class="page-head__sub">Builders active across all hosted projects — an internal talent view. Descriptive only; it grants no authority anywhere.</p>
33
+ <h1 class="page-head__h">People</h1>
34
+ <p class="page-head__sub" id="people-sub">Everyone building across hosted projects, ranked by track record. Descriptive only — cross-project numbers never carry authority here.</p>
32
35
  </div>
33
36
 
34
37
  <main id="hall-main">
35
- <section class="scroll" aria-labelledby="scouting-h">
36
- <h2 id="scouting-h" class="scroll__h">Cross-project builders</h2>
37
- <div id="body" class="profile__placeholder">Loading…</div>
38
+ <!-- task 1003470: ONE directory. /people and its legacy door /scouting
39
+ render this same page from the same script — the same
40
+ /api/gds/scouting rollup used to be drawn three different ways. -->
41
+ <section class="scroll" id="people-scroll" aria-labelledby="people-h">
42
+ <h2 id="people-h" class="scroll__h">Builder directory<span class="count-pill" id="people-count" hidden></span></h2>
43
+ <div id="people-filters"></div>
44
+ <div id="people-body">
45
+ <div class="profile__placeholder">Loading the directory…</div>
46
+ </div>
38
47
  </section>
39
48
  </main>
40
49
 
@@ -50,7 +59,11 @@
50
59
  <script src="/builders/shell.js?v=2026-07-02-shell1"></script>
51
60
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
52
61
  <script src="/builders/palette.js" defer></script>
62
+ <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
63
+ branding pack's currency label, loaded BEFORE people.js. -->
64
+ <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
53
65
  <script src="/builders/brand-apply.js"></script>
54
- <script src="/builders/scouting.js"></script>
66
+ <script src="/builders/hall-kit.js"></script>
67
+ <script src="/builders/people.js"></script>
55
68
  </body>
56
69
  </html>
@@ -1,5 +1,5 @@
1
1
  {
2
- "_": "Scouting — the internal talent view, same project.curate gate as People. Carries the hall's last three-line inline <style>, which is well inside hall_audit's budget and is left alone deliberately. Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once.",
2
+ "_": "Scouting — the LEGACY DOOR to the People directory, on the same project.curate gate. Since task 1003470 it renders the People page itself (the same body, the same people.js), so this file measures the door, not a second design: the same floors at the same widths, with the People panel expected. The three-line inline <style> it used to carry went with the page it belonged to.",
3
3
  "page": "/builders/scouting",
4
4
  "surface": "hall-ui",
5
5
  "stub": {
@@ -36,7 +36,12 @@
36
36
  "wait",
37
37
  700
38
38
  ]
39
- ]
39
+ ],
40
+ "expect": {
41
+ "visible": [
42
+ "#people-scroll"
43
+ ]
44
+ }
40
45
  },
41
46
  "out": {
42
47
  "auth": false
@@ -1,16 +1,19 @@
1
- /* sessions.css — Page-local styles for the Session log page.
2
- Lifted verbatim out of the page's inline <style> block (task 1002176);
3
- the ?v= cache-bust is stamped automatically from this file's content hash
4
- at boot (src/asset-version.js). */
5
-
6
- /* Page-local styles for the session log — small additions on top of the
7
- shared .ledger / .stat-row components in style.css. */
8
- .ledger-table-wrap { overflow-x: auto; margin-top: 6px; }
9
- table.ledger th, table.ledger td { white-space: nowrap; }
1
+ /* sessions.css — page-local styles for the session ledger (task 1002176;
2
+ redesigned in task 1003470, goal 1000081). The ledger, the pager and the
3
+ totals strip are the shell's; this holds only what the page adds. Gone with
4
+ the redesign: `white-space: nowrap` on every cell, and the .ledger-table-wrap
5
+ that existed only to give it a scrollport — a scrollport the shell's sticky
6
+ header then stuck to, so the column heads sat over the first row on desktop.
7
+ The shell's card strip handles a phone, the way it does on every other
8
+ ledger page. The session id is the family's .ov-mono (oversight.css). */
9
+ /* Six figures on the strip: the shell's auto-fit strands the sixth alone on a
10
+ second row at desktop width, so the strip fixes its columns — six across
11
+ from 1100px, three across under it, the shell's own pair on a phone. */
12
+ #stats-body { grid-template-columns: repeat(3, minmax(0, 1fr)); }
13
+ @media (min-width: 1100px) { #stats-body { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
14
+ @media (max-width: 480px) { #stats-body { grid-template-columns: 1fr 1fr; } }
10
15
  .ledger td.warn { color: var(--danger); font-weight: 600; }
11
- .ledger-sid {
12
- font-family: var(--font-mono);
13
- font-size: 12.5px;
14
- color: var(--info);
15
- }
16
- .ledger-rank { font-size: 12px; color: var(--ink-faint); }
16
+ .ledger-rank { font-size: 12px; color: var(--ink-faint); }
17
+ /* The task ids: chips that are doors (the family's a.fact-chip hover). */
18
+ .ledger-tasks { display: inline-flex; flex-wrap: wrap; gap: 6px; }
19
+ .ledger-tasks .fact-chip { text-decoration: none; }
@@ -4,11 +4,14 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Sessions — {{worldName}}</title>
7
- <meta name="description" content="The record of every work session — Archon view.">
7
+ <meta name="description" content="The record of every work session — who built, what it touched, what it cost.">
8
8
  <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
11
  <link rel="stylesheet" href="/builders/style.css?v=2026-06-04-sessions">
12
+ <!-- task 1003470: the family sheet (the top-of-page strip, the mono fact,
13
+ the chip that is a door), linked before the page's own. -->
14
+ <link rel="stylesheet" href="/builders/oversight.css">
12
15
  <link rel="stylesheet" href="/builders/sessions.css">
13
16
  </head>
14
17
  <body data-page="sessions" data-page-title="Sessions">
@@ -30,35 +33,37 @@
30
33
  </div>
31
34
 
32
35
  <main id="hall-main">
33
- <!-- Access notice — shown by sessions.js when the viewer is not an Archon. -->
36
+ <!-- Access notice — shown by sessions.js when the viewer is below the
37
+ floor. The heading and the line are written from the pack's rank
38
+ label (ADR 0157: metic and up), so the static text never renders. -->
34
39
  <section class="scroll" id="gate-scroll" aria-labelledby="gate-h" hidden>
35
- <h2 id="gate-h" class="scroll__h">Archons only</h2>
36
- <p class="scroll__lede" id="gate-body">The session log is available to Archons only.</p>
40
+ <h2 id="gate-h" class="scroll__h">Higher ranks only</h2>
41
+ <p class="scroll__lede" id="gate-body">The session log opens from a higher rank.</p>
37
42
  </section>
38
43
 
39
- <!-- Totals — aggregate stats, filled by sessions.js. -->
40
- <section class="scroll" id="stats-scroll" aria-labelledby="stats-h" hidden>
41
- <h2 id="stats-h" class="scroll__h">Totals</h2>
42
- <p class="scroll__lede">Aggregate totals across every recorded work session.</p>
44
+ <!-- Totals — the shell's glass strip at the top of the page (the way
45
+ Watch, Harbor and Gate carry theirs), not a strip inside a panel. -->
46
+ <section class="ov-summary" id="stats-scroll" aria-labelledby="stats-h" hidden>
47
+ <h2 id="stats-h" class="sr-only">Totals</h2>
43
48
  <div class="stat-row" id="stats-body"></div>
44
49
  </section>
45
50
 
46
- <!-- Every recorded session — the corpus table. -->
51
+ <!-- Every recorded session — the corpus table. No wrapper: the shell's
52
+ card strip scrolls a wide ledger on a phone, and the old wrapper
53
+ turned the sticky header into a scrollport overlay on desktop. -->
47
54
  <section class="scroll" id="sessions-scroll" aria-labelledby="sessions-h" hidden>
48
55
  <h2 id="sessions-h" class="scroll__h">Recorded sessions</h2>
49
- <p class="scroll__lede">Most recent first. Each row is one recorded work session.</p>
50
- <div class="ledger-table-wrap">
51
- <table class="ledger" id="sessions-table">
52
- <thead>
53
- <tr>
54
- <th>Session</th><th>Builder</th><th>Tasks</th>
55
- <th>Turns</th><th>Tokens</th><th>Tool errs</th>
56
- <th>Duration</th><th>Recorded</th>
57
- </tr>
58
- </thead>
59
- <tbody id="sessions-body"></tbody>
60
- </table>
61
- </div>
56
+ <p class="scroll__lede">Most recent first. Each row is one recorded work session; every id on it opens its record.</p>
57
+ <table class="ledger" id="sessions-table">
58
+ <thead>
59
+ <tr>
60
+ <th>Session</th><th>Builder</th><th>Tasks</th>
61
+ <th class="ledger__num">Earned</th><th class="ledger__num">Turns</th><th class="ledger__num">Tokens</th><th class="ledger__num">Tool errors</th>
62
+ <th class="ledger__num">Duration</th><th>Recorded</th>
63
+ </tr>
64
+ </thead>
65
+ <tbody id="sessions-body"></tbody>
66
+ </table>
62
67
  <nav class="pager" id="ledger-pager" hidden aria-label="Session pages">
63
68
  <button type="button" class="btn-ghost" id="pager-prev">← Newer</button>
64
69
  <span class="pager__range" id="pager-pos"></span>
@@ -1,11 +1,14 @@
1
1
  // public-builders/sessions.js — the Session Ledger (/builders/sessions).
2
2
  //
3
- // Archon-only monitoring view of the BFG session corpus (ADR 0027 / cluster 6D).
4
- // Read-only. Fetches /api/bongos/me to learn the viewer's rank; only the Archon
5
- // sees data. The data itself comes from GET /api/bongos/sessions/search, which the
6
- // SERVER gates to Archon and AUDITS (one audit_log row per load, by design) —
7
- // this page only reflects what that endpoint returns; it is not the authority.
8
- // On a 401 it redirects into the web sign-in flow, like the other hall pages.
3
+ // Metic+ monitoring view of the BFG session corpus (ADR 0027 / cluster 6D;
4
+ // the floor moved archon→metic in ADR 0157). Read-only. Fetches /api/bongos/me
5
+ // to learn the viewer's rank. The data itself comes from GET
6
+ // /api/bongos/sessions/search, which the SERVER gates and AUDITS (one audit_log
7
+ // row per load, by design) — this page only reflects what that endpoint
8
+ // returns; it is not the authority. On a 401 it redirects into the web sign-in
9
+ // flow, like the other hall pages. Redesigned in task 1003470 (goal 1000081):
10
+ // the totals on the shell's top strip, the ledger without its nowrap + wrapper
11
+ // pair, every id on a row a door, the floor named from the pack.
9
12
 
10
13
  (() => {
11
14
  'use strict';
@@ -62,26 +65,38 @@
62
65
  .join('');
63
66
  }
64
67
 
68
+ // task 1003470 — every id on the row is a door: the task ids are chips to the
69
+ // task page (`/builders/task/N`, which both hosts serve), the builder is the
70
+ // shell's .lb-name to their profile, and the session id is a mono fact with
71
+ // the full id on hover — it was painted in the info colour, which reads as a
72
+ // link to nowhere. The row also shows what the session EARNED, a fact the
73
+ // ledger had and dropped.
65
74
  function renderTable(sessions) {
66
75
  const body = $('#sessions-body');
67
76
  if (!sessions.length) {
68
77
  body.innerHTML =
69
- '<tr><td colspan="8" class="ledger__empty">No sessions recorded yet. They land here as builders ship and end sessions.</td></tr>';
78
+ '<tr><td colspan="9" class="ledger__empty">No sessions recorded yet. They land here as builders ship and end sessions.</td></tr>';
70
79
  return;
71
80
  }
72
81
  body.innerHTML = sessions
73
82
  .map((s) => {
74
83
  const tasks = Array.isArray(s.task_ids) && s.task_ids.length
75
- ? s.task_ids.map((t) => `#${escapeHtml(t)}`).join(', ')
84
+ ? `<span class="ledger-tasks">${s.task_ids.map((t) =>
85
+ `<a class="fact-chip" href="/builders/task/${encodeURIComponent(String(t))}">task ${escapeHtml(String(t))}</a>`).join('')}</span>`
76
86
  : '—';
87
+ const who = s.github_login ? `@${escapeHtml(s.github_login)}` : `#${escapeHtml(String(s.builder_id ?? '?'))}`;
88
+ const whoHtml = s.builder_id != null
89
+ ? `<a class="lb-name" href="/builders/profile?id=${encodeURIComponent(String(s.builder_id))}">${who}</a>`
90
+ : who;
77
91
  const errs = num(s.tool_errors);
78
92
  return (
79
93
  '<tr>' +
80
- `<td><span class="ledger-sid" title="${escapeHtml(s.session_id)}">${escapeHtml(shortSid(s.session_id))}</span></td>` +
81
- `<td>${escapeHtml(s.github_login || ('#' + s.builder_id))}` +
82
- (s.rank ? ` <span class="ledger-rank">(${escapeHtml(rankLabel(s.rank))})</span>` : '') +
94
+ `<td><span class="ov-mono" title="${escapeHtml(s.session_id)}">${escapeHtml(shortSid(s.session_id))}</span></td>` +
95
+ `<td>${whoHtml}` +
96
+ (s.rank ? ` <span class="ledger-rank">${escapeHtml(rankLabel(s.rank))}</span>` : '') +
83
97
  '</td>' +
84
98
  `<td>${tasks}</td>` +
99
+ `<td class="ledger__num">${fmtInt(s.drachmae_earned)}</td>` +
85
100
  `<td class="ledger__num">${fmtInt(s.assistant_turns)}</td>` +
86
101
  `<td class="ledger__num" title="${fmtInt(s.total_tokens)} tokens">${escapeHtml(fmtCompact(s.total_tokens))}</td>` +
87
102
  `<td class="ledger__num${errs > 0 ? ' warn' : ''}">${errs}</td>` +
@@ -93,9 +108,13 @@
93
108
  .join('');
94
109
  }
95
110
 
111
+ // The floor is written from the pack's rank label (ADR 0157: metic and up),
112
+ // never a hardcoded rank name — the page used to say "Archons only".
96
113
  function showSealed(message) {
97
- $('#ledger-sub').textContent = 'Archons only.';
114
+ const floor = rankLabel('metic');
115
+ $('#ledger-sub').textContent = `${floor} and up.`;
98
116
  const gate = $('#gate-scroll');
117
+ $('#gate-h').textContent = `${floor} and up`;
99
118
  if (message) $('#gate-body').textContent = message;
100
119
  gate.hidden = false;
101
120
  $('#stats-scroll').hidden = true;
@@ -211,7 +230,7 @@
211
230
  // ADR 0157: `session.search` moved archon→metic, so the session log is a
212
231
  // Metic+ surface now. Cosmetic — the route gate is the wall.
213
232
  if (!builder || (builder.rank !== 'metic' && builder.rank !== 'archon')) {
214
- showSealed('The session log is available from the Metic rank up. Your current rank does not have access.');
233
+ showSealed(`The session log opens at the ${rankLabel('metic')} rank. Your current rank does not have access.`);
215
234
  return;
216
235
  }
217
236
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "_": "Sessions — the session ledger behind requireGovernmentPage. sessions.js pages SERVER-SIDE by offset and is the one sanctioned hand-rolled pager in the hall; this file measures it, it does not ask it to change. Written with the re-skin (task 1003307) so this page is MEASURED at three widths in both modes rather than looked at once.",
2
+ "_": "Sessions — the session ledger behind requireGovernmentPage. sessions.js pages SERVER-SIDE by offset and is the one sanctioned hand-rolled pager in the hall; this file measures it, it does not ask it to change. Written with the re-skin (task 1003307); the redesign (task 1003470) put the totals on the shell's top-of-page strip, dropped the nowrap + wrapper pair that broke the sticky header, and made every id on a row a door. Populated look: the hall-preview harness (five rows in sessions__search.json).",
3
3
  "page": "/builders/sessions",
4
4
  "surface": "hall-ui",
5
5
  "stub": {
@@ -38,7 +38,8 @@
38
38
  ],
39
39
  "expect": {
40
40
  "visible": [
41
- "#stats-scroll"
41
+ "#stats-scroll",
42
+ "#sessions-scroll"
42
43
  ]
43
44
  }
44
45
  },
@@ -5,7 +5,7 @@
5
5
  "coreVersion": "^1.19.0",
6
6
  "default": true,
7
7
  "maintenance": { "status": "core-maintained" },
8
- "contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects"] },
8
+ "contributes": { "skills": ["design", "design-sync", "figma-design-sync", "design-taste-frontend", "design-taste-frontend-v1", "high-end-visual-design", "redesign-existing-projects", "impeccable"] },
9
9
  "provides": [],
10
10
  "consumes": []
11
11
  }