@fix-portal/ci-frontend 0.1.1 → 0.3.0
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/dist/index.d.ts +6 -3
- package/dist/index.js +91 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,21 +4,24 @@ import { ReactNode } from 'react';
|
|
|
4
4
|
interface CiBoardProps {
|
|
5
5
|
/** Whether the viewer is an admin: sees private repos + actionable PR links. Host-computed. */
|
|
6
6
|
adminSignal: boolean;
|
|
7
|
-
/** Origin of the CI backend snapshot API (no trailing slash). Defaults to the public
|
|
7
|
+
/** Origin of the CI backend snapshot API (no trailing slash). Defaults to '' (relative URLs — requires a same-origin /api/ proxy). Pass 'https://ci.fixportal.org' to reach the public FixPortal backend. */
|
|
8
8
|
apiBase?: string;
|
|
9
|
+
/** Full URL the board fetches when the viewer is admin. The host's backend should proxy this to the CI backend's /api/dashboard/snapshot/admin endpoint, adding the X-Admin-Key header server-side so the shared secret never reaches the browser. When unset, admin viewers see the public (private-repo-stripped) snapshot. */
|
|
10
|
+
adminSnapshotUrl?: string;
|
|
9
11
|
/** Brand mark for the header. Defaults to a plain text wordmark. */
|
|
10
12
|
logo?: ReactNode;
|
|
11
13
|
/** Footer node. Defaults to a generic, brand-free footer. */
|
|
12
14
|
footerSlot?: ReactNode;
|
|
13
15
|
}
|
|
14
|
-
declare function CiBoard({ adminSignal, apiBase, logo, footerSlot }: CiBoardProps): react.JSX.Element;
|
|
16
|
+
declare function CiBoard({ adminSignal, apiBase, adminSnapshotUrl, logo, footerSlot }: CiBoardProps): react.JSX.Element;
|
|
15
17
|
|
|
16
18
|
declare function DefaultFooter(): react.JSX.Element;
|
|
17
19
|
|
|
18
20
|
interface CiConfig {
|
|
19
21
|
apiBase: string;
|
|
22
|
+
adminSnapshotUrl?: string;
|
|
20
23
|
}
|
|
21
|
-
declare const DEFAULT_CI_API_BASE = "
|
|
24
|
+
declare const DEFAULT_CI_API_BASE = "";
|
|
22
25
|
|
|
23
26
|
type SignalState = 'success' | 'failure' | 'running' | 'unknown';
|
|
24
27
|
interface WorkflowRun {
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// src/CiBoard.tsx
|
|
2
|
+
import { useState as useState6, useContext } from "react";
|
|
3
|
+
import { QueryClient, QueryClientProvider, QueryClientContext } from "@tanstack/react-query";
|
|
4
|
+
|
|
1
5
|
// src/CiAdminContext.tsx
|
|
2
6
|
import { createContext, use } from "react";
|
|
3
7
|
var CiAdminContext = createContext(false);
|
|
@@ -9,7 +13,7 @@ function useCiAdmin() {
|
|
|
9
13
|
|
|
10
14
|
// src/CiConfigContext.tsx
|
|
11
15
|
import { createContext as createContext2, use as use2 } from "react";
|
|
12
|
-
var DEFAULT_CI_API_BASE = "
|
|
16
|
+
var DEFAULT_CI_API_BASE = "";
|
|
13
17
|
var CiConfigContext = createContext2({ apiBase: DEFAULT_CI_API_BASE });
|
|
14
18
|
CiConfigContext.displayName = "CiConfigContext";
|
|
15
19
|
var CiConfigProvider = CiConfigContext.Provider;
|
|
@@ -18,15 +22,14 @@ function useCiConfig() {
|
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
// src/pages/CiBoardContent.tsx
|
|
21
|
-
import { useState as useState5 } from "react";
|
|
25
|
+
import { useState as useState5, useEffect as useEffect5 } from "react";
|
|
22
26
|
|
|
23
27
|
// src/hooks/useDashboardSnapshot.ts
|
|
24
28
|
import { useQuery } from "@tanstack/react-query";
|
|
25
29
|
|
|
26
30
|
// src/api/getDashboardSnapshot.ts
|
|
27
|
-
async function getDashboardSnapshot(
|
|
28
|
-
const
|
|
29
|
-
const response = await fetch(`${base}/api/dashboard/snapshot`);
|
|
31
|
+
async function getDashboardSnapshot(snapshotUrl) {
|
|
32
|
+
const response = await fetch(snapshotUrl);
|
|
30
33
|
if (response.status === 204) return null;
|
|
31
34
|
if (!response.ok) throw new Error(`Dashboard snapshot failed: ${response.status}`);
|
|
32
35
|
return response.json();
|
|
@@ -34,10 +37,12 @@ async function getDashboardSnapshot(apiBase) {
|
|
|
34
37
|
|
|
35
38
|
// src/hooks/useDashboardSnapshot.ts
|
|
36
39
|
function useDashboardSnapshot() {
|
|
37
|
-
const { apiBase } = useCiConfig();
|
|
40
|
+
const { apiBase, adminSnapshotUrl } = useCiConfig();
|
|
41
|
+
const isAdmin = useCiAdmin();
|
|
42
|
+
const snapshotUrl = isAdmin && adminSnapshotUrl ? adminSnapshotUrl : `${apiBase.replace(/\/$/, "")}/api/dashboard/snapshot`;
|
|
38
43
|
return useQuery({
|
|
39
|
-
queryKey: ["dashboard-snapshot",
|
|
40
|
-
queryFn: () => getDashboardSnapshot(
|
|
44
|
+
queryKey: ["dashboard-snapshot", snapshotUrl],
|
|
45
|
+
queryFn: () => getDashboardSnapshot(snapshotUrl),
|
|
41
46
|
refetchInterval: 6e4,
|
|
42
47
|
// The 60s poll already drives freshness; without these, an incidental tab
|
|
43
48
|
// focus refetches and re-renders the whole board between ticks. Set per-query
|
|
@@ -49,7 +54,7 @@ function useDashboardSnapshot() {
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
// src/hooks/useCollapseState.ts
|
|
52
|
-
import { useCallback, useState } from "react";
|
|
57
|
+
import { useCallback, useEffect, useState } from "react";
|
|
53
58
|
var KEY = "ci-dashboard:collapsed";
|
|
54
59
|
function load() {
|
|
55
60
|
try {
|
|
@@ -67,11 +72,13 @@ function save(set) {
|
|
|
67
72
|
}
|
|
68
73
|
function useCollapseState() {
|
|
69
74
|
const [collapsed, setCollapsed] = useState(load);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
save(collapsed);
|
|
77
|
+
}, [collapsed]);
|
|
70
78
|
const mutate = useCallback((fn) => {
|
|
71
79
|
setCollapsed((prev) => {
|
|
72
80
|
const next = new Set(prev);
|
|
73
81
|
fn(next);
|
|
74
|
-
save(next);
|
|
75
82
|
return next;
|
|
76
83
|
});
|
|
77
84
|
}, []);
|
|
@@ -88,7 +95,7 @@ function useCollapseState() {
|
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
// src/hooks/useHideNoCi.ts
|
|
91
|
-
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
98
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
92
99
|
var KEY2 = "ci-dashboard:hide-no-ci";
|
|
93
100
|
function load2() {
|
|
94
101
|
try {
|
|
@@ -99,15 +106,14 @@ function load2() {
|
|
|
99
106
|
}
|
|
100
107
|
function useHideNoCi() {
|
|
101
108
|
const [hidden, setHidden] = useState2(load2);
|
|
109
|
+
useEffect2(() => {
|
|
110
|
+
try {
|
|
111
|
+
localStorage.setItem(KEY2, String(hidden));
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}, [hidden]);
|
|
102
115
|
const toggle = useCallback2(() => {
|
|
103
|
-
setHidden((prev) =>
|
|
104
|
-
const next = !prev;
|
|
105
|
-
try {
|
|
106
|
-
localStorage.setItem(KEY2, String(next));
|
|
107
|
-
} catch {
|
|
108
|
-
}
|
|
109
|
-
return next;
|
|
110
|
-
});
|
|
116
|
+
setHidden((prev) => !prev);
|
|
111
117
|
}, []);
|
|
112
118
|
return { hidden, toggle };
|
|
113
119
|
}
|
|
@@ -157,7 +163,7 @@ function computeSummary(repos) {
|
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
// src/components/SummaryStrip.tsx
|
|
160
|
-
import { useEffect, useRef, useState as useState3 } from "react";
|
|
166
|
+
import { useEffect as useEffect3, useRef, useState as useState3 } from "react";
|
|
161
167
|
|
|
162
168
|
// src/lib/formatCompactNumber.ts
|
|
163
169
|
function formatCompactNumber(value) {
|
|
@@ -214,6 +220,22 @@ function CiWeatherBar({ trend }) {
|
|
|
214
220
|
] });
|
|
215
221
|
}
|
|
216
222
|
|
|
223
|
+
// src/lib/isAllowedHref.ts
|
|
224
|
+
function isAllowedHref(url) {
|
|
225
|
+
if (!url) return "#";
|
|
226
|
+
try {
|
|
227
|
+
const parsed = new URL(url);
|
|
228
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
|
|
229
|
+
return url;
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
if (url.startsWith("/")) {
|
|
233
|
+
return url;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return "#";
|
|
237
|
+
}
|
|
238
|
+
|
|
217
239
|
// src/components/SummaryStrip.tsx
|
|
218
240
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
219
241
|
var SUMMARY_LABELS = {
|
|
@@ -254,7 +276,7 @@ function SummaryStrip({ summary, onOpenPrs, lastMerged, nextPr = null, ciTrend =
|
|
|
254
276
|
const byKey = new Map(summary.map((s) => [s.key, s.count]));
|
|
255
277
|
const [trendInfoOpen, setTrendInfoOpen] = useState3(false);
|
|
256
278
|
const trendLabelRef = useRef(null);
|
|
257
|
-
|
|
279
|
+
useEffect3(() => {
|
|
258
280
|
if (!trendInfoOpen) return;
|
|
259
281
|
function handleOutside(e) {
|
|
260
282
|
if (!trendLabelRef.current?.contains(e.target)) {
|
|
@@ -313,7 +335,7 @@ function SummaryStrip({ summary, onOpenPrs, lastMerged, nextPr = null, ciTrend =
|
|
|
313
335
|
] }),
|
|
314
336
|
/* @__PURE__ */ jsx2("span", { className: "summary-panel__q-title", children: nextPr.title })
|
|
315
337
|
] }),
|
|
316
|
-
isReview && lastMerged && /* @__PURE__ */ jsxs2("a", { className: "summary-panel__merged", href: lastMerged.htmlUrl, target: "_blank", rel: "noopener noreferrer", children: [
|
|
338
|
+
isReview && lastMerged && /* @__PURE__ */ jsxs2("a", { className: "summary-panel__merged", href: isAllowedHref(lastMerged.htmlUrl), target: "_blank", rel: "noopener noreferrer", children: [
|
|
317
339
|
/* @__PURE__ */ jsxs2("span", { className: "summary-panel__q-lab", children: [
|
|
318
340
|
"last merged ",
|
|
319
341
|
/* @__PURE__ */ jsxs2("span", { className: "summary-panel__q-age", children: [
|
|
@@ -340,7 +362,7 @@ function SummaryStrip({ summary, onOpenPrs, lastMerged, nextPr = null, ciTrend =
|
|
|
340
362
|
className: "ci-trend-info-btn",
|
|
341
363
|
"aria-label": "CI health information",
|
|
342
364
|
"aria-expanded": trendInfoOpen,
|
|
343
|
-
"aria-controls": "ci-trend-popover",
|
|
365
|
+
"aria-controls": trendInfoOpen ? "ci-trend-popover" : void 0,
|
|
344
366
|
onClick: () => setTrendInfoOpen((o) => !o),
|
|
345
367
|
children: "i"
|
|
346
368
|
}
|
|
@@ -394,7 +416,7 @@ function stateLabel(state) {
|
|
|
394
416
|
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
395
417
|
function meta(wf) {
|
|
396
418
|
if (wf.state === "unknown") return wf.lastRun ? "no status" : "no runs";
|
|
397
|
-
return formatRelativeTime(wf.lastRun.updatedAt);
|
|
419
|
+
return wf.lastRun ? formatRelativeTime(wf.lastRun.updatedAt) : "no runs";
|
|
398
420
|
}
|
|
399
421
|
var SignalChip = memo(function SignalChip2({ workflow }) {
|
|
400
422
|
const url = workflow.lastRun?.htmlUrl;
|
|
@@ -406,7 +428,7 @@ var SignalChip = memo(function SignalChip2({ workflow }) {
|
|
|
406
428
|
/* @__PURE__ */ jsx3("span", { className: "sr-only", children: stateLabel(workflow.state) }),
|
|
407
429
|
/* @__PURE__ */ jsx3("span", { className: "chip__meta", children: meta(workflow) })
|
|
408
430
|
] });
|
|
409
|
-
return linkable ? /* @__PURE__ */ jsx3("a", { className, href: url, title: stateLabel(workflow.state), target: "_blank", rel: "noopener noreferrer", children: body }) : /* @__PURE__ */ jsx3("span", { className, title: stateLabel(workflow.state), children: body });
|
|
431
|
+
return linkable ? /* @__PURE__ */ jsx3("a", { className, href: isAllowedHref(url), title: stateLabel(workflow.state), target: "_blank", rel: "noopener noreferrer", children: body }) : /* @__PURE__ */ jsx3("span", { className, title: stateLabel(workflow.state), children: body });
|
|
410
432
|
});
|
|
411
433
|
|
|
412
434
|
// src/components/RepoMetricsLine.tsx
|
|
@@ -444,7 +466,7 @@ function PullRequestList({ pullRequests }) {
|
|
|
444
466
|
pullRequests.length === 1 ? "" : "s"
|
|
445
467
|
] }),
|
|
446
468
|
/* @__PURE__ */ jsx5("ul", { children: pullRequests.map((pr) => /* @__PURE__ */ jsxs5("li", { className: pr.isDraft ? "repo-prs__item repo-prs__item--draft" : "repo-prs__item", children: [
|
|
447
|
-
/* @__PURE__ */ jsxs5("a", { href: pr.htmlUrl, target: "_blank", rel: "noopener noreferrer", children: [
|
|
469
|
+
/* @__PURE__ */ jsxs5("a", { href: isAllowedHref(pr.htmlUrl), target: "_blank", rel: "noopener noreferrer", children: [
|
|
448
470
|
/* @__PURE__ */ jsxs5("span", { className: "repo-prs__num", children: [
|
|
449
471
|
"#",
|
|
450
472
|
pr.number
|
|
@@ -490,7 +512,7 @@ var JobLaneRow = memo2(function JobLaneRow2({
|
|
|
490
512
|
"a",
|
|
491
513
|
{
|
|
492
514
|
className: `chip chip--${s.state} chip--joblane`,
|
|
493
|
-
href: s.htmlUrl,
|
|
515
|
+
href: isAllowedHref(s.htmlUrl),
|
|
494
516
|
title: `${s.workflow} \xB7 ${s.state}`,
|
|
495
517
|
target: "_blank",
|
|
496
518
|
rel: "noopener noreferrer",
|
|
@@ -554,7 +576,7 @@ var RepoBoard = memo3(function RepoBoard2({
|
|
|
554
576
|
noCi && /* @__PURE__ */ jsx8("span", { className: "repo-board__noci-tag", children: "No CI" }),
|
|
555
577
|
/* @__PURE__ */ jsx8(RepoMetricsLine, { metrics: repository.metrics }),
|
|
556
578
|
/* @__PURE__ */ jsx8(RepoActivityIndicator, { repository }),
|
|
557
|
-
/* @__PURE__ */ jsx8("a", { className: "repo-board__gh-link", href: repository.htmlUrl, target: "_blank", rel: "noopener noreferrer", "aria-label": `Open ${repository.name} on GitHub`, children: "GitHub \u2197" })
|
|
579
|
+
/* @__PURE__ */ jsx8("a", { className: "repo-board__gh-link", href: isAllowedHref(repository.htmlUrl), target: "_blank", rel: "noopener noreferrer", "aria-label": `Open ${repository.name} on GitHub`, children: "GitHub \u2197" })
|
|
558
580
|
] }),
|
|
559
581
|
!collapsed && /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
560
582
|
repository.workflows.length === 0 ? /* @__PURE__ */ jsx8("div", { className: "repo-board__empty", children: "no workflows" }) : /* @__PURE__ */ jsxs8("div", { className: "repo-workflows", children: [
|
|
@@ -641,7 +663,7 @@ function StatusLegend() {
|
|
|
641
663
|
}
|
|
642
664
|
|
|
643
665
|
// src/components/PullRequestStepper.tsx
|
|
644
|
-
import { useEffect as
|
|
666
|
+
import { useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
|
|
645
667
|
|
|
646
668
|
// src/lib/prAgeTone.ts
|
|
647
669
|
function prAgeTone(createdAtIso, now = Date.now()) {
|
|
@@ -658,14 +680,17 @@ function PullRequestStepper({ prs, onClose }) {
|
|
|
658
680
|
const dialogRef = useRef2(null);
|
|
659
681
|
const safeIndex = Math.min(i, Math.max(0, prs.length - 1));
|
|
660
682
|
const pr = prs[safeIndex];
|
|
661
|
-
|
|
683
|
+
useEffect4(() => {
|
|
662
684
|
const previouslyFocused = document.activeElement;
|
|
663
685
|
const dlg = dialogRef.current;
|
|
664
686
|
dlg?.showModal();
|
|
665
687
|
dlg?.focus();
|
|
666
|
-
return () =>
|
|
688
|
+
return () => {
|
|
689
|
+
previouslyFocused?.focus?.();
|
|
690
|
+
dlg?.close();
|
|
691
|
+
};
|
|
667
692
|
}, []);
|
|
668
|
-
|
|
693
|
+
useEffect4(() => {
|
|
669
694
|
if (prs.length === 0) onClose();
|
|
670
695
|
}, [prs.length, onClose]);
|
|
671
696
|
if (!pr) return null;
|
|
@@ -716,7 +741,7 @@ function PullRequestStepper({ prs, onClose }) {
|
|
|
716
741
|
"@",
|
|
717
742
|
pr.author
|
|
718
743
|
] }),
|
|
719
|
-
/* @__PURE__ */ jsx12("a", { className: "pr-card__gh", href: pr.htmlUrl, target: "_blank", rel: "noopener noreferrer", children: "Open on GitHub \u2197" })
|
|
744
|
+
/* @__PURE__ */ jsx12("a", { className: "pr-card__gh", href: isAllowedHref(pr.htmlUrl), target: "_blank", rel: "noopener noreferrer", children: "Open on GitHub \u2197" })
|
|
720
745
|
] })
|
|
721
746
|
] }),
|
|
722
747
|
prs.length > 1 && /* @__PURE__ */ jsxs12("div", { className: "pr-modal__nav", children: [
|
|
@@ -741,6 +766,11 @@ function CiBoardContent() {
|
|
|
741
766
|
const hideNoCi = useHideNoCi();
|
|
742
767
|
const isAdmin = useCiAdmin();
|
|
743
768
|
const [stepperOpen, setStepperOpen] = useState5(false);
|
|
769
|
+
useEffect5(() => {
|
|
770
|
+
if (openPrs.length === 0) {
|
|
771
|
+
setStepperOpen(false);
|
|
772
|
+
}
|
|
773
|
+
}, [openPrs.length]);
|
|
744
774
|
if (snapshot.isPending) {
|
|
745
775
|
return /* @__PURE__ */ jsx13("main", { className: "dashboard-page", children: /* @__PURE__ */ jsx13("div", { className: "state-msg", children: "Loading dashboard\u2026" }) });
|
|
746
776
|
}
|
|
@@ -752,11 +782,11 @@ function CiBoardContent() {
|
|
|
752
782
|
}
|
|
753
783
|
const { refreshedAt, repositories: allRepositories, lastMergedPr: rawLastMerged } = snapshot.data;
|
|
754
784
|
const repositories = isAdmin ? allRepositories : allRepositories.filter((r) => !r.private);
|
|
755
|
-
const summary = isAdmin ? snapshot.data.summary : computeSummary(
|
|
756
|
-
const lastMergedPr = rawLastMerged &&
|
|
757
|
-
const repoNames = repositories.map((r) => r.name);
|
|
785
|
+
const summary = isAdmin && !hideNoCi.hidden ? snapshot.data.summary : computeSummary(visibleRepos);
|
|
786
|
+
const lastMergedPr = rawLastMerged && visibleRepos.some((r) => r.name === rawLastMerged.repo) ? rawLastMerged : null;
|
|
758
787
|
const noCiCount = repositories.filter(isNoCi).length;
|
|
759
788
|
const visibleRepos = hideNoCi.hidden ? repositories.filter((r) => !isNoCi(r)) : repositories;
|
|
789
|
+
const repoNames = visibleRepos.map((r) => r.name);
|
|
760
790
|
const hiddenCount = repositories.length - visibleRepos.length;
|
|
761
791
|
const publicRepos = visibleRepos.filter((r) => !r.private);
|
|
762
792
|
const privateRepos = visibleRepos.filter((r) => r.private);
|
|
@@ -858,7 +888,7 @@ function CiBoardContent() {
|
|
|
858
888
|
/* @__PURE__ */ jsx13("div", { className: "repo-list", children: repoListContent }),
|
|
859
889
|
/* @__PURE__ */ jsx13(StatusLegend, {}),
|
|
860
890
|
/* @__PURE__ */ jsx13(MetricsLegend, {}),
|
|
861
|
-
stepperOpen && /* @__PURE__ */ jsx13(PullRequestStepper, { prs: openPrs, onClose: () => setStepperOpen(false) })
|
|
891
|
+
stepperOpen && openPrs.length > 0 && /* @__PURE__ */ jsx13(PullRequestStepper, { prs: openPrs, onClose: () => setStepperOpen(false) })
|
|
862
892
|
] });
|
|
863
893
|
}
|
|
864
894
|
|
|
@@ -869,9 +899,29 @@ function DefaultFooter() {
|
|
|
869
899
|
}
|
|
870
900
|
|
|
871
901
|
// src/CiBoard.tsx
|
|
872
|
-
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
873
|
-
function
|
|
874
|
-
|
|
902
|
+
import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
903
|
+
function QueryClientSafeProvider({ children }) {
|
|
904
|
+
const existingClient = useContext(QueryClientContext);
|
|
905
|
+
const [localClient] = useState6(() => {
|
|
906
|
+
if (!existingClient) {
|
|
907
|
+
return new QueryClient({
|
|
908
|
+
defaultOptions: {
|
|
909
|
+
queries: {
|
|
910
|
+
retry: 1,
|
|
911
|
+
refetchOnWindowFocus: false
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
return null;
|
|
917
|
+
});
|
|
918
|
+
if (existingClient) {
|
|
919
|
+
return /* @__PURE__ */ jsx15(Fragment6, { children });
|
|
920
|
+
}
|
|
921
|
+
return /* @__PURE__ */ jsx15(QueryClientProvider, { client: localClient, children });
|
|
922
|
+
}
|
|
923
|
+
function CiBoard({ adminSignal, apiBase = DEFAULT_CI_API_BASE, adminSnapshotUrl, logo, footerSlot }) {
|
|
924
|
+
return /* @__PURE__ */ jsx15(CiConfigProvider, { value: { apiBase, adminSnapshotUrl }, children: /* @__PURE__ */ jsx15(QueryClientSafeProvider, { children: /* @__PURE__ */ jsxs14("div", { className: "ci-page", children: [
|
|
875
925
|
/* @__PURE__ */ jsxs14("div", { className: "ci-embed", children: [
|
|
876
926
|
/* @__PURE__ */ jsx15("header", { className: "ci-embed__header", children: /* @__PURE__ */ jsxs14("span", { className: "ci-embed__lockup", children: [
|
|
877
927
|
logo ?? /* @__PURE__ */ jsx15("span", { className: "ci-embed__wordmark-text", children: "CI Dashboard" }),
|
|
@@ -883,7 +933,7 @@ function CiBoard({ adminSignal, apiBase = DEFAULT_CI_API_BASE, logo, footerSlot
|
|
|
883
933
|
/* @__PURE__ */ jsx15(CiAdminProvider, { value: adminSignal, children: /* @__PURE__ */ jsx15(CiBoardContent, {}) })
|
|
884
934
|
] }),
|
|
885
935
|
footerSlot ?? /* @__PURE__ */ jsx15(DefaultFooter, {})
|
|
886
|
-
] }) });
|
|
936
|
+
] }) }) });
|
|
887
937
|
}
|
|
888
938
|
export {
|
|
889
939
|
CiBoard,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/CiAdminContext.tsx","../src/CiConfigContext.tsx","../src/pages/CiBoardContent.tsx","../src/hooks/useDashboardSnapshot.ts","../src/api/getDashboardSnapshot.ts","../src/hooks/useCollapseState.ts","../src/hooks/useHideNoCi.ts","../src/lib/isNoCi.ts","../src/lib/computeSummary.ts","../src/components/SummaryStrip.tsx","../src/lib/formatCompactNumber.ts","../src/lib/relativeTime.ts","../src/components/CiWeatherBar.tsx","../src/components/RepoBoard.tsx","../src/components/SignalChip.tsx","../src/lib/stateLabel.ts","../src/components/RepoMetricsLine.tsx","../src/components/PullRequestList.tsx","../src/components/JobLaneRow.tsx","../src/lib/dedupeJobLabel.ts","../src/lib/worstState.ts","../src/components/RepoActivityIndicator.tsx","../src/components/RepoSection.tsx","../src/components/MetricsLegend.tsx","../src/components/StatusLegend.tsx","../src/components/PullRequestStepper.tsx","../src/lib/prAgeTone.ts","../src/lib/flattenOpenPrs.ts","../src/DefaultFooter.tsx","../src/CiBoard.tsx"],"sourcesContent":["import { createContext, use } from 'react'\n\n// Whether the current viewer is the signed-in platform admin. Drives whether PR\n// rows render as actionable GitHub links or as plain, non-interactive text.\n// Defaults to false (anonymous → read-only).\nconst CiAdminContext = createContext(false)\nCiAdminContext.displayName = 'CiAdminContext'\n\nexport const CiAdminProvider = CiAdminContext.Provider\n\nexport function useCiAdmin(): boolean {\n return use(CiAdminContext)\n}\n","import { createContext, use } from 'react'\n\n// Runtime config for the CI board. apiBase is the origin of the CI backend\n// snapshot API (no trailing slash). Injected by the host so the library carries\n// no build-time env assumption; falls back to the public default.\nexport interface CiConfig {\n apiBase: string\n}\n\nexport const DEFAULT_CI_API_BASE = 'https://ci.fixportal.org'\n\nconst CiConfigContext = createContext<CiConfig>({ apiBase: DEFAULT_CI_API_BASE })\nCiConfigContext.displayName = 'CiConfigContext'\n\nexport const CiConfigProvider = CiConfigContext.Provider\n\nexport function useCiConfig(): CiConfig {\n return use(CiConfigContext)\n}\n","import { useState } from 'react'\nimport { useDashboardSnapshot } from '../hooks/useDashboardSnapshot'\nimport { useCollapseState } from '../hooks/useCollapseState'\nimport { useHideNoCi } from '../hooks/useHideNoCi'\nimport { useCiAdmin } from '../CiAdminContext'\nimport { isNoCi } from '../lib/isNoCi'\nimport { computeSummary } from '../lib/computeSummary'\nimport { SummaryStrip } from '../components/SummaryStrip'\nimport { RepoBoard } from '../components/RepoBoard'\nimport { RepoSection } from '../components/RepoSection'\nimport { MetricsLegend } from '../components/MetricsLegend'\nimport { StatusLegend } from '../components/StatusLegend'\nimport { PullRequestStepper } from '../components/PullRequestStepper'\nimport { flattenOpenPrs } from '../lib/flattenOpenPrs'\nimport { formatRelativeTime } from '../lib/relativeTime'\n\nexport function CiBoardContent() {\n const snapshot = useDashboardSnapshot()\n const collapse = useCollapseState()\n const hideNoCi = useHideNoCi()\n const isAdmin = useCiAdmin()\n const [stepperOpen, setStepperOpen] = useState(false)\n\n if (snapshot.isPending) {\n return (\n <main className=\"dashboard-page\">\n <div className=\"state-msg\">Loading dashboard…</div>\n </main>\n )\n }\n\n if (snapshot.isError) {\n return (\n <main className=\"dashboard-page\">\n <div className=\"state-msg state-msg--error\">Dashboard unavailable.</div>\n </main>\n )\n }\n\n // 204 -> null: backend is up but has not produced a snapshot yet.\n if (!snapshot.data) {\n return (\n <main className=\"dashboard-page\">\n <div className=\"state-msg\">Waiting for the first refresh…</div>\n </main>\n )\n }\n\n const { refreshedAt, repositories: allRepositories, lastMergedPr: rawLastMerged } = snapshot.data\n // Non-admin viewers see only public repos; admin sees all. Filtering here drives\n // everything downstream — summary counts, stepper PRs, and next-in-queue all\n // reflect exactly the repos displayed on screen.\n const repositories = isAdmin ? allRepositories : allRepositories.filter(r => !r.private)\n const summary = isAdmin ? snapshot.data.summary : computeSummary(repositories)\n // Only surface lastMergedPr when its repo is in the visible set; a private-repo\n // merge is invisible to the public viewer and the link would 404 for them.\n const lastMergedPr = rawLastMerged && repositories.some(r => r.name === rawLastMerged.repo)\n ? rawLastMerged\n : null\n // Compute the names list and the all-collapsed flag once — they were rebuilt\n // and re-traversed twice per render (the onClick and the button label).\n const repoNames = repositories.map(r => r.name)\n const noCiCount = repositories.filter(isNoCi).length\n const visibleRepos = hideNoCi.hidden ? repositories.filter(r => !isNoCi(r)) : repositories\n const hiddenCount = repositories.length - visibleRepos.length\n const publicRepos = visibleRepos.filter(r => !r.private)\n const privateRepos = visibleRepos.filter(r => r.private)\n const showGroups = publicRepos.length > 0 && privateRepos.length > 0\n const KEY_PUBLIC = 'section:public'\n const KEY_PRIVATE = 'section:private'\n const sectionKeys = showGroups ? [KEY_PUBLIC, KEY_PRIVATE] : []\n const allCollapsed = collapse.allCollapsed([...repoNames, ...sectionKeys])\n // The stepper opens at the head of this oldest-first list, so its first entry\n // is \"next in queue\" — derive it from visibleRepos (the Hide No-CI filter\n // applied) so the card and stepper never advertise a PR from a repo the board\n // is currently hiding.\n const openPrs = flattenOpenPrs(visibleRepos)\n const nextPr = openPrs[0] ?? null\n\n let repoListContent\n if (visibleRepos.length === 0 && hideNoCi.hidden) {\n repoListContent = <div className=\"state-msg\">All repositories are No-CI — hidden.</div>\n } else if (showGroups) {\n repoListContent = (\n <>\n <RepoSection\n label=\"Public\"\n count={publicRepos.length}\n collapsed={collapse.isCollapsed(KEY_PUBLIC)}\n onToggle={() => collapse.toggle(KEY_PUBLIC)}\n />\n {!collapse.isCollapsed(KEY_PUBLIC) &&\n publicRepos.map(repository => (\n <RepoBoard\n key={repository.name}\n repository={repository}\n collapsed={collapse.isCollapsed(repository.name)}\n onToggle={collapse.toggle}\n />\n ))\n }\n <RepoSection\n label=\"Private\"\n count={privateRepos.length}\n collapsed={collapse.isCollapsed(KEY_PRIVATE)}\n onToggle={() => collapse.toggle(KEY_PRIVATE)}\n />\n {!collapse.isCollapsed(KEY_PRIVATE) &&\n privateRepos.map(repository => (\n <RepoBoard\n key={repository.name}\n repository={repository}\n collapsed={collapse.isCollapsed(repository.name)}\n onToggle={collapse.toggle}\n />\n ))\n }\n </>\n )\n } else {\n repoListContent = visibleRepos.map(repository => (\n <RepoBoard\n key={repository.name}\n repository={repository}\n collapsed={collapse.isCollapsed(repository.name)}\n onToggle={collapse.toggle}\n />\n ))\n }\n\n return (\n <main className=\"dashboard-page\">\n <div className=\"dashboard__toolbar\">\n <span className=\"dashboard__scope\">{snapshot.data.org} · {isAdmin ? 'all repositories' : 'public repositories'}</span>\n <span className=\"dashboard__toolbar-right\">\n {noCiCount > 0 && (\n <button\n type=\"button\"\n className={`dashboard__hide-noci${hideNoCi.hidden ? ' dashboard__hide-noci--on' : ''}`}\n onClick={hideNoCi.toggle}\n aria-pressed={hideNoCi.hidden}\n >\n {hideNoCi.hidden ? `Show No-CI · ${hiddenCount} hidden` : 'Hide No-CI'}\n </button>\n )}\n <button\n type=\"button\"\n className=\"dashboard__collapse-all\"\n onClick={() => (allCollapsed ? collapse.expandAll() : collapse.collapseAll([...repoNames, ...sectionKeys]))}\n >\n {allCollapsed ? '⊞ Expand all' : '⊟ Collapse all'}\n </button>\n <span className=\"dashboard__refreshed\">\n <span className=\"live-dot\" aria-hidden=\"true\" />\n updated {formatRelativeTime(refreshedAt)}\n </span>\n </span>\n </div>\n <SummaryStrip summary={summary} onOpenPrs={isAdmin ? () => setStepperOpen(true) : undefined} lastMerged={lastMergedPr} nextPr={nextPr} ciTrend={snapshot.data.ciTrend ?? []} />\n <div className=\"repo-list\">\n {repoListContent}\n </div>\n <StatusLegend />\n <MetricsLegend />\n {stepperOpen && (\n <PullRequestStepper prs={openPrs} onClose={() => setStepperOpen(false)} />\n )}\n </main>\n )\n}\n","import { useQuery } from '@tanstack/react-query'\nimport { getDashboardSnapshot } from '../api/getDashboardSnapshot'\nimport { useCiConfig } from '../CiConfigContext'\n\nexport function useDashboardSnapshot() {\n const { apiBase } = useCiConfig()\n return useQuery({\n queryKey: ['dashboard-snapshot', apiBase],\n queryFn: () => getDashboardSnapshot(apiBase),\n refetchInterval: 60_000,\n // The 60s poll already drives freshness; without these, an incidental tab\n // focus refetches and re-renders the whole board between ticks. Set per-query\n // (not on the shared app QueryClient) so the host app is unaffected;\n // structural sharing then lets the memoised boards skip a no-change tick.\n staleTime: 30_000,\n refetchOnWindowFocus: false,\n })\n}\n","import type { DashboardSnapshot } from './types'\n\n// 204 No Content is the documented \"no snapshot yet\" state. Return null rather\n// than calling response.json() on an empty body. apiBase is supplied by the\n// caller (from CiConfigContext) so the library makes no build-time env\n// assumption; a trailing slash is trimmed so the path never doubles up.\nexport async function getDashboardSnapshot(apiBase: string): Promise<DashboardSnapshot | null> {\n const base = apiBase.replace(/\\/$/, '')\n const response = await fetch(`${base}/api/dashboard/snapshot`)\n if (response.status === 204) return null\n if (!response.ok) throw new Error(`Dashboard snapshot failed: ${response.status}`)\n return response.json()\n}\n","import { useCallback, useState } from 'react'\n\nconst KEY = 'ci-dashboard:collapsed'\n\nfunction load(): Set<string> {\n try {\n const raw = localStorage.getItem(KEY)\n return new Set(raw ? (JSON.parse(raw) as string[]) : [])\n } catch {\n return new Set()\n }\n}\n\nfunction save(set: Set<string>) {\n try {\n localStorage.setItem(KEY, JSON.stringify([...set]))\n } catch {\n // ignore (private mode / quota) — collapse state is best-effort\n }\n}\n\n// A repo absent from the set is expanded, so new repos default to expanded.\nexport function useCollapseState() {\n const [collapsed, setCollapsed] = useState<Set<string>>(load)\n\n const mutate = useCallback((fn: (next: Set<string>) => void) => {\n setCollapsed(prev => {\n const next = new Set(prev)\n fn(next)\n save(next)\n return next\n })\n }, [])\n\n return {\n isCollapsed: useCallback((name: string) => collapsed.has(name), [collapsed]),\n allCollapsed: useCallback(\n (names: string[]) => names.length > 0 && names.every(n => collapsed.has(n)),\n [collapsed],\n ),\n toggle: useCallback((name: string) => mutate(s => (s.has(name) ? s.delete(name) : s.add(name))), [mutate]),\n collapseAll: useCallback((names: string[]) => mutate(s => names.forEach(n => s.add(n))), [mutate]),\n expandAll: useCallback(() => mutate(s => s.clear()), [mutate]),\n }\n}\n","import { useCallback, useState } from 'react'\n\nconst KEY = 'ci-dashboard:hide-no-ci'\n\nfunction load(): boolean {\n try {\n return localStorage.getItem(KEY) === 'true'\n } catch {\n return false\n }\n}\n\n// A single persisted boolean: whether No-CI repos are hidden from the board.\n// Default false (shown). Mirrors useCollapseState's best-effort persistence.\nexport function useHideNoCi() {\n const [hidden, setHidden] = useState<boolean>(load)\n\n const toggle = useCallback(() => {\n setHidden(prev => {\n const next = !prev\n try {\n localStorage.setItem(KEY, String(next))\n } catch {\n // ignore (private mode / quota) — hide state is best-effort\n }\n return next\n })\n }, [])\n\n return { hidden, toggle }\n}\n","import type { RepositorySnapshot } from '../api/types'\n\n// A repository is \"No CI\" when it has no workflows at all — the same definition\n// the backend uses for the `no-ci` summary count (repos with Workflows.Count == 0).\nexport function isNoCi(repository: RepositorySnapshot): boolean {\n return (repository.workflows?.length ?? 0) === 0\n}\n","import type { RepositorySnapshot, SummaryCount } from '../api/types'\nimport { isNoCi } from './isNoCi'\n\n// Keys that always appear in the summary, even at zero, so the strip structure\n// is stable regardless of the filtered repo set. Mirrors what the server sends\n// in snapshot.data.summary so admin and guest panels look the same.\nconst ALWAYS_VISIBLE_KEYS = new Set([\n 'repos', 'workflows', 'nloc', // inventory\n 'open-prs', // Review panel (carries next-in-queue / last-merged)\n 'running', 'failing', 'no-ci', // core CI status\n 'deploys-running', 'deploys-failing', // deploy lane (zero = nothing deploying / all clean)\n 'packages-failing', // package lane\n])\n\n// Recomputes summary counts from a filtered repo list. Mirrors the server-side\n// aggregation so the strip reflects exactly the repos being displayed.\nexport function computeSummary(repos: RepositorySnapshot[]): SummaryCount[] {\n const workflows = repos.flatMap(r => r.workflows)\n const deploys = repos.flatMap(r => r.deploys ?? [])\n const packages = repos.flatMap(r => r.packages ?? [])\n const openPrs = repos.flatMap(r => r.pullRequests ?? [])\n const nloc = repos.reduce((acc, r) => acc + (r.metrics?.nloc ?? 0), 0)\n\n const all: SummaryCount[] = [\n { key: 'repos', count: repos.length },\n { key: 'workflows', count: workflows.length },\n { key: 'failing', count: workflows.filter(w => w.state === 'failure').length },\n { key: 'running', count: workflows.filter(w => w.state === 'running').length },\n { key: 'no-ci', count: repos.filter(isNoCi).length },\n { key: 'open-prs', count: openPrs.length },\n { key: 'nloc', count: nloc },\n { key: 'deploys-failing', count: deploys.filter(d => d.state === 'failure').length },\n { key: 'deploys-running', count: deploys.filter(d => d.state === 'running').length },\n { key: 'packages-failing', count: packages.filter(p => p.state === 'failure').length },\n ]\n\n return all.filter(c => ALWAYS_VISIBLE_KEYS.has(c.key) || c.count > 0)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type { CiTrendBucket, MergedPr, SummaryCount } from '../api/types'\nimport type { OpenPr } from '../lib/flattenOpenPrs'\nimport { formatCompactNumber } from '../lib/formatCompactNumber'\nimport { formatRelativeTime } from '../lib/relativeTime'\nimport { CiWeatherBar } from './CiWeatherBar'\nconst SUMMARY_LABELS: Record<string, string> = {\n repos: 'Repositories',\n workflows: 'Workflows',\n failing: 'Failing',\n running: 'Running',\n 'no-ci': 'No CI',\n 'open-prs': 'Open PRs',\n nloc: 'Lines of code',\n 'deploys-failing': 'Deploys failing',\n 'deploys-running': 'Deploys running',\n 'packages-failing': 'Packages failing',\n}\n\n// Three panels group the counts by what the operator is looking for: review work,\n// pipeline health, and inventory. Keys appear in this fixed order regardless of the\n// summary array's order; a panel with no present keys is hidden.\nconst PANELS: { title: string; keys: string[] }[] = [\n { title: 'Review', keys: ['open-prs'] },\n { title: 'CI status', keys: ['running', 'failing', 'packages-failing', 'deploys-running', 'deploys-failing', 'no-ci'] },\n { title: 'Inventory', keys: ['repos', 'workflows', 'nloc'] },\n]\n\nconst NEUTRAL_KEYS = new Set(['repos', 'workflows', 'nloc'])\nconst EMPTY_TREND: CiTrendBucket[] = []\n\nfunction labelFor(key: string, count: number) {\n // 'Open PRs' is the only count-driven noun on the strip; singularise it so a\n // single PR doesn't read as '1 Open PRs'.\n if (key === 'open-prs') return count === 1 ? 'Open PR' : 'Open PRs'\n return SUMMARY_LABELS[key] ?? key.replaceAll('-', ' ')\n}\n\nfunction formatCount(key: string, count: number) {\n return key === 'nloc' ? formatCompactNumber(count) : count\n}\n\n// A non-zero count is coloured to mirror its chip: failures red, running blue,\n// no-ci indigo, the rest amber. open-prs gets its own non-alarm \"review\" tone.\n// Zero / inventory quiet.\nfunction toneFor(key: string, count: number): string {\n if (count === 0 || NEUTRAL_KEYS.has(key)) return 'ok'\n if (key === 'open-prs') return 'review'\n if (key === 'failing' || key === 'deploys-failing' || key === 'packages-failing') return 'fail'\n if (key === 'running' || key === 'deploys-running') return 'run'\n if (key === 'no-ci') return 'no-ci'\n return 'alert'\n}\n\nexport function SummaryStrip({ summary, onOpenPrs, lastMerged, nextPr = null, ciTrend = EMPTY_TREND }: { summary: SummaryCount[]; onOpenPrs?: () => void; lastMerged: MergedPr | null; nextPr?: OpenPr | null; ciTrend?: CiTrendBucket[] }) {\n const byKey = new Map(summary.map(s => [s.key, s.count]))\n\n const [trendInfoOpen, setTrendInfoOpen] = useState(false)\n const trendLabelRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n if (!trendInfoOpen) return\n function handleOutside(e: MouseEvent) {\n if (!trendLabelRef.current?.contains(e.target as Node)) {\n setTrendInfoOpen(false)\n }\n }\n function handleEsc(e: KeyboardEvent) {\n if (e.key === 'Escape') setTrendInfoOpen(false)\n }\n document.addEventListener('mousedown', handleOutside)\n document.addEventListener('keydown', handleEsc)\n return () => {\n document.removeEventListener('mousedown', handleOutside)\n document.removeEventListener('keydown', handleEsc)\n }\n }, [trendInfoOpen])\n\n return (\n <section className=\"summary-panels\">\n {PANELS.map(panel => {\n const items: { key: string; count: number }[] = []\n for (const k of panel.keys) {\n const count = byKey.get(k)\n if (count !== undefined) items.push({ key: k, count })\n }\n if (items.length === 0) return null\n const isReview = panel.title === 'Review'\n const isCiStatus = panel.title === 'CI status'\n return (\n <div key={panel.title} className={`summary-panel${isReview ? ' summary-panel--review' : ''}${isCiStatus ? ' summary-panel--ci' : ''}`}>\n <span className=\"summary-panel__title\">{panel.title}</span>\n <div className=\"summary-panel__items\">\n {items.map(item => {\n const body = (\n <>\n <span className=\"summary__count\">{formatCount(item.key, item.count)}</span>\n <span className=\"summary__label\">{labelFor(item.key, item.count)}</span>\n </>\n )\n if (item.key === 'open-prs' && onOpenPrs) {\n return (\n <button\n key={item.key}\n type=\"button\"\n className=\"summary__item summary__item--btn\"\n data-key={item.key}\n data-tone={toneFor(item.key, item.count)}\n onClick={onOpenPrs}\n disabled={item.count === 0}\n >{body}</button>\n )\n }\n return (\n <div key={item.key} className=\"summary__item\" data-key={item.key} data-tone={toneFor(item.key, item.count)}>{body}</div>\n )\n })}\n </div>\n {isReview && nextPr && (\n <div className=\"summary-panel__next\">\n <span className=\"summary-panel__q-lab\">next in queue</span>\n <span className=\"summary-panel__q-body\">{nextPr.repo} #{nextPr.number}</span>\n <span className=\"summary-panel__q-title\">{nextPr.title}</span>\n </div>\n )}\n {isReview && lastMerged && (\n <a className=\"summary-panel__merged\" href={lastMerged.htmlUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n <span className=\"summary-panel__q-lab\">\n last merged <span className=\"summary-panel__q-age\">({formatRelativeTime(lastMerged.mergedAt)})</span>\n </span>\n <span className=\"summary-panel__q-body\">{lastMerged.repo} #{lastMerged.number}</span>\n <span className=\"summary-panel__q-title\">{lastMerged.title}</span>\n </a>\n )}\n {panel.title === 'CI status' && ciTrend.length > 0 && (\n <div className=\"summary-panel__trend\">\n <CiWeatherBar trend={ciTrend} />\n <div ref={trendLabelRef} className=\"summary-panel__trend-label-row\">\n <span className=\"summary-panel__trend-lab\">CI health · 24h</span>\n <button\n type=\"button\"\n className=\"ci-trend-info-btn\"\n aria-label=\"CI health information\"\n aria-expanded={trendInfoOpen}\n aria-controls=\"ci-trend-popover\"\n onClick={() => setTrendInfoOpen(o => !o)}\n >\n i\n </button>\n {trendInfoOpen && (\n <section\n id=\"ci-trend-popover\"\n aria-label=\"CI health explanation\"\n className=\"ci-trend-popover\"\n >\n <div className=\"ci-trend-popover__title\">CI health · 24h</div>\n <p>Each bar is a 1-hour bucket of CI activity across the whole org.</p>\n <p><span aria-hidden=\"true\" className=\"ci-trend-popover__swatch ci-trend-popover__swatch--fail\">■</span> Red — any run failed that hour (on any branch).</p>\n <p><span aria-hidden=\"true\" className=\"ci-trend-popover__swatch ci-trend-popover__swatch--pass\">■</span> Green — runs present, none failed. Quiet hours inherit the previous state.</p>\n <p>Oldest bar on the left, newest on the right.</p>\n <div className=\"ci-trend-popover__caret\" aria-hidden=\"true\" />\n </section>\n )}\n </div>\n </div>\n )}\n </div>\n )\n })}\n </section>\n )\n}\n","/** Compact integer formatting for large counts: 12345 -> \"12.3k\", 980 -> \"980\". */\nexport function formatCompactNumber(value: number): string {\n if (value < 1000) return String(value)\n if (value < 1_000_000) return `${(value / 1000).toFixed(1)}k`\n return `${(value / 1_000_000).toFixed(1)}M`\n}\n","/** Compact \"x ago\" formatting for observed-at timestamps in the status board. */\nexport function formatRelativeTime(iso: string): string {\n const then = new Date(iso).getTime()\n if (Number.isNaN(then)) return ''\n\n const minutes = Math.round((Date.now() - then) / 60_000)\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes}m ago`\n\n const hours = Math.round(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n\n const days = Math.round(hours / 24)\n return `${days}d ago`\n}\n","import type { CiTrendBucket, CiTrendState } from '../api/types'\nimport { formatRelativeTime } from '../lib/relativeTime'\n\n// A status-page \"weather\" strip: one block per hourly bucket, coloured by the\n// carry-forward worst-state from the backend. Pure presentational; colours come\n// from CSS via the data-state attribute.\nconst BLOCK_WORD: Record<CiTrendState, string> = {\n passing: 'healthy',\n failing: 'failing',\n noData: 'no data',\n}\n\nexport function CiWeatherBar({ trend }: { trend: CiTrendBucket[] }) {\n if (trend.length === 0) return null\n const failing = trend.filter(b => b.state === 'failing').length\n const healthy = trend.filter(b => b.state === 'passing').length\n const label = `CI health, last 24h: ${failing} failing, ${healthy} healthy`\n return (\n <>\n <div className=\"ci-weather\" role=\"img\" aria-label={label}>\n {trend.map((b, i) => (\n // Per-block hover reveals which hour a block is and its state — the\n // data was previously exposed only to screen readers via aria-label.\n <span\n key={i}\n className=\"ci-weather__block\"\n data-state={b.state}\n title={`${formatRelativeTime(b.bucketStart)} · ${BLOCK_WORD[b.state]}`}\n />\n ))}\n </div>\n {/* Visible count parity with the aria-label (which already announces it,\n so this is hidden from SR to avoid a double read). */}\n <span className=\"ci-weather__readout\" aria-hidden=\"true\">\n {failing} failing · {healthy} healthy\n </span>\n </>\n )\n}\n","import { memo } from 'react'\nimport type { RepositorySnapshot } from '../api/types'\nimport { isNoCi } from '../lib/isNoCi'\nimport { SignalChip } from './SignalChip'\nimport { RepoMetricsLine } from './RepoMetricsLine'\nimport { PullRequestList } from './PullRequestList'\nimport { JobLaneRow } from './JobLaneRow'\nimport { RepoActivityIndicator } from './RepoActivityIndicator'\n\n// Memoised so a poll tick that returns the same data (React Query preserves the\n// repository reference via structural sharing) doesn't re-render every board.\n// onToggle takes the repo name so the parent can pass one stable callback rather\n// than a fresh per-repo closure that would defeat the memo.\nexport const RepoBoard = memo(function RepoBoard({\n repository, collapsed, onToggle,\n}: {\n repository: RepositorySnapshot\n collapsed: boolean\n onToggle: (name: string) => void\n}) {\n const pullRequests = repository.pullRequests ?? []\n const noCi = isNoCi(repository)\n return (\n <section className={`repo-board${collapsed ? ' repo-board--collapsed' : ''}${noCi ? ' repo-board--no-ci' : ''}`}>\n <header>\n <button type=\"button\" className=\"repo-board__toggle\" onClick={() => onToggle(repository.name)} aria-expanded={!collapsed}>\n <span className=\"repo-board__chev\" aria-hidden=\"true\">▸</span>\n {repository.name}\n </button>\n {noCi && <span className=\"repo-board__noci-tag\">No CI</span>}\n <RepoMetricsLine metrics={repository.metrics} />\n <RepoActivityIndicator repository={repository} />\n <a className=\"repo-board__gh-link\" href={repository.htmlUrl} target=\"_blank\" rel=\"noopener noreferrer\" aria-label={`Open ${repository.name} on GitHub`}>\n GitHub ↗\n </a>\n </header>\n {!collapsed && (\n <>\n {repository.workflows.length === 0 ? (\n <div className=\"repo-board__empty\">no workflows</div>\n ) : (\n <div className=\"repo-workflows\">\n <span className=\"repo-workflows__label\">Workflows · {repository.workflows.length}</span>\n <div className=\"repo-top-signals\">\n {repository.workflows.map(wf => (\n <SignalChip key={wf.file} workflow={wf} />\n ))}\n </div>\n </div>\n )}\n <JobLaneRow kind=\"deploys\" glyph=\"▲\" label=\"Deploys\" signals={repository.deploys ?? []} />\n <JobLaneRow kind=\"packages\" glyph=\"▣\" label=\"Packages\" signals={repository.packages ?? []} />\n <PullRequestList pullRequests={pullRequests} />\n </>\n )}\n </section>\n )\n})\n","import { memo } from 'react'\nimport type { WorkflowSnapshot } from '../api/types'\nimport { formatRelativeTime } from '../lib/relativeTime'\nimport { stateLabel } from '../lib/stateLabel'\n\nfunction meta(wf: WorkflowSnapshot): string {\n // Unknown carries no trustworthy run time, so say why rather than show a\n // misleading age; known states show how long ago the run last updated.\n if (wf.state === 'unknown') return wf.lastRun ? 'no status' : 'no runs'\n return formatRelativeTime(wf.lastRun!.updatedAt)\n}\n\n// Memoised: on a no-change poll tick React Query preserves the workflow object\n// reference (structural sharing), so the chip skips re-rendering.\nexport const SignalChip = memo(function SignalChip({ workflow }: { workflow: WorkflowSnapshot }) {\n const url = workflow.lastRun?.htmlUrl\n const linkable = Boolean(url)\n const className = `chip chip--${workflow.state}${linkable ? '' : ' chip--static'}`\n const body = (\n <>\n <span className=\"chip__dot\" aria-hidden=\"true\" />\n <span className=\"chip__label\">{workflow.name}</span>\n {/* State in words for SR / colour-blind users — the dot is colour+shape only. */}\n <span className=\"sr-only\">{stateLabel(workflow.state)}</span>\n <span className=\"chip__meta\">{meta(workflow)}</span>\n </>\n )\n // Open the run in a new tab so the always-on board never navigates away.\n return linkable ? (\n <a className={className} href={url} title={stateLabel(workflow.state)} target=\"_blank\" rel=\"noopener noreferrer\">{body}</a>\n ) : (\n <span className={className} title={stateLabel(workflow.state)}>{body}</span>\n )\n})\n","import type { SignalState } from '../api/types'\n\n// The spoken form of a workflow/job state. Status on the board is carried\n// visually by dot colour + dot shape; this is the text equivalent rendered\n// into each chip's accessible name (an .sr-only span) so screen-reader and\n// colour-blind users get the state in words — not a colour-only signal.\n// (WCAG 2.2 SC 1.4.1 / 1.1.1.)\nconst STATE_LABELS: Record<SignalState, string> = {\n success: 'passing',\n failure: 'failing',\n running: 'running',\n unknown: 'status unknown',\n}\n\nexport function stateLabel(state: SignalState): string {\n return STATE_LABELS[state]\n}\n","import type { RepoMetrics } from '../api/types'\nimport { formatCompactNumber } from '../lib/formatCompactNumber'\n\nexport function RepoMetricsLine({ metrics }: { metrics: RepoMetrics | null }) {\n if (!metrics || metrics.nloc === 0) return null\n return (\n <dl className=\"repo-metrics\" aria-label=\"code metrics\">\n <div title=\"non-comment lines of code\"><dt>NLOC</dt><dd>{formatCompactNumber(metrics.nloc)}</dd></div>\n <div title=\"average cyclomatic complexity (branch paths per function)\"><dt>avg CCN</dt><dd>{metrics.avgComplexity.toFixed(1)}</dd></div>\n <div title=\"number of functions\"><dt>functions</dt><dd>{formatCompactNumber(metrics.functionCount)}</dd></div>\n {metrics.highComplexityCount > 0 && (\n <div className=\"repo-metrics__complex\" title=\"functions over CCN 15 — refactor candidates\">\n <dt>complex</dt><dd>{metrics.highComplexityCount}</dd>\n </div>\n )}\n </dl>\n )\n}\n","import type { PullRequest } from '../api/types'\nimport { formatRelativeTime } from '../lib/relativeTime'\n\nexport function PullRequestList({ pullRequests }: { pullRequests: PullRequest[] }) {\n if (pullRequests.length === 0) return null\n return (\n <div className=\"repo-prs\">\n <span className=\"repo-prs__count\">\n {pullRequests.length} open PR{pullRequests.length === 1 ? '' : 's'}\n </span>\n <ul>\n {pullRequests.map(pr => (\n <li key={pr.number} className={pr.isDraft ? 'repo-prs__item repo-prs__item--draft' : 'repo-prs__item'}>\n <a href={pr.htmlUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n <span className=\"repo-prs__num\">#{pr.number}</span>\n <span className=\"repo-prs__title\">{pr.title}</span>\n </a>\n <span className=\"repo-prs__meta\">\n {pr.author} · {formatRelativeTime(pr.createdAt)}{pr.isDraft ? ' · draft' : ''}\n </span>\n </li>\n ))}\n </ul>\n </div>\n )\n}\n","import { memo } from 'react'\nimport type { JobSignal } from '../api/types'\nimport { formatRelativeTime } from '../lib/relativeTime'\nimport { dedupeJobLabel } from '../lib/dedupeJobLabel'\nimport { stateLabel } from '../lib/stateLabel'\n// Memoised: the signals array reference is preserved across no-change poll ticks\n// (React Query structural sharing), so the lane skips re-rendering.\nexport const JobLaneRow = memo(function JobLaneRow({\n kind, glyph, label, signals,\n}: {\n kind: 'deploys' | 'packages'\n glyph: string\n label: string\n signals: JobSignal[]\n}) {\n if (signals.length === 0) return null\n return (\n <div className={`repo-joblane repo-joblane--${kind}`}>\n <span className=\"repo-joblane__label\">{glyph} {label}</span>\n <div className=\"repo-joblane__chips\">\n {signals.map((s, i) => (\n <a\n key={`${s.workflow}/${s.name}/${i}`}\n className={`chip chip--${s.state} chip--joblane`}\n href={s.htmlUrl}\n title={`${s.workflow} · ${s.state}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <span className=\"chip__dot\" aria-hidden=\"true\" />\n <span className=\"chip__label\">{dedupeJobLabel(s.name)}</span>\n {/* State in words for SR / colour-blind users — the dot is colour+shape only. */}\n <span className=\"sr-only\">{stateLabel(s.state)}</span>\n <span className=\"chip__meta\">{formatRelativeTime(s.updatedAt)}</span>\n </a>\n ))}\n </div>\n </div>\n )\n})\n","// GitHub renders reusable-workflow / matrix job names as \"caller / called\", which\n// for a deploy or publish job is often the same segment twice\n// (\"Deploy (x) / Deploy (x)\"). Collapse consecutive identical segments so the chip\n// reads once; genuinely different segments are preserved.\nexport function dedupeJobLabel(name: string): string {\n const parts = name.split(' / ')\n const deduped = parts.filter((part, i) => i === 0 || part !== parts[i - 1])\n return deduped.join(' / ')\n}\n","import type { SignalState } from '../api/types'\n\nexport type Activity = 'failure' | 'running' | 'success' | 'none'\n\n// Precedence for a repo's worst signal across a set of states: a failure dominates,\n// then anything running, then a clean success; an empty/all-unknown set reads as\n// \"none\" (hollow indicator).\nexport function worstState(states: SignalState[]): Activity {\n if (states.includes('failure')) return 'failure'\n if (states.includes('running')) return 'running'\n if (states.includes('success')) return 'success'\n return 'none'\n}\n","import type { RepositorySnapshot } from '../api/types'\nimport { worstState } from '../lib/worstState'\n\nexport function RepoActivityIndicator({ repository }: { repository: RepositorySnapshot }) {\n const prCount = (repository.pullRequests ?? []).length\n const ci = worstState((repository.workflows ?? []).map(w => w.state))\n const cd = worstState([...(repository.deploys ?? []), ...(repository.packages ?? [])].map(s => s.state))\n return (\n <span className=\"repo-activity\">\n {prCount > 0 && <span className=\"repo-activity__pr\">{prCount} PR</span>}\n <span className=\"repo-activity__sig\">CI<span className=\"repo-activity__dot\" data-activity={ci} aria-label={`CI ${ci}`} /></span>\n <span className=\"repo-activity__sig\">CD<span className=\"repo-activity__dot\" data-activity={cd} aria-label={`CD ${cd}`} /></span>\n </span>\n )\n}\n","export function RepoSection({ label, count, collapsed, onToggle }: {\n label: string\n count: number\n collapsed: boolean\n onToggle: () => void\n}) {\n return (\n <button\n type=\"button\"\n className=\"repo-section\"\n aria-expanded={!collapsed}\n onClick={onToggle}\n >\n <span className=\"repo-section__chevron\" aria-hidden=\"true\">\n {collapsed ? '▸' : '▾'}\n </span>\n <span className=\"repo-section__label\">{label}</span>\n <span className=\"repo-section__count\" aria-hidden=\"true\">· {count}</span>\n </button>\n )\n}\n","// Defines the abbreviations on each repo's Lizard metrics line. Rendered once at\n// the foot of the board; the same wording backs the per-metric hover tooltips.\nconst ITEMS: ReadonlyArray<readonly [string, string]> = [\n ['NLOC', 'non-comment lines of code'],\n ['avg CCN', 'average cyclomatic complexity (branch paths per function)'],\n ['functions', 'number of functions'],\n ['complex', 'functions over CCN 15 — refactor candidates'],\n]\n\nexport function MetricsLegend() {\n return (\n <footer className=\"metrics-legend\" aria-label=\"Lizard metrics legend\">\n <span className=\"metrics-legend__title\">Lizard metrics</span>\n {ITEMS.map(([term, meaning]) => (\n <span key={term} className=\"metrics-legend__item\">\n <b>{term}</b> {meaning}\n </span>\n ))}\n </footer>\n )\n}\n","import type { SignalState } from '../api/types'\n\n// Decodes the board's primary visual language — the status dot colours and\n// shapes — for a first-time visitor. Sits beside the Lizard MetricsLegend at\n// the foot of the board. Swatches mirror the live dot treatment: colour plus a\n// non-colour shape cue (square = failing) so the key reads in grayscale too.\nconst STATUS_ITEMS: ReadonlyArray<readonly [SignalState, string]> = [\n ['success', 'passing'],\n ['failure', 'failing'],\n ['running', 'running'],\n ['unknown', 'unknown / no runs'],\n]\n\nexport function StatusLegend() {\n return (\n <footer className=\"status-legend\" aria-label=\"Status colour key\">\n <span className=\"status-legend__title\">Status</span>\n {STATUS_ITEMS.map(([state, label]) => (\n <span key={state} className=\"status-legend__item\">\n <span className=\"status-legend__dot\" data-state={state} aria-hidden=\"true\" />\n {label}\n </span>\n ))}\n <span className=\"status-legend__item\">\n <span className=\"status-legend__noci\" aria-hidden=\"true\" />\n No-CI repo\n </span>\n <span className=\"status-legend__gloss\">\n <b>CI</b> workflow runs · <b>CD</b> deploys & packages\n </span>\n </footer>\n )\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type { OpenPr } from '../lib/flattenOpenPrs'\nimport { formatRelativeTime } from '../lib/relativeTime'\nimport { prAgeTone } from '../lib/prAgeTone'\nexport function PullRequestStepper({ prs, onClose }: { prs: OpenPr[]; onClose: () => void }) {\n const [i, setI] = useState(0)\n const dialogRef = useRef<HTMLDialogElement>(null)\n // Background CI polling can shrink `prs` while the stepper is open (e.g. the\n // PR being viewed just merged). Clamp against the live length so `pr` never\n // reads undefined — an undefined read would return null and unmount the\n // <dialog> WITHOUT firing onClose, wedging the parent's stepperOpen at true\n // and permanently breaking the Open-PRs button for the session.\n const safeIndex = Math.min(i, Math.max(0, prs.length - 1))\n const pr = prs[safeIndex]\n\n useEffect(() => {\n const previouslyFocused = document.activeElement as HTMLElement | null\n const dlg = dialogRef.current\n dlg?.showModal()\n dlg?.focus()\n return () => previouslyFocused?.focus?.()\n }, [])\n\n // If the list drains entirely, there is nothing to step through — close via\n // the callback so the parent resets stepperOpen, rather than silently\n // rendering null and leaving a dangling open flag.\n useEffect(() => {\n if (prs.length === 0) onClose()\n }, [prs.length, onClose])\n\n if (!pr) return null\n\n return (\n <dialog\n ref={dialogRef}\n className=\"pr-modal\"\n aria-label=\"Open pull requests\"\n tabIndex={-1}\n onCancel={e => { e.preventDefault(); onClose() }}\n onClick={e => { if (e.target === e.currentTarget) onClose() }}\n // Left/Right arrow paging, scoped to the dialog. Escape closes via the\n // native cancel event above.\n onKeyDown={e => {\n if (e.key === 'ArrowRight') setI(Math.min(safeIndex + 1, prs.length - 1))\n if (e.key === 'ArrowLeft') setI(Math.max(safeIndex - 1, 0))\n }}\n >\n <div className=\"pr-modal__top\">\n <span className=\"pr-modal__title\">Open pull requests</span>\n <span className=\"pr-modal__counter\">{safeIndex + 1} / {prs.length}</span>\n <button type=\"button\" className=\"pr-modal__x\" onClick={onClose} aria-label=\"Close\">✕</button>\n </div>\n <div className={`pr-card pr-card--${prAgeTone(pr.createdAt)}${pr.isDraft ? ' pr-card--draft' : ''}`}>\n <div className=\"pr-card__head\">\n <span className=\"pr-card__repo\">{pr.repo}</span>\n <span className=\"pr-card__num\">#{pr.number}</span>\n <span className=\"pr-card__meta\">{formatRelativeTime(pr.createdAt)} · {pr.isDraft ? 'draft' : 'ready'}</span>\n </div>\n <div className=\"pr-card__title\">{pr.title}</div>\n <div className=\"pr-card__foot\">\n <span className=\"pr-card__author\">@{pr.author}</span>\n <a className=\"pr-card__gh\" href={pr.htmlUrl} target=\"_blank\" rel=\"noopener noreferrer\">Open on GitHub ↗</a>\n </div>\n </div>\n {/* One PR needs no nav — the '1 / 1' counter already says so; two disabled\n buttons would just be dead chrome. */}\n {prs.length > 1 && (\n <div className=\"pr-modal__nav\">\n <button type=\"button\" onClick={() => setI(Math.max(safeIndex - 1, 0))} disabled={safeIndex === 0}>‹ Prev</button>\n <button type=\"button\" onClick={() => setI(Math.min(safeIndex + 1, prs.length - 1))} disabled={safeIndex === prs.length - 1}>Next ›</button>\n </div>\n )}\n </dialog>\n )\n}\n","// Age-based emphasis for an open PR card edge: stale PRs warm up.\nexport type AgeTone = 'red' | 'amber' | 'quiet'\n\nexport function prAgeTone(createdAtIso: string, now: number = Date.now()): AgeTone {\n const days = (now - new Date(createdAtIso).getTime()) / 86_400_000\n if (days > 14) return 'red'\n if (days > 7) return 'amber'\n return 'quiet'\n}\n","import type { PullRequest, RepositorySnapshot } from '../api/types'\n\nexport type OpenPr = PullRequest & { repo: string }\n\n// Aggregate every repo's open PRs into one list tagged with its repo, oldest-first\n// (the most stale surface first for triage).\nexport function flattenOpenPrs(repositories: RepositorySnapshot[]): OpenPr[] {\n return repositories\n .flatMap(r => (r.pullRequests ?? []).map(pr => ({ ...pr, repo: r.name })))\n .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())\n}\n","// Neutral footer shipped as the CiBoard default. No FixPortal branding or\n// personal attribution -- reusers pass their own node via CiBoard's footerSlot.\nexport function DefaultFooter() {\n return (\n <footer className=\"site-footer\">\n <div className=\"site-footer__band\" aria-hidden=\"true\">\n <span className=\"site-footer__tagline\">Continuous-integration overview</span>\n </div>\n </footer>\n )\n}\n","import type { ReactNode } from 'react'\nimport { CiAdminProvider } from './CiAdminContext'\nimport { CiConfigProvider, DEFAULT_CI_API_BASE } from './CiConfigContext'\nimport { CiBoardContent } from './pages/CiBoardContent'\nimport { DefaultFooter } from './DefaultFooter'\n\nexport interface CiBoardProps {\n /** Whether the viewer is an admin: sees private repos + actionable PR links. Host-computed. */\n adminSignal: boolean\n /** Origin of the CI backend snapshot API (no trailing slash). Defaults to the public CI host. */\n apiBase?: string\n /** Brand mark for the header. Defaults to a plain text wordmark. */\n logo?: ReactNode\n /** Footer node. Defaults to a generic, brand-free footer. */\n footerSlot?: ReactNode\n}\n\n// The board is style-free at the component level: consumers import the\n// stylesheets explicitly -- `@fix-portal/ci-frontend/board.css` (always) and\n// optionally `@fix-portal/ci-frontend/tokens.css` if they have no design system\n// of their own. This keeps CSS out of the JS bundle and lets a host with its\n// own tokens (e.g. the simulator) skip the vendored set.\nexport function CiBoard({ adminSignal, apiBase = DEFAULT_CI_API_BASE, logo, footerSlot }: CiBoardProps) {\n return (\n <CiConfigProvider value={{ apiBase }}>\n <div className=\"ci-page\">\n <div className=\"ci-embed\">\n <header className=\"ci-embed__header\">\n <span className=\"ci-embed__lockup\">\n {logo ?? <span className=\"ci-embed__wordmark-text\">CI Dashboard</span>}\n <span className=\"ci-embed__descriptor\">\n CI Dashboard {adminSignal ? '[Admin]' : '[Guest]'}\n </span>\n </span>\n </header>\n <CiAdminProvider value={adminSignal}>\n <CiBoardContent />\n </CiAdminProvider>\n </div>\n {footerSlot ?? <DefaultFooter />}\n </div>\n </CiConfigProvider>\n )\n}\n"],"mappings":"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","names":["createContext","use","useState","useCallback","useState","KEY","load","useState","Fragment","jsx","jsxs","useState","memo","Fragment","jsx","jsxs","SignalChip","jsx","jsxs","jsx","jsxs","memo","jsx","jsxs","memo","JobLaneRow","jsx","jsxs","Fragment","jsx","jsxs","memo","RepoBoard","jsx","jsxs","jsx","jsxs","jsx","jsxs","useEffect","useRef","useState","jsx","jsxs","useState","useRef","useEffect","Fragment","jsx","jsxs","useState","jsx","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/CiBoard.tsx","../src/CiAdminContext.tsx","../src/CiConfigContext.tsx","../src/pages/CiBoardContent.tsx","../src/hooks/useDashboardSnapshot.ts","../src/api/getDashboardSnapshot.ts","../src/hooks/useCollapseState.ts","../src/hooks/useHideNoCi.ts","../src/lib/isNoCi.ts","../src/lib/computeSummary.ts","../src/components/SummaryStrip.tsx","../src/lib/formatCompactNumber.ts","../src/lib/relativeTime.ts","../src/components/CiWeatherBar.tsx","../src/lib/isAllowedHref.ts","../src/components/RepoBoard.tsx","../src/components/SignalChip.tsx","../src/lib/stateLabel.ts","../src/components/RepoMetricsLine.tsx","../src/components/PullRequestList.tsx","../src/components/JobLaneRow.tsx","../src/lib/dedupeJobLabel.ts","../src/lib/worstState.ts","../src/components/RepoActivityIndicator.tsx","../src/components/RepoSection.tsx","../src/components/MetricsLegend.tsx","../src/components/StatusLegend.tsx","../src/components/PullRequestStepper.tsx","../src/lib/prAgeTone.ts","../src/lib/flattenOpenPrs.ts","../src/DefaultFooter.tsx"],"sourcesContent":["import { useState, useContext } from 'react'\nimport type { ReactNode } from 'react'\nimport { QueryClient, QueryClientProvider, QueryClientContext } from '@tanstack/react-query'\nimport { CiAdminProvider } from './CiAdminContext'\nimport { CiConfigProvider, DEFAULT_CI_API_BASE } from './CiConfigContext'\nimport { CiBoardContent } from './pages/CiBoardContent'\nimport { DefaultFooter } from './DefaultFooter'\n\nexport interface CiBoardProps {\n /** Whether the viewer is an admin: sees private repos + actionable PR links. Host-computed. */\n adminSignal: boolean\n /** Origin of the CI backend snapshot API (no trailing slash). Defaults to '' (relative URLs — requires a same-origin /api/ proxy). Pass 'https://ci.fixportal.org' to reach the public FixPortal backend. */\n apiBase?: string\n /** Full URL the board fetches when the viewer is admin. The host's backend should proxy this to the CI backend's /api/dashboard/snapshot/admin endpoint, adding the X-Admin-Key header server-side so the shared secret never reaches the browser. When unset, admin viewers see the public (private-repo-stripped) snapshot. */\n adminSnapshotUrl?: string\n /** Brand mark for the header. Defaults to a plain text wordmark. */\n logo?: ReactNode\n /** Footer node. Defaults to a generic, brand-free footer. */\n footerSlot?: ReactNode\n}\n\nfunction QueryClientSafeProvider({ children }: { children: ReactNode }) {\n const existingClient = useContext(QueryClientContext)\n const [localClient] = useState(() => {\n if (!existingClient) {\n return new QueryClient({\n defaultOptions: {\n queries: {\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n })\n }\n return null\n })\n\n if (existingClient) {\n return <>{children}</>\n }\n\n return (\n <QueryClientProvider client={localClient!}>\n {children}\n </QueryClientProvider>\n )\n}\n\n// The board is style-free at the component level: consumers import the\n// stylesheets explicitly -- `@fix-portal/ci-frontend/board.css` (always) and\n// optionally `@fix-portal/ci-frontend/tokens.css` if they have no design system\n// of their own. This keeps CSS out of the JS bundle and lets a host with its\n// own tokens (e.g. the simulator) skip the vendored set.\nexport function CiBoard({ adminSignal, apiBase = DEFAULT_CI_API_BASE, adminSnapshotUrl, logo, footerSlot }: CiBoardProps) {\n return (\n <CiConfigProvider value={{ apiBase, adminSnapshotUrl }}>\n <QueryClientSafeProvider>\n <div className=\"ci-page\">\n <div className=\"ci-embed\">\n <header className=\"ci-embed__header\">\n <span className=\"ci-embed__lockup\">\n {logo ?? <span className=\"ci-embed__wordmark-text\">CI Dashboard</span>}\n <span className=\"ci-embed__descriptor\">\n CI Dashboard {adminSignal ? '[Admin]' : '[Guest]'}\n </span>\n </span>\n </header>\n <CiAdminProvider value={adminSignal}>\n <CiBoardContent />\n </CiAdminProvider>\n </div>\n {footerSlot ?? <DefaultFooter />}\n </div>\n </QueryClientSafeProvider>\n </CiConfigProvider>\n )\n}\n","import { createContext, use } from 'react'\n\n// Whether the current viewer is the signed-in platform admin. Drives whether PR\n// rows render as actionable GitHub links or as plain, non-interactive text.\n// Defaults to false (anonymous → read-only).\nconst CiAdminContext = createContext(false)\nCiAdminContext.displayName = 'CiAdminContext'\n\nexport const CiAdminProvider = CiAdminContext.Provider\n\nexport function useCiAdmin(): boolean {\n return use(CiAdminContext)\n}\n","import { createContext, use } from 'react'\n\n// Runtime config for the CI board. apiBase is the origin of the CI backend\n// snapshot API (no trailing slash). Empty string means relative URLs — works\n// with any nginx proxy (Docker Compose or www.fixportal.org/ci).\n// adminSnapshotUrl: when set, the board fetches this URL directly (instead of\n// apiBase + /api/dashboard/snapshot) when the viewer is admin. Lets a host\n// proxy the admin endpoint server-side without exposing the shared key to the\n// browser — the host passes a same-origin relative URL here, and its backend\n// adds the X-Admin-Key before forwarding to the CI backend.\nexport interface CiConfig {\n apiBase: string\n adminSnapshotUrl?: string\n}\n\nexport const DEFAULT_CI_API_BASE = ''\n\nconst CiConfigContext = createContext<CiConfig>({ apiBase: DEFAULT_CI_API_BASE })\nCiConfigContext.displayName = 'CiConfigContext'\n\nexport const CiConfigProvider = CiConfigContext.Provider\n\nexport function useCiConfig(): CiConfig {\n return use(CiConfigContext)\n}\n","import { useState, useEffect } from 'react'\nimport { useDashboardSnapshot } from '../hooks/useDashboardSnapshot'\nimport { useCollapseState } from '../hooks/useCollapseState'\nimport { useHideNoCi } from '../hooks/useHideNoCi'\nimport { useCiAdmin } from '../CiAdminContext'\nimport { isNoCi } from '../lib/isNoCi'\nimport { computeSummary } from '../lib/computeSummary'\nimport { SummaryStrip } from '../components/SummaryStrip'\nimport { RepoBoard } from '../components/RepoBoard'\nimport { RepoSection } from '../components/RepoSection'\nimport { MetricsLegend } from '../components/MetricsLegend'\nimport { StatusLegend } from '../components/StatusLegend'\nimport { PullRequestStepper } from '../components/PullRequestStepper'\nimport { flattenOpenPrs } from '../lib/flattenOpenPrs'\nimport { formatRelativeTime } from '../lib/relativeTime'\n\nexport function CiBoardContent() {\n const snapshot = useDashboardSnapshot()\n const collapse = useCollapseState()\n const hideNoCi = useHideNoCi()\n const isAdmin = useCiAdmin()\n const [stepperOpen, setStepperOpen] = useState(false)\n\n useEffect(() => {\n if (openPrs.length === 0) {\n setStepperOpen(false)\n }\n }, [openPrs.length])\n\n if (snapshot.isPending) {\n return (\n <main className=\"dashboard-page\">\n <div className=\"state-msg\">Loading dashboard…</div>\n </main>\n )\n }\n\n if (snapshot.isError) {\n return (\n <main className=\"dashboard-page\">\n <div className=\"state-msg state-msg--error\">Dashboard unavailable.</div>\n </main>\n )\n }\n\n // 204 -> null: backend is up but has not produced a snapshot yet.\n if (!snapshot.data) {\n return (\n <main className=\"dashboard-page\">\n <div className=\"state-msg\">Waiting for the first refresh…</div>\n </main>\n )\n }\n\n const { refreshedAt, repositories: allRepositories, lastMergedPr: rawLastMerged } = snapshot.data\n // Non-admin viewers see only public repos; admin sees all. Filtering here drives\n // everything downstream — summary counts, stepper PRs, and next-in-queue all\n // reflect exactly the repos displayed on screen.\n const repositories = isAdmin ? allRepositories : allRepositories.filter(r => !r.private)\n const summary = (isAdmin && !hideNoCi.hidden) ? snapshot.data.summary : computeSummary(visibleRepos)\n // Only surface lastMergedPr when its repo is in the visible set; a private-repo\n // merge is invisible to the public viewer and the link would 404 for them.\n const lastMergedPr = rawLastMerged && visibleRepos.some(r => r.name === rawLastMerged.repo)\n ? rawLastMerged\n : null\n // Compute the names list and the all-collapsed flag once — they were rebuilt\n // and re-traversed twice per render (the onClick and the button label).\n const noCiCount = repositories.filter(isNoCi).length\n const visibleRepos = hideNoCi.hidden ? repositories.filter(r => !isNoCi(r)) : repositories\n const repoNames = visibleRepos.map(r => r.name)\n const hiddenCount = repositories.length - visibleRepos.length\n const publicRepos = visibleRepos.filter(r => !r.private)\n const privateRepos = visibleRepos.filter(r => r.private)\n const showGroups = publicRepos.length > 0 && privateRepos.length > 0\n const KEY_PUBLIC = 'section:public'\n const KEY_PRIVATE = 'section:private'\n const sectionKeys = showGroups ? [KEY_PUBLIC, KEY_PRIVATE] : []\n const allCollapsed = collapse.allCollapsed([...repoNames, ...sectionKeys])\n // The stepper opens at the head of this oldest-first list, so its first entry\n // is \"next in queue\" — derive it from visibleRepos (the Hide No-CI filter\n // applied) so the card and stepper never advertise a PR from a repo the board\n // is currently hiding.\n const openPrs = flattenOpenPrs(visibleRepos)\n const nextPr = openPrs[0] ?? null\n\n let repoListContent\n if (visibleRepos.length === 0 && hideNoCi.hidden) {\n repoListContent = <div className=\"state-msg\">All repositories are No-CI — hidden.</div>\n } else if (showGroups) {\n repoListContent = (\n <>\n <RepoSection\n label=\"Public\"\n count={publicRepos.length}\n collapsed={collapse.isCollapsed(KEY_PUBLIC)}\n onToggle={() => collapse.toggle(KEY_PUBLIC)}\n />\n {!collapse.isCollapsed(KEY_PUBLIC) &&\n publicRepos.map(repository => (\n <RepoBoard\n key={repository.name}\n repository={repository}\n collapsed={collapse.isCollapsed(repository.name)}\n onToggle={collapse.toggle}\n />\n ))\n }\n <RepoSection\n label=\"Private\"\n count={privateRepos.length}\n collapsed={collapse.isCollapsed(KEY_PRIVATE)}\n onToggle={() => collapse.toggle(KEY_PRIVATE)}\n />\n {!collapse.isCollapsed(KEY_PRIVATE) &&\n privateRepos.map(repository => (\n <RepoBoard\n key={repository.name}\n repository={repository}\n collapsed={collapse.isCollapsed(repository.name)}\n onToggle={collapse.toggle}\n />\n ))\n }\n </>\n )\n } else {\n repoListContent = visibleRepos.map(repository => (\n <RepoBoard\n key={repository.name}\n repository={repository}\n collapsed={collapse.isCollapsed(repository.name)}\n onToggle={collapse.toggle}\n />\n ))\n }\n\n return (\n <main className=\"dashboard-page\">\n <div className=\"dashboard__toolbar\">\n <span className=\"dashboard__scope\">{snapshot.data.org} · {isAdmin ? 'all repositories' : 'public repositories'}</span>\n <span className=\"dashboard__toolbar-right\">\n {noCiCount > 0 && (\n <button\n type=\"button\"\n className={`dashboard__hide-noci${hideNoCi.hidden ? ' dashboard__hide-noci--on' : ''}`}\n onClick={hideNoCi.toggle}\n aria-pressed={hideNoCi.hidden}\n >\n {hideNoCi.hidden ? `Show No-CI · ${hiddenCount} hidden` : 'Hide No-CI'}\n </button>\n )}\n <button\n type=\"button\"\n className=\"dashboard__collapse-all\"\n onClick={() => (allCollapsed ? collapse.expandAll() : collapse.collapseAll([...repoNames, ...sectionKeys]))}\n >\n {allCollapsed ? '⊞ Expand all' : '⊟ Collapse all'}\n </button>\n <span className=\"dashboard__refreshed\">\n <span className=\"live-dot\" aria-hidden=\"true\" />\n updated {formatRelativeTime(refreshedAt)}\n </span>\n </span>\n </div>\n <SummaryStrip summary={summary} onOpenPrs={isAdmin ? () => setStepperOpen(true) : undefined} lastMerged={lastMergedPr} nextPr={nextPr} ciTrend={snapshot.data.ciTrend ?? []} />\n <div className=\"repo-list\">\n {repoListContent}\n </div>\n <StatusLegend />\n <MetricsLegend />\n {stepperOpen && openPrs.length > 0 && (\n <PullRequestStepper prs={openPrs} onClose={() => setStepperOpen(false)} />\n )}\n </main>\n )\n}\n","import { useQuery } from '@tanstack/react-query'\nimport { getDashboardSnapshot } from '../api/getDashboardSnapshot'\nimport { useCiConfig } from '../CiConfigContext'\nimport { useCiAdmin } from '../CiAdminContext'\n\nexport function useDashboardSnapshot() {\n const { apiBase, adminSnapshotUrl } = useCiConfig()\n const isAdmin = useCiAdmin()\n // Admin viewers use the host-proxied admin URL so the shared key stays\n // server-side. Fall back to the public endpoint for non-admin or when no\n // admin URL was wired up by the host.\n const snapshotUrl = isAdmin && adminSnapshotUrl\n ? adminSnapshotUrl\n : `${apiBase.replace(/\\/$/, '')}/api/dashboard/snapshot`\n return useQuery({\n queryKey: ['dashboard-snapshot', snapshotUrl],\n queryFn: () => getDashboardSnapshot(snapshotUrl),\n refetchInterval: 60_000,\n // The 60s poll already drives freshness; without these, an incidental tab\n // focus refetches and re-renders the whole board between ticks. Set per-query\n // (not on the shared app QueryClient) so the host app is unaffected;\n // structural sharing then lets the memoised boards skip a no-change tick.\n staleTime: 30_000,\n refetchOnWindowFocus: false,\n })\n}\n","import type { DashboardSnapshot } from './types'\n\n// 204 No Content is the documented \"no snapshot yet\" state. Return null rather\n// than calling response.json() on an empty body. snapshotUrl is the resolved\n// URL to fetch — callers compute it from apiBase + /api/dashboard/snapshot or\n// from an adminSnapshotUrl override when the viewer is an admin.\nexport async function getDashboardSnapshot(snapshotUrl: string): Promise<DashboardSnapshot | null> {\n const response = await fetch(snapshotUrl)\n if (response.status === 204) return null\n if (!response.ok) throw new Error(`Dashboard snapshot failed: ${response.status}`)\n return response.json()\n}\n","import { useCallback, useEffect, useState } from 'react'\n\nconst KEY = 'ci-dashboard:collapsed'\n\nfunction load(): Set<string> {\n try {\n const raw = localStorage.getItem(KEY)\n return new Set(raw ? (JSON.parse(raw) as string[]) : [])\n } catch {\n return new Set()\n }\n}\n\nfunction save(set: Set<string>) {\n try {\n localStorage.setItem(KEY, JSON.stringify([...set]))\n } catch {\n // ignore (private mode / quota) — collapse state is best-effort\n }\n}\n\n// A repo absent from the set is expanded, so new repos default to expanded.\nexport function useCollapseState() {\n const [collapsed, setCollapsed] = useState<Set<string>>(load)\n\n useEffect(() => {\n save(collapsed)\n }, [collapsed])\n\n const mutate = useCallback((fn: (next: Set<string>) => void) => {\n setCollapsed(prev => {\n const next = new Set(prev)\n fn(next)\n return next\n })\n }, [])\n\n return {\n isCollapsed: useCallback((name: string) => collapsed.has(name), [collapsed]),\n allCollapsed: useCallback(\n (names: string[]) => names.length > 0 && names.every(n => collapsed.has(n)),\n [collapsed],\n ),\n toggle: useCallback((name: string) => mutate(s => (s.has(name) ? s.delete(name) : s.add(name))), [mutate]),\n collapseAll: useCallback((names: string[]) => mutate(s => names.forEach(n => s.add(n))), [mutate]),\n expandAll: useCallback(() => mutate(s => s.clear()), [mutate]),\n }\n}\n","import { useCallback, useEffect, useState } from 'react'\n\nconst KEY = 'ci-dashboard:hide-no-ci'\n\nfunction load(): boolean {\n try {\n return localStorage.getItem(KEY) === 'true'\n } catch {\n return false\n }\n}\n\n// A single persisted boolean: whether No-CI repos are hidden from the board.\n// Default false (shown). Mirrors useCollapseState's best-effort persistence.\nexport function useHideNoCi() {\n const [hidden, setHidden] = useState<boolean>(load)\n\n useEffect(() => {\n try {\n localStorage.setItem(KEY, String(hidden))\n } catch {\n // ignore (private mode / quota) — hide state is best-effort\n }\n }, [hidden])\n\n const toggle = useCallback(() => {\n setHidden(prev => !prev)\n }, [])\n\n return { hidden, toggle }\n}\n","import type { RepositorySnapshot } from '../api/types'\n\n// A repository is \"No CI\" when it has no workflows at all — the same definition\n// the backend uses for the `no-ci` summary count (repos with Workflows.Count == 0).\nexport function isNoCi(repository: RepositorySnapshot): boolean {\n return (repository.workflows?.length ?? 0) === 0\n}\n","import type { RepositorySnapshot, SummaryCount } from '../api/types'\nimport { isNoCi } from './isNoCi'\n\n// Keys that always appear in the summary, even at zero, so the strip structure\n// is stable regardless of the filtered repo set. Mirrors what the server sends\n// in snapshot.data.summary so admin and guest panels look the same.\nconst ALWAYS_VISIBLE_KEYS = new Set([\n 'repos', 'workflows', 'nloc', // inventory\n 'open-prs', // Review panel (carries next-in-queue / last-merged)\n 'running', 'failing', 'no-ci', // core CI status\n 'deploys-running', 'deploys-failing', // deploy lane (zero = nothing deploying / all clean)\n 'packages-failing', // package lane\n])\n\n// Recomputes summary counts from a filtered repo list. Mirrors the server-side\n// aggregation so the strip reflects exactly the repos being displayed.\nexport function computeSummary(repos: RepositorySnapshot[]): SummaryCount[] {\n const workflows = repos.flatMap(r => r.workflows)\n const deploys = repos.flatMap(r => r.deploys ?? [])\n const packages = repos.flatMap(r => r.packages ?? [])\n const openPrs = repos.flatMap(r => r.pullRequests ?? [])\n const nloc = repos.reduce((acc, r) => acc + (r.metrics?.nloc ?? 0), 0)\n\n const all: SummaryCount[] = [\n { key: 'repos', count: repos.length },\n { key: 'workflows', count: workflows.length },\n { key: 'failing', count: workflows.filter(w => w.state === 'failure').length },\n { key: 'running', count: workflows.filter(w => w.state === 'running').length },\n { key: 'no-ci', count: repos.filter(isNoCi).length },\n { key: 'open-prs', count: openPrs.length },\n { key: 'nloc', count: nloc },\n { key: 'deploys-failing', count: deploys.filter(d => d.state === 'failure').length },\n { key: 'deploys-running', count: deploys.filter(d => d.state === 'running').length },\n { key: 'packages-failing', count: packages.filter(p => p.state === 'failure').length },\n ]\n\n return all.filter(c => ALWAYS_VISIBLE_KEYS.has(c.key) || c.count > 0)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type { CiTrendBucket, MergedPr, SummaryCount } from '../api/types'\nimport type { OpenPr } from '../lib/flattenOpenPrs'\nimport { formatCompactNumber } from '../lib/formatCompactNumber'\nimport { formatRelativeTime } from '../lib/relativeTime'\nimport { CiWeatherBar } from './CiWeatherBar'\nimport { isAllowedHref } from '../lib/isAllowedHref'\nconst SUMMARY_LABELS: Record<string, string> = {\n repos: 'Repositories',\n workflows: 'Workflows',\n failing: 'Failing',\n running: 'Running',\n 'no-ci': 'No CI',\n 'open-prs': 'Open PRs',\n nloc: 'Lines of code',\n 'deploys-failing': 'Deploys failing',\n 'deploys-running': 'Deploys running',\n 'packages-failing': 'Packages failing',\n}\n\n// Three panels group the counts by what the operator is looking for: review work,\n// pipeline health, and inventory. Keys appear in this fixed order regardless of the\n// summary array's order; a panel with no present keys is hidden.\nconst PANELS: { title: string; keys: string[] }[] = [\n { title: 'Review', keys: ['open-prs'] },\n { title: 'CI status', keys: ['running', 'failing', 'packages-failing', 'deploys-running', 'deploys-failing', 'no-ci'] },\n { title: 'Inventory', keys: ['repos', 'workflows', 'nloc'] },\n]\n\nconst NEUTRAL_KEYS = new Set(['repos', 'workflows', 'nloc'])\nconst EMPTY_TREND: CiTrendBucket[] = []\n\nfunction labelFor(key: string, count: number) {\n // 'Open PRs' is the only count-driven noun on the strip; singularise it so a\n // single PR doesn't read as '1 Open PRs'.\n if (key === 'open-prs') return count === 1 ? 'Open PR' : 'Open PRs'\n return SUMMARY_LABELS[key] ?? key.replaceAll('-', ' ')\n}\n\nfunction formatCount(key: string, count: number) {\n return key === 'nloc' ? formatCompactNumber(count) : count\n}\n\n// A non-zero count is coloured to mirror its chip: failures red, running blue,\n// no-ci indigo, the rest amber. open-prs gets its own non-alarm \"review\" tone.\n// Zero / inventory quiet.\nfunction toneFor(key: string, count: number): string {\n if (count === 0 || NEUTRAL_KEYS.has(key)) return 'ok'\n if (key === 'open-prs') return 'review'\n if (key === 'failing' || key === 'deploys-failing' || key === 'packages-failing') return 'fail'\n if (key === 'running' || key === 'deploys-running') return 'run'\n if (key === 'no-ci') return 'no-ci'\n return 'alert'\n}\n\nexport function SummaryStrip({ summary, onOpenPrs, lastMerged, nextPr = null, ciTrend = EMPTY_TREND }: { summary: SummaryCount[]; onOpenPrs?: () => void; lastMerged: MergedPr | null; nextPr?: OpenPr | null; ciTrend?: CiTrendBucket[] }) {\n const byKey = new Map(summary.map(s => [s.key, s.count]))\n\n const [trendInfoOpen, setTrendInfoOpen] = useState(false)\n const trendLabelRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n if (!trendInfoOpen) return\n function handleOutside(e: MouseEvent) {\n if (!trendLabelRef.current?.contains(e.target as Node)) {\n setTrendInfoOpen(false)\n }\n }\n function handleEsc(e: KeyboardEvent) {\n if (e.key === 'Escape') setTrendInfoOpen(false)\n }\n document.addEventListener('mousedown', handleOutside)\n document.addEventListener('keydown', handleEsc)\n return () => {\n document.removeEventListener('mousedown', handleOutside)\n document.removeEventListener('keydown', handleEsc)\n }\n }, [trendInfoOpen])\n\n return (\n <section className=\"summary-panels\">\n {PANELS.map(panel => {\n const items: { key: string; count: number }[] = []\n for (const k of panel.keys) {\n const count = byKey.get(k)\n if (count !== undefined) items.push({ key: k, count })\n }\n if (items.length === 0) return null\n const isReview = panel.title === 'Review'\n const isCiStatus = panel.title === 'CI status'\n return (\n <div key={panel.title} className={`summary-panel${isReview ? ' summary-panel--review' : ''}${isCiStatus ? ' summary-panel--ci' : ''}`}>\n <span className=\"summary-panel__title\">{panel.title}</span>\n <div className=\"summary-panel__items\">\n {items.map(item => {\n const body = (\n <>\n <span className=\"summary__count\">{formatCount(item.key, item.count)}</span>\n <span className=\"summary__label\">{labelFor(item.key, item.count)}</span>\n </>\n )\n if (item.key === 'open-prs' && onOpenPrs) {\n return (\n <button\n key={item.key}\n type=\"button\"\n className=\"summary__item summary__item--btn\"\n data-key={item.key}\n data-tone={toneFor(item.key, item.count)}\n onClick={onOpenPrs}\n disabled={item.count === 0}\n >{body}</button>\n )\n }\n return (\n <div key={item.key} className=\"summary__item\" data-key={item.key} data-tone={toneFor(item.key, item.count)}>{body}</div>\n )\n })}\n </div>\n {isReview && nextPr && (\n <div className=\"summary-panel__next\">\n <span className=\"summary-panel__q-lab\">next in queue</span>\n <span className=\"summary-panel__q-body\">{nextPr.repo} #{nextPr.number}</span>\n <span className=\"summary-panel__q-title\">{nextPr.title}</span>\n </div>\n )}\n {isReview && lastMerged && (\n <a className=\"summary-panel__merged\" href={isAllowedHref(lastMerged.htmlUrl)} target=\"_blank\" rel=\"noopener noreferrer\">\n <span className=\"summary-panel__q-lab\">\n last merged <span className=\"summary-panel__q-age\">({formatRelativeTime(lastMerged.mergedAt)})</span>\n </span>\n <span className=\"summary-panel__q-body\">{lastMerged.repo} #{lastMerged.number}</span>\n <span className=\"summary-panel__q-title\">{lastMerged.title}</span>\n </a>\n )}\n {panel.title === 'CI status' && ciTrend.length > 0 && (\n <div className=\"summary-panel__trend\">\n <CiWeatherBar trend={ciTrend} />\n <div ref={trendLabelRef} className=\"summary-panel__trend-label-row\">\n <span className=\"summary-panel__trend-lab\">CI health · 24h</span>\n <button\n type=\"button\"\n className=\"ci-trend-info-btn\"\n aria-label=\"CI health information\"\n aria-expanded={trendInfoOpen}\n aria-controls={trendInfoOpen ? 'ci-trend-popover' : undefined}\n onClick={() => setTrendInfoOpen(o => !o)}\n >\n i\n </button>\n {trendInfoOpen && (\n <section\n id=\"ci-trend-popover\"\n aria-label=\"CI health explanation\"\n className=\"ci-trend-popover\"\n >\n <div className=\"ci-trend-popover__title\">CI health · 24h</div>\n <p>Each bar is a 1-hour bucket of CI activity across the whole org.</p>\n <p><span aria-hidden=\"true\" className=\"ci-trend-popover__swatch ci-trend-popover__swatch--fail\">■</span> Red — any run failed that hour (on any branch).</p>\n <p><span aria-hidden=\"true\" className=\"ci-trend-popover__swatch ci-trend-popover__swatch--pass\">■</span> Green — runs present, none failed. Quiet hours inherit the previous state.</p>\n <p>Oldest bar on the left, newest on the right.</p>\n <div className=\"ci-trend-popover__caret\" aria-hidden=\"true\" />\n </section>\n )}\n </div>\n </div>\n )}\n </div>\n )\n })}\n </section>\n )\n}\n","/** Compact integer formatting for large counts: 12345 -> \"12.3k\", 980 -> \"980\". */\nexport function formatCompactNumber(value: number): string {\n if (value < 1000) return String(value)\n if (value < 1_000_000) return `${(value / 1000).toFixed(1)}k`\n return `${(value / 1_000_000).toFixed(1)}M`\n}\n","/** Compact \"x ago\" formatting for observed-at timestamps in the status board. */\nexport function formatRelativeTime(iso: string): string {\n const then = new Date(iso).getTime()\n if (Number.isNaN(then)) return ''\n\n const minutes = Math.round((Date.now() - then) / 60_000)\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes}m ago`\n\n const hours = Math.round(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n\n const days = Math.round(hours / 24)\n return `${days}d ago`\n}\n","import type { CiTrendBucket, CiTrendState } from '../api/types'\nimport { formatRelativeTime } from '../lib/relativeTime'\n\n// A status-page \"weather\" strip: one block per hourly bucket, coloured by the\n// carry-forward worst-state from the backend. Pure presentational; colours come\n// from CSS via the data-state attribute.\nconst BLOCK_WORD: Record<CiTrendState, string> = {\n passing: 'healthy',\n failing: 'failing',\n noData: 'no data',\n}\n\nexport function CiWeatherBar({ trend }: { trend: CiTrendBucket[] }) {\n if (trend.length === 0) return null\n const failing = trend.filter(b => b.state === 'failing').length\n const healthy = trend.filter(b => b.state === 'passing').length\n const label = `CI health, last 24h: ${failing} failing, ${healthy} healthy`\n return (\n <>\n <div className=\"ci-weather\" role=\"img\" aria-label={label}>\n {trend.map((b, i) => (\n // Per-block hover reveals which hour a block is and its state — the\n // data was previously exposed only to screen readers via aria-label.\n <span\n key={i}\n className=\"ci-weather__block\"\n data-state={b.state}\n title={`${formatRelativeTime(b.bucketStart)} · ${BLOCK_WORD[b.state]}`}\n />\n ))}\n </div>\n {/* Visible count parity with the aria-label (which already announces it,\n so this is hidden from SR to avoid a double read). */}\n <span className=\"ci-weather__readout\" aria-hidden=\"true\">\n {failing} failing · {healthy} healthy\n </span>\n </>\n )\n}\n","export function isAllowedHref(url: string | undefined): string {\n if (!url) return '#'\n // Accept only http: and https: protocols to prevent javascript: or data: injection.\n // Using a try/catch with URL parser to be safe against malformed strings.\n try {\n const parsed = new URL(url)\n if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {\n return url\n }\n } catch {\n // If it's a relative URL or invalid, check if it starts with / (same-origin relative URL is safe)\n if (url.startsWith('/')) {\n return url\n }\n }\n return '#'\n}\n","import { memo } from 'react'\nimport type { RepositorySnapshot } from '../api/types'\nimport { isNoCi } from '../lib/isNoCi'\nimport { isAllowedHref } from '../lib/isAllowedHref'\nimport { SignalChip } from './SignalChip'\nimport { RepoMetricsLine } from './RepoMetricsLine'\nimport { PullRequestList } from './PullRequestList'\nimport { JobLaneRow } from './JobLaneRow'\nimport { RepoActivityIndicator } from './RepoActivityIndicator'\n\n// Memoised so a poll tick that returns the same data (React Query preserves the\n// repository reference via structural sharing) doesn't re-render every board.\n// onToggle takes the repo name so the parent can pass one stable callback rather\n// than a fresh per-repo closure that would defeat the memo.\nexport const RepoBoard = memo(function RepoBoard({\n repository, collapsed, onToggle,\n}: {\n repository: RepositorySnapshot\n collapsed: boolean\n onToggle: (name: string) => void\n}) {\n const pullRequests = repository.pullRequests ?? []\n const noCi = isNoCi(repository)\n return (\n <section className={`repo-board${collapsed ? ' repo-board--collapsed' : ''}${noCi ? ' repo-board--no-ci' : ''}`}>\n <header>\n <button type=\"button\" className=\"repo-board__toggle\" onClick={() => onToggle(repository.name)} aria-expanded={!collapsed}>\n <span className=\"repo-board__chev\" aria-hidden=\"true\">▸</span>\n {repository.name}\n </button>\n {noCi && <span className=\"repo-board__noci-tag\">No CI</span>}\n <RepoMetricsLine metrics={repository.metrics} />\n <RepoActivityIndicator repository={repository} />\n <a className=\"repo-board__gh-link\" href={isAllowedHref(repository.htmlUrl)} target=\"_blank\" rel=\"noopener noreferrer\" aria-label={`Open ${repository.name} on GitHub`}>\n GitHub ↗\n </a>\n </header>\n {!collapsed && (\n <>\n {repository.workflows.length === 0 ? (\n <div className=\"repo-board__empty\">no workflows</div>\n ) : (\n <div className=\"repo-workflows\">\n <span className=\"repo-workflows__label\">Workflows · {repository.workflows.length}</span>\n <div className=\"repo-top-signals\">\n {repository.workflows.map(wf => (\n <SignalChip key={wf.file} workflow={wf} />\n ))}\n </div>\n </div>\n )}\n <JobLaneRow kind=\"deploys\" glyph=\"▲\" label=\"Deploys\" signals={repository.deploys ?? []} />\n <JobLaneRow kind=\"packages\" glyph=\"▣\" label=\"Packages\" signals={repository.packages ?? []} />\n <PullRequestList pullRequests={pullRequests} />\n </>\n )}\n </section>\n )\n})\n","import { memo } from 'react'\nimport type { WorkflowSnapshot } from '../api/types'\nimport { formatRelativeTime } from '../lib/relativeTime'\nimport { stateLabel } from '../lib/stateLabel'\nimport { isAllowedHref } from '../lib/isAllowedHref'\n\nfunction meta(wf: WorkflowSnapshot): string {\n // Unknown carries no trustworthy run time, so say why rather than show a\n // misleading age; known states show how long ago the run last updated.\n if (wf.state === 'unknown') return wf.lastRun ? 'no status' : 'no runs'\n return wf.lastRun ? formatRelativeTime(wf.lastRun.updatedAt) : 'no runs'\n}\n\n// Memoised: on a no-change poll tick React Query preserves the workflow object\n// reference (structural sharing), so the chip skips re-rendering.\nexport const SignalChip = memo(function SignalChip({ workflow }: { workflow: WorkflowSnapshot }) {\n const url = workflow.lastRun?.htmlUrl\n const linkable = Boolean(url)\n const className = `chip chip--${workflow.state}${linkable ? '' : ' chip--static'}`\n const body = (\n <>\n <span className=\"chip__dot\" aria-hidden=\"true\" />\n <span className=\"chip__label\">{workflow.name}</span>\n {/* State in words for SR / colour-blind users — the dot is colour+shape only. */}\n <span className=\"sr-only\">{stateLabel(workflow.state)}</span>\n <span className=\"chip__meta\">{meta(workflow)}</span>\n </>\n )\n // Open the run in a new tab so the always-on board never navigates away.\n return linkable ? (\n <a className={className} href={isAllowedHref(url)} title={stateLabel(workflow.state)} target=\"_blank\" rel=\"noopener noreferrer\">{body}</a>\n ) : (\n <span className={className} title={stateLabel(workflow.state)}>{body}</span>\n )\n})\n","import type { SignalState } from '../api/types'\n\n// The spoken form of a workflow/job state. Status on the board is carried\n// visually by dot colour + dot shape; this is the text equivalent rendered\n// into each chip's accessible name (an .sr-only span) so screen-reader and\n// colour-blind users get the state in words — not a colour-only signal.\n// (WCAG 2.2 SC 1.4.1 / 1.1.1.)\nconst STATE_LABELS: Record<SignalState, string> = {\n success: 'passing',\n failure: 'failing',\n running: 'running',\n unknown: 'status unknown',\n}\n\nexport function stateLabel(state: SignalState): string {\n return STATE_LABELS[state]\n}\n","import type { RepoMetrics } from '../api/types'\nimport { formatCompactNumber } from '../lib/formatCompactNumber'\n\nexport function RepoMetricsLine({ metrics }: { metrics: RepoMetrics | null }) {\n if (!metrics || metrics.nloc === 0) return null\n return (\n <dl className=\"repo-metrics\" aria-label=\"code metrics\">\n <div title=\"non-comment lines of code\"><dt>NLOC</dt><dd>{formatCompactNumber(metrics.nloc)}</dd></div>\n <div title=\"average cyclomatic complexity (branch paths per function)\"><dt>avg CCN</dt><dd>{metrics.avgComplexity.toFixed(1)}</dd></div>\n <div title=\"number of functions\"><dt>functions</dt><dd>{formatCompactNumber(metrics.functionCount)}</dd></div>\n {metrics.highComplexityCount > 0 && (\n <div className=\"repo-metrics__complex\" title=\"functions over CCN 15 — refactor candidates\">\n <dt>complex</dt><dd>{metrics.highComplexityCount}</dd>\n </div>\n )}\n </dl>\n )\n}\n","import type { PullRequest } from '../api/types'\nimport { formatRelativeTime } from '../lib/relativeTime'\nimport { isAllowedHref } from '../lib/isAllowedHref'\n\nexport function PullRequestList({ pullRequests }: { pullRequests: PullRequest[] }) {\n if (pullRequests.length === 0) return null\n return (\n <div className=\"repo-prs\">\n <span className=\"repo-prs__count\">\n {pullRequests.length} open PR{pullRequests.length === 1 ? '' : 's'}\n </span>\n <ul>\n {pullRequests.map(pr => (\n <li key={pr.number} className={pr.isDraft ? 'repo-prs__item repo-prs__item--draft' : 'repo-prs__item'}>\n <a href={isAllowedHref(pr.htmlUrl)} target=\"_blank\" rel=\"noopener noreferrer\">\n <span className=\"repo-prs__num\">#{pr.number}</span>\n <span className=\"repo-prs__title\">{pr.title}</span>\n </a>\n <span className=\"repo-prs__meta\">\n {pr.author} · {formatRelativeTime(pr.createdAt)}{pr.isDraft ? ' · draft' : ''}\n </span>\n </li>\n ))}\n </ul>\n </div>\n )\n}\n","import { memo } from 'react'\nimport type { JobSignal } from '../api/types'\nimport { formatRelativeTime } from '../lib/relativeTime'\nimport { dedupeJobLabel } from '../lib/dedupeJobLabel'\nimport { stateLabel } from '../lib/stateLabel'\nimport { isAllowedHref } from '../lib/isAllowedHref'\n// Memoised: the signals array reference is preserved across no-change poll ticks\n// (React Query structural sharing), so the lane skips re-rendering.\nexport const JobLaneRow = memo(function JobLaneRow({\n kind, glyph, label, signals,\n}: {\n kind: 'deploys' | 'packages'\n glyph: string\n label: string\n signals: JobSignal[]\n}) {\n if (signals.length === 0) return null\n return (\n <div className={`repo-joblane repo-joblane--${kind}`}>\n <span className=\"repo-joblane__label\">{glyph} {label}</span>\n <div className=\"repo-joblane__chips\">\n {signals.map((s, i) => (\n <a\n key={`${s.workflow}/${s.name}/${i}`}\n className={`chip chip--${s.state} chip--joblane`}\n href={isAllowedHref(s.htmlUrl)}\n title={`${s.workflow} · ${s.state}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <span className=\"chip__dot\" aria-hidden=\"true\" />\n <span className=\"chip__label\">{dedupeJobLabel(s.name)}</span>\n {/* State in words for SR / colour-blind users — the dot is colour+shape only. */}\n <span className=\"sr-only\">{stateLabel(s.state)}</span>\n <span className=\"chip__meta\">{formatRelativeTime(s.updatedAt)}</span>\n </a>\n ))}\n </div>\n </div>\n )\n})\n","// GitHub renders reusable-workflow / matrix job names as \"caller / called\", which\n// for a deploy or publish job is often the same segment twice\n// (\"Deploy (x) / Deploy (x)\"). Collapse consecutive identical segments so the chip\n// reads once; genuinely different segments are preserved.\nexport function dedupeJobLabel(name: string): string {\n const parts = name.split(' / ')\n const deduped = parts.filter((part, i) => i === 0 || part !== parts[i - 1])\n return deduped.join(' / ')\n}\n","import type { SignalState } from '../api/types'\n\nexport type Activity = 'failure' | 'running' | 'success' | 'none'\n\n// Precedence for a repo's worst signal across a set of states: a failure dominates,\n// then anything running, then a clean success; an empty/all-unknown set reads as\n// \"none\" (hollow indicator).\nexport function worstState(states: SignalState[]): Activity {\n if (states.includes('failure')) return 'failure'\n if (states.includes('running')) return 'running'\n if (states.includes('success')) return 'success'\n return 'none'\n}\n","import type { RepositorySnapshot } from '../api/types'\nimport { worstState } from '../lib/worstState'\n\nexport function RepoActivityIndicator({ repository }: { repository: RepositorySnapshot }) {\n const prCount = (repository.pullRequests ?? []).length\n const ci = worstState((repository.workflows ?? []).map(w => w.state))\n const cd = worstState([...(repository.deploys ?? []), ...(repository.packages ?? [])].map(s => s.state))\n return (\n <span className=\"repo-activity\">\n {prCount > 0 && <span className=\"repo-activity__pr\">{prCount} PR</span>}\n <span className=\"repo-activity__sig\">CI<span className=\"repo-activity__dot\" data-activity={ci} aria-label={`CI ${ci}`} /></span>\n <span className=\"repo-activity__sig\">CD<span className=\"repo-activity__dot\" data-activity={cd} aria-label={`CD ${cd}`} /></span>\n </span>\n )\n}\n","export function RepoSection({ label, count, collapsed, onToggle }: {\n label: string\n count: number\n collapsed: boolean\n onToggle: () => void\n}) {\n return (\n <button\n type=\"button\"\n className=\"repo-section\"\n aria-expanded={!collapsed}\n onClick={onToggle}\n >\n <span className=\"repo-section__chevron\" aria-hidden=\"true\">\n {collapsed ? '▸' : '▾'}\n </span>\n <span className=\"repo-section__label\">{label}</span>\n <span className=\"repo-section__count\" aria-hidden=\"true\">· {count}</span>\n </button>\n )\n}\n","// Defines the abbreviations on each repo's Lizard metrics line. Rendered once at\n// the foot of the board; the same wording backs the per-metric hover tooltips.\nconst ITEMS: ReadonlyArray<readonly [string, string]> = [\n ['NLOC', 'non-comment lines of code'],\n ['avg CCN', 'average cyclomatic complexity (branch paths per function)'],\n ['functions', 'number of functions'],\n ['complex', 'functions over CCN 15 — refactor candidates'],\n]\n\nexport function MetricsLegend() {\n return (\n <footer className=\"metrics-legend\" aria-label=\"Lizard metrics legend\">\n <span className=\"metrics-legend__title\">Lizard metrics</span>\n {ITEMS.map(([term, meaning]) => (\n <span key={term} className=\"metrics-legend__item\">\n <b>{term}</b> {meaning}\n </span>\n ))}\n </footer>\n )\n}\n","import type { SignalState } from '../api/types'\n\n// Decodes the board's primary visual language — the status dot colours and\n// shapes — for a first-time visitor. Sits beside the Lizard MetricsLegend at\n// the foot of the board. Swatches mirror the live dot treatment: colour plus a\n// non-colour shape cue (square = failing) so the key reads in grayscale too.\nconst STATUS_ITEMS: ReadonlyArray<readonly [SignalState, string]> = [\n ['success', 'passing'],\n ['failure', 'failing'],\n ['running', 'running'],\n ['unknown', 'unknown / no runs'],\n]\n\nexport function StatusLegend() {\n return (\n <footer className=\"status-legend\" aria-label=\"Status colour key\">\n <span className=\"status-legend__title\">Status</span>\n {STATUS_ITEMS.map(([state, label]) => (\n <span key={state} className=\"status-legend__item\">\n <span className=\"status-legend__dot\" data-state={state} aria-hidden=\"true\" />\n {label}\n </span>\n ))}\n <span className=\"status-legend__item\">\n <span className=\"status-legend__noci\" aria-hidden=\"true\" />\n No-CI repo\n </span>\n <span className=\"status-legend__gloss\">\n <b>CI</b> workflow runs · <b>CD</b> deploys & packages\n </span>\n </footer>\n )\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type { OpenPr } from '../lib/flattenOpenPrs'\nimport { formatRelativeTime } from '../lib/relativeTime'\nimport { prAgeTone } from '../lib/prAgeTone'\nimport { isAllowedHref } from '../lib/isAllowedHref'\nexport function PullRequestStepper({ prs, onClose }: { prs: OpenPr[]; onClose: () => void }) {\n const [i, setI] = useState(0)\n const dialogRef = useRef<HTMLDialogElement>(null)\n // Background CI polling can shrink `prs` while the stepper is open (e.g. the\n // PR being viewed just merged). Clamp against the live length so `pr` never\n // reads undefined — an undefined read would return null and unmount the\n // <dialog> WITHOUT firing onClose, wedging the parent's stepperOpen at true\n // and permanently breaking the Open-PRs button for the session.\n const safeIndex = Math.min(i, Math.max(0, prs.length - 1))\n const pr = prs[safeIndex]\n\n useEffect(() => {\n const previouslyFocused = document.activeElement as HTMLElement | null\n const dlg = dialogRef.current\n dlg?.showModal()\n dlg?.focus()\n return () => {\n previouslyFocused?.focus?.()\n dlg?.close()\n }\n }, [])\n\n // If the list drains entirely, there is nothing to step through — close via\n // the callback so the parent resets stepperOpen, rather than silently\n // rendering null and leaving a dangling open flag.\n useEffect(() => {\n if (prs.length === 0) onClose()\n }, [prs.length, onClose])\n\n if (!pr) return null\n\n return (\n <dialog\n ref={dialogRef}\n className=\"pr-modal\"\n aria-label=\"Open pull requests\"\n tabIndex={-1}\n onCancel={e => { e.preventDefault(); onClose() }}\n onClick={e => { if (e.target === e.currentTarget) onClose() }}\n // Left/Right arrow paging, scoped to the dialog. Escape closes via the\n // native cancel event above.\n onKeyDown={e => {\n if (e.key === 'ArrowRight') setI(Math.min(safeIndex + 1, prs.length - 1))\n if (e.key === 'ArrowLeft') setI(Math.max(safeIndex - 1, 0))\n }}\n >\n <div className=\"pr-modal__top\">\n <span className=\"pr-modal__title\">Open pull requests</span>\n <span className=\"pr-modal__counter\">{safeIndex + 1} / {prs.length}</span>\n <button type=\"button\" className=\"pr-modal__x\" onClick={onClose} aria-label=\"Close\">✕</button>\n </div>\n <div className={`pr-card pr-card--${prAgeTone(pr.createdAt)}${pr.isDraft ? ' pr-card--draft' : ''}`}>\n <div className=\"pr-card__head\">\n <span className=\"pr-card__repo\">{pr.repo}</span>\n <span className=\"pr-card__num\">#{pr.number}</span>\n <span className=\"pr-card__meta\">{formatRelativeTime(pr.createdAt)} · {pr.isDraft ? 'draft' : 'ready'}</span>\n </div>\n <div className=\"pr-card__title\">{pr.title}</div>\n <div className=\"pr-card__foot\">\n <span className=\"pr-card__author\">@{pr.author}</span>\n <a className=\"pr-card__gh\" href={isAllowedHref(pr.htmlUrl)} target=\"_blank\" rel=\"noopener noreferrer\">Open on GitHub ↗</a>\n </div>\n </div>\n {/* One PR needs no nav — the '1 / 1' counter already says so; two disabled\n buttons would just be dead chrome. */}\n {prs.length > 1 && (\n <div className=\"pr-modal__nav\">\n <button type=\"button\" onClick={() => setI(Math.max(safeIndex - 1, 0))} disabled={safeIndex === 0}>‹ Prev</button>\n <button type=\"button\" onClick={() => setI(Math.min(safeIndex + 1, prs.length - 1))} disabled={safeIndex === prs.length - 1}>Next ›</button>\n </div>\n )}\n </dialog>\n )\n}\n","// Age-based emphasis for an open PR card edge: stale PRs warm up.\nexport type AgeTone = 'red' | 'amber' | 'quiet'\n\nexport function prAgeTone(createdAtIso: string, now: number = Date.now()): AgeTone {\n const days = (now - new Date(createdAtIso).getTime()) / 86_400_000\n if (days > 14) return 'red'\n if (days > 7) return 'amber'\n return 'quiet'\n}\n","import type { PullRequest, RepositorySnapshot } from '../api/types'\n\nexport type OpenPr = PullRequest & { repo: string }\n\n// Aggregate every repo's open PRs into one list tagged with its repo, oldest-first\n// (the most stale surface first for triage).\nexport function flattenOpenPrs(repositories: RepositorySnapshot[]): OpenPr[] {\n return repositories\n .flatMap(r => (r.pullRequests ?? []).map(pr => ({ ...pr, repo: r.name })))\n .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())\n}\n","// Neutral footer shipped as the CiBoard default. No FixPortal branding or\n// personal attribution -- reusers pass their own node via CiBoard's footerSlot.\nexport function DefaultFooter() {\n return (\n <footer className=\"site-footer\">\n <div className=\"site-footer__band\" aria-hidden=\"true\">\n <span className=\"site-footer__tagline\">Continuous-integration overview</span>\n </div>\n </footer>\n )\n}\n"],"mappings":"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","names":["useState","createContext","use","useState","useEffect","useCallback","useEffect","useState","KEY","load","useEffect","useState","Fragment","jsx","jsxs","useState","useEffect","memo","Fragment","jsx","jsxs","SignalChip","jsx","jsxs","jsx","jsxs","memo","jsx","jsxs","memo","JobLaneRow","jsx","jsxs","Fragment","jsx","jsxs","memo","RepoBoard","jsx","jsxs","jsx","jsxs","jsx","jsxs","useEffect","useRef","useState","jsx","jsxs","useState","useRef","useEffect","Fragment","jsx","jsxs","useState","useEffect","jsx","Fragment","jsx","jsxs","useState"]}
|
package/package.json
CHANGED