@continuous-excellence/ze-great-dashboard-aws 0.3.2 → 0.4.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.
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "index.html": {
3
- "file": "assets/index-DOvxvZo2.js",
3
+ "file": "assets/index-BCH1wUwb.js",
4
4
  "name": "index",
5
5
  "src": "index.html",
6
6
  "isEntry": true,
7
7
  "css": [
8
- "assets/index-r7iEInfY.css"
8
+ "assets/index-CwWbINCq.css"
9
9
  ]
10
10
  }
11
11
  }
@@ -0,0 +1,3 @@
1
+ import{z as e}from"zod";import{StrictMode as t,useEffect as n,useRef as r,useState as i}from"react";import{createRoot as a}from"react-dom/client";import{Fragment as o,jsx as s,jsxs as c}from"react/jsx-runtime";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=/^(\d+)(ms|s|m|h)$/,u={ms:1,s:1e3,m:6e4,h:36e5};function d(e){let t=l.exec(e);if(!t)return null;let[,n,r]=t;if(n===void 0||r===void 0)return null;let i=Number(n);return i<=0?null:i*u[r]}var f=e.string().refine(e=>d(e)!==null,{message:`must be a positive duration like "30s", "5m", or "1h"`}).brand(),p=e.object({x:e.number().int().min(0),y:e.number().int().min(0),w:e.number().int().min(1).max(12),h:e.number().int().min(1)}),m=e.looseObject({id:e.string().min(1),type:e.string().min(1),source:e.string().min(1).optional(),position:p.optional(),refresh:f.optional(),link:e.url().optional(),url:e.url().optional(),json_path:e.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/,`must be a simple JSON path`).optional()}),h=e.looseObject({type:e.string().min(1),token_env:e.string().min(1).optional()}),g=e.object({refresh:f.optional(),panels:e.array(m).min(1).superRefine((e,t)=>{let n=new Map;e.forEach((e,r)=>{let i=n.get(e.id);if(i===void 0){n.set(e.id,r);return}t.addIssue({code:`custom`,path:[r,`id`],message:`duplicate panel id "${e.id}" (already used by panel at index ${i})`})})})}),_=e.object({issuer:e.url(),client_id:e.string().min(1).optional(),allow:e.object({groups:e.array(e.string().min(1)).optional(),subjects:e.array(e.string().min(1)).optional()}).optional()});e.object({sources:e.record(e.string().min(1),h).default({}),boards:e.record(e.string().min(1),g),auth:_.optional()});function v(e){let t=e.panelOverrideMillis??e.boardDefaultMillis;return Math.max(t,e.adapterFloorMillis)}var y=e.object({assetPath:e.string().min(1),proxyPath:e.string().min(1),board:e.string().min(1),clientVersion:e.string().min(1),auth:e.object({issuer:e.string().min(1),clientId:e.string().min(1).optional()}).optional()});function b(t=globalThis.window?.env){let n=y.safeParse(t);if(!n.success)throw Error(`window.env is missing or invalid \u2014 the server did not inject usable configuration.
2
+ ${e.prettifyError(n.error)}`);return n.data}var x=e.enum([`unreachable`,`unauthorized`,`not-found`,`no-runs`,`upstream-error`]),S=e.object({kind:x,message:e.string()}),C=e.discriminatedUnion(`state`,[e.object({panelId:e.string().min(1),state:e.literal(`ok`),observedAt:e.iso.datetime(),link:e.url().nullable(),signal:e.unknown()}),e.object({panelId:e.string().min(1),state:e.literal(`error`),observedAt:e.iso.datetime(),link:e.url().nullable(),error:S})]),w=e.object({type:e.literal(`pipeline-status`),status:e.enum([`passed`,`failed`,`running`,`cancelled`,`unknown`]),rawStatus:e.string(),name:e.string().min(1),branch:e.string().min(1).optional(),durationMs:e.number().int().nonnegative().optional(),sourceUpdatedAt:e.iso.datetime().optional()}),T=e.object({type:e.literal(`http-value`),value:e.union([e.string(),e.number(),e.boolean()])});function E({log:e}){let[t,r]=i(!1),[a,o]=i(e.count());return n(()=>e.subscribe(()=>o(e.count())),[e]),c(`section`,{className:`diagnostics`,children:[c(`button`,{className:`diagnostics__toggle`,type:`button`,"aria-expanded":t,onClick:()=>r(!t),children:[`Diagnostics (`,a,`)`]}),t&&c(`div`,{className:`diagnostics__area`,children:[c(`p`,{children:[a,` retained browser-local events. They are never uploaded.`]}),c(`div`,{className:`diagnostics__actions`,children:[s(`button`,{type:`button`,onClick:()=>{let t=new Blob([JSON.stringify(e.export(),null,2)],{type:`application/json`}),n=URL.createObjectURL(t),r=document.createElement(`a`);r.href=n,r.download=`dashboard-diagnostics-${new Date().toISOString().replaceAll(`:`,`-`)}.json`,r.click(),URL.revokeObjectURL(n)},children:`Download`}),s(`button`,{type:`button`,onClick:()=>{window.confirm(`Clear this browser’s retained dashboard diagnostics?`)&&e.clear()},children:`Clear`})]})]})]})}var D=`ze-great-dashboard.diagnostics.v1`,O=6048e5,k=[`session-start`,`board-fetch-start`,`board-fetch-response`,`board-fetch-parse-failure`,`board-fetch-failure`,`panel-fetch-start`,`panel-fetch-response`,`panel-fetch-parse-failure`,`panel-fetch-failure`,`panel-rendered`],A=class{env;storage;now;events=[];listeners=new Set;sessionId=crypto.randomUUID?.()??`${Date.now()}-${Math.random()}`;constructor(e,t=P(),n=()=>new Date){this.env=e,this.storage=t,this.now=n,this.events=this.read(),this.record({kind:`session-start`})}record(e){this.events=M([...this.events,{...e,at:this.now().toISOString(),sessionId:this.sessionId,board:this.env.board}],this.now()),this.persist(),this.notify()}count(){return this.events.length}clear(){this.events=[];try{this.storage?.removeItem(D)}catch{}this.notify()}subscribe(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}export(){return{schemaVersion:1,exportedAt:this.now().toISOString(),client:{version:this.env.clientVersion,assetPath:this.env.assetPath,board:this.env.board,sessionId:this.sessionId},events:this.events}}read(){try{let e=this.storage?.getItem(D);if(!e)return[];let t=JSON.parse(e);return t.schemaVersion!==1||!Array.isArray(t.events)||!t.events.every(N)?(this.storage?.removeItem(D),[]):M(t.events,this.now())}catch{try{this.storage?.removeItem(D)}catch{}return[]}}persist(){try{this.storage?.setItem(D,JSON.stringify({schemaVersion:1,events:this.events}))}catch{}}notify(){for(let e of this.listeners)e()}};function j(e){let t=Object.fromEntries([[`cacheControl`,`cache-control`],[`etag`,`etag`],[`lastModified`,`last-modified`],[`date`,`date`],[`age`,`age`]].flatMap(([t,n])=>{let r=e.get(n);return r?[[t,r]]:[]}));return Object.keys(t).length?t:void 0}function M(e,t){let n=t.valueOf()-O;return e.filter(e=>Number.isFinite(new Date(e.at).valueOf())&&new Date(e.at).valueOf()>=n).slice(-2e3)}function N(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.at==`string`&&Number.isFinite(new Date(t.at).valueOf())&&typeof t.sessionId==`string`&&typeof t.board==`string`&&k.includes(t.kind)}function P(){try{return window.localStorage}catch{return}}function F({value:e,label:t=`As of`}){let n=new Date(e),r=n.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`}),i=Date.now()-n.getTime(),a=i>3e5,o=I(i);return c(`p`,{className:`panel__hint panel__observed${a?` panel__observed--stale`:``}`,children:[s(`span`,{"aria-hidden":`true`,children:`◷`}),` `,t,` `,r,` · `,o]})}function I(e){if(e<6e4)return`just now`;let t=Math.floor(e/6e4);return t<60?`${t}m ago`:`${Math.floor(t/60)}h ${t%60}m ago`}function L(e){let t=e.position;if(t)return{"--panel-column":`${t.x+1} / span ${t.w}`,"--panel-row":`${t.y+1} / span ${t.h}`}}function R({panel:e,data:t}){if(!t)return c(`section`,{className:`panel`,style:L(e),"aria-busy":`true`,children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return c(`section`,{className:`panel panel--error`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__status`,children:`⚠ Unable to read`}),s(`p`,{className:`panel__hint`,children:t.error.message}),s(F,{value:t.observedAt})]});let n=T.safeParse(t.signal);if(!n.success)return c(`section`,{className:`panel panel--error`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__status`,children:`⚠ Invalid value`}),s(F,{value:t.observedAt})]});let r=c(o,{children:[s(`p`,{className:`panel__status`,children:String(n.data.value)}),s(F,{value:t.observedAt})]});return c(`section`,{className:`panel`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),t.link?s(`a`,{className:`panel__link`,href:t.link,children:r}):r]})}function z({label:e,hint:t,wide:n=!1}){return c(`section`,{className:n?`panel panel--wide`:`panel`,children:[s(`h2`,{className:`panel__label`,children:e}),s(`p`,{className:`panel__hint`,children:t})]})}function B({panel:e,data:t}){if(!t)return c(`section`,{className:`panel`,style:L(e),"aria-busy":`true`,children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return c(`section`,{className:`panel panel--error`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),c(`p`,{className:`panel__status`,children:[`⚠ `,t.error.kind===`no-runs`?`No workflow runs`:`Unable to read`]}),s(`p`,{className:`panel__hint`,children:t.error.message}),s(F,{value:t.observedAt})]});let n=w.safeParse(t.signal);if(!n.success)return c(`section`,{className:`panel panel--error`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__status`,children:`⚠ Invalid signal`}),s(F,{value:t.observedAt})]});let r=H(n.data.status),i=c(o,{children:[c(`p`,{className:`panel__status panel__status--${n.data.status}`,children:[r.glyph,` `,r.label]}),c(`p`,{className:`panel__hint`,children:[n.data.name,` · `,n.data.rawStatus,n.data.branch&&c(`span`,{className:`panel__branch`,title:`Branch: ${n.data.branch}`,children:[s(`span`,{"aria-hidden":`true`,children:` · ⎇ `}),s(`span`,{className:`screen-reader-only`,children:`Branch: `}),n.data.branch]})]}),n.data.status!==`running`&&n.data.durationMs!==void 0&&c(`p`,{className:`panel__hint`,children:[`Took `,V(n.data.durationMs)]}),n.data.sourceUpdatedAt&&s(F,{value:n.data.sourceUpdatedAt,label:`Run updated`}),s(F,{value:t.observedAt})]});return c(`section`,{className:`panel`,style:L(e),children:[s(`h2`,{className:`panel__label`,children:e.id}),t.link?s(`a`,{className:`panel__link`,href:t.link,children:i}):i]})}function V(e){let t=Math.floor(e/1e3),n=Math.floor(t/3600),r=Math.floor(t%3600/60),i=t%60;return n>0?`${n}h ${r}m`:r>0?`${r}m ${i}s`:`${i}s`}function H(e){switch(e){case`passed`:return{glyph:`✓`,label:`Passed`};case`failed`:return{glyph:`✕`,label:`Failed`};case`running`:return{glyph:`↻`,label:`Running`};case`cancelled`:return{glyph:`⊘`,label:`Cancelled`};case`unknown`:return{glyph:`?`,label:`Unknown`}}}function U(e){let t=C.safeParse(e);return t.success?t.data:void 0}function W({env:e}){let[t,a]=i(),[o,l]=i(),[u,f]=i({}),p=r(null),m=r({});p.current||=new A(e);let h=p.current;return n(()=>{let t=!1;a(void 0),l(void 0),f({}),m.current={};let n=`${e.proxyPath}/boards/${encodeURIComponent(e.board)}`;return h.record({kind:`board-fetch-start`,path:n}),fetch(n).then(async e=>{if(h.record({kind:`board-fetch-response`,path:n,status:e.status,cache:j(e.headers)}),!e.ok)throw Error(`Board configuration returned ${e.status}`);try{return await e.json()}catch(e){throw h.record({kind:`board-fetch-parse-failure`,path:n,message:G(e)}),new K(e)}}).then(r=>{if(!t){a(r),l(e.board);let t=Array.isArray(r.panels)?r.panels:[];h.record({kind:`board-fetch-response`,path:n,boardSummary:{panelCount:t.length,panelIds:t.flatMap(e=>typeof e.id==`string`?[e.id]:[])}})}}).catch(r=>{r instanceof K||h.record({kind:`board-fetch-failure`,path:n,message:G(r)}),t||(a({panels:[]}),l(e.board))}),()=>{t=!0}},[h,e.board,e.proxyPath]),n(()=>{if(!t||o!==e.board)return;let n=!1,r=[];for(let i of t.panels){if(i.type!==`pipeline-status`&&i.type!==`http-value`)continue;let a=!1,o=v({boardDefaultMillis:d(t.refresh??`60s`)??6e4,panelOverrideMillis:i.refresh?d(i.refresh)??void 0:void 0,adapterFloorMillis:0}),s=()=>{if(n||a)return;a=!0;let t=`${e.proxyPath}/panel/${encodeURIComponent(e.board)}/${encodeURIComponent(i.id)}`;h.record({kind:`panel-fetch-start`,panelId:i.id,path:t}),fetch(t).then(async e=>{if(h.record({kind:`panel-fetch-response`,panelId:i.id,path:t,status:e.status,cache:j(e.headers)}),e.status!==304)try{return await e.json()}catch(e){throw h.record({kind:`panel-fetch-parse-failure`,panelId:i.id,path:t,message:G(e)}),new K(e)}}).then(e=>{let r=U(e);if(e!==void 0&&!r&&h.record({kind:`panel-fetch-parse-failure`,panelId:i.id,path:t,message:`Response was not a valid signal envelope.`}),!n&&r){h.record({kind:`panel-fetch-response`,panelId:i.id,path:t,envelope:r});let e=m.current[i.id];J(e,r)&&h.record({kind:`panel-rendered`,panelId:i.id,path:t,rendered:q(r)}),m.current={...m.current,[i.id]:r},f(m.current)}}).catch(e=>{e instanceof K||h.record({kind:`panel-fetch-failure`,panelId:i.id,path:t,message:G(e)})}).finally(()=>{a=!1})};s(),r.push(window.setInterval(s,o))}return()=>{n=!0;for(let e of r)window.clearInterval(e)}},[t,h,e.board,e.proxyPath,o]),c(`div`,{className:`board`,children:[c(`header`,{className:`board__header`,children:[s(`h1`,{className:`board__title`,children:e.board}),c(`dl`,{className:`board__meta`,children:[c(`div`,{children:[s(`dt`,{children:`client`}),s(`dd`,{children:e.clientVersion})]}),c(`div`,{children:[s(`dt`,{children:`assets`}),s(`dd`,{children:e.assetPath})]})]})]}),c(`main`,{className:`board__grid`,children:[!t&&s(z,{label:`board`,hint:`Loading configuration…`,wide:!0}),t?.panels.map(e=>e.type===`pipeline-status`?s(B,{panel:e,data:u[e.id]},e.id):e.type===`http-value`?s(R,{panel:e,data:u[e.id]},e.id):s(z,{label:e.type,hint:`Not wired yet`,wide:!0},e.id))]}),c(`footer`,{className:`board__footer`,children:[s(`span`,{children:`Signals are read live from their configured authorities.`}),s(E,{log:h})]})]})}function G(e){return e instanceof Error?e.message:String(e)}var K=class extends Error{constructor(e){super(G(e))}};function q(e){if(e.state===`error`)return{state:e.state,link:e.link};let t=w.safeParse(e.signal);return{state:e.state,status:t.success?t.data.status:void 0,link:e.link}}function J(e,t){if(!e)return!0;let n=q(e),r=q(t);return n.state!==r.state||n.status!==r.status||n.link!==r.link}function Y({message:e}){return c(`div`,{className:`config-error`,role:`alert`,children:[s(`h1`,{children:`⚠️ Dashboard misconfigured`}),s(`p`,{children:`The page loaded, but the server did not hand it usable configuration.`}),s(`pre`,{children:e})]})}var X=document.getElementById(`root`);if(!X)throw Error(`No #root element in the document — the template is not the one we expect.`);var Z=a(X);try{Z.render(s(t,{children:s(W,{env:b()})}))}catch(e){Z.render(s(Y,{message:e instanceof Error?e.message:String(e)}))}
3
+ //# sourceMappingURL=index-BCH1wUwb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BCH1wUwb.js","names":[],"sources":["../../../shared/dist/index.js","../../src/Diagnostics.tsx","../../src/diagnostics.ts","../../src/ObservedAt.tsx","../../src/panel-layout.ts","../../src/HttpValuePanel.tsx","../../src/PanelPlaceholder.tsx","../../src/PipelinePanel.tsx","../../src/App.tsx","../../src/ConfigError.tsx","../../src/main.tsx"],"sourcesContent":["// packages/shared/src/board-config.ts\nimport { z as z2 } from \"zod\";\n\n// packages/shared/src/duration.ts\nimport { z } from \"zod\";\nvar DURATION_PATTERN = /^(\\d+)(ms|s|m|h)$/;\nvar UNIT_MILLIS = {\n ms: 1,\n s: 1e3,\n m: 6e4,\n h: 36e5\n};\nfunction parseDuration(value) {\n const match = DURATION_PATTERN.exec(value);\n if (!match) return null;\n const [, rawAmount, rawUnit] = match;\n if (rawAmount === void 0 || rawUnit === void 0) return null;\n const amount = Number(rawAmount);\n if (amount <= 0) return null;\n return amount * UNIT_MILLIS[rawUnit];\n}\nvar durationSchema = z.string().refine((value) => parseDuration(value) !== null, {\n message: 'must be a positive duration like \"30s\", \"5m\", or \"1h\"'\n}).brand();\n\n// packages/shared/src/board-config.ts\nvar positionSchema = z2.object({\n x: z2.number().int().min(0),\n y: z2.number().int().min(0),\n w: z2.number().int().min(1).max(12),\n h: z2.number().int().min(1)\n});\nvar panelSchema = z2.looseObject({\n id: z2.string().min(1),\n type: z2.string().min(1),\n source: z2.string().min(1).optional(),\n /** Advisory in v1 — a panel without a position renders in config order rather than not at all. */\n position: positionSchema.optional(),\n refresh: durationSchema.optional(),\n /** A deliberate override only. Adapters derive links; hand-written ones drift. */\n link: z2.url().optional(),\n /** Source-agnostic endpoint used by the http-value signal. */\n url: z2.url().optional(),\n /** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */\n json_path: z2.string().regex(/^\\$(?:\\.[A-Za-z_][A-Za-z0-9_]*|\\[\\d+\\])*$/, \"must be a simple JSON path\").optional()\n});\nvar sourceSchema = z2.looseObject({\n type: z2.string().min(1),\n /** Credentials are never in the config — only the name of the env var holding one. */\n token_env: z2.string().min(1).optional()\n});\nvar boardSchema = z2.object({\n refresh: durationSchema.optional(),\n panels: z2.array(panelSchema).min(1).superRefine((panels, ctx) => {\n const seen = /* @__PURE__ */ new Map();\n panels.forEach((panel, index) => {\n const firstIndex = seen.get(panel.id);\n if (firstIndex === void 0) {\n seen.set(panel.id, index);\n return;\n }\n ctx.addIssue({\n code: \"custom\",\n path: [index, \"id\"],\n message: `duplicate panel id \"${panel.id}\" (already used by panel at index ${firstIndex})`\n });\n });\n })\n});\nvar authSchema = z2.object({\n issuer: z2.url(),\n /** Absent in gateway mode — the gateway does the flow, the proxy still validates. */\n client_id: z2.string().min(1).optional(),\n allow: z2.object({\n groups: z2.array(z2.string().min(1)).optional(),\n subjects: z2.array(z2.string().min(1)).optional()\n }).optional()\n});\nvar boardConfigSchema = z2.object({\n sources: z2.record(z2.string().min(1), sourceSchema).default({}),\n boards: z2.record(z2.string().min(1), boardSchema),\n auth: authSchema.optional()\n});\nfunction resolveRefreshMillis(args) {\n const requested = args.panelOverrideMillis ?? args.boardDefaultMillis;\n return Math.max(requested, args.adapterFloorMillis);\n}\n\n// packages/shared/src/client-env.ts\nimport { z as z3 } from \"zod\";\nvar clientEnvSchema = z3.object({\n /** Where the immutable client assets live. The one variable that repoints the whole client. */\n assetPath: z3.string().min(1),\n /** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */\n proxyPath: z3.string().min(1),\n /** Which board this is, resolved server-side so the client parses no URLs. */\n board: z3.string().min(1),\n /** The client version being served, so two published versions are visibly distinguishable. */\n clientVersion: z3.string().min(1),\n /** Omitted entirely when unauthenticated — presence is what turns auth on. */\n auth: z3.object({\n issuer: z3.string().min(1),\n clientId: z3.string().min(1).optional()\n }).optional()\n});\nfunction readClientEnv(source = globalThis.window?.env) {\n const result = clientEnvSchema.safeParse(source);\n if (!result.success) {\n throw new Error(\n `window.env is missing or invalid \\u2014 the server did not inject usable configuration.\n${z3.prettifyError(result.error)}`\n );\n }\n return result.data;\n}\n\n// packages/shared/src/envelope.ts\nimport { z as z4 } from \"zod\";\nvar errorKindSchema = z4.enum([\n \"unreachable\",\n \"unauthorized\",\n \"not-found\",\n \"no-runs\",\n \"upstream-error\"\n]);\nvar envelopeErrorSchema = z4.object({\n kind: errorKindSchema,\n message: z4.string()\n});\nvar envelopeSchema = z4.discriminatedUnion(\"state\", [\n z4.object({\n panelId: z4.string().min(1),\n state: z4.literal(\"ok\"),\n /** From the upstream response `Date`. A cached value must never be shown without its age. */\n observedAt: z4.iso.datetime(),\n /** Adapter-derived. The authority for this panel; the dashboard itself asserts nothing. */\n link: z4.url().nullable(),\n signal: z4.unknown()\n }),\n z4.object({\n panelId: z4.string().min(1),\n state: z4.literal(\"error\"),\n /**\n * Populated even on error: knowing *when* a failure was observed is the difference between\n * \"broken just now\" and \"broken all morning\", and a panel that can't reach its source is\n * exactly when a viewer most wants to click through.\n */\n observedAt: z4.iso.datetime(),\n link: z4.url().nullable(),\n error: envelopeErrorSchema\n })\n]);\nvar pipelineStatusSchema = z4.object({\n type: z4.literal(\"pipeline-status\"),\n status: z4.enum([\"passed\", \"failed\", \"running\", \"cancelled\", \"unknown\"]),\n /** The source's unmodified status/result vocabulary, for an honest display. */\n rawStatus: z4.string(),\n name: z4.string().min(1),\n /** The branch represented by this status, when the upstream supplies or filters by one. */\n branch: z4.string().min(1).optional(),\n /** Elapsed execution time, supplied only after the upstream run has completed. */\n durationMs: z4.number().int().nonnegative().optional(),\n /** When this workflow run was last updated by its source, distinct from our observation time. */\n sourceUpdatedAt: z4.iso.datetime().optional()\n});\nvar httpValueSchema = z4.object({\n type: z4.literal(\"http-value\"),\n value: z4.union([z4.string(), z4.number(), z4.boolean()])\n});\nexport {\n authSchema,\n boardConfigSchema,\n boardSchema,\n clientEnvSchema,\n durationSchema,\n envelopeSchema,\n errorKindSchema,\n httpValueSchema,\n panelSchema,\n parseDuration,\n pipelineStatusSchema,\n positionSchema,\n readClientEnv,\n resolveRefreshMillis,\n sourceSchema\n};\n","import { useEffect, useState } from 'react'\nimport type { DiagnosticLog } from './diagnostics.ts'\n\nexport function Diagnostics({ log }: { log: DiagnosticLog }) {\n const [open, setOpen] = useState(false)\n const [count, setCount] = useState(log.count())\n useEffect(() => log.subscribe(() => setCount(log.count())), [log])\n const download = () => {\n const blob = new Blob([JSON.stringify(log.export(), null, 2)], { type: 'application/json' })\n const url = URL.createObjectURL(blob)\n const link = document.createElement('a')\n link.href = url\n link.download = `dashboard-diagnostics-${new Date().toISOString().replaceAll(':', '-')}.json`\n link.click()\n URL.revokeObjectURL(url)\n }\n const clear = () => {\n if (!window.confirm('Clear this browser’s retained dashboard diagnostics?')) return\n log.clear()\n }\n\n return (\n <section className=\"diagnostics\">\n <button\n className=\"diagnostics__toggle\"\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n >\n Diagnostics ({count})\n </button>\n {open && (\n <div className=\"diagnostics__area\">\n <p>{count} retained browser-local events. They are never uploaded.</p>\n <div className=\"diagnostics__actions\">\n <button type=\"button\" onClick={download}>\n Download\n </button>\n <button type=\"button\" onClick={clear}>\n Clear\n </button>\n </div>\n </div>\n )}\n </section>\n )\n}\n","import type { ClientEnv, Envelope } from '@ze-great-dashboard/shared'\n\nconst storageKey = 'ze-great-dashboard.diagnostics.v1'\nexport const diagnosticsSchemaVersion = 1\nconst maximumEvents = 2_000\nconst maximumAgeMillis = 7 * 24 * 60 * 60 * 1_000\nconst diagnosticKinds = [\n 'session-start',\n 'board-fetch-start',\n 'board-fetch-response',\n 'board-fetch-parse-failure',\n 'board-fetch-failure',\n 'panel-fetch-start',\n 'panel-fetch-response',\n 'panel-fetch-parse-failure',\n 'panel-fetch-failure',\n 'panel-rendered',\n] as const\n\ntype CacheMetadata = Record<'cacheControl' | 'etag' | 'lastModified' | 'date' | 'age', string>\n\nexport type DiagnosticEvent = {\n at: string\n sessionId: string\n board: string\n kind:\n | 'session-start'\n | 'board-fetch-start'\n | 'board-fetch-response'\n | 'board-fetch-parse-failure'\n | 'board-fetch-failure'\n | 'panel-fetch-start'\n | 'panel-fetch-response'\n | 'panel-fetch-parse-failure'\n | 'panel-fetch-failure'\n | 'panel-rendered'\n panelId?: string\n path?: string\n status?: number\n cache?: CacheMetadata\n envelope?: Envelope\n rendered?: { state: Envelope['state']; status?: string; link: string | null }\n message?: string\n boardSummary?: { panelCount: number; panelIds: string[] }\n}\n\ntype StoredDiagnostics = { schemaVersion: number; events: DiagnosticEvent[] }\n\ntype StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>\n\nexport class DiagnosticLog {\n private events: DiagnosticEvent[] = []\n private readonly listeners = new Set<() => void>()\n private readonly sessionId = crypto.randomUUID?.() ?? `${Date.now()}-${Math.random()}`\n\n constructor(\n private readonly env: ClientEnv,\n private readonly storage: StorageLike | undefined = storageOrUndefined(),\n private readonly now: () => Date = () => new Date(),\n ) {\n this.events = this.read()\n this.record({ kind: 'session-start' })\n }\n\n record(event: Omit<DiagnosticEvent, 'at' | 'sessionId' | 'board'>) {\n this.events = prune(\n [\n ...this.events,\n {\n ...event,\n at: this.now().toISOString(),\n sessionId: this.sessionId,\n board: this.env.board,\n },\n ],\n this.now(),\n )\n this.persist()\n this.notify()\n }\n\n count() {\n return this.events.length\n }\n\n clear() {\n this.events = []\n try {\n this.storage?.removeItem(storageKey)\n } catch {\n // Diagnostics must not interrupt the radiator when browser storage is unavailable.\n }\n this.notify()\n }\n\n subscribe(listener: () => void) {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n export() {\n return {\n schemaVersion: diagnosticsSchemaVersion,\n exportedAt: this.now().toISOString(),\n client: {\n version: this.env.clientVersion,\n assetPath: this.env.assetPath,\n board: this.env.board,\n sessionId: this.sessionId,\n },\n events: this.events,\n }\n }\n\n private read(): DiagnosticEvent[] {\n try {\n const raw = this.storage?.getItem(storageKey)\n if (!raw) return []\n const parsed = JSON.parse(raw) as Partial<StoredDiagnostics>\n if (parsed.schemaVersion !== diagnosticsSchemaVersion || !Array.isArray(parsed.events)) {\n this.storage?.removeItem(storageKey)\n return []\n }\n if (!parsed.events.every(isDiagnosticEvent)) {\n this.storage?.removeItem(storageKey)\n return []\n }\n return prune(parsed.events, this.now())\n } catch {\n try {\n this.storage?.removeItem(storageKey)\n } catch {\n // Storage can fail on both reads and writes (for example, private browsing policies).\n }\n return []\n }\n }\n\n private persist() {\n try {\n this.storage?.setItem(\n storageKey,\n JSON.stringify({ schemaVersion: diagnosticsSchemaVersion, events: this.events }),\n )\n } catch {\n // Keep the in-memory record for this page, but never make diagnostics a rendering failure.\n }\n }\n\n private notify() {\n for (const listener of this.listeners) listener()\n }\n}\n\nexport function cacheMetadata(headers: Headers): CacheMetadata | undefined {\n const entries = [\n ['cacheControl', 'cache-control'],\n ['etag', 'etag'],\n ['lastModified', 'last-modified'],\n ['date', 'date'],\n ['age', 'age'],\n ] as const\n const metadata = Object.fromEntries(\n entries.flatMap(([key, header]) => {\n const value = headers.get(header)\n return value ? [[key, value]] : []\n }),\n ) as CacheMetadata\n return Object.keys(metadata).length ? metadata : undefined\n}\n\nfunction prune(events: DiagnosticEvent[], now: Date) {\n const cutoff = now.valueOf() - maximumAgeMillis\n return events\n .filter(\n (event) =>\n Number.isFinite(new Date(event.at).valueOf()) && new Date(event.at).valueOf() >= cutoff,\n )\n .slice(-maximumEvents)\n}\n\nfunction isDiagnosticEvent(value: unknown): value is DiagnosticEvent {\n if (!value || typeof value !== 'object') return false\n const event = value as Partial<DiagnosticEvent>\n return (\n typeof event.at === 'string' &&\n Number.isFinite(new Date(event.at).valueOf()) &&\n typeof event.sessionId === 'string' &&\n typeof event.board === 'string' &&\n diagnosticKinds.includes(event.kind as (typeof diagnosticKinds)[number])\n )\n}\n\nfunction storageOrUndefined(): StorageLike | undefined {\n try {\n return window.localStorage\n } catch {\n return undefined\n }\n}\n","export function ObservedAt({ value, label = 'As of' }: { value: string; label?: string }) {\n const observed = new Date(value)\n const formatted = observed.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n const age = Date.now() - observed.getTime()\n const stale = age > 5 * 60 * 1000\n const ageText = formatAge(age)\n return (\n <p className={`panel__hint panel__observed${stale ? ' panel__observed--stale' : ''}`}>\n <span aria-hidden=\"true\">◷</span> {label} {formatted} · {ageText}\n </p>\n )\n}\n\nexport function formatAge(milliseconds: number) {\n if (milliseconds < 60_000) return 'just now'\n const minutes = Math.floor(milliseconds / 60_000)\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n return `${hours}h ${minutes % 60}m ago`\n}\n","import type { Panel } from '@ze-great-dashboard/shared'\nimport type { CSSProperties } from 'react'\n\n/** Convert the advisory board position into CSS grid placement. */\nexport function panelLayout(panel: Panel): CSSProperties | undefined {\n const position = panel.position\n if (!position) return undefined\n\n return {\n '--panel-column': `${position.x + 1} / span ${position.w}`,\n '--panel-row': `${position.y + 1} / span ${position.h}`,\n } as CSSProperties\n}\n","import type { Panel } from '@ze-great-dashboard/shared'\nimport { type Envelope, httpValueSchema } from '@ze-great-dashboard/shared'\nimport { ObservedAt } from './ObservedAt.tsx'\nimport { panelLayout } from './panel-layout.ts'\n\nexport function HttpValuePanel({ panel, data }: { panel: Panel; data: Envelope | undefined }) {\n if (!data)\n return (\n <section className=\"panel\" style={panelLayout(panel)} aria-busy=\"true\">\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__hint\">Loading…</p>\n </section>\n )\n if (data.state === 'error')\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Unable to read</p>\n <p className=\"panel__hint\">{data.error.message}</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n const signal = httpValueSchema.safeParse(data.signal)\n if (!signal.success)\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Invalid value</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n const content = (\n <>\n <p className=\"panel__status\">{String(signal.data.value)}</p>\n <ObservedAt value={data.observedAt} />\n </>\n )\n return (\n <section className=\"panel\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n {data.link ? (\n <a className=\"panel__link\" href={data.link}>\n {content}\n </a>\n ) : (\n content\n )}\n </section>\n )\n}\n","/**\n * A panel-shaped hole, so the grid can be looked at and argued with before any signal exists.\n * Replaced by real panels in Stage 2 — nothing here is meant to survive.\n */\nexport function PanelPlaceholder({\n label,\n hint,\n wide = false,\n}: {\n label: string\n hint: string\n wide?: boolean\n}) {\n return (\n <section className={wide ? 'panel panel--wide' : 'panel'}>\n <h2 className=\"panel__label\">{label}</h2>\n <p className=\"panel__hint\">{hint}</p>\n </section>\n )\n}\n","import {\n type Envelope,\n envelopeSchema,\n type Panel,\n pipelineStatusSchema,\n} from '@ze-great-dashboard/shared'\nimport { ObservedAt } from './ObservedAt.tsx'\nimport { panelLayout } from './panel-layout.ts'\n\nexport function PipelinePanel({ panel, data }: { panel: Panel; data: Envelope | undefined }) {\n if (!data)\n return (\n <section className=\"panel\" style={panelLayout(panel)} aria-busy=\"true\">\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__hint\">Loading…</p>\n </section>\n )\n if (data.state === 'error') {\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">\n ⚠ {data.error.kind === 'no-runs' ? 'No workflow runs' : 'Unable to read'}\n </p>\n <p className=\"panel__hint\">{data.error.message}</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n }\n\n const signal = pipelineStatusSchema.safeParse(data.signal)\n if (!signal.success)\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Invalid signal</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n const presentation = statusPresentation(signal.data.status)\n const content = (\n <>\n <p className={`panel__status panel__status--${signal.data.status}`}>\n {presentation.glyph} {presentation.label}\n </p>\n <p className=\"panel__hint\">\n {signal.data.name} · {signal.data.rawStatus}\n {signal.data.branch && (\n <span className=\"panel__branch\" title={`Branch: ${signal.data.branch}`}>\n <span aria-hidden=\"true\"> · ⎇ </span>\n <span className=\"screen-reader-only\">Branch: </span>\n {signal.data.branch}\n </span>\n )}\n </p>\n {signal.data.status !== 'running' && signal.data.durationMs !== undefined && (\n <p className=\"panel__hint\">Took {formatDuration(signal.data.durationMs)}</p>\n )}\n {signal.data.sourceUpdatedAt && (\n <ObservedAt value={signal.data.sourceUpdatedAt} label=\"Run updated\" />\n )}\n <ObservedAt value={data.observedAt} />\n </>\n )\n return (\n <section className=\"panel\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n {data.link ? (\n <a className=\"panel__link\" href={data.link}>\n {content}\n </a>\n ) : (\n content\n )}\n </section>\n )\n}\n\nfunction formatDuration(durationMs: number) {\n const totalSeconds = Math.floor(durationMs / 1_000)\n const hours = Math.floor(totalSeconds / 3_600)\n const minutes = Math.floor((totalSeconds % 3_600) / 60)\n const seconds = totalSeconds % 60\n\n if (hours > 0) return `${hours}h ${minutes}m`\n if (minutes > 0) return `${minutes}m ${seconds}s`\n return `${seconds}s`\n}\n\nfunction statusPresentation(status: 'passed' | 'failed' | 'running' | 'cancelled' | 'unknown') {\n switch (status) {\n case 'passed':\n return { glyph: '✓', label: 'Passed' }\n case 'failed':\n return { glyph: '✕', label: 'Failed' }\n case 'running':\n return { glyph: '↻', label: 'Running' }\n case 'cancelled':\n return { glyph: '⊘', label: 'Cancelled' }\n case 'unknown':\n return { glyph: '?', label: 'Unknown' }\n }\n}\n\nexport function parseEnvelope(value: unknown): Envelope | undefined {\n const result = envelopeSchema.safeParse(value)\n return result.success ? result.data : undefined\n}\n","import {\n type Board,\n type ClientEnv,\n type Envelope,\n parseDuration,\n pipelineStatusSchema,\n resolveRefreshMillis,\n} from '@ze-great-dashboard/shared'\nimport { useEffect, useRef, useState } from 'react'\nimport { Diagnostics } from './Diagnostics.tsx'\nimport { cacheMetadata, DiagnosticLog } from './diagnostics.ts'\nimport { HttpValuePanel } from './HttpValuePanel.tsx'\nimport { PanelPlaceholder } from './PanelPlaceholder.tsx'\nimport { PipelinePanel, parseEnvelope } from './PipelinePanel.tsx'\n\n/**\n * The board shell.\n *\n * Stage 1 has no data: there is no proxy endpoint to poll yet. What this proves is the plumbing —\n * that the client was handed its configuration by the server and knows which version of itself it\n * is. The version readout is what makes two published versions visibly different, which is the\n * whole point of the Stage 1 exit criterion.\n */\nexport function App({ env }: { env: ClientEnv }) {\n const [board, setBoard] = useState<Board>()\n const [loadedBoardName, setLoadedBoardName] = useState<string>()\n const [signals, setSignals] = useState<Record<string, Envelope | undefined>>({})\n const diagnosticsRef = useRef<DiagnosticLog | null>(null)\n const signalsRef = useRef<Record<string, Envelope | undefined>>({})\n if (!diagnosticsRef.current) diagnosticsRef.current = new DiagnosticLog(env)\n const diagnostics = diagnosticsRef.current\n\n useEffect(() => {\n let cancelled = false\n setBoard(undefined)\n setLoadedBoardName(undefined)\n setSignals({})\n signalsRef.current = {}\n const path = `${env.proxyPath}/boards/${encodeURIComponent(env.board)}`\n diagnostics.record({ kind: 'board-fetch-start', path })\n fetch(path)\n .then(async (response) => {\n diagnostics.record({\n kind: 'board-fetch-response',\n path,\n status: response.status,\n cache: cacheMetadata(response.headers),\n })\n if (!response.ok) throw new Error(`Board configuration returned ${response.status}`)\n try {\n return await response.json()\n } catch (error) {\n diagnostics.record({\n kind: 'board-fetch-parse-failure',\n path,\n message: errorMessage(error),\n })\n throw new DiagnosticParseFailure(error)\n }\n })\n .then((value: unknown) => {\n if (!cancelled) {\n setBoard(value as Board)\n setLoadedBoardName(env.board)\n const panels = Array.isArray((value as { panels?: unknown }).panels)\n ? (value as { panels: Array<{ id?: unknown }> }).panels\n : []\n diagnostics.record({\n kind: 'board-fetch-response',\n path,\n boardSummary: {\n panelCount: panels.length,\n panelIds: panels.flatMap((panel) => (typeof panel.id === 'string' ? [panel.id] : [])),\n },\n })\n }\n })\n .catch((error) => {\n if (!(error instanceof DiagnosticParseFailure)) {\n diagnostics.record({ kind: 'board-fetch-failure', path, message: errorMessage(error) })\n }\n if (!cancelled) {\n setBoard({ panels: [] })\n setLoadedBoardName(env.board)\n }\n })\n return () => {\n cancelled = true\n }\n }, [diagnostics, env.board, env.proxyPath])\n\n useEffect(() => {\n if (!board || loadedBoardName !== env.board) return\n let cancelled = false\n const timers: number[] = []\n\n for (const panel of board.panels) {\n if (panel.type !== 'pipeline-status' && panel.type !== 'http-value') continue\n\n let inFlight = false\n const refreshMillis = resolveRefreshMillis({\n boardDefaultMillis: parseDuration(board.refresh ?? '60s') ?? 60_000,\n panelOverrideMillis: panel.refresh\n ? (parseDuration(panel.refresh) ?? undefined)\n : undefined,\n adapterFloorMillis: 0,\n })\n\n const refresh = () => {\n if (cancelled || inFlight) return\n inFlight = true\n const path = `${env.proxyPath}/panel/${encodeURIComponent(env.board)}/${encodeURIComponent(panel.id)}`\n diagnostics.record({ kind: 'panel-fetch-start', panelId: panel.id, path })\n fetch(path)\n .then(async (response) => {\n diagnostics.record({\n kind: 'panel-fetch-response',\n panelId: panel.id,\n path,\n status: response.status,\n cache: cacheMetadata(response.headers),\n })\n if (response.status === 304) return undefined\n try {\n return await response.json()\n } catch (error) {\n diagnostics.record({\n kind: 'panel-fetch-parse-failure',\n panelId: panel.id,\n path,\n message: errorMessage(error),\n })\n throw new DiagnosticParseFailure(error)\n }\n })\n .then((value: unknown) => {\n const envelope = parseEnvelope(value)\n if (value !== undefined && !envelope) {\n diagnostics.record({\n kind: 'panel-fetch-parse-failure',\n panelId: panel.id,\n path,\n message: 'Response was not a valid signal envelope.',\n })\n }\n if (!cancelled && envelope) {\n diagnostics.record({\n kind: 'panel-fetch-response',\n panelId: panel.id,\n path,\n envelope,\n })\n const previous = signalsRef.current[panel.id]\n if (renderedChanged(previous, envelope)) {\n diagnostics.record({\n kind: 'panel-rendered',\n panelId: panel.id,\n path,\n rendered: renderedState(envelope),\n })\n }\n signalsRef.current = { ...signalsRef.current, [panel.id]: envelope }\n setSignals(signalsRef.current)\n }\n })\n .catch((error) => {\n if (!(error instanceof DiagnosticParseFailure)) {\n diagnostics.record({\n kind: 'panel-fetch-failure',\n panelId: panel.id,\n path,\n message: errorMessage(error),\n })\n }\n })\n .finally(() => {\n inFlight = false\n })\n }\n\n refresh()\n timers.push(window.setInterval(refresh, refreshMillis))\n }\n return () => {\n cancelled = true\n for (const timer of timers) window.clearInterval(timer)\n }\n }, [board, diagnostics, env.board, env.proxyPath, loadedBoardName])\n\n return (\n <div className=\"board\">\n <header className=\"board__header\">\n <h1 className=\"board__title\">{env.board}</h1>\n <dl className=\"board__meta\">\n <div>\n <dt>client</dt>\n <dd>{env.clientVersion}</dd>\n </div>\n <div>\n <dt>assets</dt>\n <dd>{env.assetPath}</dd>\n </div>\n </dl>\n </header>\n\n <main className=\"board__grid\">\n {!board && <PanelPlaceholder label=\"board\" hint=\"Loading configuration…\" wide />}\n {board?.panels.map((panel) =>\n panel.type === 'pipeline-status' ? (\n <PipelinePanel key={panel.id} panel={panel} data={signals[panel.id]} />\n ) : panel.type === 'http-value' ? (\n <HttpValuePanel key={panel.id} panel={panel} data={signals[panel.id]} />\n ) : (\n <PanelPlaceholder key={panel.id} label={panel.type} hint=\"Not wired yet\" wide />\n ),\n )}\n </main>\n\n <footer className=\"board__footer\">\n <span>Signals are read live from their configured authorities.</span>\n <Diagnostics log={diagnostics} />\n </footer>\n </div>\n )\n}\n\nfunction errorMessage(error: unknown) {\n return error instanceof Error ? error.message : String(error)\n}\n\nclass DiagnosticParseFailure extends Error {\n constructor(error: unknown) {\n super(errorMessage(error))\n }\n}\n\nfunction renderedState(envelope: Envelope) {\n if (envelope.state === 'error') return { state: envelope.state, link: envelope.link }\n const signal = pipelineStatusSchema.safeParse(envelope.signal)\n return {\n state: envelope.state,\n status: signal.success ? signal.data.status : undefined,\n link: envelope.link,\n }\n}\n\nfunction renderedChanged(previous: Envelope | undefined, next: Envelope) {\n if (!previous) return true\n const before = renderedState(previous)\n const after = renderedState(next)\n return (\n before.state !== after.state || before.status !== after.status || before.link !== after.link\n )\n}\n","export function ConfigError({ message }: { message: string }) {\n return (\n <div className=\"config-error\" role=\"alert\">\n <h1>⚠️ Dashboard misconfigured</h1>\n <p>The page loaded, but the server did not hand it usable configuration.</p>\n <pre>{message}</pre>\n </div>\n )\n}\n","import { readClientEnv } from '@ze-great-dashboard/shared'\nimport { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { App } from './App.tsx'\nimport { ConfigError } from './ConfigError.tsx'\nimport './styles.css'\n\nconst container = document.getElementById('root')\nif (!container) {\n throw new Error('No #root element in the document — the template is not the one we expect.')\n}\n\nconst root = createRoot(container)\n\ntry {\n root.render(\n <StrictMode>\n <App env={readClientEnv()} />\n </StrictMode>,\n )\n} catch (error) {\n // A radiator that fails silently is worse than one that says what's wrong. If configuration\n // never arrived, say so on screen rather than rendering an empty board.\n root.render(<ConfigError message={error instanceof Error ? error.message : String(error)} />)\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- :root{--surface:#12140f;--surface-raised:#1a1a19;--ink-primary:#fff;--ink-secondary:#c3c2b7;--ink-muted:#898781;--edge:#2f312b;--status-good:#0ca30c;--status-warning:#fab219;--status-serious:#ec835a;--status-critical:#d03b3b;--gap:1rem;--radius:.5rem;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}.screen-reader-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}html,body{height:100%;margin:0}body{background:var(--surface);color:var(--ink-primary);font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Helvetica,Arial,sans-serif;font-size:clamp(16px,1.1vw + .5rem,28px);line-height:1.4}.board{gap:var(--gap);min-height:100%;padding:var(--gap);flex-direction:column;display:flex}.board__header{justify-content:space-between;align-items:baseline;gap:var(--gap);border-bottom:1px solid var(--edge);padding-bottom:var(--gap);flex-wrap:wrap;display:flex}.board__title{letter-spacing:-.01em;margin:0;font-size:1.75em;font-weight:650}.board__meta{gap:calc(var(--gap) * 1.5);flex-wrap:wrap;margin:0;font-size:.7em;display:flex}.board__meta div{flex-direction:column;display:flex}.board__meta dt{color:var(--ink-muted);text-transform:uppercase;letter-spacing:.08em;font-size:.8em}.board__meta dd{color:var(--ink-secondary);overflow-wrap:anywhere;margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.board__grid{gap:var(--gap);flex:1;grid-template-columns:repeat(12,1fr);align-content:start;display:grid}.panel{grid-column:var(--panel-column,span 6);grid-row:var(--panel-row,auto);min-height:6em;padding:var(--gap);border:1px solid var(--edge);border-radius:var(--radius);background:var(--surface-raised);flex-direction:column;justify-content:center;gap:.25em;display:flex;overflow:hidden}.panel--wide{grid-column:span 12}.panel__label{color:var(--ink-secondary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1em;font-weight:600}.panel__hint{color:var(--ink-muted);margin:0;font-size:.75em}.panel__branch{white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.panel__observed{align-items:center;gap:.35em;display:flex}.panel__observed--stale{color:var(--status-warning)}.panel__status{margin:0;font-size:1.35em;font-weight:650}.panel__status--passed{color:var(--status-good)}.panel__status--failed{color:var(--status-critical)}.panel__status--running{color:var(--status-warning)}.panel__status--cancelled,.panel__status--unknown{color:var(--ink-secondary)}.panel--error{border-color:var(--status-serious)}.panel__link{color:inherit;text-decoration:none}.panel__link:hover .panel__status{text-decoration:underline}.board__footer{color:var(--ink-muted);font-size:.75em}.config-error{max-width:60ch;color:var(--ink-primary);margin:0 auto;padding:2rem}.config-error pre{border-radius:var(--radius);border:1px solid var(--status-critical);background:var(--surface-raised);color:var(--ink-secondary);white-space:pre-wrap;overflow-wrap:anywhere;padding:1rem;font-size:.75em}@media (width<=60rem){.board__grid{grid-template-columns:1fr}.panel{grid-area:auto/1/auto/-1}}
1
+ :root{--surface:#12140f;--surface-raised:#1a1a19;--ink-primary:#fff;--ink-secondary:#c3c2b7;--ink-muted:#898781;--edge:#2f312b;--status-good:#0ca30c;--status-warning:#fab219;--status-serious:#ec835a;--status-critical:#d03b3b;--gap:1rem;--radius:.5rem;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}.screen-reader-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}html,body{height:100%;margin:0}body{background:var(--surface);color:var(--ink-primary);font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Helvetica,Arial,sans-serif;font-size:clamp(16px,1.1vw + .5rem,28px);line-height:1.4}.board{gap:var(--gap);min-height:100%;padding:var(--gap);flex-direction:column;display:flex}.board__header{justify-content:space-between;align-items:baseline;gap:var(--gap);border-bottom:1px solid var(--edge);padding-bottom:var(--gap);flex-wrap:wrap;display:flex}.board__title{letter-spacing:-.01em;margin:0;font-size:1.75em;font-weight:650}.board__meta{gap:calc(var(--gap) * 1.5);flex-wrap:wrap;margin:0;font-size:.7em;display:flex}.board__meta div{flex-direction:column;display:flex}.board__meta dt{color:var(--ink-muted);text-transform:uppercase;letter-spacing:.08em;font-size:.8em}.board__meta dd{color:var(--ink-secondary);overflow-wrap:anywhere;margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.board__grid{gap:var(--gap);flex:1;grid-template-columns:repeat(12,1fr);align-content:start;display:grid}.panel{grid-column:var(--panel-column,span 6);grid-row:var(--panel-row,auto);min-height:6em;padding:var(--gap);border:1px solid var(--edge);border-radius:var(--radius);background:var(--surface-raised);flex-direction:column;justify-content:center;gap:.25em;display:flex;overflow:hidden}.panel--wide{grid-column:span 12}.panel__label{color:var(--ink-secondary);margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1em;font-weight:600}.panel__hint{color:var(--ink-muted);margin:0;font-size:.75em}.panel__branch{white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.panel__observed{align-items:center;gap:.35em;display:flex}.panel__observed--stale{color:var(--status-warning)}.panel__status{margin:0;font-size:1.35em;font-weight:650}.panel__status--passed{color:var(--status-good)}.panel__status--failed{color:var(--status-critical)}.panel__status--running{color:var(--status-warning)}.panel__status--cancelled,.panel__status--unknown{color:var(--ink-secondary)}.panel--error{border-color:var(--status-serious)}.panel__link{color:inherit;text-decoration:none}.panel__link:hover .panel__status{text-decoration:underline}.board__footer{color:var(--ink-muted);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:.5rem;font-size:.75em;display:flex}.diagnostics{position:relative}.diagnostics button{border:1px solid var(--edge);background:var(--surface-raised);color:var(--ink-secondary);font:inherit;border-radius:.25rem;padding:.3em .55em}.diagnostics__area{z-index:1;border:1px solid var(--edge);border-radius:var(--radius);background:var(--surface-raised);width:min(24rem,90vw);padding:.75rem;position:absolute;bottom:calc(100% + .5rem);right:0;box-shadow:0 .5rem 1.5rem #0008}.diagnostics__area p{margin:0 0 .5rem}.diagnostics__actions{gap:.5rem;display:flex}.config-error{max-width:60ch;color:var(--ink-primary);margin:0 auto;padding:2rem}.config-error pre{border-radius:var(--radius);border:1px solid var(--status-critical);background:var(--surface-raised);color:var(--ink-secondary);white-space:pre-wrap;overflow-wrap:anywhere;padding:1rem;font-size:.75em}@media (width<=60rem){.board__grid{grid-template-columns:1fr}.panel{grid-area:auto/1/auto/-1}}
package/client/index.html CHANGED
@@ -11,8 +11,8 @@
11
11
  <title>Trust Dashboard</title>
12
12
 
13
13
  <script type="importmap">{"imports":{"react":"https://esm.sh/react@19.2.8","react/":"https://esm.sh/react@19.2.8/","react-dom":"https://esm.sh/react-dom@19.2.8","react-dom/":"https://esm.sh/react-dom@19.2.8/","scheduler":"https://esm.sh/scheduler@0.27.0","scheduler/":"https://esm.sh/scheduler@0.27.0/","zod":"https://esm.sh/zod@4.4.3","zod/":"https://esm.sh/zod@4.4.3/"}}</script>
14
- <script type="module" crossorigin src="/__ASSET_PATH__/assets/index-DOvxvZo2.js"></script>
15
- <link rel="stylesheet" crossorigin href="/__ASSET_PATH__/assets/index-r7iEInfY.css">
14
+ <script type="module" crossorigin src="/__ASSET_PATH__/assets/index-BCH1wUwb.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/__ASSET_PATH__/assets/index-CwWbINCq.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="root"></div>
package/dist/lambda.mjs CHANGED
@@ -24407,6 +24407,7 @@ async function fetchGithubActionsPipeline(args) {
24407
24407
  rawStatus: run.conclusion ?? run.status,
24408
24408
  name: run.name,
24409
24409
  branch: parsedSource.branch,
24410
+ ...run.updated_at ? { sourceUpdatedAt: run.updated_at } : {},
24410
24411
  ...run.status === "completed" ? completedRunDuration(run) : {}
24411
24412
  };
24412
24413
  const envelope = {
@@ -24893,7 +24894,9 @@ var pipelineStatusSchema = external_exports.object({
24893
24894
  /** The branch represented by this status, when the upstream supplies or filters by one. */
24894
24895
  branch: external_exports.string().min(1).optional(),
24895
24896
  /** Elapsed execution time, supplied only after the upstream run has completed. */
24896
- durationMs: external_exports.number().int().nonnegative().optional()
24897
+ durationMs: external_exports.number().int().nonnegative().optional(),
24898
+ /** When this workflow run was last updated by its source, distinct from our observation time. */
24899
+ sourceUpdatedAt: external_exports.iso.datetime().optional()
24897
24900
  });
24898
24901
  var httpValueSchema = external_exports.object({
24899
24902
  type: external_exports.literal("http-value"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@continuous-excellence/ze-great-dashboard-aws",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "AWS Lambda and CloudFormation adapter for Ze Great Dashboard.",
6
6
  "keywords": [
@@ -1,3 +0,0 @@
1
- import{z as e}from"zod";import{StrictMode as t,useEffect as n,useState as r}from"react";import{createRoot as i}from"react-dom/client";import{Fragment as a,jsx as o,jsxs as s}from"react/jsx-runtime";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var c=/^(\d+)(ms|s|m|h)$/,l={ms:1,s:1e3,m:6e4,h:36e5};function u(e){let t=c.exec(e);if(!t)return null;let[,n,r]=t;if(n===void 0||r===void 0)return null;let i=Number(n);return i<=0?null:i*l[r]}var d=e.string().refine(e=>u(e)!==null,{message:`must be a positive duration like "30s", "5m", or "1h"`}).brand(),f=e.object({x:e.number().int().min(0),y:e.number().int().min(0),w:e.number().int().min(1).max(12),h:e.number().int().min(1)}),p=e.looseObject({id:e.string().min(1),type:e.string().min(1),source:e.string().min(1).optional(),position:f.optional(),refresh:d.optional(),link:e.url().optional(),url:e.url().optional(),json_path:e.string().regex(/^\$(?:\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/,`must be a simple JSON path`).optional()}),m=e.looseObject({type:e.string().min(1),token_env:e.string().min(1).optional()}),h=e.object({refresh:d.optional(),panels:e.array(p).min(1).superRefine((e,t)=>{let n=new Map;e.forEach((e,r)=>{let i=n.get(e.id);if(i===void 0){n.set(e.id,r);return}t.addIssue({code:`custom`,path:[r,`id`],message:`duplicate panel id "${e.id}" (already used by panel at index ${i})`})})})}),g=e.object({issuer:e.url(),client_id:e.string().min(1).optional(),allow:e.object({groups:e.array(e.string().min(1)).optional(),subjects:e.array(e.string().min(1)).optional()}).optional()});e.object({sources:e.record(e.string().min(1),m).default({}),boards:e.record(e.string().min(1),h),auth:g.optional()});function _(e){let t=e.panelOverrideMillis??e.boardDefaultMillis;return Math.max(t,e.adapterFloorMillis)}var v=e.object({assetPath:e.string().min(1),proxyPath:e.string().min(1),board:e.string().min(1),clientVersion:e.string().min(1),auth:e.object({issuer:e.string().min(1),clientId:e.string().min(1).optional()}).optional()});function y(t=globalThis.window?.env){let n=v.safeParse(t);if(!n.success)throw Error(`window.env is missing or invalid \u2014 the server did not inject usable configuration.
2
- ${e.prettifyError(n.error)}`);return n.data}var b=e.enum([`unreachable`,`unauthorized`,`not-found`,`no-runs`,`upstream-error`]),x=e.object({kind:b,message:e.string()}),S=e.discriminatedUnion(`state`,[e.object({panelId:e.string().min(1),state:e.literal(`ok`),observedAt:e.iso.datetime(),link:e.url().nullable(),signal:e.unknown()}),e.object({panelId:e.string().min(1),state:e.literal(`error`),observedAt:e.iso.datetime(),link:e.url().nullable(),error:x})]),C=e.object({type:e.literal(`pipeline-status`),status:e.enum([`passed`,`failed`,`running`,`cancelled`,`unknown`]),rawStatus:e.string(),name:e.string().min(1),branch:e.string().min(1).optional(),durationMs:e.number().int().nonnegative().optional()}),w=e.object({type:e.literal(`http-value`),value:e.union([e.string(),e.number(),e.boolean()])});function T({value:e}){let t=new Date(e),n=t.toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`}),r=Date.now()-t.getTime(),i=r>3e5,a=E(r);return s(`p`,{className:`panel__hint panel__observed${i?` panel__observed--stale`:``}`,children:[o(`span`,{"aria-hidden":`true`,children:`◷`}),` As of `,n,` · `,a]})}function E(e){if(e<6e4)return`just now`;let t=Math.floor(e/6e4);return t<60?`${t}m ago`:`${Math.floor(t/60)}h ${t%60}m ago`}function D(e){let t=e.position;if(t)return{"--panel-column":`${t.x+1} / span ${t.w}`,"--panel-row":`${t.y+1} / span ${t.h}`}}function O({panel:e,data:t}){if(!t)return s(`section`,{className:`panel`,style:D(e),"aria-busy":`true`,children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return s(`section`,{className:`panel panel--error`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__status`,children:`⚠ Unable to read`}),o(`p`,{className:`panel__hint`,children:t.error.message}),o(T,{value:t.observedAt})]});let n=w.safeParse(t.signal);if(!n.success)return s(`section`,{className:`panel panel--error`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__status`,children:`⚠ Invalid value`}),o(T,{value:t.observedAt})]});let r=s(a,{children:[o(`p`,{className:`panel__status`,children:String(n.data.value)}),o(T,{value:t.observedAt})]});return s(`section`,{className:`panel`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),t.link?o(`a`,{className:`panel__link`,href:t.link,children:r}):r]})}function k({label:e,hint:t,wide:n=!1}){return s(`section`,{className:n?`panel panel--wide`:`panel`,children:[o(`h2`,{className:`panel__label`,children:e}),o(`p`,{className:`panel__hint`,children:t})]})}function A({panel:e,data:t}){if(!t)return s(`section`,{className:`panel`,style:D(e),"aria-busy":`true`,children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return s(`section`,{className:`panel panel--error`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),s(`p`,{className:`panel__status`,children:[`⚠ `,t.error.kind===`no-runs`?`No workflow runs`:`Unable to read`]}),o(`p`,{className:`panel__hint`,children:t.error.message}),o(T,{value:t.observedAt})]});let n=C.safeParse(t.signal);if(!n.success)return s(`section`,{className:`panel panel--error`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),o(`p`,{className:`panel__status`,children:`⚠ Invalid signal`}),o(T,{value:t.observedAt})]});let r=M(n.data.status),i=s(a,{children:[s(`p`,{className:`panel__status panel__status--${n.data.status}`,children:[r.glyph,` `,r.label]}),s(`p`,{className:`panel__hint`,children:[n.data.name,` · `,n.data.rawStatus,n.data.branch&&s(`span`,{className:`panel__branch`,title:`Branch: ${n.data.branch}`,children:[o(`span`,{"aria-hidden":`true`,children:` · ⎇ `}),o(`span`,{className:`screen-reader-only`,children:`Branch: `}),n.data.branch]})]}),n.data.status!==`running`&&n.data.durationMs!==void 0&&s(`p`,{className:`panel__hint`,children:[`Took `,j(n.data.durationMs)]}),o(T,{value:t.observedAt})]});return s(`section`,{className:`panel`,style:D(e),children:[o(`h2`,{className:`panel__label`,children:e.id}),t.link?o(`a`,{className:`panel__link`,href:t.link,children:i}):i]})}function j(e){let t=Math.floor(e/1e3),n=Math.floor(t/3600),r=Math.floor(t%3600/60),i=t%60;return n>0?`${n}h ${r}m`:r>0?`${r}m ${i}s`:`${i}s`}function M(e){switch(e){case`passed`:return{glyph:`✓`,label:`Passed`};case`failed`:return{glyph:`✕`,label:`Failed`};case`running`:return{glyph:`↻`,label:`Running`};case`cancelled`:return{glyph:`⊘`,label:`Cancelled`};case`unknown`:return{glyph:`?`,label:`Unknown`}}}function N(e){let t=S.safeParse(e);return t.success?t.data:void 0}function P({env:e}){let[t,i]=r(),[a,c]=r(),[l,d]=r({});return n(()=>{let t=!1;return i(void 0),c(void 0),d({}),fetch(`${e.proxyPath}/boards/${encodeURIComponent(e.board)}`).then(e=>e.ok?e.json():Promise.reject(Error(`Board configuration returned ${e.status}`))).then(n=>{t||(i(n),c(e.board))}).catch(()=>{t||(i({panels:[]}),c(e.board))}),()=>{t=!0}},[e.board,e.proxyPath]),n(()=>{if(!t||a!==e.board)return;let n=!1,r=[];for(let i of t.panels){if(i.type!==`pipeline-status`&&i.type!==`http-value`)continue;let a=!1,o=_({boardDefaultMillis:u(t.refresh??`60s`)??6e4,panelOverrideMillis:i.refresh?u(i.refresh)??void 0:void 0,adapterFloorMillis:0}),s=()=>{n||a||(a=!0,fetch(`${e.proxyPath}/panel/${encodeURIComponent(e.board)}/${encodeURIComponent(i.id)}`).then(e=>e.status===304?void 0:e.json()).then(e=>{let t=N(e);!n&&t&&d(e=>({...e,[i.id]:t}))}).catch(()=>void 0).finally(()=>{a=!1}))};s(),r.push(window.setInterval(s,o))}return()=>{n=!0;for(let e of r)window.clearInterval(e)}},[t,e.board,e.proxyPath,a]),s(`div`,{className:`board`,children:[s(`header`,{className:`board__header`,children:[o(`h1`,{className:`board__title`,children:e.board}),s(`dl`,{className:`board__meta`,children:[s(`div`,{children:[o(`dt`,{children:`client`}),o(`dd`,{children:e.clientVersion})]}),s(`div`,{children:[o(`dt`,{children:`assets`}),o(`dd`,{children:e.assetPath})]})]})]}),s(`main`,{className:`board__grid`,children:[!t&&o(k,{label:`board`,hint:`Loading configuration…`,wide:!0}),t?.panels.map(e=>e.type===`pipeline-status`?o(A,{panel:e,data:l[e.id]},e.id):e.type===`http-value`?o(O,{panel:e,data:l[e.id]},e.id):o(k,{label:e.type,hint:`Not wired yet`,wide:!0},e.id))]}),o(`footer`,{className:`board__footer`,children:`Signals are read live from their configured authorities.`})]})}function F({message:e}){return s(`div`,{className:`config-error`,role:`alert`,children:[o(`h1`,{children:`⚠️ Dashboard misconfigured`}),o(`p`,{children:`The page loaded, but the server did not hand it usable configuration.`}),o(`pre`,{children:e})]})}var I=document.getElementById(`root`);if(!I)throw Error(`No #root element in the document — the template is not the one we expect.`);var L=i(I);try{L.render(o(t,{children:o(P,{env:y()})}))}catch(e){L.render(o(F,{message:e instanceof Error?e.message:String(e)}))}
3
- //# sourceMappingURL=index-DOvxvZo2.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-DOvxvZo2.js","names":[],"sources":["../../../shared/dist/index.js","../../src/ObservedAt.tsx","../../src/panel-layout.ts","../../src/HttpValuePanel.tsx","../../src/PanelPlaceholder.tsx","../../src/PipelinePanel.tsx","../../src/App.tsx","../../src/ConfigError.tsx","../../src/main.tsx"],"sourcesContent":["// packages/shared/src/board-config.ts\nimport { z as z2 } from \"zod\";\n\n// packages/shared/src/duration.ts\nimport { z } from \"zod\";\nvar DURATION_PATTERN = /^(\\d+)(ms|s|m|h)$/;\nvar UNIT_MILLIS = {\n ms: 1,\n s: 1e3,\n m: 6e4,\n h: 36e5\n};\nfunction parseDuration(value) {\n const match = DURATION_PATTERN.exec(value);\n if (!match) return null;\n const [, rawAmount, rawUnit] = match;\n if (rawAmount === void 0 || rawUnit === void 0) return null;\n const amount = Number(rawAmount);\n if (amount <= 0) return null;\n return amount * UNIT_MILLIS[rawUnit];\n}\nvar durationSchema = z.string().refine((value) => parseDuration(value) !== null, {\n message: 'must be a positive duration like \"30s\", \"5m\", or \"1h\"'\n}).brand();\n\n// packages/shared/src/board-config.ts\nvar positionSchema = z2.object({\n x: z2.number().int().min(0),\n y: z2.number().int().min(0),\n w: z2.number().int().min(1).max(12),\n h: z2.number().int().min(1)\n});\nvar panelSchema = z2.looseObject({\n id: z2.string().min(1),\n type: z2.string().min(1),\n source: z2.string().min(1).optional(),\n /** Advisory in v1 — a panel without a position renders in config order rather than not at all. */\n position: positionSchema.optional(),\n refresh: durationSchema.optional(),\n /** A deliberate override only. Adapters derive links; hand-written ones drift. */\n link: z2.url().optional(),\n /** Source-agnostic endpoint used by the http-value signal. */\n url: z2.url().optional(),\n /** Small, deliberate JSON path subset: $.version, $.deployment.version, or $.response.docs[0].latestVersion. */\n json_path: z2.string().regex(/^\\$(?:\\.[A-Za-z_][A-Za-z0-9_]*|\\[\\d+\\])*$/, \"must be a simple JSON path\").optional()\n});\nvar sourceSchema = z2.looseObject({\n type: z2.string().min(1),\n /** Credentials are never in the config — only the name of the env var holding one. */\n token_env: z2.string().min(1).optional()\n});\nvar boardSchema = z2.object({\n refresh: durationSchema.optional(),\n panels: z2.array(panelSchema).min(1).superRefine((panels, ctx) => {\n const seen = /* @__PURE__ */ new Map();\n panels.forEach((panel, index) => {\n const firstIndex = seen.get(panel.id);\n if (firstIndex === void 0) {\n seen.set(panel.id, index);\n return;\n }\n ctx.addIssue({\n code: \"custom\",\n path: [index, \"id\"],\n message: `duplicate panel id \"${panel.id}\" (already used by panel at index ${firstIndex})`\n });\n });\n })\n});\nvar authSchema = z2.object({\n issuer: z2.url(),\n /** Absent in gateway mode — the gateway does the flow, the proxy still validates. */\n client_id: z2.string().min(1).optional(),\n allow: z2.object({\n groups: z2.array(z2.string().min(1)).optional(),\n subjects: z2.array(z2.string().min(1)).optional()\n }).optional()\n});\nvar boardConfigSchema = z2.object({\n sources: z2.record(z2.string().min(1), sourceSchema).default({}),\n boards: z2.record(z2.string().min(1), boardSchema),\n auth: authSchema.optional()\n});\nfunction resolveRefreshMillis(args) {\n const requested = args.panelOverrideMillis ?? args.boardDefaultMillis;\n return Math.max(requested, args.adapterFloorMillis);\n}\n\n// packages/shared/src/client-env.ts\nimport { z as z3 } from \"zod\";\nvar clientEnvSchema = z3.object({\n /** Where the immutable client assets live. The one variable that repoints the whole client. */\n assetPath: z3.string().min(1),\n /** Same-origin path the proxy is mounted at, so there is no CORS and no cookie problem. */\n proxyPath: z3.string().min(1),\n /** Which board this is, resolved server-side so the client parses no URLs. */\n board: z3.string().min(1),\n /** The client version being served, so two published versions are visibly distinguishable. */\n clientVersion: z3.string().min(1),\n /** Omitted entirely when unauthenticated — presence is what turns auth on. */\n auth: z3.object({\n issuer: z3.string().min(1),\n clientId: z3.string().min(1).optional()\n }).optional()\n});\nfunction readClientEnv(source = globalThis.window?.env) {\n const result = clientEnvSchema.safeParse(source);\n if (!result.success) {\n throw new Error(\n `window.env is missing or invalid \\u2014 the server did not inject usable configuration.\n${z3.prettifyError(result.error)}`\n );\n }\n return result.data;\n}\n\n// packages/shared/src/envelope.ts\nimport { z as z4 } from \"zod\";\nvar errorKindSchema = z4.enum([\n \"unreachable\",\n \"unauthorized\",\n \"not-found\",\n \"no-runs\",\n \"upstream-error\"\n]);\nvar envelopeErrorSchema = z4.object({\n kind: errorKindSchema,\n message: z4.string()\n});\nvar envelopeSchema = z4.discriminatedUnion(\"state\", [\n z4.object({\n panelId: z4.string().min(1),\n state: z4.literal(\"ok\"),\n /** From the upstream response `Date`. A cached value must never be shown without its age. */\n observedAt: z4.iso.datetime(),\n /** Adapter-derived. The authority for this panel; the dashboard itself asserts nothing. */\n link: z4.url().nullable(),\n signal: z4.unknown()\n }),\n z4.object({\n panelId: z4.string().min(1),\n state: z4.literal(\"error\"),\n /**\n * Populated even on error: knowing *when* a failure was observed is the difference between\n * \"broken just now\" and \"broken all morning\", and a panel that can't reach its source is\n * exactly when a viewer most wants to click through.\n */\n observedAt: z4.iso.datetime(),\n link: z4.url().nullable(),\n error: envelopeErrorSchema\n })\n]);\nvar pipelineStatusSchema = z4.object({\n type: z4.literal(\"pipeline-status\"),\n status: z4.enum([\"passed\", \"failed\", \"running\", \"cancelled\", \"unknown\"]),\n /** The source's unmodified status/result vocabulary, for an honest display. */\n rawStatus: z4.string(),\n name: z4.string().min(1),\n /** The branch represented by this status, when the upstream supplies or filters by one. */\n branch: z4.string().min(1).optional(),\n /** Elapsed execution time, supplied only after the upstream run has completed. */\n durationMs: z4.number().int().nonnegative().optional()\n});\nvar httpValueSchema = z4.object({\n type: z4.literal(\"http-value\"),\n value: z4.union([z4.string(), z4.number(), z4.boolean()])\n});\nexport {\n authSchema,\n boardConfigSchema,\n boardSchema,\n clientEnvSchema,\n durationSchema,\n envelopeSchema,\n errorKindSchema,\n httpValueSchema,\n panelSchema,\n parseDuration,\n pipelineStatusSchema,\n positionSchema,\n readClientEnv,\n resolveRefreshMillis,\n sourceSchema\n};\n","export function ObservedAt({ value }: { value: string }) {\n const observed = new Date(value)\n const formatted = observed.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n const age = Date.now() - observed.getTime()\n const stale = age > 5 * 60 * 1000\n const ageText = formatAge(age)\n return (\n <p className={`panel__hint panel__observed${stale ? ' panel__observed--stale' : ''}`}>\n <span aria-hidden=\"true\">◷</span> As of {formatted} · {ageText}\n </p>\n )\n}\n\nfunction formatAge(milliseconds: number) {\n if (milliseconds < 60_000) return 'just now'\n const minutes = Math.floor(milliseconds / 60_000)\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n return `${hours}h ${minutes % 60}m ago`\n}\n","import type { Panel } from '@ze-great-dashboard/shared'\nimport type { CSSProperties } from 'react'\n\n/** Convert the advisory board position into CSS grid placement. */\nexport function panelLayout(panel: Panel): CSSProperties | undefined {\n const position = panel.position\n if (!position) return undefined\n\n return {\n '--panel-column': `${position.x + 1} / span ${position.w}`,\n '--panel-row': `${position.y + 1} / span ${position.h}`,\n } as CSSProperties\n}\n","import type { Panel } from '@ze-great-dashboard/shared'\nimport { type Envelope, httpValueSchema } from '@ze-great-dashboard/shared'\nimport { ObservedAt } from './ObservedAt.tsx'\nimport { panelLayout } from './panel-layout.ts'\n\nexport function HttpValuePanel({ panel, data }: { panel: Panel; data: Envelope | undefined }) {\n if (!data)\n return (\n <section className=\"panel\" style={panelLayout(panel)} aria-busy=\"true\">\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__hint\">Loading…</p>\n </section>\n )\n if (data.state === 'error')\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Unable to read</p>\n <p className=\"panel__hint\">{data.error.message}</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n const signal = httpValueSchema.safeParse(data.signal)\n if (!signal.success)\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Invalid value</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n const content = (\n <>\n <p className=\"panel__status\">{String(signal.data.value)}</p>\n <ObservedAt value={data.observedAt} />\n </>\n )\n return (\n <section className=\"panel\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n {data.link ? (\n <a className=\"panel__link\" href={data.link}>\n {content}\n </a>\n ) : (\n content\n )}\n </section>\n )\n}\n","/**\n * A panel-shaped hole, so the grid can be looked at and argued with before any signal exists.\n * Replaced by real panels in Stage 2 — nothing here is meant to survive.\n */\nexport function PanelPlaceholder({\n label,\n hint,\n wide = false,\n}: {\n label: string\n hint: string\n wide?: boolean\n}) {\n return (\n <section className={wide ? 'panel panel--wide' : 'panel'}>\n <h2 className=\"panel__label\">{label}</h2>\n <p className=\"panel__hint\">{hint}</p>\n </section>\n )\n}\n","import {\n type Envelope,\n envelopeSchema,\n type Panel,\n pipelineStatusSchema,\n} from '@ze-great-dashboard/shared'\nimport { ObservedAt } from './ObservedAt.tsx'\nimport { panelLayout } from './panel-layout.ts'\n\nexport function PipelinePanel({ panel, data }: { panel: Panel; data: Envelope | undefined }) {\n if (!data)\n return (\n <section className=\"panel\" style={panelLayout(panel)} aria-busy=\"true\">\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__hint\">Loading…</p>\n </section>\n )\n if (data.state === 'error') {\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">\n ⚠ {data.error.kind === 'no-runs' ? 'No workflow runs' : 'Unable to read'}\n </p>\n <p className=\"panel__hint\">{data.error.message}</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n }\n\n const signal = pipelineStatusSchema.safeParse(data.signal)\n if (!signal.success)\n return (\n <section className=\"panel panel--error\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n <p className=\"panel__status\">⚠ Invalid signal</p>\n <ObservedAt value={data.observedAt} />\n </section>\n )\n const presentation = statusPresentation(signal.data.status)\n const content = (\n <>\n <p className={`panel__status panel__status--${signal.data.status}`}>\n {presentation.glyph} {presentation.label}\n </p>\n <p className=\"panel__hint\">\n {signal.data.name} · {signal.data.rawStatus}\n {signal.data.branch && (\n <span className=\"panel__branch\" title={`Branch: ${signal.data.branch}`}>\n <span aria-hidden=\"true\"> · ⎇ </span>\n <span className=\"screen-reader-only\">Branch: </span>\n {signal.data.branch}\n </span>\n )}\n </p>\n {signal.data.status !== 'running' && signal.data.durationMs !== undefined && (\n <p className=\"panel__hint\">Took {formatDuration(signal.data.durationMs)}</p>\n )}\n <ObservedAt value={data.observedAt} />\n </>\n )\n return (\n <section className=\"panel\" style={panelLayout(panel)}>\n <h2 className=\"panel__label\">{panel.id}</h2>\n {data.link ? (\n <a className=\"panel__link\" href={data.link}>\n {content}\n </a>\n ) : (\n content\n )}\n </section>\n )\n}\n\nfunction formatDuration(durationMs: number) {\n const totalSeconds = Math.floor(durationMs / 1_000)\n const hours = Math.floor(totalSeconds / 3_600)\n const minutes = Math.floor((totalSeconds % 3_600) / 60)\n const seconds = totalSeconds % 60\n\n if (hours > 0) return `${hours}h ${minutes}m`\n if (minutes > 0) return `${minutes}m ${seconds}s`\n return `${seconds}s`\n}\n\nfunction statusPresentation(status: 'passed' | 'failed' | 'running' | 'cancelled' | 'unknown') {\n switch (status) {\n case 'passed':\n return { glyph: '✓', label: 'Passed' }\n case 'failed':\n return { glyph: '✕', label: 'Failed' }\n case 'running':\n return { glyph: '↻', label: 'Running' }\n case 'cancelled':\n return { glyph: '⊘', label: 'Cancelled' }\n case 'unknown':\n return { glyph: '?', label: 'Unknown' }\n }\n}\n\nexport function parseEnvelope(value: unknown): Envelope | undefined {\n const result = envelopeSchema.safeParse(value)\n return result.success ? result.data : undefined\n}\n","import {\n type Board,\n type ClientEnv,\n type Envelope,\n parseDuration,\n resolveRefreshMillis,\n} from '@ze-great-dashboard/shared'\nimport { useEffect, useState } from 'react'\nimport { HttpValuePanel } from './HttpValuePanel.tsx'\nimport { PanelPlaceholder } from './PanelPlaceholder.tsx'\nimport { PipelinePanel, parseEnvelope } from './PipelinePanel.tsx'\n\n/**\n * The board shell.\n *\n * Stage 1 has no data: there is no proxy endpoint to poll yet. What this proves is the plumbing —\n * that the client was handed its configuration by the server and knows which version of itself it\n * is. The version readout is what makes two published versions visibly different, which is the\n * whole point of the Stage 1 exit criterion.\n */\nexport function App({ env }: { env: ClientEnv }) {\n const [board, setBoard] = useState<Board>()\n const [loadedBoardName, setLoadedBoardName] = useState<string>()\n const [signals, setSignals] = useState<Record<string, Envelope | undefined>>({})\n\n useEffect(() => {\n let cancelled = false\n setBoard(undefined)\n setLoadedBoardName(undefined)\n setSignals({})\n fetch(`${env.proxyPath}/boards/${encodeURIComponent(env.board)}`)\n .then((response) =>\n response.ok\n ? response.json()\n : Promise.reject(new Error(`Board configuration returned ${response.status}`)),\n )\n .then((value: unknown) => {\n if (!cancelled) {\n setBoard(value as Board)\n setLoadedBoardName(env.board)\n }\n })\n .catch(() => {\n if (!cancelled) {\n setBoard({ panels: [] })\n setLoadedBoardName(env.board)\n }\n })\n return () => {\n cancelled = true\n }\n }, [env.board, env.proxyPath])\n\n useEffect(() => {\n if (!board || loadedBoardName !== env.board) return\n let cancelled = false\n const timers: number[] = []\n\n for (const panel of board.panels) {\n if (panel.type !== 'pipeline-status' && panel.type !== 'http-value') continue\n\n let inFlight = false\n const refreshMillis = resolveRefreshMillis({\n boardDefaultMillis: parseDuration(board.refresh ?? '60s') ?? 60_000,\n panelOverrideMillis: panel.refresh\n ? (parseDuration(panel.refresh) ?? undefined)\n : undefined,\n adapterFloorMillis: 0,\n })\n\n const refresh = () => {\n if (cancelled || inFlight) return\n inFlight = true\n fetch(\n `${env.proxyPath}/panel/${encodeURIComponent(env.board)}/${encodeURIComponent(panel.id)}`,\n )\n .then((response) => (response.status === 304 ? undefined : response.json()))\n .then((value: unknown) => {\n const envelope = parseEnvelope(value)\n if (!cancelled && envelope)\n setSignals((current) => ({ ...current, [panel.id]: envelope }))\n })\n .catch(() => undefined)\n .finally(() => {\n inFlight = false\n })\n }\n\n refresh()\n timers.push(window.setInterval(refresh, refreshMillis))\n }\n return () => {\n cancelled = true\n for (const timer of timers) window.clearInterval(timer)\n }\n }, [board, env.board, env.proxyPath, loadedBoardName])\n\n return (\n <div className=\"board\">\n <header className=\"board__header\">\n <h1 className=\"board__title\">{env.board}</h1>\n <dl className=\"board__meta\">\n <div>\n <dt>client</dt>\n <dd>{env.clientVersion}</dd>\n </div>\n <div>\n <dt>assets</dt>\n <dd>{env.assetPath}</dd>\n </div>\n </dl>\n </header>\n\n <main className=\"board__grid\">\n {!board && <PanelPlaceholder label=\"board\" hint=\"Loading configuration…\" wide />}\n {board?.panels.map((panel) =>\n panel.type === 'pipeline-status' ? (\n <PipelinePanel key={panel.id} panel={panel} data={signals[panel.id]} />\n ) : panel.type === 'http-value' ? (\n <HttpValuePanel key={panel.id} panel={panel} data={signals[panel.id]} />\n ) : (\n <PanelPlaceholder key={panel.id} label={panel.type} hint=\"Not wired yet\" wide />\n ),\n )}\n </main>\n\n <footer className=\"board__footer\">\n Signals are read live from their configured authorities.\n </footer>\n </div>\n )\n}\n","export function ConfigError({ message }: { message: string }) {\n return (\n <div className=\"config-error\" role=\"alert\">\n <h1>⚠️ Dashboard misconfigured</h1>\n <p>The page loaded, but the server did not hand it usable configuration.</p>\n <pre>{message}</pre>\n </div>\n )\n}\n","import { readClientEnv } from '@ze-great-dashboard/shared'\nimport { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { App } from './App.tsx'\nimport { ConfigError } from './ConfigError.tsx'\nimport './styles.css'\n\nconst container = document.getElementById('root')\nif (!container) {\n throw new Error('No #root element in the document — the template is not the one we expect.')\n}\n\nconst root = createRoot(container)\n\ntry {\n root.render(\n <StrictMode>\n <App env={readClientEnv()} />\n </StrictMode>,\n )\n} catch (error) {\n // A radiator that fails silently is worse than one that says what's wrong. If configuration\n // never arrived, say so on screen rather than rendering an empty board.\n root.render(<ConfigError message={error instanceof Error ? error.message : String(error)} />)\n}\n"],"mappings":"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"}