@bongos/core 1.19.447 → 1.19.449

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.
@@ -1331,5 +1331,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
1331
1331
  landed since 1.19.445 with no explicit bump. run 33341626697. (task 1002620)
1332
1332
  1.19.447 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1333
1333
  landed since 1.19.446 with no explicit bump. run 33345047021. (task 1002620)
1334
+ 1.19.448 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1335
+ landed since 1.19.447 with no explicit bump. run 33348050270. (task 1002620)
1336
+ 1.19.449 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1337
+ landed since 1.19.448 with no explicit bump. run 33406714743. (task 1002620)
1334
1338
  ---------------------------------------------------------------------------
1335
1339
  ```
@@ -1,89 +1,156 @@
1
- /* modules/hall-ui/public/idea.css — the idea-detail page (task 1002929 / R153
2
- of goal 1000062).
1
+ /* idea.css — what the IDEA record has that the task record does not
2
+ (task 1002929 / R153 of goal 1000062; re-skinned into the chrome world by
3
+ task 1003305, goal 1000081).
3
4
 
4
- Promoted out of an inline <style> because tests/hall_audit.mjs budgets an inline
5
- block at 15 lines and this is ~60: past that the documented pattern (task 2176)
6
- is a per-page stylesheet. The exceptions in that budget map are grandfathered or
7
- deliberate; a brand-new page has no business claiming one.
5
+ The shared composition — the way back, the kind eyebrow, the title, the
6
+ sections, the honest failure heading — is record.css, which idea.html loads
7
+ first. What is left here is the four shapes only an idea has: its grade badge,
8
+ its attribution pair, its completeness readout, and the author-only mark
9
+ control.
8
10
 
9
- Built on the hall's tokens. No --line/--panel: neither is defined in style.css,
10
- so a fallback is all you would ever get and both assume a dark ground the hall
11
- does not have by default (--bg is #f5f6f8). */
11
+ Retired with the re-skin: `.idea-detail__back` (the archetype's `.rec-back`),
12
+ `.ig-head` and `.ig-title` (the archetype's `.rec-kind` and `.rec-title`), and
13
+ every compat alias this sheet used to spend — --surface-2 → --glass,
14
+ --border → --rule-soft, --accent-weak/--accent-text → --accent-tint/--accent-ink.
15
+ The aliases still EXIST for the pages nobody has re-skinned, which is exactly
16
+ why spending one here would be legal CSS that quietly opts this sheet back out
17
+ of the world. `--line` is a WIDTH; the hairline's colour is --rule/--rule-soft. */
12
18
 
13
- /* Page-local, like task.html (task 751) — this page adds a handful of shapes
14
- and reuses the hall's tokens for everything else. No --line/--panel here:
15
- neither is defined in style.css, so a fallback is all you would ever get
16
- and both assume a dark ground the hall does not have by default. */
17
-
18
- .idea-detail__back { font-size: 13px; color: var(--ink-soft); text-decoration: none; display: inline-block; margin-bottom: 10px; }
19
- .idea-detail__back:hover { color: var(--ink); text-decoration: underline; }
20
-
21
- /* The grade is the first thing to read: it decides what the rest of the page
22
- even contains. Three states, distinguished by colour AND by word — a badge
23
- that only differs by hue is unreadable to a chunk of readers. */
24
- .ig-grade {
25
- display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 10px;
26
- border-radius: 999px; font-size: 12px; font-weight: 600;
27
- background: var(--surface-2); border: 1px solid var(--border); color: var(--ink-soft);
28
- }
29
- .ig-grade--full { background: var(--ok-weak); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
30
- .ig-grade--spark { background: var(--warn-weak); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
31
- .ig-grade--quick { background: var(--accent-weak); color: var(--accent-text); border-color: color-mix(in srgb, var(--accent) 30%, transparent); }
32
-
33
- .ig-head { display: flex; flex-wrap: wrap; gap: 8px 10px; align-items: center; margin-bottom: 4px; }
34
- .ig-title { margin: 0; font-size: 1.15rem; text-wrap: balance; }
19
+ /* ---- the grade badge is NOT here ------------------------------------------
20
+ It is the archetype's `.rec-pill`, spent by idea.js's gradeBadge() through
21
+ GRADE_TONE. The badge is the first thing to read — it decides what the rest of
22
+ the page even contains — and it is distinguished by colour AND by word,
23
+ because a badge that differs only by hue is unreadable to a chunk of readers.
24
+ But a grade CLASSIFIES the record, which is the same job the task record's
25
+ status pill does, so it is the same component and not a lookalike: this sheet
26
+ carried a near-verbatim copy of `.rec-pill` down to the token triples, which
27
+ is a hundred lines of the archetype waiting to drift the first time one of the
28
+ two was touched. That is precisely what record.css exists to prevent. */
35
29
 
30
+ /* ---- the status line ----------------------------------------------------- */
36
31
  .ig-meta {
37
- font-size: 13px; color: var(--ink-soft); margin: 0 0 16px;
38
- display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: baseline;
32
+ display: flex;
33
+ flex-wrap: wrap;
34
+ gap: 4px 16px;
35
+ align-items: baseline;
36
+ margin: 0 0 var(--sp-md);
37
+ font-size: 14px;
38
+ color: var(--ink-soft);
39
+ font-variant-numeric: tabular-nums;
40
+ }
41
+ .ig-meta strong { color: var(--ink); font-weight: 500; }
42
+ .ig-meta a {
43
+ display: inline-block;
44
+ min-height: 24px;
45
+ padding: 3px 0;
46
+ line-height: 18px;
47
+ color: var(--ink);
48
+ border-bottom: var(--line) solid var(--rule);
39
49
  }
40
- .ig-meta strong { color: var(--ink); font-weight: 600; }
50
+ .ig-meta a:hover { border-bottom-color: var(--ink); text-decoration: none; }
41
51
 
42
- /* Attribution is its own block, not a line in the meta run: on a developed
43
- idea it carries TWO people and the whole point of R146 was that both
44
- survive. Burying that in a comma list is how one of them gets lost. */
45
- .ig-credit {
46
- display: grid; gap: 6px;
47
- padding: 12px 14px; margin: 0 0 18px;
48
- border: 1px solid var(--border); border-radius: var(--radius-sm);
49
- background: var(--surface-2); font-size: 13px;
52
+ /* ---- attribution --------------------------------------------------------- */
53
+ /* Its own block, not a line in the meta run: a developed idea carries TWO people
54
+ and the whole point of R146 was that both survive. Burying that in a comma
55
+ list is how one of them gets lost. v1's bare ledger inside a v3 panel — a
56
+ hairline on top, air, and the first row drops both — rather than the boxed
57
+ well it used to be, because in v3 tone does the separation and a bordered card
58
+ inside a card is the treatment the owner's pick removed. */
59
+ .ig-credit { display: grid; margin: 0 0 var(--sp-md); }
60
+ .ig-credit__row {
61
+ display: flex;
62
+ flex-wrap: wrap;
63
+ gap: 4px 12px;
64
+ align-items: baseline;
65
+ border-top: var(--line) solid var(--rule-soft);
66
+ padding: 11px 0;
67
+ font-size: 14px;
50
68
  }
51
- .ig-credit__row { display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: baseline; }
52
- .ig-credit__who { color: var(--ink); font-weight: 600; }
69
+ .ig-credit__row:first-child { border-top: 0; padding-top: 0; }
70
+ .ig-credit__who { color: var(--ink); font-weight: 500; }
53
71
  .ig-credit__role { color: var(--ink-faint); }
54
72
 
55
- /* The five answers, laid out to be READ: one column, generous leading, a
56
- measure that stops around 70 characters. */
57
- .ig-answers { display: grid; gap: 18px; margin: 4px 0 0; }
58
- .ig-answer__q { margin: 0 0 4px; font-size: 0.92rem; font-weight: 600; }
59
- .ig-answer__a { margin: 0; max-width: 70ch; line-height: 1.65; white-space: pre-wrap; }
73
+ /* ---- the five answers ---------------------------------------------------- */
74
+ /* Laid out to be READ: one column, generous leading, and the measured reading
75
+ width. `--measure` is 48ch MEASURED to render ~60 characters — the old `70ch`
76
+ here rendered closer to 88, because a `ch` is the zero glyph's width and
77
+ Manrope's zero is much wider than its average lowercase. */
78
+ .ig-answers { display: grid; gap: var(--sp-md); margin: var(--sp-md) 0 0; }
79
+ .ig-answer__q {
80
+ margin: 0 0 8px;
81
+ font-family: var(--label-face);
82
+ font-size: var(--label-size);
83
+ font-weight: 400;
84
+ letter-spacing: var(--label-space);
85
+ text-transform: var(--label-case);
86
+ color: var(--ink-faint);
87
+ }
88
+ .ig-answer__a {
89
+ margin: 0;
90
+ max-width: var(--measure);
91
+ font-size: 15px;
92
+ line-height: 1.65;
93
+ color: var(--ink-soft);
94
+ white-space: pre-wrap;
95
+ }
60
96
  .ig-answer__a[data-empty="1"] { color: var(--ink-faint); font-style: italic; }
61
97
 
62
- .ig-score { display: flex; align-items: center; gap: 10px; margin: 0 0 6px; }
63
- .ig-score__track { width: 140px; height: 8px; border-radius: 4px; background: var(--border); overflow: hidden; }
64
- .ig-score__fill { height: 100%; border-radius: 4px; background: var(--accent); }
98
+ /* ---- the completeness readout -------------------------------------------- */
99
+ /* A READOUT, never an input: every number in it comes from the server's scorer.
100
+ The three bands describe the TEXT, not a verdict — the pass bar lives in the
101
+ economy module and this bar deliberately does not know it — which is why they
102
+ are the state colours and not the accent. */
103
+ .ig-score { display: flex; align-items: center; gap: 12px; margin: 0 0 var(--sp-xs); }
104
+ .ig-score__track { width: 140px; height: 6px; border-radius: 3px; background: var(--skeleton); overflow: hidden; }
105
+ .ig-score__fill { display: block; height: 100%; border-radius: 3px; background: var(--info); }
65
106
  .ig-score__fill[data-band="full"] { background: var(--ok); }
66
107
  .ig-score__fill[data-band="part"] { background: var(--warn); }
67
108
  .ig-score__fill[data-band="thin"] { background: var(--danger); }
68
- .ig-score__n { font-size: 13px; font-variant-numeric: tabular-nums; color: var(--ink-soft); }
69
-
70
- .ig-body { max-width: 70ch; line-height: 1.65; white-space: pre-wrap; margin: 4px 0 0; }
71
- .ig-empty { color: var(--ink-faint); font-size: 0.9rem; }
72
- .ig-note { font-size: 0.82rem; color: var(--ink-faint); line-height: 1.55; margin: 14px 0 0; }
73
- .ig-sub { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); }
74
- .ig-sub__h { margin: 0 0 8px; font-size: 0.95rem; }
109
+ .ig-score__n {
110
+ font-family: var(--label-face);
111
+ font-size: var(--label-size);
112
+ letter-spacing: var(--label-space);
113
+ text-transform: var(--label-case);
114
+ color: var(--ink-faint);
115
+ font-variant-numeric: tabular-nums;
116
+ }
75
117
 
76
- /* ---- the mark control (task 1002930 / R154) -----------------------------
77
- Author-only, and rendered as its own block rather than a button in a toolbar:
78
- offering your idea up for someone else to develop is a decision, and the one
79
- sentence explaining what it means to you is the part that makes it one. */
80
- .ig-mark {
81
- margin-top: 20px;
82
- padding: 14px;
83
- border: 1px solid var(--border);
84
- border-radius: var(--radius-sm);
85
- background: var(--surface-2);
118
+ /* ---- the prose ----------------------------------------------------------- */
119
+ .ig-body {
120
+ margin: var(--sp-xs) 0 0;
121
+ max-width: var(--measure);
122
+ font-size: 15px;
123
+ line-height: 1.65;
124
+ color: var(--ink-soft);
125
+ white-space: pre-wrap;
86
126
  }
87
- .ig-mark__lede { margin: 0 0 10px; font-size: 13px; color: var(--ink-soft); max-width: 62ch; line-height: 1.55; }
88
- .ig-mark__err { margin: 8px 0 0; font-size: 13px; color: var(--danger); }
127
+ .ig-body[data-empty="1"] { color: var(--ink-faint); font-style: italic; }
128
+ .ig-empty { margin: 0; font-size: 14px; color: var(--ink-faint); }
129
+ .ig-note {
130
+ margin: var(--sp-md) 0 0;
131
+ max-width: var(--measure);
132
+ font-size: 13.5px;
133
+ line-height: 1.55;
134
+ color: var(--ink-faint);
135
+ }
136
+ .ig-note a { display: inline-block; min-height: 24px; padding: 3px 4px; margin: 0 -4px; line-height: 18px; }
137
+ .ig-sub { margin-top: var(--sp-lg); padding-top: var(--sp-sm); border-top: var(--line) solid var(--rule-soft); }
138
+ .ig-sub__h {
139
+ margin: 0;
140
+ font-family: var(--label-face);
141
+ font-size: var(--label-size);
142
+ font-weight: 400;
143
+ letter-spacing: var(--label-space);
144
+ text-transform: var(--label-case);
145
+ color: var(--ink-faint);
146
+ }
147
+
148
+ /* ---- the mark control (task 1002930 / R154) ------------------------------ */
149
+ /* Author-only, and its own block rather than a button in a toolbar: offering
150
+ your idea up for someone else to develop is a decision, and the one sentence
151
+ explaining what it means to you is the part that makes it one. It is the page's
152
+ one live thing, so `.btn` — the accent door — is warm here and nowhere else. */
153
+ .ig-mark { margin-top: var(--sp-lg); padding-top: var(--sp-sm); border-top: var(--line) solid var(--rule-soft); }
154
+ .ig-mark__lede { margin: 0 0 var(--sp-sm); max-width: var(--measure); font-size: 14px; line-height: 1.55; color: var(--ink-soft); }
155
+ .ig-mark__err { margin: var(--sp-xs) 0 0; font-size: 13.5px; color: var(--danger); }
89
156
  .ig-mark__err:empty { display: none; }
@@ -9,6 +9,9 @@
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
+ <!-- The record archetype, shared with task.html (task 1003305). idea.css below
13
+ carries only what an idea record has and a task record does not. -->
14
+ <link rel="stylesheet" href="/builders/record.css">
12
15
  <link rel="stylesheet" href="/builders/idea.css">
13
16
  </head>
14
17
  <body data-page="idea" data-page-title="Idea">
@@ -23,14 +26,21 @@
23
26
  <!-- This page is deliberately not in the nav (it is always reached from a
24
27
  link), so a deep-linked visitor needs a way back before the body loads
25
28
  — the task.html precedent. -->
26
- <a class="idea-detail__back" href="/ideas">← Back to Ideas</a>
27
- <p class="page-head__sub" id="idea-sub">Loading idea…</p>
29
+ <div class="rec-crumb">
30
+ <a class="rec-back" href="/ideas">
31
+ <svg class="rec-back__ic" viewBox="0 0 16 16" aria-hidden="true"><path d="M9.4 3.6 5 8l4.4 4.4"/><path d="M5 8h7.2"/></svg>Back to ideas</a>
32
+ <p class="rec-sub" id="idea-sub">Loading idea</p>
33
+ </div>
28
34
  </div>
29
35
 
30
36
  <main id="hall-main">
31
37
  <section class="scroll" id="idea-scroll" aria-labelledby="idea-h">
32
38
  <div id="idea-body">
33
- <p class="deeplink-card__title" id="idea-h">Loading…</p>
39
+ <!-- The h1 is present in EVERY state, this one included: the
40
+ one-header-main-h1 floor measures the rendered DOM, and a page
41
+ whose heading only arrives with the fetch has none while it
42
+ loads, and none at all when the read fails. -->
43
+ <h1 class="rec-big" id="idea-h">Loading the idea</h1>
34
44
  </div>
35
45
  </section>
36
46
  </main>
@@ -64,10 +64,16 @@
64
64
  // Colour AND word. A badge distinguished only by hue tells a colour-blind
65
65
  // reader nothing, and this badge decides how to read the whole page.
66
66
  const GRADE_WORD = { full: 'Full Idea', spark: 'Big Idea Spark', quick: 'Quick idea' };
67
+ // A grade classifies the record, which is the status vocabulary's job — the
68
+ // same job the task record's status pill does, so it is the same COMPONENT.
69
+ // `.rec-pill` in record.css is that component; a private copy in idea.css was
70
+ // a hundred lines of the archetype waiting to drift, which is the exact thing
71
+ // the shared sheet exists to prevent.
72
+ const GRADE_TONE = { full: 'ok', spark: 'warn', quick: 'info' };
67
73
 
68
74
  function gradeBadge(grade) {
69
75
  const g = GRADE_WORD[grade] ? grade : 'quick';
70
- return `<span class="ig-grade ig-grade--${escapeHtml(g)}">${escapeHtml(GRADE_WORD[g])}</span>`;
76
+ return `<span class="rec-pill rec-pill--${escapeHtml(GRADE_TONE[g])}">${escapeHtml(GRADE_WORD[g])}</span>`;
71
77
  }
72
78
 
73
79
  function scoreRow(score) {
@@ -213,8 +219,11 @@
213
219
  const grade = GRADE_WORD[idea.grade] ? idea.grade : 'quick';
214
220
  const parts = [];
215
221
 
216
- parts.push(`<div class="ig-head">${gradeBadge(grade)}</div>`);
217
- parts.push(`<h1 class="ig-title" id="idea-h">${escapeHtml(idea.title || `Idea ${idea.id}`)}</h1>`);
222
+ // The record's head, in the shared archetype: the kind eyebrow carries the
223
+ // grade badge the way the task record's carries its status, so the two pages
224
+ // open on the same line even though they hold different facts.
225
+ parts.push(`<p class="rec-kind">Idea ${gradeBadge(grade)}</p>`);
226
+ parts.push(`<h1 class="rec-title" id="idea-h"><span class="rec-title__id">${escapeHtml(String(idea.id))}</span>${escapeHtml(idea.title || 'Untitled idea')}</h1>`);
218
227
  parts.push(statusLine(idea));
219
228
  parts.push(credit(idea));
220
229
 
@@ -261,8 +270,12 @@
261
270
  if (btn) btn.addEventListener('click', () => { toggleMark(idea); });
262
271
  }
263
272
 
273
+ // The honest failure. It keeps the <h1> for the same reason the loading state
274
+ // does: a render is a render, and one-header-main-h1 measures the DOM it gets.
264
275
  function fail(msg) {
265
- $('#idea-body').innerHTML = `<p class="deeplink-card__title" id="idea-h">${escapeHtml(msg)}</p>`;
276
+ $('#idea-body').innerHTML =
277
+ `<p class="rec-kind">Idea</p><h1 class="rec-big" id="idea-h">${escapeHtml(msg)}</h1>` +
278
+ `<div class="rec-actions rec-sec"><a class="btn btn--ghost" href="${hallBase()}/ideas">Open the idea composer</a></div>`;
266
279
  $('#idea-sub').textContent = '';
267
280
  }
268
281
 
@@ -0,0 +1,14 @@
1
+ {
2
+ "_": "The hall's IDEA RECORD through modules/ui-design/kit/render.js (task 1003305). Same harness note as task.states.json: the idea's five answers, its completeness bar and its author-only mark control need a LOADED idea, so run it against the hall-preview harness:\n\n node scripts/hall-preview/server.js --port 4612 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/idea.states.json --base-in http://builders.localhost:4612 --base-out http://builders.localhost:4612",
3
+ "page": "/idea/12",
4
+ "surface": "hall-ui",
5
+ "stub": { "prefix": "/builders/" },
6
+ "modeQuery": false,
7
+ "ignoreRequests": ["/api/(gds|bongos)/"],
8
+ "ignoreConsole": ["/api/(gds|bongos)/", "\b401\b", "\b404\b", "Unauthorized", "Not Found", "load failed"],
9
+ "states": {
10
+ "record": { "auth": true, "actions": [["wait", 600]], "expect": { "visible": ["#idea-body", "#idea-h"] } },
11
+ "missing": { "auth": true, "url": "/idea/999999", "actions": [["wait", 600]], "expect": { "visible": ["#idea-h"] } }
12
+ },
13
+ "reducedMotion": { "auth": true, "actions": [["wait", 600]] }
14
+ }