@bongos/core 1.21.39 → 1.21.41

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 (88) hide show
  1. package/.bongos-core.json +144 -84
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +16 -0
  4. package/clients/bongos-client/index.cjs +16 -0
  5. package/clients/bongos-client/index.d.ts +25 -0
  6. package/clients/bongos-client/index.mjs +16 -0
  7. package/docs/api/openapi.json +492 -3
  8. package/docs/api-reference.md +13 -1
  9. package/docs/copy-inventory.md +29 -13
  10. package/docs/copy-registry.json +177 -23
  11. package/docs/file-map.md +1 -0
  12. package/docs/module-api-changelog.md +4 -0
  13. package/docs/page-inventory.json +14 -2
  14. package/docs/page-readings.json +601 -554
  15. package/modules/government/docket.js +14 -0
  16. package/modules/hall-ui/public/atlas.html +3 -3
  17. package/modules/hall-ui/public/blockers.html +3 -3
  18. package/modules/hall-ui/public/board-room.html +3 -3
  19. package/modules/hall-ui/public/city.html +3 -3
  20. package/modules/hall-ui/public/collab.html +3 -3
  21. package/modules/hall-ui/public/commands.html +3 -3
  22. package/modules/hall-ui/public/copy-desk.html +3 -3
  23. package/modules/hall-ui/public/craft-mode.js +62 -0
  24. package/modules/hall-ui/public/deploy.html +3 -3
  25. package/modules/hall-ui/public/diagrams.html +3 -3
  26. package/modules/hall-ui/public/drachmae.html +3 -3
  27. package/modules/hall-ui/public/fleet.html +3 -3
  28. package/modules/hall-ui/public/gate.html +3 -3
  29. package/modules/hall-ui/public/goal-map.html +3 -3
  30. package/modules/hall-ui/public/goals.html +3 -3
  31. package/modules/hall-ui/public/government.html +3 -3
  32. package/modules/hall-ui/public/idea.html +3 -3
  33. package/modules/hall-ui/public/ideas.html +3 -3
  34. package/modules/hall-ui/public/index.html +3 -3
  35. package/modules/hall-ui/public/modules.html +3 -3
  36. package/modules/hall-ui/public/page-notes.js +320 -0
  37. package/modules/hall-ui/public/primer.html +3 -3
  38. package/modules/hall-ui/public/profile.html +3 -3
  39. package/modules/hall-ui/public/project-settings.html +3 -3
  40. package/modules/hall-ui/public/ranks.html +3 -3
  41. package/modules/hall-ui/public/roadmap.html +3 -3
  42. package/modules/hall-ui/public/roster.html +3 -3
  43. package/modules/hall-ui/public/sessions.html +3 -3
  44. package/modules/hall-ui/public/settings.html +3 -3
  45. package/modules/hall-ui/public/street-card.css +21 -2
  46. package/modules/hall-ui/public/street-card.js +198 -64
  47. package/modules/hall-ui/public/street-card.states.json +495 -7
  48. package/modules/hall-ui/public/street-desk.js +47 -2
  49. package/modules/hall-ui/public/street.css +1 -3
  50. package/modules/hall-ui/public/street.html +3 -4
  51. package/modules/hall-ui/public/street.js +42 -15
  52. package/modules/hall-ui/public/studio.html +3 -3
  53. package/modules/hall-ui/public/style.css +144 -0
  54. package/modules/hall-ui/public/task.html +3 -3
  55. package/modules/hall-ui/public/thinking.html +3 -3
  56. package/modules/hall-ui/public/tweak-editor.html +3 -3
  57. package/modules/hall-ui/public/watch.html +3 -3
  58. package/modules/hall-ui/public/work.html +3 -3
  59. package/modules/hall-ui/records/page-notes.md +16 -0
  60. package/modules/ideas/governor-docket.js +2 -0
  61. package/modules/lifecycle/governor-docket.js +4 -0
  62. package/modules/onboarding/routes/access-requests.js +2 -0
  63. package/modules/page-notes/CLAUDE.md +15 -0
  64. package/modules/page-notes/db.js +147 -0
  65. package/modules/page-notes/migrations/page_notes_001_notes.sql +68 -0
  66. package/modules/page-notes/module.json +13 -0
  67. package/modules/page-notes/notes.js +174 -0
  68. package/modules/page-notes/routes/page-notes.js +172 -0
  69. package/modules/page-notes/split.js +68 -0
  70. package/modules/provisioning/starter-bundles.js +1 -0
  71. package/modules/security/routes/reports.js +3 -0
  72. package/modules/ui-design/kit/serve.js +1 -1
  73. package/package-lock.json +2 -2
  74. package/package.json +1 -1
  75. package/release-notes.json +12 -0
  76. package/scripts/gds/publish-manifest.js +1 -0
  77. package/src/bongos/module-rechecks.js +12 -1
  78. package/src/bongos/module-scope-map.js +4 -0
  79. package/src/bongos/routes.js +4 -1
  80. package/src/module-api.js +1 -1
  81. package/tests/governor_docket.mjs +20 -0
  82. package/tests/hall_matter_card.mjs +143 -18
  83. package/tests/hall_street.mjs +1 -1
  84. package/tests/hall_tweak_editor.mjs +1 -1
  85. package/tests/module_loader.mjs +1 -1
  86. package/tests/page_notes.mjs +289 -0
  87. package/tests/page_notes_db.mjs +140 -0
  88. package/tests/page_notes_panel.mjs +145 -0
@@ -0,0 +1,320 @@
1
+ // page-notes.js — the Artistry palette's feedback doc (task 1004496, goal
2
+ // 1000095 WA6). Loaded on demand by craft-mode.js: when the palette button is
3
+ // pressed (open) or a page arrives with ?note=N (openNote). Never on page load.
4
+ //
5
+ // A side sheet over the page: write a doc about THIS page (as long as it needs
6
+ // to be), add reference images, and "Turn into ideas" — the server's AI split
7
+ // files each point as its own idea under your name, and the doc stays as the
8
+ // page's reference. Below the writing area, the page's earlier docs; each opens
9
+ // in place. The draft saves as you type, so closing loses nothing.
10
+ //
11
+ // The walls are the server's (modules/page-notes): a draft is its author's, a
12
+ // submitted doc is any signed-in builder's. This file only draws.
13
+ (function () {
14
+ 'use strict';
15
+
16
+ var API = '/api/bongos/page-notes';
17
+ var IMAGE_RE = /!\[([^\]\n]{0,120})\]\(\/api\/bongos\/page-notes\/images\/(\d{1,18})\)/g;
18
+ var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
19
+ var MAX_IMAGES = 8;
20
+ var TYPES = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
21
+
22
+ var sheet = null, returnFocus = null, state = null, saveTimer = null, pollTimer = null;
23
+
24
+ function esc(s) {
25
+ return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
26
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
27
+ });
28
+ }
29
+ function page() {
30
+ var b = document.body || { dataset: {} };
31
+ var slug = String((b.dataset && b.dataset.page) || 'home').toLowerCase().replace(/[^a-z0-9_-]/g, '-').slice(0, 64) || 'home';
32
+ var title = (b.dataset && b.dataset.pageTitle) || String(document.title || '').split(/\s[—-]\s/)[0] || slug;
33
+ var path = String((window.location && window.location.pathname) || '/').replace(/[^A-Za-z0-9/_.-]/g, '').slice(0, 200) || '/';
34
+ return { id: 'builders:' + slug, path: path, title: title.slice(0, 120) };
35
+ }
36
+ function api(method, path, body) {
37
+ var opts = { method: method, credentials: 'same-origin', headers: {} };
38
+ if (body !== undefined) { opts.headers['content-type'] = 'application/json'; opts.body = JSON.stringify(body); }
39
+ return fetch(API + path, opts).then(function (r) {
40
+ return r.json().catch(function () { return {}; }).then(function (j) {
41
+ if (!r.ok) {
42
+ var e = new Error((j && j.error && j.error.message) || (r.status === 401 ? 'Sign in to write page feedback.' : 'That did not work. Try again in a moment.'));
43
+ e.status = r.status;
44
+ throw e;
45
+ }
46
+ return j;
47
+ });
48
+ });
49
+ }
50
+ function day(iso) {
51
+ var d = iso ? new Date(iso) : null;
52
+ return d && !isNaN(d) ? d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) : '';
53
+ }
54
+ function plural(n, one, many) { return n + ' ' + (n === 1 ? one : many); }
55
+
56
+ // The doc, read: headings, lists, paragraphs, and only this module's own images.
57
+ function renderBody(md) {
58
+ var out = [], para = [], list = [];
59
+ function flushPara() { if (para.length) { out.push('<p>' + para.join('<br>') + '</p>'); para = []; } }
60
+ function flushList() { if (list.length) { out.push('<ul>' + list.map(function (x) { return '<li>' + x + '</li>'; }).join('') + '</ul>'); list = []; } }
61
+ function inline(line) {
62
+ return esc(line).replace(IMAGE_RE, function (_m, alt, id) {
63
+ return '<img class="pn-doc__img" src="' + API + '/images/' + id + '" alt="' + alt + '" loading="lazy">';
64
+ });
65
+ }
66
+ String(md || '').split('\n').forEach(function (raw) {
67
+ var line = raw.replace(/\s+$/, '');
68
+ var h = /^(#{1,3})\s+(.*)$/.exec(line);
69
+ var li = /^\s*[-*]\s+(.*)$/.exec(line);
70
+ if (!line.trim()) { flushPara(); flushList(); return; }
71
+ if (h) { flushPara(); flushList(); out.push('<h3 class="pn-doc__h">' + inline(h[2]) + '</h3>'); return; }
72
+ if (li) { flushPara(); list.push(inline(li[1])); return; }
73
+ flushList();
74
+ para.push(inline(line));
75
+ });
76
+ flushPara(); flushList();
77
+ return out.join('');
78
+ }
79
+
80
+ // ---------- the sheet ----------
81
+ function shellHtml(p) {
82
+ return '<div class="pn__panel" role="dialog" aria-modal="true" aria-labelledby="pn-title">' +
83
+ '<div class="pn__head">' +
84
+ '<p class="pn__eyebrow">Page feedback · ' + esc(p.title) + '</p>' +
85
+ '<h2 class="pn__title" id="pn-title">What should this page be?</h2>' +
86
+ '<button class="pn__close" type="button" data-pn-close aria-label="Close">' +
87
+ '<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><path d="M5 5l8 8M13 5l-8 8"/></svg></button>' +
88
+ '</div>' +
89
+ '<div class="pn__body" id="pn-body"><p class="pn__quiet">Opening your doc…</p></div>' +
90
+ '</div>';
91
+ }
92
+ function writeHtml() {
93
+ var n = state.note, imgs = state.images;
94
+ return '<label class="pn__label" for="pn-text">Your feedback</label>' +
95
+ '<textarea class="pn__text" id="pn-text" rows="14" placeholder="Write what you see on this page and what it should be. Long is fine: each separate point becomes its own idea.">' + esc(n.body_md) + '</textarea>' +
96
+ '<div class="pn__tools">' +
97
+ '<label class="pn__add"><input type="file" id="pn-file" accept="' + TYPES.join(',') + '" multiple>Add images</label>' +
98
+ '<span class="pn__quiet" id="pn-count">' + imgs.length + ' of ' + MAX_IMAGES + ' images</span>' +
99
+ '<span class="pn__quiet pn__saved" id="pn-saved" aria-live="polite"></span>' +
100
+ '</div>' +
101
+ (imgs.length ? '<div class="pn__thumbs">' + imgs.map(function (i) {
102
+ return '<img class="pn__thumb" src="' + esc(i.url) + '" alt="' + esc(i.name || 'reference image') + '">';
103
+ }).join('') + '</div>' : '') +
104
+ '<p class="pn__error" id="pn-error" role="alert"></p>' +
105
+ '<div class="pn__foot">' +
106
+ '<p class="pn__hint">When you submit, an AI splits this into separate ideas, filed under your name. The doc stays here as this page\'s reference.</p>' +
107
+ '<button class="pn__submit" type="button" data-pn-submit>Turn into ideas</button>' +
108
+ '</div>' +
109
+ listHtml();
110
+ }
111
+ function listHtml() {
112
+ var rows = state.list || [];
113
+ return '<section class="pn__list" aria-labelledby="pn-list-h"><h3 class="pn__list-h" id="pn-list-h">This page\'s reference docs</h3>' +
114
+ (rows.length ? '<ul class="pn__rows">' + rows.map(function (r) {
115
+ return '<li><button class="pn__row" type="button" data-pn-note="' + esc(r.id) + '">' +
116
+ '<span class="pn__row-meta">' + esc(r.author_name) + ' · ' + esc(day(r.submitted_at)) + ' · ' +
117
+ (r.status === 'filed' ? esc(plural(r.idea_count, 'idea', 'ideas')) : 'splitting…') + '</span>' +
118
+ '<span class="pn__row-text">' + esc(String(r.excerpt || '').replace(IMAGE_RE, '[image]').replace(/\s+/g, ' ').slice(0, 160)) + '</span>' +
119
+ '</button></li>';
120
+ }).join('') + '</ul>' : '<p class="pn__quiet">None yet. Yours will be the first.</p>') +
121
+ '</section>';
122
+ }
123
+ function ideasHtml(ideas) {
124
+ if (!ideas || !ideas.length) return '';
125
+ return '<ul class="pn__ideas">' + ideas.map(function (i) {
126
+ return '<li><a href="/#/idea/' + esc(i.id) + '">' + esc(i.title) + '</a></li>';
127
+ }).join('') + '</ul>';
128
+ }
129
+ function readHtml(doc) {
130
+ var n = doc.note;
131
+ var filed = n.status === 'filed';
132
+ return '<button class="pn__back" type="button" data-pn-back>← ' + (state && state.note ? 'Back to your doc' : 'This page\'s docs') + '</button>' +
133
+ '<p class="pn__row-meta">' + esc(n.author_name) + ' · ' + esc(day(n.submitted_at)) + ' · ' + esc(n.page_title || n.page_id) + '</p>' +
134
+ '<div class="pn-doc">' + renderBody(n.body_md) + '</div>' +
135
+ (filed
136
+ ? '<h3 class="pn__list-h">It became ' + esc(plural(doc.ideas.length, 'idea', 'ideas')) + '</h3>' + ideasHtml(doc.ideas)
137
+ : '<p class="pn__quiet">Being split into ideas…</p>');
138
+ }
139
+ function doneHtml(doc) {
140
+ return '<p class="pn__done">Filed as ' + esc(plural(doc.ideas.length, 'idea', 'ideas')) + ' under your name.</p>' +
141
+ ideasHtml(doc.ideas) +
142
+ '<p class="pn__hint">The doc is now this page\'s reference. Every idea links back to it.</p>' +
143
+ '<button class="pn__back" type="button" data-pn-new>Write another</button>';
144
+ }
145
+
146
+ function body() { return sheet && sheet.querySelector('#pn-body'); }
147
+ function setError(msg) { var e = sheet && sheet.querySelector('#pn-error'); if (e) e.textContent = msg || ''; }
148
+ function setSaved(msg) { var e = sheet && sheet.querySelector('#pn-saved'); if (e) e.textContent = msg || ''; }
149
+
150
+ function setTitle(text) { var h = sheet && sheet.querySelector('#pn-title'); if (h) h.textContent = text; }
151
+ function showWrite() {
152
+ var b = body();
153
+ if (!b) return;
154
+ setTitle('What should this page be?');
155
+ b.innerHTML = writeHtml();
156
+ var t = b.querySelector('#pn-text');
157
+ if (t) { t.addEventListener('input', queueSave); t.focus(); }
158
+ var f = b.querySelector('#pn-file');
159
+ if (f) f.addEventListener('change', function () { addImages(Array.prototype.slice.call(f.files || [])); f.value = ''; });
160
+ }
161
+ function currentText() { var t = sheet && sheet.querySelector('#pn-text'); return t ? t.value : (state && state.note ? state.note.body_md : ''); }
162
+ function queueSave() {
163
+ setSaved('');
164
+ if (saveTimer) clearTimeout(saveTimer);
165
+ saveTimer = setTimeout(save, 700);
166
+ }
167
+ function save() {
168
+ if (saveTimer) { clearTimeout(saveTimer); saveTimer = null; }
169
+ if (!state || !state.note || state.note.status !== 'draft') return Promise.resolve();
170
+ var text = currentText();
171
+ if (text === state.note.body_md) return Promise.resolve();
172
+ return api('PATCH', '/' + state.note.id, { body_md: text }).then(function () {
173
+ state.note.body_md = text;
174
+ setSaved('Saved');
175
+ }, function (e) { setError(e.message); });
176
+ }
177
+ function readFile(file) {
178
+ return new Promise(function (resolve, reject) {
179
+ var r = new FileReader();
180
+ r.onload = function () { resolve(String(r.result || '')); };
181
+ r.onerror = function () { reject(new Error('That file could not be read.')); };
182
+ r.readAsDataURL(file);
183
+ });
184
+ }
185
+ function addImages(files) {
186
+ setError('');
187
+ var chain = Promise.resolve();
188
+ files.forEach(function (file) {
189
+ chain = chain.then(function () {
190
+ if (TYPES.indexOf(file.type) === -1) throw new Error('Images only: PNG, JPG, GIF or WebP.');
191
+ if (file.size > MAX_IMAGE_BYTES) throw new Error('Each image can be up to 4 MB.');
192
+ if (state.images.length >= MAX_IMAGES) throw new Error('A doc can carry up to ' + MAX_IMAGES + ' images.');
193
+ return readFile(file).then(function (data) {
194
+ return api('POST', '/' + state.note.id + '/images', { content_type: file.type, data: data, name: String(file.name || '').slice(0, 120) });
195
+ }).then(function (j) {
196
+ state.images.push(j.image);
197
+ var name = String(j.image.name || 'image').replace(/[\]\[()]/g, '');
198
+ var t = sheet.querySelector('#pn-text');
199
+ var text = t ? t.value : '';
200
+ var at = t && typeof t.selectionStart === 'number' ? t.selectionStart : text.length;
201
+ var md = (at && text[at - 1] !== '\n' ? '\n' : '') + '![' + name + '](' + j.image.url + ')\n';
202
+ state.note.body_md = text;
203
+ var next = text.slice(0, at) + md + text.slice(at);
204
+ return api('PATCH', '/' + state.note.id, { body_md: next }).then(function () {
205
+ state.note.body_md = next;
206
+ showWrite();
207
+ setSaved('Saved');
208
+ });
209
+ });
210
+ });
211
+ });
212
+ chain.catch(function (e) { setError(e.message); });
213
+ }
214
+ function submit() {
215
+ setError('');
216
+ var btn = sheet.querySelector('[data-pn-submit]');
217
+ if (btn) btn.disabled = true;
218
+ save().then(function () {
219
+ return api('POST', '/' + state.note.id + '/submit');
220
+ }).then(function () {
221
+ var b = body();
222
+ if (b) b.innerHTML = '<p class="pn__done" aria-live="polite">Splitting your feedback into ideas…</p>';
223
+ poll(state.note.id, 0);
224
+ }, function (e) { if (btn) btn.disabled = false; setError(e.message); });
225
+ }
226
+ function poll(id, tries) {
227
+ pollTimer = setTimeout(function () {
228
+ api('GET', '/' + id).then(function (doc) {
229
+ if (!sheet) return;
230
+ if (doc.note.status === 'filed') { state.note = null; body().innerHTML = doneHtml(doc); refreshList(); return; }
231
+ if (tries < 60) poll(id, tries + 1);
232
+ else body().innerHTML = '<p class="pn__quiet">Still splitting. It will appear in this page\'s docs when it is done.</p>';
233
+ }, function () { if (tries < 60) poll(id, tries + 1); });
234
+ }, tries ? 2000 : 1200);
235
+ }
236
+ function refreshList() {
237
+ return api('GET', '?page_id=' + encodeURIComponent(page().id)).then(function (j) { if (state) state.list = j.notes || []; }, function () { /* the list is a convenience */ });
238
+ }
239
+ function openDraft() {
240
+ var p = page();
241
+ return Promise.all([
242
+ api('POST', '', { page_id: p.id, page_path: p.path, page_title: p.title }),
243
+ refreshList(),
244
+ ]).then(function (r) {
245
+ state.note = r[0].note;
246
+ state.images = r[0].images || [];
247
+ showWrite();
248
+ }, function (e) {
249
+ var b = body();
250
+ if (b) b.innerHTML = '<p class="pn__error" role="alert">' + esc(e.message) + '</p>';
251
+ });
252
+ }
253
+ function showNote(id) {
254
+ var b = body();
255
+ setTitle('Feedback on this page');
256
+ if (b) b.innerHTML = '<p class="pn__quiet">Opening the doc…</p>';
257
+ return save().then(function () { return api('GET', '/' + id); }).then(function (doc) {
258
+ if (b) { b.innerHTML = readHtml(doc); var back = b.querySelector('[data-pn-back]'); if (back) back.focus(); }
259
+ }, function (e) {
260
+ if (b) b.innerHTML = '<p class="pn__error" role="alert">' + esc(e.message) + '</p><button class="pn__back" type="button" data-pn-back>← Back</button>';
261
+ });
262
+ }
263
+
264
+ function onClick(e) {
265
+ if (e.target === sheet) { close(); return; }
266
+ var btn = e.target.closest && e.target.closest('button');
267
+ if (!btn) return;
268
+ if (btn.hasAttribute('data-pn-close')) close();
269
+ else if (btn.hasAttribute('data-pn-submit')) submit();
270
+ else if (btn.hasAttribute('data-pn-note')) showNote(btn.getAttribute('data-pn-note'));
271
+ else if (btn.hasAttribute('data-pn-back') || btn.hasAttribute('data-pn-new')) {
272
+ if (state.note && !btn.hasAttribute('data-pn-new')) showWrite();
273
+ else { body().innerHTML = '<p class="pn__quiet">Opening your doc…</p>'; openDraft(); }
274
+ }
275
+ }
276
+ function onKey(e) {
277
+ if (!sheet) return;
278
+ if (e.key === 'Escape') { e.preventDefault(); close(); return; }
279
+ if (e.key !== 'Tab') return;
280
+ var f = Array.prototype.slice.call(sheet.querySelectorAll('button, textarea, input, a[href]')).filter(function (x) { return !x.disabled; });
281
+ if (!f.length) return;
282
+ if (e.shiftKey && document.activeElement === f[0]) { e.preventDefault(); f[f.length - 1].focus(); }
283
+ else if (!e.shiftKey && document.activeElement === f[f.length - 1]) { e.preventDefault(); f[0].focus(); }
284
+ }
285
+ function mountSheet() {
286
+ if (sheet) return false;
287
+ returnFocus = document.activeElement;
288
+ state = { note: null, images: [], list: [] };
289
+ sheet = document.createElement('div');
290
+ sheet.className = 'pn';
291
+ sheet.id = 'page-notes';
292
+ sheet.innerHTML = shellHtml(page());
293
+ sheet.addEventListener('click', onClick);
294
+ document.addEventListener('keydown', onKey, true);
295
+ document.body.appendChild(sheet);
296
+ document.documentElement.classList.add('pn-open');
297
+ var c = sheet.querySelector('[data-pn-close]');
298
+ if (c) c.focus();
299
+ return true;
300
+ }
301
+ function close() {
302
+ if (!sheet) return;
303
+ save();
304
+ if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; }
305
+ document.removeEventListener('keydown', onKey, true);
306
+ if (sheet.parentNode) sheet.parentNode.removeChild(sheet);
307
+ sheet = null;
308
+ document.documentElement.classList.remove('pn-open');
309
+ if (returnFocus && returnFocus.focus) { try { returnFocus.focus(); } catch (_) { /* gone */ } }
310
+ returnFocus = null;
311
+ }
312
+
313
+ window.OTBPageNotes = {
314
+ open: function () { if (mountSheet()) openDraft(); },
315
+ openNote: function (id) { if (mountSheet()) refreshList().then(function () { return showNote(id); }); },
316
+ close: close,
317
+ renderBody: renderBody,
318
+ page: page,
319
+ };
320
+ })();
@@ -115,9 +115,9 @@
115
115
 
116
116
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
117
117
 
118
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
119
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
120
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
118
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
119
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
120
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
121
121
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
122
122
  <script src="/builders/palette.js" defer></script>
123
123
  <!-- the shared markdown reader (window.OTBMdReader) — primer.js renders through
@@ -206,9 +206,9 @@
206
206
 
207
207
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
208
208
  chrome exists before the page script runs. -->
209
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
210
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
211
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
209
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
210
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
211
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
212
212
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
213
213
  <script src="/builders/palette.js" defer></script>
214
214
  <!-- shared frontend toolkit (window.OTB) — loaded before profile.js, which pulls from it. -->
@@ -58,9 +58,9 @@
58
58
 
59
59
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
60
60
 
61
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
62
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
63
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
61
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
62
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
63
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
64
64
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
65
65
  <script src="/builders/palette.js" defer></script>
66
66
  <script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
@@ -77,9 +77,9 @@
77
77
 
78
78
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
79
79
  chrome exists before the page script runs. -->
80
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
81
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
82
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
80
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
81
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
82
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
83
83
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
84
84
  <script src="/builders/palette.js" defer></script>
85
85
  <!-- shared frontend toolkit (window.OTB) — loaded before ranks.js, which pulls from it. -->
@@ -41,9 +41,9 @@
41
41
 
42
42
  <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
43
43
 
44
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
45
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
46
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
44
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
45
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
46
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
47
47
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
48
48
  <script src="/builders/palette.js" defer></script>
49
49
  <script src="/builders/dom-utils.js?v=2026-06-14-domutils"></script>
@@ -53,9 +53,9 @@
53
53
 
54
54
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
55
55
  chrome exists before the page script runs. -->
56
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
57
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
58
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
56
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
57
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
58
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
59
59
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
60
60
  <script src="/builders/palette.js" defer></script>
61
61
  <!-- shared frontend toolkit (window.OTB — rank labels, number formats) + the
@@ -111,9 +111,9 @@
111
111
 
112
112
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
113
113
  chrome exists before the page script runs. -->
114
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
115
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
116
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
114
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
115
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
116
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
117
117
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
118
118
  <script src="/builders/palette.js" defer></script>
119
119
  <!-- shared frontend toolkit (window.OTB) — loaded BEFORE sessions.js so it can pull from it. -->
@@ -435,9 +435,9 @@
435
435
 
436
436
  <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
437
437
  chrome exists before the page scripts run. -->
438
- <script src="/builders/shell-trail.js?v=2026-10-03-looks"></script>
439
- <script src="/builders/craft-mode.js?v=2026-10-03-looks"></script>
440
- <script src="/builders/shell.js?v=2026-10-03-looks"></script>
438
+ <script src="/builders/shell-trail.js?v=2026-10-04-palette"></script>
439
+ <script src="/builders/craft-mode.js?v=2026-10-04-palette"></script>
440
+ <script src="/builders/shell.js?v=2026-10-04-palette"></script>
441
441
  <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
442
442
  <script src="/builders/palette.js" defer></script>
443
443
  <!-- shared frontend toolkit (window.OTB) — loaded before consuming IIFEs. -->
@@ -12,6 +12,8 @@
12
12
  /* the full card holds more than the short one did: it may stand taller over
13
13
  the street before it scrolls, so the acts are in view on a laptop */
14
14
  .street-card { max-height: min(480px, calc(100vh - 120px)); }
15
+ /* "more" open (task 1004566): the same card grows in place and scrolls inside */
16
+ .street-card[data-more="open"] { max-height: min(640px, calc(100vh - 96px)); }
15
17
  body[data-page="street"] {
16
18
  /* an objecting seat: the terracotta, deepened so its white initial clears AA */
17
19
  --mcard-against: color-mix(in srgb, var(--terracotta) 82%, #000000);
@@ -59,7 +61,24 @@ body[data-page="street"] {
59
61
  .mcard-textbtn { align-self: flex-start; background: none; border: 0; padding: 6px 0; min-height: 24px; text-decoration: underline; text-underline-offset: 4px; font-family: var(--font-body); font-size: 14px; color: var(--ink-soft); cursor: pointer; text-align: left; }
60
62
  .mcard-textbtn--accent { color: var(--street-accent-text); }
61
63
 
62
- /* the two forms: an objection on the record, and the revised words */
64
+ /* an act that asks: its one-tap chips (the desk row's), or one GitHub name */
65
+ .mcard-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 10px 12px; border-radius: 10px; background: var(--street-wash); }
66
+ .mcard-chips__ask { flex-basis: 100%; font-size: 12.5px; color: var(--ink-faint); }
67
+ .mcard-chip { height: 32px; padding: 0 14px; border-radius: 999px; font-family: var(--font-body); font-size: 13.5px; font-weight: 500; cursor: pointer; white-space: nowrap; text-transform: lowercase; background: var(--bg-card); border: 1px solid var(--street-rule-strong); color: var(--ink); }
68
+ .mcard-chip:hover { filter: brightness(1.06); }
69
+ .mcard-chip[disabled] { opacity: .55; cursor: progress; }
70
+ .mcard-chips__field { flex: 1 1 140px; min-width: 0; height: 32px; box-sizing: border-box; padding: 0 10px; font-family: var(--font-body); font-size: 14px; color: var(--ink); background: var(--bg); border: 1px solid var(--street-rule-strong); border-radius: 8px; }
71
+
72
+ /* more: the detail, in place */
73
+ .mcard-more__toggle { margin-top: -2px; }
74
+ .mcard-more { display: flex; flex-direction: column; gap: 6px; padding-top: 10px; border-top: 1px solid var(--street-rule); }
75
+ .mcard-more__text { margin: 0 0 4px; font-size: 14px; line-height: 1.5; white-space: pre-line; overflow-wrap: anywhere; }
76
+ .mcard-more__meta { margin: 0; font-size: 13px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
77
+ .mcard-more__history { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; }
78
+ .mcard-more__history li { padding: 6px 0; border-top: 1px solid var(--street-rule); font-size: 13.5px; line-height: 1.4; color: var(--ink-soft); overflow-wrap: anywhere; }
79
+ .mcard-more__queue { display: flex; flex-direction: column; align-items: flex-start; }
80
+
81
+ /* the revised words */
63
82
  .mcard-form { display: flex; flex-direction: column; gap: 6px; }
64
83
  .mcard-form__label { font-size: 12.5px; color: var(--ink-faint); }
65
84
  .mcard-form__text, .mcard-form__select { font-family: var(--font-body); font-size: 14px; line-height: 1.45; color: var(--ink); background: var(--bg); border: 1px solid var(--street-rule-strong); border-radius: 10px; padding: 8px 10px; box-sizing: border-box; width: 100%; }
@@ -68,7 +87,7 @@ body[data-page="street"] {
68
87
  .mcard-form .mcard-actions { margin-top: 4px; }
69
88
  .mcard-error { margin: 0; font-size: 13.5px; line-height: 1.4; color: var(--street-accent-text); }
70
89
 
71
- .mcard-btn:focus-visible, .mcard-textbtn:focus-visible, .mcard-form__text:focus-visible, .mcard-form__select:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
90
+ .mcard-btn:focus-visible, .mcard-textbtn:focus-visible, .mcard-form__text:focus-visible, .mcard-form__select:focus-visible, .mcard-chip:focus-visible, .mcard-chips__field:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
72
91
 
73
92
  /* the toast: one short line over the street */
74
93
  .street-toast { position: absolute; left: 50%; top: 192px; transform: translateX(-50%); max-width: calc(100% - 32px); box-sizing: border-box; font-size: 15px; line-height: 1.35; color: var(--ink); padding: 11px 20px; border-radius: 999px; background: var(--bg-card); border: 1px solid var(--street-rule-strong); z-index: 10; pointer-events: none; text-align: center; }