@bongos/core 1.19.534 → 1.19.535
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.
- package/.bongos-core.json +28 -23
- package/docs/copy-inventory.md +34 -32
- package/docs/copy-registry.json +66 -38
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/hall-kit.js +93 -7
- package/modules/hall-ui/public/roadmap-timeline.js +105 -0
- package/modules/hall-ui/public/roadmap.html +7 -1
- package/modules/hall-ui/public/roadmap.js +16 -0
- package/modules/hall-ui/public/style.css +4 -2
- package/modules/hall-ui/public/watch.html +4 -0
- package/modules/hall-ui/public/watch.js +116 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/hall-preview/refresh-fixtures.js +6 -0
- package/src/module-api.js +1 -1
- package/tests/hall_kit_charts.mjs +173 -0
- package/tests/hall_oversight_world.mjs +159 -0
- package/tests/hall_reading_world.mjs +153 -0
|
@@ -520,3 +520,156 @@ test('the vouched shapes still become links, exactly as before', () => {
|
|
|
520
520
|
const degraded = MD.parseMarkdown('[t](../../CLAUDE.md)\n', MD.makeResolveLinkHref({ baseDir: 'docs/onboarding/' })).html;
|
|
521
521
|
assert.ok(degraded.includes('<a href="../../CLAUDE.md"'), 'and with no bases a repo path still degrades to a link, as §10 pins above');
|
|
522
522
|
});
|
|
523
|
+
|
|
524
|
+
// ---- the deck draws its dates (task 1003447, `legibility-series-are-drawn`) --
|
|
525
|
+
//
|
|
526
|
+
// /public/versions has always carried started_at and shipped_at per version, and
|
|
527
|
+
// the deck drew NEITHER: both dates reached the browser and came out as "Sep
|
|
528
|
+
// 2026" on a shipped pill. The SERIES lives in roadmap-timeline.js and
|
|
529
|
+
// tests/hall_kit_charts.mjs runs it against the real fixture; what belongs here
|
|
530
|
+
// is the deck's half — that the section is composed, gated and placed the way
|
|
531
|
+
// the reading rooms compose everything else.
|
|
532
|
+
|
|
533
|
+
test('the deck asks the kit to draw its timeline, and hand-rolls nothing', () => {
|
|
534
|
+
const js = read('roadmap.js');
|
|
535
|
+
assert.match(js, /window\.OTBRoadmapTimeline/, 'the series comes from its own module');
|
|
536
|
+
assert.match(js, /timelineCardHtml\(all, now\)/, 'over the merged versions');
|
|
537
|
+
assert.match(js, /html \+= timelineHtml\(all, Date\.now\(\)\)/, 'and lands in the deck');
|
|
538
|
+
// §6b holds this globally; pinned here because a Gantt is the shape most
|
|
539
|
+
// likely to be reached for as "just a few divs with a left offset".
|
|
540
|
+
for (const f of ['roadmap.js', 'roadmap-timeline.js']) {
|
|
541
|
+
assert.doesNotMatch(read(f), /<(?:path|polyline|rect)\b[^>]*\b(?:d|points)="\$\{/,
|
|
542
|
+
`${f} builds no SVG geometry by hand`);
|
|
543
|
+
assert.doesNotMatch(read(f), /class="[^"]*(?:__|-)(?:fill|bar|track)[^"]*"[^>]*style="width:/,
|
|
544
|
+
`${f} draws no bar by hand`);
|
|
545
|
+
}
|
|
546
|
+
assert.match(read('roadmap-timeline.js'), /k\.barSeriesHtml\(/, 'the spans are the kit\'s bar series');
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test('a missing timeline module leaves the deck exactly as it was', () => {
|
|
550
|
+
// The section is additive: the module is a <script> that can 404 behind a bad
|
|
551
|
+
// deploy, and when it does the deck must render its hero and its cards rather
|
|
552
|
+
// than an empty band or a thrown boot.
|
|
553
|
+
const js = read('roadmap.js');
|
|
554
|
+
const fn = js.slice(js.indexOf('function timelineHtml'), js.indexOf('function sectionHtml'));
|
|
555
|
+
assert.match(fn, /mod \? mod\.timelineCardHtml/, 'the module is checked before it is called');
|
|
556
|
+
assert.match(fn, /card \? sectionHtml\([\s\S]*?\) : ''/, 'and a null card renders no section at all');
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Boot roadmap.js in a stub DOM with the real dom-utils, the real kit and the
|
|
560
|
+
// real timeline module, answer /public/progress + /public/versions with the
|
|
561
|
+
// checked-in fixtures, and hand back what landed in #rm-root.
|
|
562
|
+
//
|
|
563
|
+
// This is the half the source pins and the kit's own RUN test both miss: the
|
|
564
|
+
// GLUE. `mergeVersions()` maps `m.started_at`/`m.shipped_at` onto `start`/`ship`
|
|
565
|
+
// and the timeline reads those names — a rename or a typo on either side leaves
|
|
566
|
+
// every span unparsable, the section renders nothing, and every other test in
|
|
567
|
+
// this file still passes because none of them run the mapping.
|
|
568
|
+
async function bootRoadmap({ progress, versions } = {}) {
|
|
569
|
+
const FIX = path.join(ROOT, 'scripts', 'hall-preview', 'fixtures');
|
|
570
|
+
const fx = (f) => JSON.parse(fs.readFileSync(path.join(FIX, f), 'utf8'));
|
|
571
|
+
const prog = progress || fx('public__progress.json');
|
|
572
|
+
// public__versions.json, not versions.json: the deck calls the PUBLIC route.
|
|
573
|
+
const vers = versions || fx('public__versions.json');
|
|
574
|
+
|
|
575
|
+
const els = new Map();
|
|
576
|
+
const makeEl = () => ({
|
|
577
|
+
dataset: {}, style: {}, textContent: '', hidden: false, _html: '',
|
|
578
|
+
classList: { add() {}, remove() {}, toggle() { return false; }, contains() { return false; } },
|
|
579
|
+
setAttribute() {}, getAttribute() { return null; },
|
|
580
|
+
addEventListener() {}, appendChild() {}, insertAdjacentHTML(_, h) { this._html += h; },
|
|
581
|
+
closest() { return null; }, querySelector() { return null; }, querySelectorAll() { return []; },
|
|
582
|
+
set innerHTML(v) { this._html = String(v); }, get innerHTML() { return this._html; },
|
|
583
|
+
});
|
|
584
|
+
const documentObj = {
|
|
585
|
+
getElementById(id) { if (!els.has(id)) els.set(id, makeEl()); return els.get(id); },
|
|
586
|
+
querySelector(sel) {
|
|
587
|
+
const s = String(sel);
|
|
588
|
+
return s.startsWith('#') ? this.getElementById(s.slice(1)) : makeEl();
|
|
589
|
+
},
|
|
590
|
+
querySelectorAll() { return []; },
|
|
591
|
+
createElement() { return makeEl(); },
|
|
592
|
+
addEventListener() {},
|
|
593
|
+
};
|
|
594
|
+
const client = {
|
|
595
|
+
request: async (_m, urlPath) => {
|
|
596
|
+
if (urlPath.includes('/public/progress')) return { ok: true, status: 200, data: prog };
|
|
597
|
+
if (urlPath.includes('/public/versions')) return { ok: true, status: 200, data: vers };
|
|
598
|
+
// No goals: goalSummary() swallows the failure by design, and the goal
|
|
599
|
+
// layer is not what this test is about.
|
|
600
|
+
return { ok: false, status: 500, data: {} };
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
const windowObj = {
|
|
604
|
+
BongosClient: { createClient: () => client },
|
|
605
|
+
__BRANDING__: {}, __MODULES__: { enabled: {} },
|
|
606
|
+
localStorage: { getItem: () => null, setItem() {}, removeItem() {} },
|
|
607
|
+
location: { hash: '', pathname: '/roadmap', search: '', assign() {} },
|
|
608
|
+
addEventListener() {}, matchMedia: () => ({ matches: false, addEventListener() {} }),
|
|
609
|
+
};
|
|
610
|
+
const sandbox = {
|
|
611
|
+
window: windowObj, document: documentObj, location: windowObj.location,
|
|
612
|
+
localStorage: windowObj.localStorage,
|
|
613
|
+
CSS: { escape: (s) => String(s) }, history: { replaceState() {} },
|
|
614
|
+
console: { ...console, error() {}, warn() {} },
|
|
615
|
+
setTimeout, clearTimeout, URLSearchParams, Intl,
|
|
616
|
+
};
|
|
617
|
+
sandbox.globalThis = sandbox;
|
|
618
|
+
vm.createContext(sandbox);
|
|
619
|
+
for (const f of ['dom-utils.js', 'hall-kit.js', 'roadmap-timeline.js', 'roadmap.js']) {
|
|
620
|
+
vm.runInContext(read(f), sandbox, { filename: f });
|
|
621
|
+
}
|
|
622
|
+
for (let i = 0; i < 10; i++) await new Promise((r) => setTimeout(r, 0));
|
|
623
|
+
return { html: documentObj.getElementById('rm-root').innerHTML, prog, vers };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
test('RUN: the deck really draws its timeline — mergeVersions\' field mapping included', async () => {
|
|
627
|
+
const { html, vers } = await bootRoadmap();
|
|
628
|
+
assert.ok(html.length > 0, 'the deck rendered');
|
|
629
|
+
assert.match(html, /class="rm-sec__t">In time</, 'the timeline section is on the page');
|
|
630
|
+
assert.match(html, /class="kit-bars"/, 'and it is the kit drawing it');
|
|
631
|
+
assert.doesNotMatch(html, /NaN|Infinity|undefined/, 'no broken geometry reaches the DOM');
|
|
632
|
+
|
|
633
|
+
// Every version the fixture gives a start date has a bar, labelled by its id.
|
|
634
|
+
// This is the assertion that fails if `start`/`ship` ever drift from
|
|
635
|
+
// `started_at`/`shipped_at`: the mapping breaks, no date parses, and the
|
|
636
|
+
// section quietly disappears.
|
|
637
|
+
const started = vers.versions.filter((v) => v.started_at);
|
|
638
|
+
assert.ok(started.length >= 2, 'the fixture has enough started versions to be a timeline');
|
|
639
|
+
for (const v of started) {
|
|
640
|
+
assert.ok(html.includes(`class="kit-bars__label">${v.id}<`), `${v.id} has a bar`);
|
|
641
|
+
}
|
|
642
|
+
// A version still running says "now" rather than naming a date nothing recorded.
|
|
643
|
+
if (vers.versions.some((v) => v.started_at && !v.shipped_at)) {
|
|
644
|
+
assert.match(html, /→ now</, 'an open span reads "now"');
|
|
645
|
+
}
|
|
646
|
+
// Later versions sit further along the shared axis — the whole point of a
|
|
647
|
+
// timeline, and the thing a plain bar series would NOT show.
|
|
648
|
+
const offsets = [...html.matchAll(/margin-left:(\d+)%/g)].map((m) => Number(m[1]));
|
|
649
|
+
assert.ok(offsets.length >= 1, 'spans are offset, not all flush left');
|
|
650
|
+
assert.deepEqual(offsets, [...offsets].sort((a, b) => a - b), 'offsets grow with start date');
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
test('RUN: a deck whose versions carry no dates renders every other section unharmed', async () => {
|
|
654
|
+
// The degradation that matters: /public/versions is fetched with a
|
|
655
|
+
// `.catch(() => [])`, so a 404 or a stripped payload must cost the timeline
|
|
656
|
+
// and nothing else. Before this task the whole page relied on that silently.
|
|
657
|
+
const { html } = await bootRoadmap({
|
|
658
|
+
versions: { versions: [{ id: 'BONGOS-V1', name: 'x', status: 'building', started_at: null, shipped_at: null }] },
|
|
659
|
+
});
|
|
660
|
+
assert.doesNotMatch(html, /In time</, 'no timeline section');
|
|
661
|
+
assert.match(html, /rm-hero/, 'but the hero still renders');
|
|
662
|
+
assert.doesNotMatch(html, /NaN|undefined/);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test('the timeline module is a pure builder, so it is testable and cannot boot itself', () => {
|
|
666
|
+
const src = read('roadmap-timeline.js');
|
|
667
|
+
// The node-require-safe tail (the hall-kit.js / dom-utils.js contract): the
|
|
668
|
+
// ONLY eval-time side effect is the assignment, and only when a window exists.
|
|
669
|
+
assert.match(src, /if \(typeof window !== 'undefined'\) window\.OTBRoadmapTimeline = api;/);
|
|
670
|
+
assert.match(src, /if \(typeof module !== 'undefined' && module\.exports\) module\.exports = api;/);
|
|
671
|
+
// It must not reach for the page: no document, no fetch, no client.
|
|
672
|
+
for (const forbidden of [/\bdocument\./, /\bfetch\(/, /BongosClient/]) {
|
|
673
|
+
assert.doesNotMatch(src, forbidden, `roadmap-timeline.js touches ${forbidden} — it is a builder, not a page`);
|
|
674
|
+
}
|
|
675
|
+
});
|