@fluojs/studio 1.0.0-beta.2 → 1.0.0-beta.3

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.ko.md CHANGED
@@ -41,7 +41,7 @@ pnpm add @fluojs/studio
41
41
 
42
42
  ## 빠른 시작
43
43
 
44
- Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다. 런타임은 snapshot을 생산하고, CLI는 검사 데이터를 내보내거나 위임하며, Studio는 뷰어와 자동화 호출자가 사용할 수 있도록 snapshot을 파싱, 필터링, 보기, 렌더링하는 공개 헬퍼를 소유합니다.
44
+ Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다. 런타임은 snapshot을 생산하고, CLI는 검사 데이터를 내보내거나 위임하며, Studio는 뷰어와 자동화 호출자가 사용할 수 있도록 snapshot을 파싱, 필터링, 보기, 렌더링하는 공개 헬퍼를 소유합니다. 지원되는 inspect artifact에는 raw snapshot, standalone timing diagnostics, snapshot-plus-timing envelope, `fluo inspect --report`가 생성한 report artifact가 포함됩니다.
45
45
 
46
46
  1. **Snapshot 내보내기**:
47
47
  ```bash
@@ -79,6 +79,7 @@ Studio는 주로 웹 애플리케이션이지만, 배포된 패키지는 도구/
79
79
  | `PlatformShellSnapshot` | 애플리케이션 상태를 나타내는 핵심 데이터 구조입니다. |
80
80
  | `PlatformDiagnosticIssue` | 플랫폼 오류 보고 및 수정을 위한 스키마입니다. |
81
81
  | `parseStudioPayload(rawJson)` | CLI/export JSON을 Studio snapshot/timing envelope로 검증합니다. |
82
+ | `StudioReportArtifact` | CI/support 자동화를 위해 summary, snapshot, timing 데이터를 함께 보존한 `fluo inspect --report` artifact입니다. |
82
83
  | `applyFilters(snapshot, filter)` | 원본 snapshot을 변경하지 않고 readiness/severity/query 필터를 적용합니다. |
83
84
  | `renderMermaid(snapshot)` | 내부 컴포넌트 dependency edge와 외부 dependency node를 포함해 로드된 플랫폼 그래프를 Mermaid 텍스트로 변환합니다. |
84
85
 
package/README.md CHANGED
@@ -41,7 +41,7 @@ The published package serves two caller-facing entrypoints:
41
41
 
42
42
  ## Quick Start
43
43
 
44
- Studio consumes JSON exports from the fluo CLI. Runtime produces snapshots, the CLI exports or delegates inspection data, and Studio owns the public helpers that parse, filter, view, and render those snapshots for viewer and automation callers.
44
+ Studio consumes JSON exports from the fluo CLI. Runtime produces snapshots, the CLI exports or delegates inspection data, and Studio owns the public helpers that parse, filter, view, and render those snapshots for viewer and automation callers. Supported inspect artifacts include raw snapshots, standalone timing diagnostics, snapshot-plus-timing envelopes, and report artifacts produced by `fluo inspect --report`.
45
45
 
46
46
  1. **Export a snapshot**:
47
47
  ```bash
@@ -79,6 +79,7 @@ Studio is primarily a web application, but the published package also exposes th
79
79
  | `PlatformShellSnapshot` | The core data structure representing the application state. |
80
80
  | `PlatformDiagnosticIssue` | Schema for reporting and fixing platform errors. |
81
81
  | `parseStudioPayload(rawJson)` | Validates CLI/exported JSON into the Studio snapshot/timing envelope. |
82
+ | `StudioReportArtifact` | Preserved `fluo inspect --report` artifact with summary, snapshot, and timing data for CI/support automation. |
82
83
  | `applyFilters(snapshot, filter)` | Applies readiness/severity/query filters without mutating the source snapshot. |
83
84
  | `renderMermaid(snapshot)` | Produces Mermaid graph text from the loaded platform graph, including internal component dependency edges and external dependency nodes. |
84
85
 
@@ -0,0 +1,128 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))p(a);new MutationObserver(a=>{for(const o of a)if(o.type==="childList")for(const r of o.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&p(r)}).observe(document,{childList:!0,subtree:!0});function n(a){const o={};return a.integrity&&(o.integrity=a.integrity),a.referrerPolicy&&(o.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?o.credentials="include":a.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function p(a){if(a.ep)return;a.ep=!0;const o=n(a);fetch(a.href,o)}})();function u(e){return typeof e=="object"&&e!==null}function $(e,t){return Object.hasOwn(e,t)}function L(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function E(e){return e==="ready"||e==="not-ready"||e==="degraded"}function x(e){return e==="healthy"||e==="unhealthy"||e==="degraded"}function M(e){return e==="error"||e==="warning"||e==="info"}function j(e){if(!u(e))return null;if(typeof e.generatedAt!="string"||!u(e.readiness)||!u(e.health)||!Array.isArray(e.components)||!Array.isArray(e.diagnostics))throw new Error("Invalid platform snapshot payload.");if(!E(e.readiness.status)||typeof e.readiness.critical!="boolean")throw new Error("Invalid aggregate readiness in platform snapshot payload.");if(!x(e.health.status))throw new Error("Invalid aggregate health in platform snapshot payload.");for(const t of e.components){if(!u(t))throw new Error("Invalid component entry in platform snapshot payload.");if(typeof t.id!="string"||typeof t.kind!="string"||typeof t.state!="string"||!u(t.readiness)||!u(t.health)||!L(t.dependencies)||!u(t.telemetry)||!u(t.ownership)||!u(t.details))throw new Error("Invalid component shape in platform snapshot payload.");if(!E(t.readiness.status)||typeof t.readiness.critical!="boolean")throw new Error("Invalid component readiness in platform snapshot payload.");if(!x(t.health.status))throw new Error("Invalid component health in platform snapshot payload.");if(typeof t.telemetry.namespace!="string"||!u(t.telemetry.tags)||typeof t.ownership.ownsResources!="boolean"||typeof t.ownership.externallyManaged!="boolean")throw new Error("Invalid component telemetry/ownership in platform snapshot payload.")}for(const t of e.diagnostics){if(!u(t))throw new Error("Invalid diagnostics issue entry in platform snapshot payload.");if(typeof t.code!="string"||!M(t.severity)||typeof t.componentId!="string"||typeof t.message!="string")throw new Error("Invalid diagnostics issue shape in platform snapshot payload.");if(t.cause!==void 0&&typeof t.cause!="string"||t.fixHint!==void 0&&typeof t.fixHint!="string"||t.docsUrl!==void 0&&typeof t.docsUrl!="string"||t.dependsOn!==void 0&&!L(t.dependsOn))throw new Error("Invalid optional diagnostics issue fields in platform snapshot payload.")}return e}function k(e){if(!u(e))return null;if(e.version!==1)throw new Error("Unsupported bootstrap timing version. Expected version: 1.");if(typeof e.totalMs!="number"||!Array.isArray(e.phases))throw new Error("Invalid bootstrap timing payload.");for(const t of e.phases)if(!u(t)||typeof t.name!="string"||typeof t.durationMs!="number")throw new Error("Invalid phase entry in bootstrap timing payload.");return e}function T(e){if(!u(e))throw new Error("Invalid inspect report summary payload.");if(typeof e.componentCount!="number"||typeof e.diagnosticCount!="number"||typeof e.errorCount!="number"||!x(e.healthStatus)||!E(e.readinessStatus)||typeof e.timingTotalMs!="number"||typeof e.warningCount!="number")throw new Error("Invalid inspect report summary payload.");return e}function D(e){return e.summary!==void 0||$(e,"snapshot")&&$(e,"timing")&&($(e,"generatedAt")||$(e,"version"))}function J(e,t,n){if(!u(e)||!D(e))return null;if(e.summary===void 0||e.version!==1||typeof e.generatedAt!="string"||!t||!n)throw new Error("Invalid inspect report artifact payload.");return{generatedAt:e.generatedAt,snapshot:t,summary:T(e.summary),timing:n,version:1}}function q(e){const t=JSON.parse(e),n=u(t)?t:void 0,p=n!==void 0&&$(n,"snapshot"),a=n!==void 0&&$(n,"timing"),o=n!==void 0&&!p&&!a&&$(n,"version")&&$(n,"totalMs")&&$(n,"phases"),r=j(p?n.snapshot:o?void 0:t),d=k(a?n.timing:r?void 0:t),l=J(t,r,d);if(!r&&!d)throw new Error("Unsupported file format. Expected platform snapshot JSON or timing JSON.");return{payload:{...l?{report:l}:{},...r?{snapshot:r}:{},...d?{timing:d}:{}},rawJson:e}}function R(e,t){const n=t.query.trim().toLowerCase(),p=e.components.filter(o=>t.readinessStatuses.length>0&&!t.readinessStatuses.includes(o.readiness.status)?!1:n?o.id.toLowerCase().includes(n)||o.kind.toLowerCase().includes(n)||o.dependencies.some(r=>r.toLowerCase().includes(n)):!0),a=e.diagnostics.filter(o=>{var r,d,l,w;return t.severities.length>0&&!t.severities.includes(o.severity)?!1:n?o.code.toLowerCase().includes(n)||o.componentId.toLowerCase().includes(n)||o.message.toLowerCase().includes(n)||(((r=o.cause)==null?void 0:r.toLowerCase().includes(n))??!1)||(((d=o.fixHint)==null?void 0:d.toLowerCase().includes(n))??!1)||(((l=o.docsUrl)==null?void 0:l.toLowerCase().includes(n))??!1)||(((w=o.dependsOn)==null?void 0:w.some(g=>g.toLowerCase().includes(n)))??!1):!0});return{...e,components:p,diagnostics:a}}function b(e){return e.replaceAll('"','\\"')}function U(e){return e.replaceAll(/[^a-zA-Z0-9_]/g,"_")}function P(e){let t=2166136261;for(const n of e)t^=n.codePointAt(0)??0,t=Math.imul(t,16777619)>>>0;return t.toString(16).padStart(8,"0")}function F(e){return`EXT_${U(e)}_${P(e)}`}function O(e){const t=["graph TD"],n=new Map,p=new Map;if(e.components.length===0)return t.push(' EMPTY["No registered platform components"]'),t.join(`
2
+ `);for(const[r,d]of e.components.entries()){const l=`C${String(r+1)}`;n.set(d.id,l),t.push(` ${l}["${b(d.id)}\\nkind: ${b(d.kind)}\\nreadiness: ${d.readiness.status}\\nhealth: ${d.health.status}"]`)}for(const r of e.components){const d=n.get(r.id);if(d)for(const l of r.dependencies){const w=n.get(l);if(w){t.push(` ${d} --> ${w}`);continue}let g=p.get(l);g||(g=F(l),p.set(l,g),t.push(` ${g}["${b(l)}"]`)),t.push(` ${d} --> ${g}`)}}const a=[],o=[];for(const r of e.components){const d=n.get(r.id);d&&(r.readiness.status==="degraded"&&a.push(d),r.readiness.status==="not-ready"&&o.push(d))}return a.length>0&&(t.push(` class ${a.join(",")} degraded`),t.push(" classDef degraded stroke:#f59e0b,stroke-width:2px")),o.length>0&&(t.push(` class ${o.join(",")} notReady`),t.push(" classDef notReady stroke:#ef4444,stroke-width:2px")),t.join(`
3
+ `)}const N=document.querySelector("#app");if(!N)throw new Error("App root not found.");const H=N,_=["ready","degraded","not-ready"],z=["error","warning","info"],i={filter:{query:"",readinessStatuses:[],severities:[]}};function c(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#039;")}function Y(e,t){const n=new Blob([t],{type:"application/json"}),p=URL.createObjectURL(n),a=document.createElement("a");a.href=p,a.download=e,a.click(),URL.revokeObjectURL(p)}async function A(e){if(!navigator.clipboard)throw new Error("Clipboard API is unavailable.");await navigator.clipboard.writeText(e)}function C(e,t){return e.includes(t)?e.filter(n=>n!==t):[...e,t]}function I(e,t){return!e||e.components.length===0?void 0:(t?e.components.find(p=>p.id===t):void 0)??e.components[0]}function S(){var n;const e=(n=i.payload)==null?void 0:n.snapshot;if(!e){i.filteredSnapshot=void 0,i.selectedComponentId=void 0;return}i.filteredSnapshot=R(e,i.filter);const t=I(i.filteredSnapshot,i.selectedComponentId);i.selectedComponentId=t==null?void 0:t.id}function X(e,t){const a=Math.min(900,460)/2-70,o=900/2,r=460/2,d=e.components,l=new Map;d.forEach((m,s)=>{const f=Math.PI*2*s/Math.max(d.length,1);l.set(m.id,{x:o+a*Math.cos(f),y:r+a*Math.sin(f)})});const w=d.flatMap(m=>m.dependencies.map(s=>{const f=l.get(m.id),h=l.get(s);return!f||!h?"":`<line x1="${f.x}" y1="${f.y}" x2="${h.x}" y2="${h.y}" class="edge-line" marker-end="url(#arrow)" />`})).join(""),g=d.map(m=>{const s=l.get(m.id);if(!s)return"";const h=["module-node",m.readiness.status==="not-ready"?"component-not-ready":m.readiness.status==="degraded"?"component-degraded":"component-ready",m.id===t?"module-selected":""].filter(Boolean).join(" ");return`<g>
4
+ <circle cx="${s.x}" cy="${s.y}" r="34" class="${h}" data-component="${c(m.id)}" />
5
+ <text x="${s.x}" y="${s.y+4}" text-anchor="middle" class="module-label">${c(m.id)}</text>
6
+ </g>`}).join("");return`<svg viewBox="0 0 900 460" role="img" aria-label="Platform component dependency graph">
7
+ <defs>
8
+ <marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
9
+ <polygon points="0 0, 10 3.5, 0 7" class="edge-arrow" />
10
+ </marker>
11
+ </defs>
12
+ ${w}
13
+ ${g}
14
+ </svg>`}function B(e){if(!e)return'<p class="muted">No component selected.</p>';const t=e.dependencies.length>0?e.dependencies.map(n=>`<span class="chip">dependsOn: ${c(n)}</span>`).join(""):'<span class="chip">dependsOn: none</span>';return`
15
+ <h3>${c(e.id)}</h3>
16
+ <p class="muted">kind: <strong>${c(e.kind)}</strong> · state: <strong>${c(e.state)}</strong></p>
17
+ <div class="chips">
18
+ <span class="chip">readiness: ${c(e.readiness.status)}</span>
19
+ <span class="chip">critical: ${e.readiness.critical?"true":"false"}</span>
20
+ <span class="chip">health: ${c(e.health.status)}</span>
21
+ <span class="chip">ownership: owns=${e.ownership.ownsResources?"true":"false"}/external=${e.ownership.externallyManaged?"true":"false"}</span>
22
+ ${t}
23
+ </div>
24
+ <p class="muted">telemetry namespace: <code>${c(e.telemetry.namespace)}</code></p>
25
+ <h4>Sanitized details</h4>
26
+ <pre>${c(JSON.stringify(e.details,null,2))}</pre>
27
+ `}function V(){var t;const e=(t=i.payload)==null?void 0:t.timing;return e?`
28
+ <p><strong>Total:</strong> ${e.totalMs.toFixed(3)}ms</p>
29
+ <table>
30
+ <thead>
31
+ <tr><th>phase</th><th>duration (ms)</th></tr>
32
+ </thead>
33
+ <tbody>
34
+ ${e.phases.map(n=>`<tr><td>${c(n.name)}</td><td>${n.durationMs.toFixed(3)}</td></tr>`).join("")}
35
+ </tbody>
36
+ </table>
37
+ `:'<p class="muted">Timing not collected.</p>'}function G(e){if(!e)return'<p class="muted">No platform snapshot loaded.</p>';const t={degraded:e.components.filter(n=>n.readiness.status==="degraded").length,notReady:e.components.filter(n=>n.readiness.status==="not-ready").length,ready:e.components.filter(n=>n.readiness.status==="ready").length};return`
38
+ <div class="chips">
39
+ <span class="chip">generatedAt: ${c(e.generatedAt)}</span>
40
+ <span class="chip">aggregate readiness: ${c(e.readiness.status)}</span>
41
+ <span class="chip">aggregate health: ${c(e.health.status)}</span>
42
+ <span class="chip">components: ${String(e.components.length)}</span>
43
+ <span class="chip">diagnostics: ${String(e.diagnostics.length)}</span>
44
+ <span class="chip">ready/degraded/not-ready: ${t.ready}/${t.degraded}/${t.notReady}</span>
45
+ </div>
46
+ `}function K(e){return e?e.diagnostics.length===0?'<p class="muted">No diagnostics issues.</p>':`<div class="diagnostics-list">
47
+ ${e.diagnostics.map(t=>{const n=t.dependsOn&&t.dependsOn.length>0?`<div class="chips">${t.dependsOn.map(p=>`<span class="chip">dependsOn: ${c(p)}</span>`).join("")}</div>`:"";return`<article class="card issue severity-${c(t.severity)}">
48
+ <h3>${c(t.code)}</h3>
49
+ <p><strong>severity:</strong> ${c(t.severity)} · <strong>component:</strong> ${c(t.componentId)}</p>
50
+ <p>${c(t.message)}</p>
51
+ ${t.cause?`<p><strong>cause:</strong> ${c(t.cause)}</p>`:""}
52
+ ${t.fixHint?`<p><strong>fix hint:</strong> ${c(t.fixHint)}</p>`:""}
53
+ ${t.docsUrl?`<p><strong>docs:</strong> <a href="${c(t.docsUrl)}" target="_blank" rel="noreferrer">${c(t.docsUrl)}</a></p>`:""}
54
+ ${n}
55
+ </article>`}).join("")}
56
+ </div>`:'<p class="muted">No platform snapshot loaded.</p>'}function y(e){const t=i.filteredSnapshot,n=I(t,i.selectedComponentId),p=t?O(t):"",a=t&&t.components.length>0?X(t,n==null?void 0:n.id):'<p class="muted">No platform components loaded.</p>';H.innerHTML=`
57
+ <main>
58
+ <header>
59
+ <h1>Fluo Studio Platform Snapshot Viewer</h1>
60
+ <p>Load JSON exported by <code>fluo inspect --json</code> (shared platform snapshot/diagnostic schema) and optionally timing JSON from <code>--timing</code>.</p>
61
+ </header>
62
+
63
+ <section class="card uploader" id="drop-zone">
64
+ <h2>Diagnostics file input</h2>
65
+ <p>Drag & drop a JSON file, or choose one manually.</p>
66
+ <input type="file" id="file-input" accept="application/json" />
67
+ <div class="actions">
68
+ <button id="download-json" ${i.rawJson?"":"disabled"}>Download loaded JSON</button>
69
+ <button id="copy-json" ${i.rawJson?"":"disabled"}>Copy loaded JSON</button>
70
+ <button id="copy-mermaid" ${t?"":"disabled"}>Copy Mermaid</button>
71
+ </div>
72
+ ${e?`<p class="notice">${c(e)}</p>`:""}
73
+ </section>
74
+
75
+ <section class="card">
76
+ <h2>Snapshot summary</h2>
77
+ ${G(t)}
78
+ </section>
79
+
80
+ <section class="split-grid">
81
+ <div class="card">
82
+ <h2>Search and filtering</h2>
83
+ <label>
84
+ Search component/diagnostic
85
+ <input type="text" id="search" value="${c(i.filter.query)}" placeholder="e.g. redis.default or QUEUE_DEPENDENCY_NOT_READY" />
86
+ </label>
87
+
88
+ <div class="filter-row">
89
+ <span>Component readiness</span>
90
+ ${_.map(s=>`<label><input type="checkbox" data-readiness="${s}" ${i.filter.readinessStatuses.includes(s)?"checked":""}/> ${s}</label>`).join("")}
91
+ </div>
92
+
93
+ <div class="filter-row">
94
+ <span>Diagnostic severity</span>
95
+ ${z.map(s=>`<label><input type="checkbox" data-severity="${s}" ${i.filter.severities.includes(s)?"checked":""}/> ${s}</label>`).join("")}
96
+ </div>
97
+ </div>
98
+
99
+ <div class="card">
100
+ <h2>Timing</h2>
101
+ ${V()}
102
+ </div>
103
+ </section>
104
+
105
+ <section class="card">
106
+ <h2>Platform dependency graph</h2>
107
+ <p class="muted">Component dependencies are rendered directly from the shared platform snapshot schema.</p>
108
+ <div id="graph-host">${a}</div>
109
+ </section>
110
+
111
+ <section class="split-grid">
112
+ <div class="card" id="details-panel">
113
+ <h2>Component details</h2>
114
+ ${B(n)}
115
+ </div>
116
+ <div class="card">
117
+ <h2>Mermaid output</h2>
118
+ <pre>${c(p||"No snapshot loaded.")}</pre>
119
+ </div>
120
+ </section>
121
+
122
+ <section class="card">
123
+ <h2>Diagnostics issues</h2>
124
+ <p class="muted">Fix hints and dependency chains are rendered from <code>diagnostics.fixHint</code> and <code>diagnostics.dependsOn</code>.</p>
125
+ ${K(t)}
126
+ </section>
127
+ </main>
128
+ `;const o=document.querySelector("#file-input"),r=document.querySelector("#drop-zone"),d=document.querySelector("#search"),l=document.querySelector("#copy-json"),w=document.querySelector("#download-json"),g=document.querySelector("#copy-mermaid"),m=async s=>{const f=await s.text();try{const h=q(f);i.payload=h.payload,i.rawJson=h.rawJson,S(),y("Diagnostics file loaded successfully.")}catch(h){i.payload=void 0,i.filteredSnapshot=void 0,i.selectedComponentId=void 0,i.rawJson=void 0,y(h instanceof Error?h.message:"Failed to parse diagnostics file.")}};o==null||o.addEventListener("change",async s=>{var v;const h=(v=s.target.files)==null?void 0:v[0];h&&await m(h)}),r==null||r.addEventListener("dragover",s=>{s.preventDefault(),r.classList.add("drag-active")}),r==null||r.addEventListener("dragleave",()=>{r.classList.remove("drag-active")}),r==null||r.addEventListener("drop",async s=>{var h,v;s.preventDefault(),r.classList.remove("drag-active");const f=(v=(h=s.dataTransfer)==null?void 0:h.files)==null?void 0:v[0];f&&await m(f)}),d==null||d.addEventListener("input",s=>{const f=s.target;i.filter.query=f.value,S(),y()}),document.querySelectorAll("input[data-readiness]").forEach(s=>{s.addEventListener("change",()=>{i.filter.readinessStatuses=C(i.filter.readinessStatuses,s.dataset.readiness),S(),y()})}),document.querySelectorAll("input[data-severity]").forEach(s=>{s.addEventListener("change",()=>{i.filter.severities=C(i.filter.severities,s.dataset.severity),S(),y()})}),l==null||l.addEventListener("click",async()=>{if(i.rawJson)try{await A(i.rawJson),y("Loaded JSON copied to clipboard.")}catch(s){y(s instanceof Error?s.message:"Failed to copy JSON.")}}),w==null||w.addEventListener("click",()=>{i.rawJson&&(Y("fluo-diagnostics.json",i.rawJson),y("Loaded JSON downloaded."))}),g==null||g.addEventListener("click",async()=>{if(t)try{await A(O(t)),y("Mermaid copied to clipboard.")}catch(s){y(s instanceof Error?s.message:"Failed to copy Mermaid text.")}}),document.querySelectorAll("[data-component]").forEach(s=>{s.addEventListener("click",()=>{const f=s.dataset.component;f&&(i.selectedComponentId=f,y())})})}S();y();
@@ -8,10 +8,33 @@ export type PlatformReadinessStatus = PlatformSnapshot['readiness']['status'];
8
8
  * Diagnostic severities supported by Studio snapshot filtering.
9
9
  */
10
10
  export type PlatformDiagnosticSeverity = PlatformDiagnosticIssue['severity'];
11
+ /**
12
+ * Stable summary emitted by `fluo inspect --report` for support and CI triage.
13
+ */
14
+ export interface StudioReportSummary {
15
+ componentCount: number;
16
+ diagnosticCount: number;
17
+ errorCount: number;
18
+ healthStatus: PlatformShellSnapshot['health']['status'];
19
+ readinessStatus: PlatformShellSnapshot['readiness']['status'];
20
+ timingTotalMs: number;
21
+ warningCount: number;
22
+ }
23
+ /**
24
+ * CI-friendly report artifact emitted by `fluo inspect --report`.
25
+ */
26
+ export interface StudioReportArtifact {
27
+ generatedAt: string;
28
+ snapshot: PlatformShellSnapshot;
29
+ summary: StudioReportSummary;
30
+ timing: BootstrapTimingDiagnostics;
31
+ version: 1;
32
+ }
11
33
  /**
12
34
  * Serializable Studio payload envelope built from inspect snapshot/timing exports.
13
35
  */
14
36
  export interface StudioPayload {
37
+ report?: StudioReportArtifact;
15
38
  snapshot?: PlatformShellSnapshot;
16
39
  timing?: BootstrapTimingDiagnostics;
17
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,uBAAuB,EAAE,CAAC;IAC7C,UAAU,EAAE,0BAA0B,EAAE,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAmID;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAkBjE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,WAAW,GAAG,qBAAqB,CAwCxG;AAyBD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAsErE"}
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC;IACxD,eAAe,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9D,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,qBAAqB,CAAC;IAChC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,MAAM,EAAE,0BAA0B,CAAC;IACnC,OAAO,EAAE,CAAC,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,uBAAuB,EAAE,CAAC;IAC7C,UAAU,EAAE,0BAA0B,EAAE,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAkLD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CA4BjE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,qBAAqB,EAAE,MAAM,EAAE,WAAW,GAAG,qBAAqB,CAwCxG;AAyBD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAsErE"}
package/dist/contracts.js CHANGED
@@ -6,6 +6,14 @@
6
6
  * Diagnostic severities supported by Studio snapshot filtering.
7
7
  */
8
8
 
9
+ /**
10
+ * Stable summary emitted by `fluo inspect --report` for support and CI triage.
11
+ */
12
+
13
+ /**
14
+ * CI-friendly report artifact emitted by `fluo inspect --report`.
15
+ */
16
+
9
17
  /**
10
18
  * Serializable Studio payload envelope built from inspect snapshot/timing exports.
11
19
  */
@@ -21,6 +29,9 @@
21
29
  function isRecord(value) {
22
30
  return typeof value === 'object' && value !== null;
23
31
  }
32
+ function hasOwn(value, key) {
33
+ return Object.hasOwn(value, key);
34
+ }
24
35
  function isStringArray(value) {
25
36
  return Array.isArray(value) && value.every(entry => typeof entry === 'string');
26
37
  }
@@ -93,6 +104,33 @@ function validateTiming(value) {
93
104
  }
94
105
  return value;
95
106
  }
107
+ function validateReportSummary(value) {
108
+ if (!isRecord(value)) {
109
+ throw new Error('Invalid inspect report summary payload.');
110
+ }
111
+ if (typeof value.componentCount !== 'number' || typeof value.diagnosticCount !== 'number' || typeof value.errorCount !== 'number' || !isHealthStatus(value.healthStatus) || !isReadinessStatus(value.readinessStatus) || typeof value.timingTotalMs !== 'number' || typeof value.warningCount !== 'number') {
112
+ throw new Error('Invalid inspect report summary payload.');
113
+ }
114
+ return value;
115
+ }
116
+ function isReportArtifactEnvelope(value) {
117
+ return value.summary !== undefined || hasOwn(value, 'snapshot') && hasOwn(value, 'timing') && (hasOwn(value, 'generatedAt') || hasOwn(value, 'version'));
118
+ }
119
+ function validateReport(value, snapshot, timing) {
120
+ if (!isRecord(value) || !isReportArtifactEnvelope(value)) {
121
+ return null;
122
+ }
123
+ if (value.summary === undefined || value.version !== 1 || typeof value.generatedAt !== 'string' || !snapshot || !timing) {
124
+ throw new Error('Invalid inspect report artifact payload.');
125
+ }
126
+ return {
127
+ generatedAt: value.generatedAt,
128
+ snapshot,
129
+ summary: validateReportSummary(value.summary),
130
+ timing,
131
+ version: 1
132
+ };
133
+ }
96
134
 
97
135
  /**
98
136
  * Parses a Studio JSON file into the documented snapshot/timing envelope.
@@ -104,13 +142,20 @@ function validateTiming(value) {
104
142
  export function parseStudioPayload(rawJson) {
105
143
  const parsed = JSON.parse(rawJson);
106
144
  const envelope = isRecord(parsed) ? parsed : undefined;
107
- const snapshot = validateSnapshot(envelope?.snapshot ?? parsed);
108
- const timing = validateTiming(envelope?.timing ?? (!snapshot ? parsed : undefined));
145
+ const hasSnapshotEnvelope = envelope !== undefined && hasOwn(envelope, 'snapshot');
146
+ const hasTimingEnvelope = envelope !== undefined && hasOwn(envelope, 'timing');
147
+ const standaloneTiming = envelope !== undefined && !hasSnapshotEnvelope && !hasTimingEnvelope && hasOwn(envelope, 'version') && hasOwn(envelope, 'totalMs') && hasOwn(envelope, 'phases');
148
+ const snapshot = validateSnapshot(hasSnapshotEnvelope ? envelope.snapshot : standaloneTiming ? undefined : parsed);
149
+ const timing = validateTiming(hasTimingEnvelope ? envelope.timing : !snapshot ? parsed : undefined);
150
+ const report = validateReport(parsed, snapshot, timing);
109
151
  if (!snapshot && !timing) {
110
152
  throw new Error('Unsupported file format. Expected platform snapshot JSON or timing JSON.');
111
153
  }
112
154
  return {
113
155
  payload: {
156
+ ...(report ? {
157
+ report
158
+ } : {}),
114
159
  ...(snapshot ? {
115
160
  snapshot
116
161
  } : {}),
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { applyFilters, parseStudioPayload, renderMermaid, type FilterState, type ParsedPayload, type PlatformDiagnosticIssue, type PlatformDiagnosticSeverity, type PlatformReadinessStatus, type PlatformShellSnapshot, type StudioPayload, } from './contracts.js';
1
+ export { applyFilters, parseStudioPayload, renderMermaid, type FilterState, type ParsedPayload, type PlatformDiagnosticIssue, type PlatformDiagnosticSeverity, type PlatformReadinessStatus, type StudioReportArtifact, type StudioReportSummary, type PlatformShellSnapshot, type StudioPayload, } from './contracts.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC"}
package/dist/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Fluo Studio</title>
7
- <script type="module" crossorigin src="/assets/index-CUoyTA_q.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-D-3gdi9Y.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-BTqPuJus.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "module-graph",
10
10
  "devtools"
11
11
  ],
12
- "version": "1.0.0-beta.2",
12
+ "version": "1.0.0-beta.3",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -41,7 +41,7 @@
41
41
  "dist"
42
42
  ],
43
43
  "dependencies": {
44
- "@fluojs/runtime": "^1.0.0-beta.1"
44
+ "@fluojs/runtime": "^1.0.0-beta.2"
45
45
  },
46
46
  "devDependencies": {
47
47
  "vitest": "^3.2.4",
@@ -1,128 +0,0 @@
1
- (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))l(o);new MutationObserver(o=>{for(const r of o)if(r.type==="childList")for(const a of r.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&l(a)}).observe(document,{childList:!0,subtree:!0});function n(o){const r={};return o.integrity&&(r.integrity=o.integrity),o.referrerPolicy&&(r.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?r.credentials="include":o.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function l(o){if(o.ep)return;o.ep=!0;const r=n(o);fetch(o.href,r)}})();function g(t){return typeof t=="object"&&t!==null}function b(t){return Array.isArray(t)&&t.every(e=>typeof e=="string")}function E(t){return t==="ready"||t==="not-ready"||t==="degraded"}function x(t){return t==="healthy"||t==="unhealthy"||t==="degraded"}function I(t){return t==="error"||t==="warning"||t==="info"}function M(t){if(!g(t))return null;if(typeof t.generatedAt!="string"||!g(t.readiness)||!g(t.health)||!Array.isArray(t.components)||!Array.isArray(t.diagnostics))throw new Error("Invalid platform snapshot payload.");if(!E(t.readiness.status)||typeof t.readiness.critical!="boolean")throw new Error("Invalid aggregate readiness in platform snapshot payload.");if(!x(t.health.status))throw new Error("Invalid aggregate health in platform snapshot payload.");for(const e of t.components){if(!g(e))throw new Error("Invalid component entry in platform snapshot payload.");if(typeof e.id!="string"||typeof e.kind!="string"||typeof e.state!="string"||!g(e.readiness)||!g(e.health)||!b(e.dependencies)||!g(e.telemetry)||!g(e.ownership)||!g(e.details))throw new Error("Invalid component shape in platform snapshot payload.");if(!E(e.readiness.status)||typeof e.readiness.critical!="boolean")throw new Error("Invalid component readiness in platform snapshot payload.");if(!x(e.health.status))throw new Error("Invalid component health in platform snapshot payload.");if(typeof e.telemetry.namespace!="string"||!g(e.telemetry.tags)||typeof e.ownership.ownsResources!="boolean"||typeof e.ownership.externallyManaged!="boolean")throw new Error("Invalid component telemetry/ownership in platform snapshot payload.")}for(const e of t.diagnostics){if(!g(e))throw new Error("Invalid diagnostics issue entry in platform snapshot payload.");if(typeof e.code!="string"||!I(e.severity)||typeof e.componentId!="string"||typeof e.message!="string")throw new Error("Invalid diagnostics issue shape in platform snapshot payload.");if(e.cause!==void 0&&typeof e.cause!="string"||e.fixHint!==void 0&&typeof e.fixHint!="string"||e.docsUrl!==void 0&&typeof e.docsUrl!="string"||e.dependsOn!==void 0&&!b(e.dependsOn))throw new Error("Invalid optional diagnostics issue fields in platform snapshot payload.")}return t}function j(t){if(!g(t))return null;if(t.version!==1)throw new Error("Unsupported bootstrap timing version. Expected version: 1.");if(typeof t.totalMs!="number"||!Array.isArray(t.phases))throw new Error("Invalid bootstrap timing payload.");for(const e of t.phases)if(!g(e)||typeof e.name!="string"||typeof e.durationMs!="number")throw new Error("Invalid phase entry in bootstrap timing payload.");return t}function k(t){const e=JSON.parse(t),n=g(e)?e:void 0,l=M((n==null?void 0:n.snapshot)??e),o=j((n==null?void 0:n.timing)??(l?void 0:e));if(!l&&!o)throw new Error("Unsupported file format. Expected platform snapshot JSON or timing JSON.");return{payload:{...l?{snapshot:l}:{},...o?{timing:o}:{}},rawJson:t}}function D(t,e){const n=e.query.trim().toLowerCase(),l=t.components.filter(r=>e.readinessStatuses.length>0&&!e.readinessStatuses.includes(r.readiness.status)?!1:n?r.id.toLowerCase().includes(n)||r.kind.toLowerCase().includes(n)||r.dependencies.some(a=>a.toLowerCase().includes(n)):!0),o=t.diagnostics.filter(r=>{var a,c,p,w;return e.severities.length>0&&!e.severities.includes(r.severity)?!1:n?r.code.toLowerCase().includes(n)||r.componentId.toLowerCase().includes(n)||r.message.toLowerCase().includes(n)||(((a=r.cause)==null?void 0:a.toLowerCase().includes(n))??!1)||(((c=r.fixHint)==null?void 0:c.toLowerCase().includes(n))??!1)||(((p=r.docsUrl)==null?void 0:p.toLowerCase().includes(n))??!1)||(((w=r.dependsOn)==null?void 0:w.some(m=>m.toLowerCase().includes(n)))??!1):!0});return{...t,components:l,diagnostics:o}}function S(t){return t.replaceAll('"','\\"')}function J(t){return t.replaceAll(/[^a-zA-Z0-9_]/g,"_")}function q(t){let e=2166136261;for(const n of t)e^=n.codePointAt(0)??0,e=Math.imul(e,16777619)>>>0;return e.toString(16).padStart(8,"0")}function T(t){return`EXT_${J(t)}_${q(t)}`}function L(t){const e=["graph TD"],n=new Map,l=new Map;if(t.components.length===0)return e.push(' EMPTY["No registered platform components"]'),e.join(`
2
- `);for(const[a,c]of t.components.entries()){const p=`C${String(a+1)}`;n.set(c.id,p),e.push(` ${p}["${S(c.id)}\\nkind: ${S(c.kind)}\\nreadiness: ${c.readiness.status}\\nhealth: ${c.health.status}"]`)}for(const a of t.components){const c=n.get(a.id);if(c)for(const p of a.dependencies){const w=n.get(p);if(w){e.push(` ${c} --> ${w}`);continue}let m=l.get(p);m||(m=T(p),l.set(p,m),e.push(` ${m}["${S(p)}"]`)),e.push(` ${c} --> ${m}`)}}const o=[],r=[];for(const a of t.components){const c=n.get(a.id);c&&(a.readiness.status==="degraded"&&o.push(c),a.readiness.status==="not-ready"&&r.push(c))}return o.length>0&&(e.push(` class ${o.join(",")} degraded`),e.push(" classDef degraded stroke:#f59e0b,stroke-width:2px")),r.length>0&&(e.push(` class ${r.join(",")} notReady`),e.push(" classDef notReady stroke:#ef4444,stroke-width:2px")),e.join(`
3
- `)}const C=document.querySelector("#app");if(!C)throw new Error("App root not found.");const R=C,U=["ready","degraded","not-ready"],P=["error","warning","info"],i={filter:{query:"",readinessStatuses:[],severities:[]}};function d(t){return t.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#039;")}function F(t,e){const n=new Blob([e],{type:"application/json"}),l=URL.createObjectURL(n),o=document.createElement("a");o.href=l,o.download=t,o.click(),URL.revokeObjectURL(l)}async function O(t){if(!navigator.clipboard)throw new Error("Clipboard API is unavailable.");await navigator.clipboard.writeText(t)}function N(t,e){return t.includes(e)?t.filter(n=>n!==e):[...t,e]}function A(t,e){return!t||t.components.length===0?void 0:(e?t.components.find(l=>l.id===e):void 0)??t.components[0]}function v(){var n;const t=(n=i.payload)==null?void 0:n.snapshot;if(!t){i.filteredSnapshot=void 0,i.selectedComponentId=void 0;return}i.filteredSnapshot=D(t,i.filter);const e=A(i.filteredSnapshot,i.selectedComponentId);i.selectedComponentId=e==null?void 0:e.id}function H(t,e){const o=Math.min(900,460)/2-70,r=900/2,a=460/2,c=t.components,p=new Map;c.forEach((u,s)=>{const f=Math.PI*2*s/Math.max(c.length,1);p.set(u.id,{x:r+o*Math.cos(f),y:a+o*Math.sin(f)})});const w=c.flatMap(u=>u.dependencies.map(s=>{const f=p.get(u.id),h=p.get(s);return!f||!h?"":`<line x1="${f.x}" y1="${f.y}" x2="${h.x}" y2="${h.y}" class="edge-line" marker-end="url(#arrow)" />`})).join(""),m=c.map(u=>{const s=p.get(u.id);if(!s)return"";const h=["module-node",u.readiness.status==="not-ready"?"component-not-ready":u.readiness.status==="degraded"?"component-degraded":"component-ready",u.id===e?"module-selected":""].filter(Boolean).join(" ");return`<g>
4
- <circle cx="${s.x}" cy="${s.y}" r="34" class="${h}" data-component="${d(u.id)}" />
5
- <text x="${s.x}" y="${s.y+4}" text-anchor="middle" class="module-label">${d(u.id)}</text>
6
- </g>`}).join("");return`<svg viewBox="0 0 900 460" role="img" aria-label="Platform component dependency graph">
7
- <defs>
8
- <marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
9
- <polygon points="0 0, 10 3.5, 0 7" class="edge-arrow" />
10
- </marker>
11
- </defs>
12
- ${w}
13
- ${m}
14
- </svg>`}function _(t){if(!t)return'<p class="muted">No component selected.</p>';const e=t.dependencies.length>0?t.dependencies.map(n=>`<span class="chip">dependsOn: ${d(n)}</span>`).join(""):'<span class="chip">dependsOn: none</span>';return`
15
- <h3>${d(t.id)}</h3>
16
- <p class="muted">kind: <strong>${d(t.kind)}</strong> · state: <strong>${d(t.state)}</strong></p>
17
- <div class="chips">
18
- <span class="chip">readiness: ${d(t.readiness.status)}</span>
19
- <span class="chip">critical: ${t.readiness.critical?"true":"false"}</span>
20
- <span class="chip">health: ${d(t.health.status)}</span>
21
- <span class="chip">ownership: owns=${t.ownership.ownsResources?"true":"false"}/external=${t.ownership.externallyManaged?"true":"false"}</span>
22
- ${e}
23
- </div>
24
- <p class="muted">telemetry namespace: <code>${d(t.telemetry.namespace)}</code></p>
25
- <h4>Sanitized details</h4>
26
- <pre>${d(JSON.stringify(t.details,null,2))}</pre>
27
- `}function z(){var e;const t=(e=i.payload)==null?void 0:e.timing;return t?`
28
- <p><strong>Total:</strong> ${t.totalMs.toFixed(3)}ms</p>
29
- <table>
30
- <thead>
31
- <tr><th>phase</th><th>duration (ms)</th></tr>
32
- </thead>
33
- <tbody>
34
- ${t.phases.map(n=>`<tr><td>${d(n.name)}</td><td>${n.durationMs.toFixed(3)}</td></tr>`).join("")}
35
- </tbody>
36
- </table>
37
- `:'<p class="muted">Timing not collected.</p>'}function Y(t){if(!t)return'<p class="muted">No platform snapshot loaded.</p>';const e={degraded:t.components.filter(n=>n.readiness.status==="degraded").length,notReady:t.components.filter(n=>n.readiness.status==="not-ready").length,ready:t.components.filter(n=>n.readiness.status==="ready").length};return`
38
- <div class="chips">
39
- <span class="chip">generatedAt: ${d(t.generatedAt)}</span>
40
- <span class="chip">aggregate readiness: ${d(t.readiness.status)}</span>
41
- <span class="chip">aggregate health: ${d(t.health.status)}</span>
42
- <span class="chip">components: ${String(t.components.length)}</span>
43
- <span class="chip">diagnostics: ${String(t.diagnostics.length)}</span>
44
- <span class="chip">ready/degraded/not-ready: ${e.ready}/${e.degraded}/${e.notReady}</span>
45
- </div>
46
- `}function X(t){return t?t.diagnostics.length===0?'<p class="muted">No diagnostics issues.</p>':`<div class="diagnostics-list">
47
- ${t.diagnostics.map(e=>{const n=e.dependsOn&&e.dependsOn.length>0?`<div class="chips">${e.dependsOn.map(l=>`<span class="chip">dependsOn: ${d(l)}</span>`).join("")}</div>`:"";return`<article class="card issue severity-${d(e.severity)}">
48
- <h3>${d(e.code)}</h3>
49
- <p><strong>severity:</strong> ${d(e.severity)} · <strong>component:</strong> ${d(e.componentId)}</p>
50
- <p>${d(e.message)}</p>
51
- ${e.cause?`<p><strong>cause:</strong> ${d(e.cause)}</p>`:""}
52
- ${e.fixHint?`<p><strong>fix hint:</strong> ${d(e.fixHint)}</p>`:""}
53
- ${e.docsUrl?`<p><strong>docs:</strong> <a href="${d(e.docsUrl)}" target="_blank" rel="noreferrer">${d(e.docsUrl)}</a></p>`:""}
54
- ${n}
55
- </article>`}).join("")}
56
- </div>`:'<p class="muted">No platform snapshot loaded.</p>'}function y(t){const e=i.filteredSnapshot,n=A(e,i.selectedComponentId),l=e?L(e):"",o=e&&e.components.length>0?H(e,n==null?void 0:n.id):'<p class="muted">No platform components loaded.</p>';R.innerHTML=`
57
- <main>
58
- <header>
59
- <h1>Fluo Studio Platform Snapshot Viewer</h1>
60
- <p>Load JSON exported by <code>fluo inspect --json</code> (shared platform snapshot/diagnostic schema) and optionally timing JSON from <code>--timing</code>.</p>
61
- </header>
62
-
63
- <section class="card uploader" id="drop-zone">
64
- <h2>Diagnostics file input</h2>
65
- <p>Drag & drop a JSON file, or choose one manually.</p>
66
- <input type="file" id="file-input" accept="application/json" />
67
- <div class="actions">
68
- <button id="download-json" ${i.rawJson?"":"disabled"}>Download loaded JSON</button>
69
- <button id="copy-json" ${i.rawJson?"":"disabled"}>Copy loaded JSON</button>
70
- <button id="copy-mermaid" ${e?"":"disabled"}>Copy Mermaid</button>
71
- </div>
72
- ${t?`<p class="notice">${d(t)}</p>`:""}
73
- </section>
74
-
75
- <section class="card">
76
- <h2>Snapshot summary</h2>
77
- ${Y(e)}
78
- </section>
79
-
80
- <section class="split-grid">
81
- <div class="card">
82
- <h2>Search and filtering</h2>
83
- <label>
84
- Search component/diagnostic
85
- <input type="text" id="search" value="${d(i.filter.query)}" placeholder="e.g. redis.default or QUEUE_DEPENDENCY_NOT_READY" />
86
- </label>
87
-
88
- <div class="filter-row">
89
- <span>Component readiness</span>
90
- ${U.map(s=>`<label><input type="checkbox" data-readiness="${s}" ${i.filter.readinessStatuses.includes(s)?"checked":""}/> ${s}</label>`).join("")}
91
- </div>
92
-
93
- <div class="filter-row">
94
- <span>Diagnostic severity</span>
95
- ${P.map(s=>`<label><input type="checkbox" data-severity="${s}" ${i.filter.severities.includes(s)?"checked":""}/> ${s}</label>`).join("")}
96
- </div>
97
- </div>
98
-
99
- <div class="card">
100
- <h2>Timing</h2>
101
- ${z()}
102
- </div>
103
- </section>
104
-
105
- <section class="card">
106
- <h2>Platform dependency graph</h2>
107
- <p class="muted">Component dependencies are rendered directly from the shared platform snapshot schema.</p>
108
- <div id="graph-host">${o}</div>
109
- </section>
110
-
111
- <section class="split-grid">
112
- <div class="card" id="details-panel">
113
- <h2>Component details</h2>
114
- ${_(n)}
115
- </div>
116
- <div class="card">
117
- <h2>Mermaid output</h2>
118
- <pre>${d(l||"No snapshot loaded.")}</pre>
119
- </div>
120
- </section>
121
-
122
- <section class="card">
123
- <h2>Diagnostics issues</h2>
124
- <p class="muted">Fix hints and dependency chains are rendered from <code>diagnostics.fixHint</code> and <code>diagnostics.dependsOn</code>.</p>
125
- ${X(e)}
126
- </section>
127
- </main>
128
- `;const r=document.querySelector("#file-input"),a=document.querySelector("#drop-zone"),c=document.querySelector("#search"),p=document.querySelector("#copy-json"),w=document.querySelector("#download-json"),m=document.querySelector("#copy-mermaid"),u=async s=>{const f=await s.text();try{const h=k(f);i.payload=h.payload,i.rawJson=h.rawJson,v(),y("Diagnostics file loaded successfully.")}catch(h){i.payload=void 0,i.filteredSnapshot=void 0,i.selectedComponentId=void 0,i.rawJson=void 0,y(h instanceof Error?h.message:"Failed to parse diagnostics file.")}};r==null||r.addEventListener("change",async s=>{var $;const h=($=s.target.files)==null?void 0:$[0];h&&await u(h)}),a==null||a.addEventListener("dragover",s=>{s.preventDefault(),a.classList.add("drag-active")}),a==null||a.addEventListener("dragleave",()=>{a.classList.remove("drag-active")}),a==null||a.addEventListener("drop",async s=>{var h,$;s.preventDefault(),a.classList.remove("drag-active");const f=($=(h=s.dataTransfer)==null?void 0:h.files)==null?void 0:$[0];f&&await u(f)}),c==null||c.addEventListener("input",s=>{const f=s.target;i.filter.query=f.value,v(),y()}),document.querySelectorAll("input[data-readiness]").forEach(s=>{s.addEventListener("change",()=>{i.filter.readinessStatuses=N(i.filter.readinessStatuses,s.dataset.readiness),v(),y()})}),document.querySelectorAll("input[data-severity]").forEach(s=>{s.addEventListener("change",()=>{i.filter.severities=N(i.filter.severities,s.dataset.severity),v(),y()})}),p==null||p.addEventListener("click",async()=>{if(i.rawJson)try{await O(i.rawJson),y("Loaded JSON copied to clipboard.")}catch(s){y(s instanceof Error?s.message:"Failed to copy JSON.")}}),w==null||w.addEventListener("click",()=>{i.rawJson&&(F("fluo-diagnostics.json",i.rawJson),y("Loaded JSON downloaded."))}),m==null||m.addEventListener("click",async()=>{if(e)try{await O(L(e)),y("Mermaid copied to clipboard.")}catch(s){y(s instanceof Error?s.message:"Failed to copy Mermaid text.")}}),document.querySelectorAll("[data-component]").forEach(s=>{s.addEventListener("click",()=>{const f=s.dataset.component;f&&(i.selectedComponentId=f,y())})})}v();y();