@bongos/core 1.19.115 → 1.19.117

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.
@@ -178,6 +178,13 @@ const PAIRS = [
178
178
  // alone do not cover them
179
179
  ['?view=new .ghrow .priv 11px', 'var(--ink-soft)', 'color-mix(in srgb,var(--ink) 8%,var(--bg-deep))', 4.5],
180
180
  ['--on-ok on an --ok fill', 'var(--on-ok)', 'var(--ok)', 4.5],
181
+ // the wizard's per-type description card (task 1002772): heading ink is a
182
+ // 40%-accent ink mix over the card's own 9%-accent tint — one row per type
183
+ ['typeCard heading · game', 'color-mix(in srgb,var(--accent) 40%,var(--ink))', 'color-mix(in srgb,var(--accent) 9%,var(--bg-card))', 4.5],
184
+ ['typeCard heading · research', 'color-mix(in srgb,var(--sea) 40%,var(--ink))', 'color-mix(in srgb,var(--sea) 9%,var(--bg-card))', 4.5],
185
+ ['typeCard heading · business', 'color-mix(in srgb,var(--terracotta) 40%,var(--ink))', 'color-mix(in srgb,var(--terracotta) 9%,var(--bg-card))', 4.5],
186
+ ['typeCard heading · non-profit', 'color-mix(in srgb,var(--gold-soft) 40%,var(--ink))', 'color-mix(in srgb,var(--gold-soft) 9%,var(--bg-card))', 4.5],
187
+ ['typeCard heading · not-sure', 'color-mix(in srgb,var(--rule-soft) 40%,var(--ink))', 'color-mix(in srgb,var(--rule-soft) 9%,var(--bg-card))', 4.5],
181
188
  ['?view=new input ::placeholder', 'var(--ink-faint)', CARD, 4.5],
182
189
  ['?view=new .ghlist .hint on deep', 'var(--ink-faint)', 'var(--bg-deep)', 4.5],
183
190
  ['--chrome-accent wordmark', 'var(--chrome-accent)', 'var(--page-bg)', 4.5],
@@ -228,11 +228,37 @@ test('/create is absorbed — the wizard lives here and the route only redirects
228
228
  const server = fs.readFileSync(path.join(ROOT, 'src', 'platform-server.js'), 'utf8');
229
229
  assert.match(server, /res\.redirect\(302, '\/projects\?view=new'/,
230
230
  '/create must 302 into the hub (302, not 301: the URL is an OAuth return target in the wild)');
231
- for (const id of ['panel-gate', 'panel-name', 'panel-home', 'panel-address', 'panel-review', 'panel-done']) {
231
+ for (const id of ['panel-gate', 'panel-name', 'panel-type', 'panel-home', 'panel-address', 'panel-review', 'panel-done']) {
232
232
  assert.match(HUB, new RegExp(`id="${id}"`), `the wizard's ${id} ships as real markup (C7)`);
233
233
  }
234
234
  });
235
235
 
236
+ test('the wizard step indices stay coupled — data-next lands one past its own panel (task 1002772)', () => {
237
+ // The whole renumbering turns on validateStep(next - 1): a form's data-next
238
+ // must sit inside panels[next - 1], or the validator guards the wrong step.
239
+ const pm = SCRIPT.match(/var panels = \{([^}]*)\}/);
240
+ assert.ok(pm, 'the panels map must exist');
241
+ const map = {};
242
+ for (const m of pm[1].matchAll(/(\d+):\s*'([a-z-]+)'/g)) map[Number(m[1])] = m[2];
243
+ const nexts = [...HUB.matchAll(/data-next="(\d+)"/g)];
244
+ assert.ok(nexts.length >= 4, 'every wizard form declares its next step');
245
+ for (const m of nexts) {
246
+ const before = HUB.slice(0, m.index);
247
+ const sec = before.lastIndexOf('id="panel-');
248
+ assert.ok(sec !== -1, 'a data-next form lives inside a panel');
249
+ const id = HUB.slice(sec + 4).match(/^(panel-[a-z]+)/)[1];
250
+ assert.equal(id, map[Number(m[1]) - 1],
251
+ `data-next="${m[1]}" must live in panels[${Number(m[1]) - 1}] — validateStep(next-1) guards that panel`);
252
+ }
253
+ for (const m of HUB.matchAll(/data-back="(\d+)"/g)) {
254
+ assert.ok(map[Number(m[1])], `data-back="${m[1]}" must point at a mapped panel`);
255
+ }
256
+ // the rail numbers every step except the gate and the done panel
257
+ const beats = [...HUB.matchAll(/data-step="(\d+)"/g)].map((x) => Number(x[1]));
258
+ assert.equal(Math.max.apply(null, beats), Object.keys(map).length - 2,
259
+ 'the rail shows every numbered step except gate and done');
260
+ });
261
+
236
262
  test('the absorbed wizard sheds its two URL landmines — /gate/ and the whole-query strip', () => {
237
263
  // create.html carried a document-level Escape that navigated to the retired
238
264
  // /gate/ (a 404 the href-only guard above cannot see: it was a
@@ -475,18 +501,21 @@ test('the row keeps its own Open link working underneath the card', () => {
475
501
  The mock's columns shipped whole — Project / Builders / Type / Tags / Status
476
502
  — but three of them rendered an em dash for every row, forever, because the
477
503
  public projection carries four fields and Type/Tags are not in the catalog
478
- schema at all. A header promising a column is a claim like any other. ── */
504
+ schema at all. A header promising a column is a claim like any other.
505
+ Builders and Runs came back with a source in task 1002771; Type came back in
506
+ task 1002772 when a project began DECLARING it at creation — a nullable
507
+ declared field, so an em dash now means "not declared", never a guess. ── */
479
508
 
480
- test('the table declares no column it has no source for', () => {
509
+ test('every table column has a source — Type is declared, never guessed', () => {
481
510
  const thead = (HUB.match(/<thead>([\s\S]*?)<\/thead>/) || [, ''])[1];
482
- // Type is still absent: the owner's call is that a project DECLARES its type
483
- // at creation, so until that field exists the hub must not guess one.
484
- assert.doesNotMatch(thead, />Type</, 'Type has no source until a project declares it');
485
- // Builders and Runs came back in task 1002771 WITH a source behind them
511
+ assert.match(thead, />Type</, 'Type has a source now: the project declares it at creation (task 1002772)');
512
+ assert.match(SCRIPT, /typeHtml\(p\.type\)/, 'the Type cell must read the declared field through the one formatter');
486
513
  assert.match(thead, />Builders</);
487
514
  assert.match(thead, />Runs</);
488
515
  assert.match(SCRIPT, /p\.builders/, 'the Builders cell must read real data');
489
516
  assert.match(SCRIPT, /tagsHtml\(p\.tags\)/, 'the Runs cell must read real data');
517
+ // the wizard files the declaration with the create — the source end of the pipe
518
+ assert.match(SCRIPT, /type: state\.projType/, 'the create request carries the declared type');
490
519
  });
491
520
 
492
521
  test('header and body agree on the column count', () => {
@@ -509,11 +538,13 @@ test('no sort control is offered for a column that cannot sort', () => {
509
538
  }
510
539
  });
511
540
 
512
- test('Type left no styling behind, and the live columns kept theirs', () => {
513
- assert.ok(!COSMOS.includes('.colType'), '.colType is dead — the column is gone');
514
- for (const live of ['.tbl .num', '.tbl .tags', '.colTags']) {
515
- assert.ok(COSMOS.includes(live), `${live} styles a column that now renders real data`);
541
+ test('every live column keeps its styling — .colType included since task 1002772', () => {
542
+ for (const live of ['.tbl .num', '.tbl .tags', '.colTags', '.colType', '.typeTag']) {
543
+ assert.ok(COSMOS.includes(live), `${live} styles a column that renders real data`);
516
544
  }
545
+ // the type tag is its own vocabulary: not the relationship .pill, not the
546
+ // liveness .chip — two different facts must not wear the same chrome
547
+ assert.doesNotMatch(SCRIPT, /typeHtml[\s\S]{0,80}?class="pill"/, 'type must not wear the relationship pill');
517
548
  });
518
549
 
519
550
  /* ── Real Builders + Runs (task 1002771) ────────────────────────────────────