@continuous-excellence/ze-great-dashboard-aws 0.4.0 → 0.5.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/README.md CHANGED
@@ -8,6 +8,11 @@ It does not create a public endpoint, choose an authentication policy, create a
8
8
  secret value, or own consumer AWS infrastructure. You provide the protected gateway and complete the
9
9
  administrator-owned bootstrap process first.
10
10
 
11
+ For a deployment workflow that verifies a consumer-owned gateway stack, configure
12
+ `--consumer-gateway-stack gateway-stack-name` during `bootstrap init`. The GitHub OIDC v2 role then
13
+ gets only `cloudformation:DescribeStacks` for that exact stack; the package still does not create,
14
+ invoke, or authenticate the gateway.
15
+
11
16
  ## Prerequisites
12
17
 
13
18
  You need Node.js 22+, npm, the AWS CLI, `jq`, and one AWS Region for the artifact bucket, Lambda,
@@ -69,6 +74,11 @@ npm exec -- ze-great-dashboard-aws doctor \
69
74
  --region us-east-1
70
75
  ```
71
76
 
77
+ If you retain a captured GitHub OIDC stack, add `--github-oidc-stack-json
78
+ github-oidc-deployed-stack.json` to have the doctor warn when the installed package contains a
79
+ newer bootstrap template revision. The warning is advisory and does not make the application
80
+ deployment perform bootstrap changes.
81
+
72
82
  Package the release. This validates the board and writes `lambda.zip`, `release.json`, and
73
83
  `template.yml` to the output directory:
74
84
 
@@ -117,6 +127,12 @@ does not grant public Lambda invoke permission.
117
127
  To upgrade, install a newer exact package version and repeat package, upload, and deploy. A change to
118
128
  `board.yaml` follows the same path; it does not require a package change.
119
129
 
130
+ After upgrading, also check the [bootstrap upgrade guidance](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/aws-bootstrap.md#check-the-bootstrap-template-on-package-upgrades).
131
+ Compatible releases may add optional bootstrap capabilities without changing the contract version.
132
+ If your workflow verifies a consumer-owned gateway stack, add `githubOidc.consumerGatewayStackName`
133
+ to the reviewed manifest and update the GitHub OIDC stack; other consumers do not need to rerun
134
+ bootstrap.
135
+
120
136
  Tokens belong in runtime secret handling, never in board YAML, `aws-dashboard-parameters.json`, or
121
137
  the generated ZIP. See [runtime secrets](https://github.com/robertfmurdock/ze-great-dashboard/blob/main/docs/aws-setup.md#runtime-secrets)
122
138
  for the supported secret reference and integration boundary.
@@ -8,9 +8,16 @@ Parameters:
8
8
  GitHubRepositoryId: { Type: String, Description: Immutable numeric GitHub repository id }
9
9
  GitHubEnvironment: { Type: String, Description: Protected GitHub Environment name }
10
10
  ApplicationStackName: { Type: String }
11
+ ConsumerGatewayStackName:
12
+ Type: String
13
+ Default: ''
14
+ Description: Optional consumer-owned gateway stack whose outputs the deploy role may read
11
15
  ArtifactBucketName: { Type: String }
12
16
  CloudFormationExecutionRoleArn: { Type: String }
13
17
 
18
+ Conditions:
19
+ HasConsumerGatewayStack: !Not [!Equals [!Ref ConsumerGatewayStackName, '']]
20
+
14
21
  Resources:
15
22
  GitHubDeployRole:
16
23
  Type: AWS::IAM::Role
@@ -49,6 +56,13 @@ Resources:
49
56
  Effect: Allow
50
57
  Action: [cloudformation:CreateChangeSet, cloudformation:DescribeChangeSet, cloudformation:ExecuteChangeSet, cloudformation:DeleteChangeSet, cloudformation:DescribeStacks, cloudformation:DescribeStackEvents, cloudformation:GetTemplate, cloudformation:GetTemplateSummary]
51
58
  Resource: !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${ApplicationStackName}/*'
59
+ - !If
60
+ - HasConsumerGatewayStack
61
+ - Sid: ReadConsumerGatewayStack
62
+ Effect: Allow
63
+ Action: cloudformation:DescribeStacks
64
+ Resource: !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${ConsumerGatewayStackName}/*'
65
+ - !Ref AWS::NoValue
52
66
  - Sid: PassCoreExecutionRole
53
67
  Effect: Allow
54
68
  Action: iam:PassRole
@@ -57,4 +71,5 @@ Resources:
57
71
 
58
72
  Outputs:
59
73
  BootstrapContractVersion: { Value: '2' }
74
+ BootstrapTemplateRevision: { Value: '2.1' }
60
75
  GitHubDeployRoleArn: { Value: !GetAtt GitHubDeployRole.Arn }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "index.html": {
3
- "file": "assets/index-BCH1wUwb.js",
3
+ "file": "assets/index-D3I3v8jB.js",
4
4
  "name": "index",
5
5
  "src": "index.html",
6
6
  "isEntry": true,
@@ -0,0 +1,3 @@
1
+ import{z as e}from"zod";import{StrictMode as t,useEffect as n,useRef as r,useState as i,useSyncExternalStore as a}from"react";import{createRoot as o}from"react-dom/client";import{Fragment as s,jsx as c,jsxs as l}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 u=/^(\d+)(ms|s|m|h)$/,d={ms:1,s:1e3,m:6e4,h:36e5};function f(e){let t=u.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*d[r]}var p=e.string().refine(e=>f(e)!==null,{message:`must be a positive duration like "30s", "5m", or "1h"`}).brand(),m=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)}),h=e.looseObject({id:e.string().min(1),type:e.string().min(1),source:e.string().min(1).optional(),position:m.optional(),refresh:p.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()}),g=e.looseObject({type:e.string().min(1),token_env:e.string().min(1).optional()}),_=e.object({refresh:p.optional(),panels:e.array(h).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})`})})})}),ee=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),g).default({}),boards:e.record(e.string().min(1),_),auth:ee.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,n]=i(!1),r=a(e.subscribe,e.count,e.count);return l(`section`,{className:`diagnostics`,children:[l(`button`,{className:`diagnostics__toggle`,type:`button`,"aria-expanded":t,onClick:()=>n(!t),children:[`Diagnostics (`,r,`)`]}),t&&l(`div`,{className:`diagnostics__area`,children:[l(`p`,{children:[r,` retained browser-local events. They are never uploaded.`]}),l(`div`,{className:`diagnostics__actions`,children:[c(`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`}),c(`button`,{type:`button`,onClick:()=>{window.confirm(`Clear this browser’s retained dashboard diagnostics?`)&&e.clear()},children:`Clear`})]})]})]})}var D=`ze-great-dashboard.diagnostics.v1`,te=6048e5,O=[`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`],k=class{env;storage;now;events=[];listeners=new Set;sessionId=crypto.randomUUID?.()??`${Date.now()}-${Math.random()}`;constructor(e,t=N(),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=j([...this.events,{...e,schemaVersion:1,at:this.now().toISOString(),sessionId:this.sessionId,board:this.env.board}],this.now()),this.persist(),this.notify()}count=()=>this.events.length;clear(){this.events=[];try{this.storage?.removeItem(D)}catch{}this.notify()}subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)});export=()=>({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(M)?(this.storage?.removeItem(D),[]):j(t.events.map(e=>({...e,schemaVersion:1})),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 A(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 j(e,t){let n=t.valueOf()-te;return e.filter(e=>Number.isFinite(new Date(e.at).valueOf())&&new Date(e.at).valueOf()>=n).slice(-2e3)}function M(e){if(!e||typeof e!=`object`)return!1;let t=e;return(t.schemaVersion===void 0||t.schemaVersion===1)&&typeof t.at==`string`&&Number.isFinite(new Date(t.at).valueOf())&&typeof t.sessionId==`string`&&typeof t.board==`string`&&O.includes(t.kind)}function N(){try{return window.localStorage}catch{return}}function P({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=F(i);return l(`p`,{className:`panel__hint panel__observed${a?` panel__observed--stale`:``}`,children:[c(`span`,{"aria-hidden":`true`,children:`◷`}),` `,t,` `,r,` · `,o]})}function F(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 I(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 L({panel:e,data:t}){if(!t)return l(`section`,{className:`panel`,style:I(e),"aria-busy":`true`,children:[c(`h2`,{className:`panel__label`,children:e.id}),c(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return l(`section`,{className:`panel panel--error`,style:I(e),children:[c(`h2`,{className:`panel__label`,children:e.id}),c(`p`,{className:`panel__status`,children:`⚠ Unable to read`}),c(`p`,{className:`panel__hint`,children:t.error.message}),c(P,{value:t.observedAt})]});let n=T.safeParse(t.signal);if(!n.success)return l(`section`,{className:`panel panel--error`,style:I(e),children:[c(`h2`,{className:`panel__label`,children:e.id}),c(`p`,{className:`panel__status`,children:`⚠ Invalid value`}),c(P,{value:t.observedAt})]});let r=l(s,{children:[c(`p`,{className:`panel__status`,children:String(n.data.value)}),c(P,{value:t.observedAt})]});return l(`section`,{className:`panel`,style:I(e),children:[c(`h2`,{className:`panel__label`,children:e.id}),t.link?c(`a`,{className:`panel__link`,href:t.link,children:r}):r]})}function R({label:e,hint:t,wide:n=!1}){return l(`section`,{className:n?`panel panel--wide`:`panel`,children:[c(`h2`,{className:`panel__label`,children:e}),c(`p`,{className:`panel__hint`,children:t})]})}function z({panel:e,data:t}){if(!t)return l(`section`,{className:`panel`,style:I(e),"aria-busy":`true`,children:[c(`h2`,{className:`panel__label`,children:e.id}),c(`p`,{className:`panel__hint`,children:`Loading…`})]});if(t.state===`error`)return l(`section`,{className:`panel panel--error`,style:I(e),children:[c(`h2`,{className:`panel__label`,children:e.id}),l(`p`,{className:`panel__status`,children:[`⚠ `,t.error.kind===`no-runs`?`No workflow runs`:`Unable to read`]}),c(`p`,{className:`panel__hint`,children:t.error.message}),c(P,{value:t.observedAt})]});let n=w.safeParse(t.signal);if(!n.success)return l(`section`,{className:`panel panel--error`,style:I(e),children:[c(`h2`,{className:`panel__label`,children:e.id}),c(`p`,{className:`panel__status`,children:`⚠ Invalid signal`}),c(P,{value:t.observedAt})]});let r=V(n.data.status),i=l(s,{children:[l(`p`,{className:`panel__status panel__status--${n.data.status}`,children:[r.glyph,` `,r.label]}),l(`p`,{className:`panel__hint`,children:[n.data.name,` · `,n.data.rawStatus,n.data.branch&&l(`span`,{className:`panel__branch`,title:`Branch: ${n.data.branch}`,children:[c(`span`,{"aria-hidden":`true`,children:` · ⎇ `}),c(`span`,{className:`screen-reader-only`,children:`Branch: `}),n.data.branch]})]}),n.data.status!==`running`&&n.data.durationMs!==void 0&&l(`p`,{className:`panel__hint`,children:[`Took `,B(n.data.durationMs)]}),n.data.sourceUpdatedAt&&c(P,{value:n.data.sourceUpdatedAt,label:`Run updated`}),c(P,{value:t.observedAt})]});return l(`section`,{className:`panel`,style:I(e),children:[c(`h2`,{className:`panel__label`,children:e.id}),t.link?c(`a`,{className:`panel__link`,href:t.link,children:i}):i]})}function B(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 V(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 H(e){let t=C.safeParse(e);return t.success?t.data:void 0}function U(e,t){if(t.state===`error`)return{state:t.state,link:t.link};if(e.type===`pipeline-status`){let e=w.safeParse(t.signal);return{state:t.state,status:e.success?e.data.status:void 0,link:t.link}}return{state:t.state,link:t.link}}function W(e,t,n){if(!e)return!0;let r=U(t,e),i=U(t,n);return r.state!==i.state||r.status!==i.status||r.link!==i.link}function G({board:e,env:t,diagnostics:a}){let[o,s]=i({}),c=r({});return n(()=>{if(c.current={},s({}),!e)return;let n=!1,r=[];for(let i of e.panels){if(i.type!==`pipeline-status`&&i.type!==`http-value`)continue;let o=!1,l=v({boardDefaultMillis:f(e.refresh??`60s`)??6e4,panelOverrideMillis:i.refresh?f(i.refresh)??void 0:void 0,adapterFloorMillis:0}),u=`${t.proxyPath}/panel/${encodeURIComponent(t.board)}/${encodeURIComponent(i.id)}`,d=()=>{n||o||(o=!0,a.record({kind:`panel-fetch-start`,panelId:i.id,path:u}),fetch(u).then(async e=>{if(a.record({kind:`panel-fetch-response`,panelId:i.id,path:u,status:e.status,cache:A(e.headers)}),e.status!==304)try{return await e.json()}catch(e){throw a.record({kind:`panel-fetch-parse-failure`,panelId:i.id,path:u,message:K(e)}),new q(e)}}).then(e=>{let t=H(e);if(e!==void 0&&!t&&a.record({kind:`panel-fetch-parse-failure`,panelId:i.id,path:u,message:`Response was not a valid signal envelope.`}),!n&&t){a.record({kind:`panel-fetch-response`,panelId:i.id,path:u,envelope:t});let e=c.current[i.id];W(e,i,t)&&a.record({kind:`panel-rendered`,panelId:i.id,path:u,rendered:U(i,t)}),c.current={...c.current,[i.id]:t},s(c.current)}}).catch(e=>{e instanceof q||a.record({kind:`panel-fetch-failure`,panelId:i.id,path:u,message:K(e)})}).finally(()=>{o=!1}))};d(),r.push(window.setInterval(d,l))}return()=>{n=!0;for(let e of r)window.clearInterval(e)}},[e,a,t.board,t.proxyPath]),o}function K(e){return e instanceof Error?e.message:String(e)}var q=class extends Error{constructor(e){super(K(e))}};function J({env:e}){let[t,a]=i(),[o,s]=i(),u=r(null);u.current||=new k(e);let d=u.current,f=G({board:o===e.board?t:void 0,env:e,diagnostics:d});return n(()=>{let t=!1;a(void 0),s(void 0);let n=`${e.proxyPath}/boards/${encodeURIComponent(e.board)}`;return d.record({kind:`board-fetch-start`,path:n}),fetch(n).then(async e=>{if(d.record({kind:`board-fetch-response`,path:n,status:e.status,cache:A(e.headers)}),!e.ok)throw Error(`Board configuration returned ${e.status}`);try{return await e.json()}catch(e){throw d.record({kind:`board-fetch-parse-failure`,path:n,message:Y(e)}),new X(e)}}).then(r=>{if(!t){a(r),s(e.board);let t=Array.isArray(r.panels)?r.panels:[];d.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 X||d.record({kind:`board-fetch-failure`,path:n,message:Y(r)}),t||(a({panels:[]}),s(e.board))}),()=>{t=!0}},[d,e.board,e.proxyPath]),l(`div`,{className:`board`,children:[l(`header`,{className:`board__header`,children:[c(`h1`,{className:`board__title`,children:e.board}),l(`dl`,{className:`board__meta`,children:[l(`div`,{children:[c(`dt`,{children:`client`}),c(`dd`,{children:e.clientVersion})]}),l(`div`,{children:[c(`dt`,{children:`assets`}),c(`dd`,{children:e.assetPath})]})]})]}),l(`main`,{className:`board__grid`,children:[!t&&c(R,{label:`board`,hint:`Loading configuration…`,wide:!0}),t?.panels.map(e=>e.type===`pipeline-status`?c(z,{panel:e,data:f[e.id]},e.id):e.type===`http-value`?c(L,{panel:e,data:f[e.id]},e.id):c(R,{label:e.type,hint:`Not wired yet`,wide:!0},e.id))]}),l(`footer`,{className:`board__footer`,children:[c(`span`,{children:`Signals are read live from their configured authorities.`}),c(E,{log:d})]})]})}function Y(e){return e instanceof Error?e.message:String(e)}var X=class extends Error{constructor(e){super(Y(e))}};function Z({message:e}){return l(`div`,{className:`config-error`,role:`alert`,children:[c(`h1`,{children:`⚠️ Dashboard misconfigured`}),c(`p`,{children:`The page loaded, but the server did not hand it usable configuration.`}),c(`pre`,{children:e})]})}var Q=document.getElementById(`root`);if(!Q)throw Error(`No #root element in the document — the template is not the one we expect.`);var $=o(Q);try{$.render(c(t,{children:c(J,{env:b()})}))}catch(e){$.render(c(Z,{message:e instanceof Error?e.message:String(e)}))}
3
+ //# sourceMappingURL=index-D3I3v8jB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-D3I3v8jB.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/panel-diagnostics.ts","../../src/usePanelSignals.ts","../../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 { useState, useSyncExternalStore } from 'react'\nimport type { BrowserDiagnosticStore } from './diagnostics.ts'\n\nexport function Diagnostics({ log }: { log: BrowserDiagnosticStore }) {\n const [open, setOpen] = useState(false)\n const count = useSyncExternalStore(log.subscribe, log.count, log.count)\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\nexport type CacheMetadata = Partial<\n Record<'cacheControl' | 'etag' | 'lastModified' | 'date' | 'age', string>\n>\n\ntype EventMetadata = {\n schemaVersion: typeof diagnosticsSchemaVersion\n at: string\n sessionId: string\n board: string\n}\n\nexport type RenderedPanelDiagnostic = {\n state: Envelope['state']\n status?: string\n link: string | null\n}\n\nexport type DiagnosticEventInput =\n | { kind: 'session-start' }\n | { kind: 'board-fetch-start'; path: string }\n | {\n kind: 'board-fetch-response'\n path: string\n status?: number\n cache?: CacheMetadata\n boardSummary?: { panelCount: number; panelIds: string[] }\n }\n | { kind: 'board-fetch-parse-failure'; path: string; message: string }\n | { kind: 'board-fetch-failure'; path: string; message: string }\n | { kind: 'panel-fetch-start'; panelId: string; path: string }\n | {\n kind: 'panel-fetch-response'\n panelId: string\n path: string\n status?: number\n cache?: CacheMetadata\n envelope?: Envelope\n }\n | { kind: 'panel-fetch-parse-failure'; panelId: string; path: string; message: string }\n | { kind: 'panel-fetch-failure'; panelId: string; path: string; message: string }\n | {\n kind: 'panel-rendered'\n panelId: string\n path: string\n rendered: RenderedPanelDiagnostic\n }\n\nexport type DiagnosticEvent = DiagnosticEventInput extends infer Event\n ? Event extends DiagnosticEventInput\n ? Event & EventMetadata\n : never\n : never\n\n/** The only interface client features need in order to emit browser diagnostics. */\nexport interface DiagnosticSink {\n record(event: DiagnosticEventInput): void\n}\n\ntype PersistedDiagnosticEvent = Omit<DiagnosticEvent, 'schemaVersion'> & { schemaVersion?: number }\ntype StoredDiagnostics = { schemaVersion: number; events: PersistedDiagnosticEvent[] }\n\ntype StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>\n\n/** Browser-local diagnostic evidence. It deliberately has no server or shared-contract counterpart. */\nexport class BrowserDiagnosticStore implements DiagnosticSink {\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: DiagnosticEventInput) {\n this.events = prune(\n [\n ...this.events,\n {\n ...event,\n schemaVersion: diagnosticsSchemaVersion,\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 // v1 retained events predate per-event versions. Their containing store was already v1, so\n // retain them and normalize on the next write rather than discarding viewer evidence.\n return prune(\n parsed.events.map(\n (event) => ({ ...event, schemaVersion: diagnosticsSchemaVersion }) as DiagnosticEvent,\n ),\n this.now(),\n )\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 PersistedDiagnosticEvent {\n if (!value || typeof value !== 'object') return false\n const event = value as Partial<DiagnosticEvent>\n return (\n (event.schemaVersion === undefined || event.schemaVersion === diagnosticsSchemaVersion) &&\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 { type Envelope, type Panel, pipelineStatusSchema } from '@ze-great-dashboard/shared'\nimport type { RenderedPanelDiagnostic } from './diagnostics.ts'\n\n/**\n * The compact public fact a viewer saw. Keep new panel projections here beside their\n * presentation boundary; this is intentionally not a source or server event contract.\n */\nexport function projectPanelDiagnostic(panel: Panel, envelope: Envelope): RenderedPanelDiagnostic {\n if (envelope.state === 'error') return { state: envelope.state, link: envelope.link }\n\n if (panel.type === 'pipeline-status') {\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\n return { state: envelope.state, link: envelope.link }\n}\n\nexport function panelDiagnosticChanged(\n previous: Envelope | undefined,\n panel: Panel,\n next: Envelope,\n) {\n if (!previous) return true\n const before = projectPanelDiagnostic(panel, previous)\n const after = projectPanelDiagnostic(panel, next)\n return (\n before.state !== after.state || before.status !== after.status || before.link !== after.link\n )\n}\n","import {\n type Board,\n type ClientEnv,\n type Envelope,\n parseDuration,\n resolveRefreshMillis,\n} from '@ze-great-dashboard/shared'\nimport { useEffect, useRef, useState } from 'react'\nimport { cacheMetadata, type DiagnosticSink } from './diagnostics.ts'\nimport { parseEnvelope } from './PipelinePanel.tsx'\nimport { panelDiagnosticChanged, projectPanelDiagnostic } from './panel-diagnostics.ts'\n\nexport function usePanelSignals({\n board,\n env,\n diagnostics,\n}: {\n board: Board | undefined\n env: ClientEnv\n diagnostics: DiagnosticSink\n}) {\n const [signals, setSignals] = useState<Record<string, Envelope | undefined>>({})\n const signalsRef = useRef<Record<string, Envelope | undefined>>({})\n\n useEffect(() => {\n signalsRef.current = {}\n setSignals({})\n if (!board) return\n\n let cancelled = false\n const timers: number[] = []\n for (const panel of board.panels) {\n if (panel.type !== 'pipeline-status' && panel.type !== 'http-value') continue\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 const path = `${env.proxyPath}/panel/${encodeURIComponent(env.board)}/${encodeURIComponent(panel.id)}`\n const refresh = () => {\n if (cancelled || inFlight) return\n inFlight = true\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 (panelDiagnosticChanged(previous, panel, envelope)) {\n diagnostics.record({\n kind: 'panel-rendered',\n panelId: panel.id,\n path,\n rendered: projectPanelDiagnostic(panel, 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 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])\n\n return signals\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","import type { Board, ClientEnv } from '@ze-great-dashboard/shared'\nimport { useEffect, useRef, useState } from 'react'\nimport { Diagnostics } from './Diagnostics.tsx'\nimport { BrowserDiagnosticStore, cacheMetadata } from './diagnostics.ts'\nimport { HttpValuePanel } from './HttpValuePanel.tsx'\nimport { PanelPlaceholder } from './PanelPlaceholder.tsx'\nimport { PipelinePanel } from './PipelinePanel.tsx'\nimport { usePanelSignals } from './usePanelSignals.ts'\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 diagnosticsRef = useRef<BrowserDiagnosticStore | null>(null)\n if (!diagnosticsRef.current) diagnosticsRef.current = new BrowserDiagnosticStore(env)\n const diagnostics = diagnosticsRef.current\n const signals = usePanelSignals({\n board: loadedBoardName === env.board ? board : undefined,\n env,\n diagnostics,\n })\n\n useEffect(() => {\n let cancelled = false\n setBoard(undefined)\n setLoadedBoardName(undefined)\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 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","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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"}
package/client/index.html CHANGED
@@ -11,7 +11,7 @@
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-BCH1wUwb.js"></script>
14
+ <script type="module" crossorigin src="/__ASSET_PATH__/assets/index-D3I3v8jB.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="/__ASSET_PATH__/assets/index-CwWbINCq.css">
16
16
  </head>
17
17
  <body>
@@ -28,11 +28,13 @@ export type BootstrapConfig = {
28
28
  ownerId?: string;
29
29
  repositoryId?: string;
30
30
  environment?: string;
31
+ consumerGatewayStackName?: string;
31
32
  };
32
33
  };
33
34
  export declare function bootstrapTemplatePath(kind: BootstrapKind): Promise<string>;
34
35
  export declare function bootstrapTemplate(kind: BootstrapKind): Promise<string>;
35
36
  export declare function bootstrapContractVersion(template: string): string;
37
+ export declare function bootstrapTemplateRevision(template: string): string;
36
38
  /** Merges a new parameter set with deployed values without silently dropping configuration. */
37
39
  export declare function mergeBootstrapParameters(requested: CloudFormationParameterValue[], deployed: CloudFormationParameterValue[]): CloudFormationParameterValue[];
38
40
  /** Validates a caller-captured `describe-stacks` result before an upgrade merges it locally. */
package/dist/cli.js CHANGED
@@ -857,6 +857,11 @@ function bootstrapContractVersion(template) {
857
857
  if (!version) throw new Error("Bootstrap template has no BootstrapContractVersion output");
858
858
  return version;
859
859
  }
860
+ function bootstrapTemplateRevision(template) {
861
+ const revision = template.match(/BootstrapTemplateRevision:\s*\{\s*Value:\s*'([^']+)'\s*}/)?.[1];
862
+ if (!revision) throw new Error("Bootstrap template has no BootstrapTemplateRevision output");
863
+ return revision;
864
+ }
860
865
  function mergeBootstrapParameters(requested, deployed) {
861
866
  const supplied = new Map(requested.map((entry) => [entry.ParameterKey, entry.ParameterValue]));
862
867
  const duplicates = requested.map((entry) => entry.ParameterKey);
@@ -910,7 +915,7 @@ function requiredBootstrapParameters(kind, values) {
910
915
  ];
911
916
  const missing = keys.filter((key) => !values[key]);
912
917
  if (missing.length) throw new Error(`Missing required bootstrap values: ${missing.join(", ")}`);
913
- const optional2 = kind === "core" ? ["RuntimeSecretArn", "ArtifactKmsKeyArn"] : [];
918
+ const optional2 = kind === "core" ? ["RuntimeSecretArn", "ArtifactKmsKeyArn"] : ["ConsumerGatewayStackName"];
914
919
  return [...keys, ...optional2].filter((key) => values[key] !== void 0).map((ParameterKey) => ({ ParameterKey, ParameterValue: values[ParameterKey] ?? "" }));
915
920
  }
916
921
 
@@ -1128,6 +1133,7 @@ async function bootstrapHandoff(input) {
1128
1133
  GitHubOwnerId: input.config.githubOidc?.ownerId,
1129
1134
  GitHubRepositoryId: input.config.githubOidc?.repositoryId,
1130
1135
  GitHubEnvironment: input.config.githubOidc?.environment,
1136
+ ConsumerGatewayStackName: input.config.githubOidc?.consumerGatewayStackName,
1131
1137
  ...coreBootstrapOutputs(core)
1132
1138
  });
1133
1139
  const templatePath = await bootstrapTemplatePath("github-oidc");
@@ -1136,7 +1142,11 @@ async function bootstrapHandoff(input) {
1136
1142
  expectedContracts,
1137
1143
  templatePath,
1138
1144
  parameterPath: "github-oidc-bootstrap.json",
1139
- expectedOutputs: ["BootstrapContractVersion", "GitHubDeployRoleArn"],
1145
+ expectedOutputs: [
1146
+ "BootstrapContractVersion",
1147
+ "BootstrapTemplateRevision",
1148
+ "GitHubDeployRoleArn"
1149
+ ],
1140
1150
  requiredCapturedFiles: [coreCapturePath],
1141
1151
  reviewCheckpoints: [
1142
1152
  "Review the exact immutable GitHub OIDC subject, audience, bucket lambda/* prefix, application stack, and execution role.",
@@ -1244,9 +1254,14 @@ async function verifyBootstrap(input) {
1244
1254
  GitHubEnvironment: github?.environment,
1245
1255
  ApplicationStackName: coreOutputs.ApplicationStackName,
1246
1256
  ArtifactBucketName: coreOutputs.ArtifactBucketName,
1247
- CloudFormationExecutionRoleArn: executionRole
1257
+ CloudFormationExecutionRoleArn: executionRole,
1258
+ ConsumerGatewayStackName: github?.consumerGatewayStackName ?? ""
1248
1259
  }))
1249
- assertEqual(oidcParameters[key], value2, `OIDC ${key}`);
1260
+ assertEqual(
1261
+ key === "ConsumerGatewayStackName" ? oidcParameters[key] ?? "" : oidcParameters[key],
1262
+ value2,
1263
+ `OIDC ${key}`
1264
+ );
1250
1265
  if (!/^\d+$/.test(github?.ownerId ?? "") || !/^\d+$/.test(github?.repositoryId ?? ""))
1251
1266
  throw new Error("GitHub immutable owner and repository IDs must be numeric");
1252
1267
  const [owner, repository, ...extra] = (github?.repository ?? "").split("/");
@@ -1373,7 +1388,8 @@ async function scaffoldBootstrapManifest(input) {
1373
1388
  repository,
1374
1389
  ownerId,
1375
1390
  repositoryId,
1376
- environment
1391
+ environment,
1392
+ ...input.consumerGatewayStackName ? { consumerGatewayStackName: input.consumerGatewayStackName } : {}
1377
1393
  }
1378
1394
  };
1379
1395
  }
@@ -1832,6 +1848,29 @@ async function runDoctor(options, dependencies = actualDependencies) {
1832
1848
  if (!response.ok) throw new Error(`${url} returned HTTP ${response.status}`);
1833
1849
  return `${url} returned HTTP ${response.status}`;
1834
1850
  });
1851
+ if (options.githubOidcStackPath) {
1852
+ const stackPath = options.githubOidcStackPath;
1853
+ const template = await bootstrapTemplate("github-oidc");
1854
+ const expectedRevision = bootstrapTemplateRevision(template);
1855
+ await check("Bootstrap template", async () => {
1856
+ const raw = JSON.parse(await readFile4(stackPath, "utf8"));
1857
+ const stack = raw && typeof raw === "object" && "Stacks" in raw ? raw.Stacks?.[0] : raw;
1858
+ if (!stack || typeof stack !== "object")
1859
+ throw new Error("captured stack JSON contains no stack");
1860
+ const actualRevision = (stack.Outputs ?? []).find(({ OutputKey }) => OutputKey === "BootstrapTemplateRevision")?.OutputValue;
1861
+ if (actualRevision !== expectedRevision)
1862
+ throw new Error(
1863
+ `captured bootstrap revision is ${actualRevision ?? "older or missing"}; current package is ${expectedRevision}; rerun the GitHub OIDC bootstrap review before deploying`
1864
+ );
1865
+ return `GitHub OIDC bootstrap revision ${expectedRevision} is current`;
1866
+ });
1867
+ const checkResult = checks.at(-1);
1868
+ if (checkResult && !checkResult.ok) {
1869
+ checkResult.ok = true;
1870
+ checkResult.warning = true;
1871
+ checkResult.detail = `WARNING: ${checkResult.detail}`;
1872
+ }
1873
+ }
1835
1874
  return checks;
1836
1875
  }
1837
1876
 
@@ -1907,6 +1946,10 @@ function bootstrapValues(config, coreOutputs = {}) {
1907
1946
  GitHubOwnerId: option("--github-owner-id", config.githubOidc?.ownerId),
1908
1947
  GitHubRepositoryId: option("--github-repository-id", config.githubOidc?.repositoryId),
1909
1948
  GitHubEnvironment: option("--github-environment", config.githubOidc?.environment),
1949
+ ConsumerGatewayStackName: option(
1950
+ "--consumer-gateway-stack",
1951
+ config.githubOidc?.consumerGatewayStackName
1952
+ ),
1910
1953
  CloudFormationExecutionRoleArn: option(
1911
1954
  "--execution-role-arn",
1912
1955
  coreOutputs.CloudFormationExecutionRoleArn
@@ -1968,7 +2011,11 @@ try {
1968
2011
  const parametersPath = option("--parameters", "aws-dashboard-parameters.json") ?? "aws-dashboard-parameters.json";
1969
2012
  const region2 = option("--region", process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1") ?? "us-east-1";
1970
2013
  const checks = await runDoctor(
1971
- { parametersPath, region: region2 },
2014
+ {
2015
+ parametersPath,
2016
+ region: region2,
2017
+ githubOidcStackPath: option("--github-oidc-stack-json")
2018
+ },
1972
2019
  {
1973
2020
  async execute(command, commandArgs) {
1974
2021
  const { execFile: execFile3 } = await import("node:child_process");
@@ -1983,7 +2030,9 @@ try {
1983
2030
  }
1984
2031
  );
1985
2032
  for (const check of checks)
1986
- console.log(`${check.ok ? "PASS" : "FAIL"} ${check.name}: ${check.detail}`);
2033
+ console.log(
2034
+ `${check.warning ? "WARN" : check.ok ? "PASS" : "FAIL"} ${check.name}: ${check.detail}`
2035
+ );
1987
2036
  if (checks.some(({ ok }) => !ok)) process.exitCode = 1;
1988
2037
  } else if (args[0] === "parameters") {
1989
2038
  const output = option("--output", "aws-dashboard-parameters.json") ?? "aws-dashboard-parameters.json";
@@ -2053,6 +2102,7 @@ try {
2053
2102
  accountId: option("--account-id"),
2054
2103
  ownerId: option("--github-owner-id"),
2055
2104
  repositoryId: option("--github-repository-id"),
2105
+ consumerGatewayStackName: option("--consumer-gateway-stack"),
2056
2106
  runner
2057
2107
  });
2058
2108
  await writeFile3(output, `${JSON.stringify(manifest, null, 2)}
@@ -2171,6 +2221,12 @@ try {
2171
2221
  JSON.parse(await readFile5(deployedStackPath, "utf8")),
2172
2222
  bootstrapContractVersion(await bootstrapTemplate(kind))
2173
2223
  );
2224
+ if (kind === "github-oidc" && !(stack.Parameters ?? []).some(
2225
+ ({ ParameterKey }) => ParameterKey === "ConsumerGatewayStackName"
2226
+ ))
2227
+ console.error(
2228
+ option("--consumer-gateway-stack", config.githubOidc?.consumerGatewayStackName) ? "NOTICE: this GitHub OIDC stack predates optional consumer gateway-stack reads; the generated parameters will add the configured gateway stack. Review and execute the GitHub OIDC UPDATE change set." : "NOTICE: this GitHub OIDC stack predates optional consumer gateway-stack reads. If your workflow verifies a consumer-owned gateway stack, configure githubOidc.consumerGatewayStackName before reviewing the UPDATE change set; otherwise no bootstrap change is needed."
2229
+ );
2174
2230
  parameters = mergeBootstrapParameters(supplied, stack.Parameters ?? []);
2175
2231
  }
2176
2232
  await writeFile3(output, `${JSON.stringify(parameters, null, 2)}
package/dist/doctor.d.ts CHANGED
@@ -2,6 +2,7 @@ export type DoctorCheck = {
2
2
  name: string;
3
3
  ok: boolean;
4
4
  detail: string;
5
+ warning?: boolean;
5
6
  };
6
7
  export type DoctorDependencies = {
7
8
  execute(command: string, args: string[]): Promise<string>;
@@ -15,4 +16,5 @@ export type DoctorDependencies = {
15
16
  export declare function runDoctor(options: {
16
17
  parametersPath: string;
17
18
  region: string;
19
+ githubOidcStackPath?: string;
18
20
  }, dependencies?: DoctorDependencies): Promise<DoctorCheck[]>;
package/dist/guided.d.ts CHANGED
@@ -19,6 +19,7 @@ export type BootstrapInitInput = {
19
19
  accountId?: string;
20
20
  ownerId?: string;
21
21
  repositoryId?: string;
22
+ consumerGatewayStackName?: string;
22
23
  runner?: CommandRunner;
23
24
  };
24
25
  /** Creates the non-secret manifest data; callers control where (and whether) it is written. */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { type BootstrapConfig, type BootstrapKind, bootstrapContractVersion, bootstrapTemplate, bootstrapTemplatePath, type CloudFormationParameterValue, coreBootstrapOutputs, type DeployedBootstrapStack, deployedBootstrapStack, mergeBootstrapParameters, requiredBootstrapParameters, } from './bootstrap.js';
1
+ export { type BootstrapConfig, type BootstrapKind, bootstrapContractVersion, bootstrapTemplate, bootstrapTemplatePath, bootstrapTemplateRevision, type CloudFormationParameterValue, coreBootstrapOutputs, type DeployedBootstrapStack, deployedBootstrapStack, mergeBootstrapParameters, requiredBootstrapParameters, } from './bootstrap.js';
2
2
  export { type BootstrapCheckStatus, type BootstrapInitInput, type BootstrapPreflight, type BootstrapPreflightCheck, bootstrapGuide, bootstrapPreflight, scaffoldBootstrapManifest, } from './guided.js';
3
3
  export { type BootstrapHandoff, type BootstrapPhase, type BootstrapProvider, type BootstrapVerification, bootstrapHandoff, type CommandRunner, githubOidcProvider, verifyBootstrap, } from './handoff.js';
4
4
  export type ReleaseMetadata = {
package/dist/index.js CHANGED
@@ -844,6 +844,11 @@ function bootstrapContractVersion(template) {
844
844
  if (!version) throw new Error("Bootstrap template has no BootstrapContractVersion output");
845
845
  return version;
846
846
  }
847
+ function bootstrapTemplateRevision(template) {
848
+ const revision = template.match(/BootstrapTemplateRevision:\s*\{\s*Value:\s*'([^']+)'\s*}/)?.[1];
849
+ if (!revision) throw new Error("Bootstrap template has no BootstrapTemplateRevision output");
850
+ return revision;
851
+ }
847
852
  function mergeBootstrapParameters(requested, deployed) {
848
853
  const supplied = new Map(requested.map((entry) => [entry.ParameterKey, entry.ParameterValue]));
849
854
  const duplicates = requested.map((entry) => entry.ParameterKey);
@@ -897,7 +902,7 @@ function requiredBootstrapParameters(kind, values) {
897
902
  ];
898
903
  const missing = keys.filter((key) => !values[key]);
899
904
  if (missing.length) throw new Error(`Missing required bootstrap values: ${missing.join(", ")}`);
900
- const optional2 = kind === "core" ? ["RuntimeSecretArn", "ArtifactKmsKeyArn"] : [];
905
+ const optional2 = kind === "core" ? ["RuntimeSecretArn", "ArtifactKmsKeyArn"] : ["ConsumerGatewayStackName"];
901
906
  return [...keys, ...optional2].filter((key) => values[key] !== void 0).map((ParameterKey) => ({ ParameterKey, ParameterValue: values[ParameterKey] ?? "" }));
902
907
  }
903
908
 
@@ -1115,6 +1120,7 @@ async function bootstrapHandoff(input) {
1115
1120
  GitHubOwnerId: input.config.githubOidc?.ownerId,
1116
1121
  GitHubRepositoryId: input.config.githubOidc?.repositoryId,
1117
1122
  GitHubEnvironment: input.config.githubOidc?.environment,
1123
+ ConsumerGatewayStackName: input.config.githubOidc?.consumerGatewayStackName,
1118
1124
  ...coreBootstrapOutputs(core)
1119
1125
  });
1120
1126
  const templatePath = await bootstrapTemplatePath("github-oidc");
@@ -1123,7 +1129,11 @@ async function bootstrapHandoff(input) {
1123
1129
  expectedContracts,
1124
1130
  templatePath,
1125
1131
  parameterPath: "github-oidc-bootstrap.json",
1126
- expectedOutputs: ["BootstrapContractVersion", "GitHubDeployRoleArn"],
1132
+ expectedOutputs: [
1133
+ "BootstrapContractVersion",
1134
+ "BootstrapTemplateRevision",
1135
+ "GitHubDeployRoleArn"
1136
+ ],
1127
1137
  requiredCapturedFiles: [coreCapturePath],
1128
1138
  reviewCheckpoints: [
1129
1139
  "Review the exact immutable GitHub OIDC subject, audience, bucket lambda/* prefix, application stack, and execution role.",
@@ -1231,9 +1241,14 @@ async function verifyBootstrap(input) {
1231
1241
  GitHubEnvironment: github?.environment,
1232
1242
  ApplicationStackName: coreOutputs.ApplicationStackName,
1233
1243
  ArtifactBucketName: coreOutputs.ArtifactBucketName,
1234
- CloudFormationExecutionRoleArn: executionRole
1244
+ CloudFormationExecutionRoleArn: executionRole,
1245
+ ConsumerGatewayStackName: github?.consumerGatewayStackName ?? ""
1235
1246
  }))
1236
- assertEqual(oidcParameters[key], value2, `OIDC ${key}`);
1247
+ assertEqual(
1248
+ key === "ConsumerGatewayStackName" ? oidcParameters[key] ?? "" : oidcParameters[key],
1249
+ value2,
1250
+ `OIDC ${key}`
1251
+ );
1237
1252
  if (!/^\d+$/.test(github?.ownerId ?? "") || !/^\d+$/.test(github?.repositoryId ?? ""))
1238
1253
  throw new Error("GitHub immutable owner and repository IDs must be numeric");
1239
1254
  const [owner, repository, ...extra] = (github?.repository ?? "").split("/");
@@ -1360,7 +1375,8 @@ async function scaffoldBootstrapManifest(input) {
1360
1375
  repository,
1361
1376
  ownerId,
1362
1377
  repositoryId,
1363
- environment
1378
+ environment,
1379
+ ...input.consumerGatewayStackName ? { consumerGatewayStackName: input.consumerGatewayStackName } : {}
1364
1380
  }
1365
1381
  };
1366
1382
  }
@@ -1694,6 +1710,7 @@ export {
1694
1710
  bootstrapPreflight,
1695
1711
  bootstrapTemplate,
1696
1712
  bootstrapTemplatePath,
1713
+ bootstrapTemplateRevision,
1697
1714
  cloudFormationTemplate,
1698
1715
  coreBootstrapOutputs,
1699
1716
  deployLambda,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@continuous-excellence/ze-great-dashboard-aws",
3
- "version": "0.4.0",
3
+ "version": "0.5.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,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
@@ -1 +0,0 @@
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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"}