@fix-portal/ci-frontend 0.1.0 → 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 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 CI host. */
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 = "https://ci.fixportal.org";
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 = "https://ci.fixportal.org";
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(apiBase) {
28
- const base = apiBase.replace(/\/$/, "");
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", apiBase],
40
- queryFn: () => getDashboardSnapshot(apiBase),
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
- useEffect(() => {
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 useEffect2, useRef as useRef2, useState as useState4 } from "react";
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()) {
@@ -655,15 +677,22 @@ function prAgeTone(createdAtIso, now = Date.now()) {
655
677
  import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
656
678
  function PullRequestStepper({ prs, onClose }) {
657
679
  const [i, setI] = useState4(0);
658
- const pr = prs[i];
659
680
  const dialogRef = useRef2(null);
660
- useEffect2(() => {
681
+ const safeIndex = Math.min(i, Math.max(0, prs.length - 1));
682
+ const pr = prs[safeIndex];
683
+ useEffect4(() => {
661
684
  const previouslyFocused = document.activeElement;
662
685
  const dlg = dialogRef.current;
663
686
  dlg?.showModal();
664
687
  dlg?.focus();
665
- return () => previouslyFocused?.focus?.();
688
+ return () => {
689
+ previouslyFocused?.focus?.();
690
+ dlg?.close();
691
+ };
666
692
  }, []);
693
+ useEffect4(() => {
694
+ if (prs.length === 0) onClose();
695
+ }, [prs.length, onClose]);
667
696
  if (!pr) return null;
668
697
  return /* @__PURE__ */ jsxs12(
669
698
  "dialog",
@@ -680,14 +709,14 @@ function PullRequestStepper({ prs, onClose }) {
680
709
  if (e.target === e.currentTarget) onClose();
681
710
  },
682
711
  onKeyDown: (e) => {
683
- if (e.key === "ArrowRight") setI((p) => Math.min(p + 1, prs.length - 1));
684
- if (e.key === "ArrowLeft") setI((p) => Math.max(p - 1, 0));
712
+ if (e.key === "ArrowRight") setI(Math.min(safeIndex + 1, prs.length - 1));
713
+ if (e.key === "ArrowLeft") setI(Math.max(safeIndex - 1, 0));
685
714
  },
686
715
  children: [
687
716
  /* @__PURE__ */ jsxs12("div", { className: "pr-modal__top", children: [
688
717
  /* @__PURE__ */ jsx12("span", { className: "pr-modal__title", children: "Open pull requests" }),
689
718
  /* @__PURE__ */ jsxs12("span", { className: "pr-modal__counter", children: [
690
- i + 1,
719
+ safeIndex + 1,
691
720
  " / ",
692
721
  prs.length
693
722
  ] }),
@@ -712,12 +741,12 @@ function PullRequestStepper({ prs, onClose }) {
712
741
  "@",
713
742
  pr.author
714
743
  ] }),
715
- /* @__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" })
716
745
  ] })
717
746
  ] }),
718
747
  prs.length > 1 && /* @__PURE__ */ jsxs12("div", { className: "pr-modal__nav", children: [
719
- /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setI((p) => Math.max(p - 1, 0)), disabled: i === 0, children: "\u2039 Prev" }),
720
- /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setI((p) => Math.min(p + 1, prs.length - 1)), disabled: i === prs.length - 1, children: "Next \u203A" })
748
+ /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setI(Math.max(safeIndex - 1, 0)), disabled: safeIndex === 0, children: "\u2039 Prev" }),
749
+ /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setI(Math.min(safeIndex + 1, prs.length - 1)), disabled: safeIndex === prs.length - 1, children: "Next \u203A" })
721
750
  ] })
722
751
  ]
723
752
  }
@@ -737,6 +766,11 @@ function CiBoardContent() {
737
766
  const hideNoCi = useHideNoCi();
738
767
  const isAdmin = useCiAdmin();
739
768
  const [stepperOpen, setStepperOpen] = useState5(false);
769
+ useEffect5(() => {
770
+ if (openPrs.length === 0) {
771
+ setStepperOpen(false);
772
+ }
773
+ }, [openPrs.length]);
740
774
  if (snapshot.isPending) {
741
775
  return /* @__PURE__ */ jsx13("main", { className: "dashboard-page", children: /* @__PURE__ */ jsx13("div", { className: "state-msg", children: "Loading dashboard\u2026" }) });
742
776
  }
@@ -748,11 +782,11 @@ function CiBoardContent() {
748
782
  }
749
783
  const { refreshedAt, repositories: allRepositories, lastMergedPr: rawLastMerged } = snapshot.data;
750
784
  const repositories = isAdmin ? allRepositories : allRepositories.filter((r) => !r.private);
751
- const summary = isAdmin ? snapshot.data.summary : computeSummary(repositories);
752
- const lastMergedPr = rawLastMerged && repositories.some((r) => r.name === rawLastMerged.repo) ? rawLastMerged : null;
753
- 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;
754
787
  const noCiCount = repositories.filter(isNoCi).length;
755
788
  const visibleRepos = hideNoCi.hidden ? repositories.filter((r) => !isNoCi(r)) : repositories;
789
+ const repoNames = visibleRepos.map((r) => r.name);
756
790
  const hiddenCount = repositories.length - visibleRepos.length;
757
791
  const publicRepos = visibleRepos.filter((r) => !r.private);
758
792
  const privateRepos = visibleRepos.filter((r) => r.private);
@@ -761,7 +795,7 @@ function CiBoardContent() {
761
795
  const KEY_PRIVATE = "section:private";
762
796
  const sectionKeys = showGroups ? [KEY_PUBLIC, KEY_PRIVATE] : [];
763
797
  const allCollapsed = collapse.allCollapsed([...repoNames, ...sectionKeys]);
764
- const openPrs = flattenOpenPrs(repositories);
798
+ const openPrs = flattenOpenPrs(visibleRepos);
765
799
  const nextPr = openPrs[0] ?? null;
766
800
  let repoListContent;
767
801
  if (visibleRepos.length === 0 && hideNoCi.hidden) {
@@ -854,7 +888,7 @@ function CiBoardContent() {
854
888
  /* @__PURE__ */ jsx13("div", { className: "repo-list", children: repoListContent }),
855
889
  /* @__PURE__ */ jsx13(StatusLegend, {}),
856
890
  /* @__PURE__ */ jsx13(MetricsLegend, {}),
857
- stepperOpen && /* @__PURE__ */ jsx13(PullRequestStepper, { prs: openPrs, onClose: () => setStepperOpen(false) })
891
+ stepperOpen && openPrs.length > 0 && /* @__PURE__ */ jsx13(PullRequestStepper, { prs: openPrs, onClose: () => setStepperOpen(false) })
858
892
  ] });
859
893
  }
860
894
 
@@ -865,9 +899,29 @@ function DefaultFooter() {
865
899
  }
866
900
 
867
901
  // src/CiBoard.tsx
868
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
869
- function CiBoard({ adminSignal, apiBase = DEFAULT_CI_API_BASE, logo, footerSlot }) {
870
- return /* @__PURE__ */ jsx15(CiConfigProvider, { value: { apiBase }, children: /* @__PURE__ */ jsxs14("div", { className: "ci-page", children: [
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: [
871
925
  /* @__PURE__ */ jsxs14("div", { className: "ci-embed", children: [
872
926
  /* @__PURE__ */ jsx15("header", { className: "ci-embed__header", children: /* @__PURE__ */ jsxs14("span", { className: "ci-embed__lockup", children: [
873
927
  logo ?? /* @__PURE__ */ jsx15("span", { className: "ci-embed__wordmark-text", children: "CI Dashboard" }),
@@ -879,7 +933,7 @@ function CiBoard({ adminSignal, apiBase = DEFAULT_CI_API_BASE, logo, footerSlot
879
933
  /* @__PURE__ */ jsx15(CiAdminProvider, { value: adminSignal, children: /* @__PURE__ */ jsx15(CiBoardContent, {}) })
880
934
  ] }),
881
935
  footerSlot ?? /* @__PURE__ */ jsx15(DefaultFooter, {})
882
- ] }) });
936
+ ] }) }) });
883
937
  }
884
938
  export {
885
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\" — surface it from the same source the Open PRs button loads.\n const openPrs = flattenOpenPrs(repositories)\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 &amp; 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 pr = prs[i]\n const dialogRef = useRef<HTMLDialogElement>(null)\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 (!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(p => Math.min(p + 1, prs.length - 1))\n if (e.key === 'ArrowLeft') setI(p => Math.max(p - 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\">{i + 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(p => Math.max(p - 1, 0))} disabled={i === 0}>‹ Prev</button>\n <button type=\"button\" onClick={() => setI(p => Math.min(p + 1, prs.length - 1))} disabled={i === 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 &amp; 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fix-portal/ci-frontend",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Open-source CI dashboard UI - React components for the FixPortal CI backend snapshot API.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",