@bongos/core 1.24.49 → 1.24.51
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 +33 -33
- package/docs/copy-inventory.md +91 -93
- package/docs/copy-registry.json +282 -290
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +5 -4
- package/docs/page-readings.json +75 -91
- package/modules/status-ui/public/explore.html +14 -4
- package/modules/status-ui/public/index.html +67 -167
- package/modules/status-ui/public/index.states.json +3 -3
- package/modules/status-ui/public/status.js +77 -52
- package/modules/status-ui/public/style.css +638 -831
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/status-mirror-sync.js +44 -45
- package/src/bongos/serve-internal.js +8 -7
- package/src/module-api.js +1 -1
- package/tests/loading_mark.mjs +6 -6
- package/tests/status_company_attribution.mjs +1 -1
- package/tests/status_page_chrome.mjs +195 -0
- package/tests/status_page_wording.mjs +129 -0
- package/modules/status-ui/public/cursors/arrow.png +0 -0
- package/modules/status-ui/public/cursors/hand-pointer.png +0 -0
|
@@ -56,10 +56,9 @@ function fmtUsd(n) {
|
|
|
56
56
|
return Math.round(Number(n)).toLocaleString();
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function
|
|
59
|
+
function fmtMoney(n) {
|
|
60
60
|
if (n == null) return '—';
|
|
61
|
-
|
|
62
|
-
return `${Math.round(Number(n)).toLocaleString()} dr.`;
|
|
61
|
+
return `$${fmtUsd(n)}`;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
function fmtRelativeTime(iso) {
|
|
@@ -99,13 +98,13 @@ async function fetchJson(path) {
|
|
|
99
98
|
// in ~756px of content width without wrapping.
|
|
100
99
|
|
|
101
100
|
const SECTIONS = [
|
|
102
|
-
{ id: 'headline-tablet', label: '
|
|
103
|
-
{ id: 'leader-tablet', label: '
|
|
104
|
-
{ id: 'foundry-tablet', label: '
|
|
105
|
-
{ id: 'versions-tablet', label: '
|
|
106
|
-
{ id: 'cost-tablet', label: '
|
|
107
|
-
{ id: 'shipped-tablet', label: '
|
|
108
|
-
{ id: 'world-tablet', label: '
|
|
101
|
+
{ id: 'headline-tablet', label: 'Overview' },
|
|
102
|
+
{ id: 'leader-tablet', label: 'Builders' },
|
|
103
|
+
{ id: 'foundry-tablet', label: 'Trends' },
|
|
104
|
+
{ id: 'versions-tablet', label: 'In progress' },
|
|
105
|
+
{ id: 'cost-tablet', label: 'Spending' },
|
|
106
|
+
{ id: 'shipped-tablet', label: 'Shipped' },
|
|
107
|
+
{ id: 'world-tablet', label: 'Snapshot' },
|
|
109
108
|
];
|
|
110
109
|
|
|
111
110
|
let navObserver = null;
|
|
@@ -354,7 +353,7 @@ async function loadVersions() {
|
|
|
354
353
|
if (rows.length === 0) {
|
|
355
354
|
const p = document.createElement('p');
|
|
356
355
|
p.className = 'empty';
|
|
357
|
-
p.textContent = '
|
|
356
|
+
p.textContent = 'Nothing is in progress right now.';
|
|
358
357
|
els.versions.appendChild(p);
|
|
359
358
|
return;
|
|
360
359
|
}
|
|
@@ -497,7 +496,7 @@ function renderCostFilterNote(data) {
|
|
|
497
496
|
const filtered = Number(data.filtered_total_usd) || 0;
|
|
498
497
|
const whole = Number(data.total_usd) || 0;
|
|
499
498
|
const pct = whole > 0 ? Math.round((filtered / whole) * 100) : 0;
|
|
500
|
-
note.textContent = `Breakdown filtered to ${parts.join(' · ')}: ${
|
|
499
|
+
note.textContent = `Breakdown filtered to ${parts.join(' · ')}: ${fmtMoney(filtered)} of ${fmtMoney(whole)} (${pct}% of all spend).`;
|
|
501
500
|
}
|
|
502
501
|
|
|
503
502
|
async function loadCosts() {
|
|
@@ -522,7 +521,7 @@ async function loadCosts() {
|
|
|
522
521
|
if (cats.length === 0) {
|
|
523
522
|
const tag = document.createElement('span');
|
|
524
523
|
tag.className = 'cost-cat';
|
|
525
|
-
tag.textContent = `No ${curLabel()} yet
|
|
524
|
+
tag.textContent = `No ${curLabel()} spent yet.`;
|
|
526
525
|
els.costCats.appendChild(tag);
|
|
527
526
|
} else {
|
|
528
527
|
for (const c of cats) {
|
|
@@ -531,7 +530,7 @@ async function loadCosts() {
|
|
|
531
530
|
const strong = document.createElement('strong');
|
|
532
531
|
strong.textContent = c.category;
|
|
533
532
|
tag.appendChild(strong);
|
|
534
|
-
tag.append(` · ${
|
|
533
|
+
tag.append(` · ${fmtMoney(c.total_usd)}`);
|
|
535
534
|
els.costCats.appendChild(tag);
|
|
536
535
|
}
|
|
537
536
|
}
|
|
@@ -557,7 +556,7 @@ async function loadCosts() {
|
|
|
557
556
|
if (total > 0) {
|
|
558
557
|
const pct = Math.round((unattributed / total) * 100);
|
|
559
558
|
els.costAttributionSplit.textContent =
|
|
560
|
-
`Attributed to builders: ${
|
|
559
|
+
`Attributed to builders: ${fmtMoney(attributed)} · Shared (unattributed): ${fmtMoney(unattributed)} (${pct}%).`;
|
|
561
560
|
} else {
|
|
562
561
|
els.costAttributionSplit.textContent = '';
|
|
563
562
|
}
|
|
@@ -577,14 +576,14 @@ async function loadCosts() {
|
|
|
577
576
|
: (b.display_name || b.github_login || `builder #${b.builder_id}`);
|
|
578
577
|
strong.textContent = label;
|
|
579
578
|
tag.appendChild(strong);
|
|
580
|
-
tag.append(` · ${
|
|
579
|
+
tag.append(` · ${fmtMoney(b.total_usd)}`);
|
|
581
580
|
els.costBuilders.appendChild(tag);
|
|
582
581
|
rendered++;
|
|
583
582
|
}
|
|
584
583
|
if (rendered === 0) {
|
|
585
584
|
const tag = document.createElement('span');
|
|
586
585
|
tag.className = 'cost-cat';
|
|
587
|
-
tag.textContent = `No ${curLabel()}
|
|
586
|
+
tag.textContent = `No ${curLabel()} attributed to a builder yet.`;
|
|
588
587
|
els.costBuilders.appendChild(tag);
|
|
589
588
|
}
|
|
590
589
|
}
|
|
@@ -643,6 +642,10 @@ async function loadCosts() {
|
|
|
643
642
|
|
|
644
643
|
if (!window.Chart) return; // Chart.js failed to load — skip
|
|
645
644
|
|
|
645
|
+
// The chart paints with the page's own tokens (task 1005089): a canvas cannot
|
|
646
|
+
// read var(), so the resolved values are read once per draw, in whichever mode
|
|
647
|
+
// the page is in. Spend is ink, value the --info tone; the faces are the pack's.
|
|
648
|
+
const t = chartTokens();
|
|
646
649
|
const ctx = els.costChart.getContext('2d');
|
|
647
650
|
if (costChart) costChart.destroy();
|
|
648
651
|
costChart = new Chart(ctx, {
|
|
@@ -654,23 +657,23 @@ async function loadCosts() {
|
|
|
654
657
|
label: `${curLabel()} spent`,
|
|
655
658
|
data: cumCost,
|
|
656
659
|
fill: true,
|
|
657
|
-
borderColor:
|
|
658
|
-
backgroundColor:
|
|
660
|
+
borderColor: t.ink,
|
|
661
|
+
backgroundColor: t.inkWash,
|
|
659
662
|
tension: 0.2,
|
|
660
663
|
pointRadius: 2.5,
|
|
661
|
-
pointBackgroundColor:
|
|
664
|
+
pointBackgroundColor: t.ink,
|
|
662
665
|
borderWidth: 1.5,
|
|
663
666
|
},
|
|
664
667
|
{
|
|
665
|
-
label: 'value
|
|
668
|
+
label: 'value produced',
|
|
666
669
|
data: cumValue,
|
|
667
670
|
fill: false,
|
|
668
|
-
borderColor:
|
|
669
|
-
backgroundColor:
|
|
671
|
+
borderColor: t.info,
|
|
672
|
+
backgroundColor: t.info,
|
|
670
673
|
borderDash: [5, 4],
|
|
671
674
|
tension: 0.2,
|
|
672
675
|
pointRadius: 2.5,
|
|
673
|
-
pointBackgroundColor:
|
|
676
|
+
pointBackgroundColor: t.info,
|
|
674
677
|
borderWidth: 1.5,
|
|
675
678
|
},
|
|
676
679
|
],
|
|
@@ -678,7 +681,7 @@ async function loadCosts() {
|
|
|
678
681
|
options: {
|
|
679
682
|
responsive: true,
|
|
680
683
|
maintainAspectRatio: false,
|
|
681
|
-
font: { family:
|
|
684
|
+
font: { family: t.body },
|
|
682
685
|
plugins: {
|
|
683
686
|
legend: {
|
|
684
687
|
display: true,
|
|
@@ -686,17 +689,19 @@ async function loadCosts() {
|
|
|
686
689
|
labels: {
|
|
687
690
|
boxWidth: 22,
|
|
688
691
|
padding: 18,
|
|
689
|
-
font: { family:
|
|
690
|
-
color:
|
|
692
|
+
font: { family: t.body, size: 13 },
|
|
693
|
+
color: t.inkSoft,
|
|
691
694
|
},
|
|
692
695
|
},
|
|
693
696
|
tooltip: {
|
|
694
|
-
backgroundColor:
|
|
695
|
-
|
|
696
|
-
|
|
697
|
+
backgroundColor: t.ink,
|
|
698
|
+
titleColor: t.card,
|
|
699
|
+
bodyColor: t.card,
|
|
700
|
+
titleFont: { family: t.body, size: 12, weight: '500' },
|
|
701
|
+
bodyFont: { family: t.body, size: 13 },
|
|
697
702
|
padding: 10,
|
|
698
703
|
callbacks: {
|
|
699
|
-
label: (c) => `${c.dataset.label}: ${c.parsed.y.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 })}
|
|
704
|
+
label: (c) => `${c.dataset.label}: ${c.parsed.y.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 })}`,
|
|
700
705
|
},
|
|
701
706
|
},
|
|
702
707
|
},
|
|
@@ -706,28 +711,28 @@ async function loadCosts() {
|
|
|
706
711
|
title: {
|
|
707
712
|
display: true,
|
|
708
713
|
text: `${curLabel()} (cumulative)`,
|
|
709
|
-
font: { family:
|
|
710
|
-
color:
|
|
714
|
+
font: { family: t.body, size: 12, weight: '500' },
|
|
715
|
+
color: t.inkSoft,
|
|
711
716
|
},
|
|
712
717
|
ticks: {
|
|
713
|
-
callback: (v) => `${v}
|
|
714
|
-
font: { family:
|
|
715
|
-
color:
|
|
718
|
+
callback: (v) => `${v}`,
|
|
719
|
+
font: { family: t.body, size: 12 },
|
|
720
|
+
color: t.inkSoft,
|
|
716
721
|
},
|
|
717
|
-
grid: { color:
|
|
722
|
+
grid: { color: t.rule, drawBorder: false },
|
|
718
723
|
},
|
|
719
724
|
x: {
|
|
720
725
|
title: {
|
|
721
726
|
display: true,
|
|
722
727
|
text: 'date',
|
|
723
|
-
font: { family:
|
|
724
|
-
color:
|
|
728
|
+
font: { family: t.body, size: 12, weight: '500' },
|
|
729
|
+
color: t.inkSoft,
|
|
725
730
|
},
|
|
726
731
|
grid: { display: false },
|
|
727
732
|
ticks: {
|
|
728
733
|
maxTicksLimit: 10,
|
|
729
|
-
font: { family:
|
|
730
|
-
color:
|
|
734
|
+
font: { family: t.body, size: 12 },
|
|
735
|
+
color: t.inkSoft,
|
|
731
736
|
},
|
|
732
737
|
},
|
|
733
738
|
},
|
|
@@ -735,6 +740,23 @@ async function loadCosts() {
|
|
|
735
740
|
});
|
|
736
741
|
}
|
|
737
742
|
|
|
743
|
+
// The resolved values of the tokens the cost chart paints with, read from the
|
|
744
|
+
// page (a canvas cannot take var()). An unset token reads '' and Chart.js keeps
|
|
745
|
+
// its own default for it.
|
|
746
|
+
function chartTokens() {
|
|
747
|
+
const cs = window.getComputedStyle(document.body);
|
|
748
|
+
const v = (name) => cs.getPropertyValue(name).trim();
|
|
749
|
+
return {
|
|
750
|
+
ink: v('--ink'),
|
|
751
|
+
inkSoft: v('--ink-soft'),
|
|
752
|
+
inkWash: v('--glass-strong'),
|
|
753
|
+
info: v('--info'),
|
|
754
|
+
rule: v('--rule-soft'),
|
|
755
|
+
card: v('--bg-card'),
|
|
756
|
+
body: v('--font-body') || 'sans-serif',
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
738
760
|
function toDayKey(d) {
|
|
739
761
|
// Postgres returns 'YYYY-MM-DD' as a string OR an ISO timestamp depending
|
|
740
762
|
// on transport — normalise to YYYY-MM-DD so dedupe works across formats.
|
|
@@ -785,7 +807,7 @@ function renderShippedPage() {
|
|
|
785
807
|
if (items.length === 0) {
|
|
786
808
|
const li = document.createElement('li');
|
|
787
809
|
li.className = 'empty';
|
|
788
|
-
li.textContent = '
|
|
810
|
+
li.textContent = 'Nothing shipped in this window.';
|
|
789
811
|
els.shipped.appendChild(li);
|
|
790
812
|
// GDS #191: pad with invisible rows so an empty window still occupies
|
|
791
813
|
// the same vertical space as a full page. Keeps the section height
|
|
@@ -827,7 +849,7 @@ function renderShippedPage() {
|
|
|
827
849
|
const by = t.display_name || t.github_login;
|
|
828
850
|
// task 1004432: an approved page is the artist's; the session that applied it is a helper.
|
|
829
851
|
const helper = t.helper_display_name || t.helper_login;
|
|
830
|
-
meta.textContent = `${by ? `
|
|
852
|
+
meta.textContent = `${by ? `shipped by ${by} · ` : ''}${helper ? `with help from ${helper} · ` : ''}${fmtRelativeTime(t.shipped_at)}`;
|
|
831
853
|
li.appendChild(title);
|
|
832
854
|
if (thumb) li.appendChild(thumb);
|
|
833
855
|
li.appendChild(meta);
|
|
@@ -1040,7 +1062,7 @@ async function loadLeaderboard() {
|
|
|
1040
1062
|
function stampRefreshTime() {
|
|
1041
1063
|
if (!els.lastUpdated) return;
|
|
1042
1064
|
const t = new Date().toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
|
|
1043
|
-
els.lastUpdated.textContent = `
|
|
1065
|
+
els.lastUpdated.textContent = `updated at ${t}`;
|
|
1044
1066
|
}
|
|
1045
1067
|
|
|
1046
1068
|
// ---------- the Foundry — ecosystem dev curve (#291, C5) ----------
|
|
@@ -1050,7 +1072,7 @@ function stampRefreshTime() {
|
|
|
1050
1072
|
// kinds-of-work fingerprint. Fed by /api/bongos/public/analytics/ecosystem.
|
|
1051
1073
|
// Inline SVG sparklines — no chart library for this tile.
|
|
1052
1074
|
|
|
1053
|
-
function foundrySparkline(values,
|
|
1075
|
+
function foundrySparkline(values, tone) {
|
|
1054
1076
|
const W = 320, H = 54, pad = 4;
|
|
1055
1077
|
const nums = (values || []).map((v) => (Number.isFinite(Number(v)) ? Number(v) : 0));
|
|
1056
1078
|
if (nums.length === 0) return '';
|
|
@@ -1060,9 +1082,12 @@ function foundrySparkline(values, color) {
|
|
|
1060
1082
|
const y = (v) => H - pad - ((v - min) / span) * (H - 2 * pad);
|
|
1061
1083
|
const line = nums.map((v, i) => `${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(' ');
|
|
1062
1084
|
const area = `${pad},${H - pad} ${line} ${(W - pad)},${H - pad}`;
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1085
|
+
// The colour is the sheet's (.foundry-spark, and --ink for the ink tone): a
|
|
1086
|
+
// token, never a literal handed in from here (task 1005089).
|
|
1087
|
+
const cls = tone === 'ink' ? 'foundry-spark foundry-spark--ink' : 'foundry-spark';
|
|
1088
|
+
return `<svg class="${cls}" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" role="img" aria-label="trend">
|
|
1089
|
+
<polyline class="foundry-spark__area" points="${area}" />
|
|
1090
|
+
<polyline class="foundry-spark__line" points="${line}" />
|
|
1066
1091
|
</svg>`;
|
|
1067
1092
|
}
|
|
1068
1093
|
|
|
@@ -1095,8 +1120,8 @@ async function loadEcosystem() {
|
|
|
1095
1120
|
const shipVals = (data.ship_rate || []).map((r) => Number(r.n));
|
|
1096
1121
|
const durVals = (data.duration_trend || []).map((r) => Number(r.avg_actual));
|
|
1097
1122
|
chartsEl.innerHTML =
|
|
1098
|
-
`<div class="foundry__chart"><div class="foundry__chart-title">
|
|
1099
|
-
`<div class="foundry__chart"><div class="foundry__chart-title">
|
|
1123
|
+
`<div class="foundry__chart"><div class="foundry__chart-title">Tasks shipped per day</div>${foundrySparkline(shipVals, 'ink') || '<p class="empty">no data</p>'}</div>` +
|
|
1124
|
+
`<div class="foundry__chart"><div class="foundry__chart-title">Average time per task (min)</div>${foundrySparkline(durVals, 'info') || '<p class="empty">no data</p>'}</div>`;
|
|
1100
1125
|
|
|
1101
1126
|
const kinds = (data.top_kinds || []).slice(0, 6);
|
|
1102
1127
|
if (kindsEl) {
|
|
@@ -1192,13 +1217,13 @@ function renderStatusDeepCard(kind, id, state) {
|
|
|
1192
1217
|
const label = `#${id}`;
|
|
1193
1218
|
panel.appendChild(mk('p', 'deeplink__kind', kind.toUpperCase()));
|
|
1194
1219
|
if (state.loading) {
|
|
1195
|
-
panel.appendChild(mk('p', 'deeplink__title', `
|
|
1220
|
+
panel.appendChild(mk('p', 'deeplink__title', `Looking up ${label}…`));
|
|
1196
1221
|
} else if (state.notFound) {
|
|
1197
1222
|
panel.appendChild(mk('p', 'deeplink__title', `${label} — no such ${kind}`));
|
|
1198
1223
|
panel.appendChild(mk('p', 'deeplink__note', 'Records number tasks, ideas, and blockers separately from GitHub pull requests — this may be a PR, not a record here.'));
|
|
1199
1224
|
} else if (state.error) {
|
|
1200
1225
|
panel.appendChild(mk('p', 'deeplink__title', `Could not resolve ${label}`));
|
|
1201
|
-
panel.appendChild(mk('p', 'deeplink__note', 'The
|
|
1226
|
+
panel.appendChild(mk('p', 'deeplink__note', 'The server could not be reached. Try again in a moment.'));
|
|
1202
1227
|
} else {
|
|
1203
1228
|
const r = state.ref || {};
|
|
1204
1229
|
panel.appendChild(mk('p', 'deeplink__title', r.title ? `${label} — ${r.title}` : label));
|