@fluojs/studio 1.0.2 → 1.0.4
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 +5 -2
- package/README.md +5 -2
- package/dist/assets/index-BOSlyIRm.js +171 -0
- package/dist/assets/index-RQNFORxf.css +1 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +10 -1
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/dist/assets/index-CQ_tQzq9.css +0 -1
- package/dist/assets/index-DbteVtYa.js +0 -134
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는
|
|
44
|
+
Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다. 런타임은 snapshot을 생산하고, CLI는 artifact export/write/delegation을 소유하며, Studio는 사람과 자동화 호출자가 사용할 수 있도록 snapshot을 파싱, 필터링, 검사, 렌더링하는 공개 헬퍼와 viewer surface를 소유합니다. 지원되는 inspect artifact에는 raw snapshot, snapshot-plus-timing envelope, `fluo inspect --report`가 생성한 report artifact, legacy standalone timing diagnostics가 포함됩니다.
|
|
45
45
|
|
|
46
46
|
1. **Snapshot 내보내기**:
|
|
47
47
|
```bash
|
|
@@ -61,7 +61,7 @@ Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다. 런타임은
|
|
|
61
61
|
pnpm --dir packages/studio dev
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
3. **파일 로드**: Studio 웹 인터페이스에 `snapshot.json` 파일을 드래그 앤 드롭합니다. Search와 filter control은 graph, diagnostics, summary가 갱신되는 동안 focus를 유지합니다.
|
|
64
|
+
3. **파일 로드**: Studio 웹 인터페이스에 `snapshot.json` 파일을 드래그 앤 드롭합니다. Search와 filter control은 graph, connection explorer, diagnostics, summary가 갱신되는 동안 focus를 유지합니다.
|
|
65
65
|
|
|
66
66
|
## 주요 패턴
|
|
67
67
|
|
|
@@ -77,6 +77,9 @@ Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다. 런타임은
|
|
|
77
77
|
2. 시각화하려는 모듈이나 컴포넌트를 선택합니다.
|
|
78
78
|
3. **Copy Mermaid** 버튼을 사용하여 문서에 사용할 수 있는 텍스트 기반 다이어그램을 가져옵니다.
|
|
79
79
|
|
|
80
|
+
### 컴포넌트 연결 탐색
|
|
81
|
+
그래프 노드를 선택한 뒤 **Connection explorer**를 사용하면 선택한 컴포넌트의 내부 의존성, 외부 의존성, 역방향 dependent, 관련 diagnostics를 확인할 수 있습니다. 이 흐름은 snapshot 생산을 Studio로 옮기지 않고 Devtools 스타일 탐색을 제공합니다. 런타임은 계속 producer로 남고, CLI는 export boundary로 남으며, Studio는 inspection과 rendering surface로 남습니다.
|
|
82
|
+
|
|
80
83
|
자동화에서는 `@fluojs/studio` 또는 `@fluojs/studio/contracts`에서 `renderMermaid(snapshot)`을 호출합니다. 이 헬퍼가 지원되는 snapshot-to-Mermaid 계약입니다. 런타임 패키지는 snapshot producer로 남고, Studio는 그래프 렌더링 시 내부 dependency edge와 외부 dependency node를 처리합니다.
|
|
81
84
|
|
|
82
85
|
## 공개 API
|
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
|
|
44
|
+
Studio consumes JSON exports from the fluo CLI. Runtime produces snapshots, the CLI owns artifact export/write/delegation, and Studio owns the public helpers and viewer surface that parse, filter, inspect, and render those snapshots for people and automation callers. Supported inspect artifacts include raw snapshots, snapshot-plus-timing envelopes, report artifacts produced by `fluo inspect --report`, and legacy standalone timing diagnostics.
|
|
45
45
|
|
|
46
46
|
1. **Export a snapshot**:
|
|
47
47
|
```bash
|
|
@@ -61,7 +61,7 @@ Studio consumes JSON exports from the fluo CLI. Runtime produces snapshots, the
|
|
|
61
61
|
pnpm --dir packages/studio dev
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
3. **Load the file**: Drag and drop `snapshot.json` into the Studio web interface. Search and filter controls preserve focus while the graph, diagnostics, and summary update.
|
|
64
|
+
3. **Load the file**: Drag and drop `snapshot.json` into the Studio web interface. Search and filter controls preserve focus while the graph, connection explorer, diagnostics, and summary update.
|
|
65
65
|
|
|
66
66
|
## Common Patterns
|
|
67
67
|
|
|
@@ -77,6 +77,9 @@ Use the **Diagnostics issues** section to see issues collected during the runtim
|
|
|
77
77
|
2. Select the modules or components you want to visualize.
|
|
78
78
|
3. Use the **Copy Mermaid** button to get a text-based diagram for your documentation.
|
|
79
79
|
|
|
80
|
+
### Exploring Component Connections
|
|
81
|
+
Use the **Connection explorer** after selecting a graph node to inspect the selected component's internal dependencies, external dependencies, reverse dependents, and related diagnostics. This mirrors the Devtools-style workflow without moving snapshot production into Studio: runtime remains the producer, CLI remains the export boundary, and Studio remains the inspection and rendering surface.
|
|
82
|
+
|
|
80
83
|
For automation, call `renderMermaid(snapshot)` from `@fluojs/studio` or `@fluojs/studio/contracts`. The helper is the supported snapshot-to-Mermaid contract: runtime packages remain snapshot producers, and Studio handles internal dependency edges plus external dependency nodes when rendering the graph.
|
|
81
84
|
|
|
82
85
|
## Public API
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))s(o);new MutationObserver(o=>{for(const i of o)if(i.type==="childList")for(const l of i.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&s(l)}).observe(document,{childList:!0,subtree:!0});function t(o){const i={};return o.integrity&&(i.integrity=o.integrity),o.referrerPolicy&&(i.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?i.credentials="include":o.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function s(o){if(o.ep)return;o.ep=!0;const i=t(o);fetch(o.href,i)}})();function h(e){return typeof e=="object"&&e!==null}function w(e,n){return Object.hasOwn(e,n)}function C(e){return Array.isArray(e)&&e.every(n=>typeof n=="string")}function I(e){return e==="ready"||e==="not-ready"||e==="degraded"}function A(e){return e==="healthy"||e==="unhealthy"||e==="degraded"}function F(e){return e==="error"||e==="warning"||e==="info"}function q(e){if(!h(e))return null;if(typeof e.generatedAt!="string"||!h(e.readiness)||!h(e.health)||!Array.isArray(e.components)||!Array.isArray(e.diagnostics))throw new Error("Invalid platform snapshot payload.");if(!I(e.readiness.status)||typeof e.readiness.critical!="boolean")throw new Error("Invalid aggregate readiness in platform snapshot payload.");if(!A(e.health.status))throw new Error("Invalid aggregate health in platform snapshot payload.");for(const n of e.components){if(!h(n))throw new Error("Invalid component entry in platform snapshot payload.");if(typeof n.id!="string"||typeof n.kind!="string"||typeof n.state!="string"||!h(n.readiness)||!h(n.health)||!C(n.dependencies)||!h(n.telemetry)||!h(n.ownership)||!h(n.details))throw new Error("Invalid component shape in platform snapshot payload.");if(!I(n.readiness.status)||typeof n.readiness.critical!="boolean")throw new Error("Invalid component readiness in platform snapshot payload.");if(!A(n.health.status))throw new Error("Invalid component health in platform snapshot payload.");if(typeof n.telemetry.namespace!="string"||!h(n.telemetry.tags)||typeof n.ownership.ownsResources!="boolean"||typeof n.ownership.externallyManaged!="boolean")throw new Error("Invalid component telemetry/ownership in platform snapshot payload.")}for(const n of e.diagnostics){if(!h(n))throw new Error("Invalid diagnostics issue entry in platform snapshot payload.");if(typeof n.code!="string"||!F(n.severity)||typeof n.componentId!="string"||typeof n.message!="string")throw new Error("Invalid diagnostics issue shape in platform snapshot payload.");if(n.cause!==void 0&&typeof n.cause!="string"||n.fixHint!==void 0&&typeof n.fixHint!="string"||n.docsUrl!==void 0&&typeof n.docsUrl!="string"||n.dependsOn!==void 0&&!C(n.dependsOn))throw new Error("Invalid optional diagnostics issue fields in platform snapshot payload.")}return e}function J(e){if(!h(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 n of e.phases)if(!h(n)||typeof n.name!="string"||typeof n.durationMs!="number")throw new Error("Invalid phase entry in bootstrap timing payload.");return e}function H(e){if(!h(e))throw new Error("Invalid inspect report summary payload.");if(typeof e.componentCount!="number"||typeof e.diagnosticCount!="number"||typeof e.errorCount!="number"||!A(e.healthStatus)||!I(e.readinessStatus)||typeof e.timingTotalMs!="number"||typeof e.warningCount!="number")throw new Error("Invalid inspect report summary payload.");return e}function P(e,n,t){const s=n.diagnostics.filter(i=>i.severity==="error").length,o=n.diagnostics.filter(i=>i.severity==="warning").length;if(e.componentCount!==n.components.length||e.diagnosticCount!==n.diagnostics.length||e.errorCount!==s||e.healthStatus!==n.health.status||e.readinessStatus!==n.readiness.status||e.timingTotalMs!==t.totalMs||e.warningCount!==o)throw new Error("Inspect report summary does not match snapshot and timing payload data.")}function U(e){return e.summary!==void 0||w(e,"snapshot")&&w(e,"timing")&&(w(e,"generatedAt")||w(e,"version"))}function _(e,n,t){if(!h(e)||!U(e))return null;if(e.summary===void 0||e.version!==1||typeof e.generatedAt!="string"||!n||!t)throw new Error("Invalid inspect report artifact payload.");const s=H(e.summary);return P(s,n,t),{generatedAt:e.generatedAt,snapshot:n,summary:s,timing:t,version:1}}function B(e){const n=JSON.parse(e),t=h(n)?n:void 0,s=t!==void 0&&w(t,"snapshot"),o=t!==void 0&&w(t,"timing"),i=t!==void 0&&!s&&!o&&w(t,"version")&&w(t,"totalMs")&&w(t,"phases"),l=q(s?t.snapshot:i?void 0:n),a=J(o?t.timing:l?void 0:n),d=_(n,l,a);if(!l&&!a)throw new Error("Unsupported file format. Expected platform snapshot JSON or timing JSON.");return{payload:{...d?{report:d}:{},...l?{snapshot:l}:{},...a?{timing:a}:{}},rawJson:e}}function z(e,n){const t=n.query.trim().toLowerCase(),s=e.components.filter(i=>n.readinessStatuses.length>0&&!n.readinessStatuses.includes(i.readiness.status)?!1:t?i.id.toLowerCase().includes(t)||i.kind.toLowerCase().includes(t)||i.dependencies.some(l=>l.toLowerCase().includes(t)):!0),o=e.diagnostics.filter(i=>{var l,a,d,g;return n.severities.length>0&&!n.severities.includes(i.severity)?!1:t?i.code.toLowerCase().includes(t)||i.componentId.toLowerCase().includes(t)||i.message.toLowerCase().includes(t)||(((l=i.cause)==null?void 0:l.toLowerCase().includes(t))??!1)||(((a=i.fixHint)==null?void 0:a.toLowerCase().includes(t))??!1)||(((d=i.docsUrl)==null?void 0:d.toLowerCase().includes(t))??!1)||(((g=i.dependsOn)==null?void 0:g.some(y=>y.toLowerCase().includes(t)))??!1):!0});return{...e,components:s,diagnostics:o}}function L(e){return e.replaceAll("\\","\\\\").replaceAll('"','\\"').replaceAll(`\r
|
|
2
|
+
`,"\\n").replaceAll("\r","\\n").replaceAll(`
|
|
3
|
+
`,"\\n")}function V(e){return e.replaceAll(/[^a-zA-Z0-9_]/g,"_")}function Y(e){let n=2166136261;for(const t of e)n^=t.codePointAt(0)??0,n=Math.imul(n,16777619)>>>0;return n.toString(16).padStart(8,"0")}function G(e){return`EXT_${V(e)}_${Y(e)}`}function N(e){const n=["graph TD"],t=new Map,s=new Map;if(e.components.length===0)return n.push(' EMPTY["No registered platform components"]'),n.join(`
|
|
4
|
+
`);for(const[l,a]of e.components.entries()){const d=`C${String(l+1)}`;t.set(a.id,d),n.push(` ${d}["${L(a.id)}\\nkind: ${L(a.kind)}\\nreadiness: ${a.readiness.status}\\nhealth: ${a.health.status}"]`)}for(const l of e.components){const a=t.get(l.id);if(a)for(const d of l.dependencies){const g=t.get(d);if(g){n.push(` ${a} --> ${g}`);continue}let y=s.get(d);y||(y=G(d),s.set(d,y),n.push(` ${y}["${L(d)}"]`)),n.push(` ${a} --> ${y}`)}}const o=[],i=[];for(const l of e.components){const a=t.get(l.id);a&&(l.readiness.status==="degraded"&&o.push(a),l.readiness.status==="not-ready"&&i.push(a))}return o.length>0&&(n.push(` class ${o.join(",")} degraded`),n.push(" classDef degraded stroke:#f59e0b,stroke-width:2px")),i.length>0&&(n.push(` class ${i.join(",")} notReady`),n.push(" classDef notReady stroke:#ef4444,stroke-width:2px")),n.join(`
|
|
5
|
+
`)}function c(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function X(e){try{const n=new URL(e);if(n.protocol==="https:"||n.protocol==="http:")return n.href}catch{return}}function K(e){const n=X(e),t=c(e);return n?`<p><strong>docs:</strong> <a href="${c(n)}" target="_blank" rel="noopener noreferrer">${t}</a></p>`:`<p><strong>docs:</strong> <span>${t}</span></p>`}function j(e){const n=new Set(e.map(o=>o.id)),t=[],s=new Set;for(const o of e)for(const i of o.dependencies)n.has(i)||s.has(i)||(s.add(i),t.push(i));return t}function Q(e,n){if(!e||e.components.length===0)return;const t=new Map(e.components.map(d=>[d.id,d])),s=n?t.get(n)??e.components[0]:e.components[0];if(!s)return;const o=[],i=[];for(const d of s.dependencies){const g=t.get(d);g?o.push(g):i.push(d)}const l=e.components.filter(d=>d.id!==s.id&&d.dependencies.includes(s.id)),a=e.diagnostics.filter(d=>{var g;return d.componentId===s.id||(((g=d.dependsOn)==null?void 0:g.includes(s.id))??!1)});return{component:s,diagnostics:a,externalDependencies:i,incoming:l,outgoing:o}}function W(e,n,t){const s=Math.min(n,t)/2-70,o=n/2,i=t/2,l=new Map,a=j(e.components),d=[...e.components.map(g=>g.id),...a];return d.forEach((g,y)=>{const v=Math.PI*2*y/Math.max(d.length,1);l.set(g,{x:o+s*Math.cos(v),y:i+s*Math.sin(v)})}),l}function Z(e,n){const o=e.components,i=W(e,900,460),l=j(o),a=n?o.find(u=>u.id===n):void 0,d=new Set((a==null?void 0:a.dependencies)??[]),g=new Set(n?o.filter(u=>u.dependencies.includes(n)).map(u=>u.id):[]),y=o.flatMap(u=>u.dependencies.map(r=>{const f=i.get(u.id),m=i.get(r);if(!f||!m)return"";const S=u.id===n||r===n;return`<line x1="${f.x}" y1="${f.y}" x2="${m.x}" y2="${m.y}" class="edge-line${S?" edge-selected":""}" marker-end="url(#arrow)" />`})).join(""),v=o.map(u=>{const r=i.get(u.id);if(!r)return"";const m=["module-node",u.readiness.status==="not-ready"?"component-not-ready":u.readiness.status==="degraded"?"component-degraded":"component-ready",u.id===n?"module-selected":"",d.has(u.id)?"module-neighbor":"",g.has(u.id)?"module-dependent":""].filter(Boolean).join(" ");return`<g>
|
|
6
|
+
<circle cx="${r.x}" cy="${r.y}" r="34" class="${m}" data-component="${c(u.id)}" tabindex="0" role="button" aria-label="Inspect ${c(u.id)}" />
|
|
7
|
+
<text x="${r.x}" y="${r.y+4}" text-anchor="middle" class="module-label">${c(u.id)}</text>
|
|
8
|
+
</g>`}).join(""),E=l.map(u=>{const r=i.get(u);if(!r)return"";const f=["module-node","component-external",d.has(u)?"module-neighbor":""].filter(Boolean).join(" ");return`<g>
|
|
9
|
+
<rect x="${r.x-42}" y="${r.y-20}" width="84" height="40" rx="10" class="${f}" />
|
|
10
|
+
<text x="${r.x}" y="${r.y+4}" text-anchor="middle" class="module-label">${c(u)}</text>
|
|
11
|
+
</g>`}).join("");return`<svg viewBox="0 0 900 460" role="img" aria-label="Platform component dependency graph">
|
|
12
|
+
<defs>
|
|
13
|
+
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
14
|
+
<polygon points="0 0, 10 3.5, 0 7" class="edge-arrow" />
|
|
15
|
+
</marker>
|
|
16
|
+
</defs>
|
|
17
|
+
${y}
|
|
18
|
+
${v}
|
|
19
|
+
${E}
|
|
20
|
+
</svg>`}function ee(e){return e?e.diagnostics.length===0?'<p class="muted">No diagnostics issues.</p>':`<div class="diagnostics-list">
|
|
21
|
+
${e.diagnostics.map(n=>{const t=n.dependsOn&&n.dependsOn.length>0?`<div class="chips">${n.dependsOn.map(s=>`<span class="chip">dependsOn: ${c(s)}</span>`).join("")}</div>`:"";return`<article class="card issue severity-${c(n.severity)}">
|
|
22
|
+
<h3>${c(n.code)}</h3>
|
|
23
|
+
<p><strong>severity:</strong> ${c(n.severity)} · <strong>component:</strong> ${c(n.componentId)}</p>
|
|
24
|
+
<p>${c(n.message)}</p>
|
|
25
|
+
${n.cause?`<p><strong>cause:</strong> ${c(n.cause)}</p>`:""}
|
|
26
|
+
${n.fixHint?`<p><strong>fix hint:</strong> ${c(n.fixHint)}</p>`:""}
|
|
27
|
+
${n.docsUrl?K(n.docsUrl):""}
|
|
28
|
+
${t}
|
|
29
|
+
</article>`}).join("")}
|
|
30
|
+
</div>`:'<p class="muted">No platform snapshot loaded.</p>'}const k=document.querySelector("#app");if(!k)throw new Error("App root not found.");const ne=k,te=["ready","degraded","not-ready"],oe=["error","warning","info"],p={filter:{query:"",readinessStatuses:[],severities:[]}};function se(e,n){const t=new Blob([n],{type:"application/json"}),s=URL.createObjectURL(t),o=document.createElement("a");o.href=s,o.download=e,o.click(),URL.revokeObjectURL(s)}async function O(e){if(!navigator.clipboard)throw new Error("Clipboard API is unavailable.");await navigator.clipboard.writeText(e)}function M(e,n){return e.includes(n)?e.filter(t=>t!==n):[...e,n]}function T(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}function re(e){return e instanceof HTMLElement||e instanceof SVGElement}function ie(){const e=document.activeElement;return!T(e)||!e.id?e instanceof SVGElement&&e.dataset.component?{componentId:e.dataset.component,kind:"graph-node"}:void 0:{id:e.id,kind:"text-control",selectionEnd:e.selectionEnd,selectionStart:e.selectionStart}}function ae(e){if(!e)return;if(e.kind==="graph-node"){const s=Array.from(document.querySelectorAll("[data-component]")).find(o=>o instanceof SVGElement&&o.dataset.component===e.componentId)??null;re(s)&&s.focus({preventScroll:!0});return}const n=document.getElementById(e.id);T(n)&&(n.focus({preventScroll:!0}),e.selectionStart!==null&&e.selectionEnd!==null&&n.setSelectionRange(e.selectionStart,e.selectionEnd))}function R(e,n){return!e||e.components.length===0?void 0:(n?e.components.find(s=>s.id===n):void 0)??e.components[0]}function x(){var t;const e=(t=p.payload)==null?void 0:t.snapshot;if(!e){p.filteredSnapshot=void 0,p.selectedComponentId=void 0;return}p.filteredSnapshot=z(e,p.filter);const n=R(p.filteredSnapshot,p.selectedComponentId);p.selectedComponentId=n==null?void 0:n.id}function de(e){if(!e)return'<p class="muted">No component selected.</p>';const n=e.dependencies.length>0?e.dependencies.map(t=>`<span class="chip">dependsOn: ${c(t)}</span>`).join(""):'<span class="chip">dependsOn: none</span>';return`
|
|
31
|
+
<h3>${c(e.id)}</h3>
|
|
32
|
+
<p class="muted">kind: <strong>${c(e.kind)}</strong> · state: <strong>${c(e.state)}</strong></p>
|
|
33
|
+
<div class="chips">
|
|
34
|
+
<span class="chip">readiness: ${c(e.readiness.status)}</span>
|
|
35
|
+
<span class="chip">critical: ${e.readiness.critical?"true":"false"}</span>
|
|
36
|
+
<span class="chip">health: ${c(e.health.status)}</span>
|
|
37
|
+
<span class="chip">ownership: owns=${e.ownership.ownsResources?"true":"false"}/external=${e.ownership.externallyManaged?"true":"false"}</span>
|
|
38
|
+
${n}
|
|
39
|
+
</div>
|
|
40
|
+
<p class="muted">telemetry namespace: <code>${c(e.telemetry.namespace)}</code></p>
|
|
41
|
+
<h4>Sanitized details</h4>
|
|
42
|
+
<pre>${c(JSON.stringify(e.details,null,2))}</pre>
|
|
43
|
+
`}function D(e,n){return`<button class="connection-button" data-select-component="${c(e.id)}" type="button">
|
|
44
|
+
<span>${c(e.id)}</span>
|
|
45
|
+
<small>${c(n)} · ${c(e.kind)} · ${c(e.readiness.status)}</small>
|
|
46
|
+
</button>`}function b(e,n,t){return`
|
|
47
|
+
<section class="connection-group">
|
|
48
|
+
<h4>${c(e)}</h4>
|
|
49
|
+
${t||`<p class="muted">${c(n)}</p>`}
|
|
50
|
+
</section>
|
|
51
|
+
`}function ce(e,n){const t=Q(e,n);if(!t)return'<p class="muted">Load a platform snapshot to explore component connections.</p>';const s=t.outgoing.map(a=>D(a,"dependency")).join(""),o=t.incoming.map(a=>D(a,"dependent")).join(""),i=t.externalDependencies.map(a=>`<span class="connection-pill external-pill">${c(a)}</span>`).join(""),l=t.diagnostics.map(a=>`<article class="connection-diagnostic severity-${c(a.severity)}">
|
|
52
|
+
<strong>${c(a.code)}</strong>
|
|
53
|
+
<span>${c(a.severity)} · ${c(a.componentId)}</span>
|
|
54
|
+
<p>${c(a.message)}</p>
|
|
55
|
+
</article>`).join("");return`
|
|
56
|
+
<div class="connection-hero">
|
|
57
|
+
<div>
|
|
58
|
+
<p class="eyebrow">Selected component</p>
|
|
59
|
+
<h3>${c(t.component.id)}</h3>
|
|
60
|
+
<p class="muted">${c(t.component.kind)} · state ${c(t.component.state)}</p>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="connection-metrics" aria-label="Selected component connection counts">
|
|
63
|
+
<span><strong>${String(t.outgoing.length)}</strong> internal deps</span>
|
|
64
|
+
<span><strong>${String(t.externalDependencies.length)}</strong> external deps</span>
|
|
65
|
+
<span><strong>${String(t.incoming.length)}</strong> dependents</span>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="connection-grid">
|
|
69
|
+
${b("Depends on","No internal component dependencies.",s)}
|
|
70
|
+
${b("Required by","No components depend on this selection.",o)}
|
|
71
|
+
${b("External dependencies","No external dependencies.",i)}
|
|
72
|
+
${b("Related diagnostics","No related diagnostics.",l)}
|
|
73
|
+
</div>
|
|
74
|
+
`}function le(){var n;const e=(n=p.payload)==null?void 0:n.timing;return e?`
|
|
75
|
+
<p><strong>Total:</strong> ${e.totalMs.toFixed(3)}ms</p>
|
|
76
|
+
<table>
|
|
77
|
+
<thead>
|
|
78
|
+
<tr><th>phase</th><th>duration (ms)</th></tr>
|
|
79
|
+
</thead>
|
|
80
|
+
<tbody>
|
|
81
|
+
${e.phases.map(t=>`<tr><td>${c(t.name)}</td><td>${t.durationMs.toFixed(3)}</td></tr>`).join("")}
|
|
82
|
+
</tbody>
|
|
83
|
+
</table>
|
|
84
|
+
`:'<p class="muted">Timing not collected.</p>'}function pe(e){if(!e)return'<p class="muted">No platform snapshot loaded.</p>';const n={degraded:e.components.filter(t=>t.readiness.status==="degraded").length,notReady:e.components.filter(t=>t.readiness.status==="not-ready").length,ready:e.components.filter(t=>t.readiness.status==="ready").length};return`
|
|
85
|
+
<div class="chips">
|
|
86
|
+
<span class="chip">generatedAt: ${c(e.generatedAt)}</span>
|
|
87
|
+
<span class="chip">aggregate readiness: ${c(e.readiness.status)}</span>
|
|
88
|
+
<span class="chip">aggregate health: ${c(e.health.status)}</span>
|
|
89
|
+
<span class="chip">components: ${String(e.components.length)}</span>
|
|
90
|
+
<span class="chip">diagnostics: ${String(e.diagnostics.length)}</span>
|
|
91
|
+
<span class="chip">ready/degraded/not-ready: ${n.ready}/${n.degraded}/${n.notReady}</span>
|
|
92
|
+
</div>
|
|
93
|
+
`}function $(e={}){const n=typeof e=="string"?e:e.message,t=typeof e=="string"||!e.preserveFocus?void 0:ie(),s=p.filteredSnapshot,o=R(s,p.selectedComponentId),i=s?N(s):"",l=s&&s.components.length>0?Z(s,o==null?void 0:o.id):'<p class="muted">No platform components loaded.</p>';ne.innerHTML=`
|
|
94
|
+
<main>
|
|
95
|
+
<header>
|
|
96
|
+
<h1>Fluo Studio Platform Snapshot Viewer</h1>
|
|
97
|
+
<p>Load JSON exported by <code>fluo inspect --json</code> (shared platform snapshot/diagnostic schema) and optionally timing JSON from <code>--timing</code>.</p>
|
|
98
|
+
</header>
|
|
99
|
+
|
|
100
|
+
<section class="card uploader" id="drop-zone">
|
|
101
|
+
<h2>Diagnostics file input</h2>
|
|
102
|
+
<p>Drag & drop a JSON file, or choose one manually.</p>
|
|
103
|
+
<input type="file" id="file-input" accept="application/json" />
|
|
104
|
+
<div class="actions">
|
|
105
|
+
<button id="download-json" ${p.rawJson?"":"disabled"}>Download loaded JSON</button>
|
|
106
|
+
<button id="copy-json" ${p.rawJson?"":"disabled"}>Copy loaded JSON</button>
|
|
107
|
+
<button id="copy-mermaid" ${s?"":"disabled"}>Copy Mermaid</button>
|
|
108
|
+
</div>
|
|
109
|
+
${n?`<p class="notice">${c(n)}</p>`:""}
|
|
110
|
+
</section>
|
|
111
|
+
|
|
112
|
+
<section class="card">
|
|
113
|
+
<h2>Snapshot summary</h2>
|
|
114
|
+
${pe(s)}
|
|
115
|
+
</section>
|
|
116
|
+
|
|
117
|
+
<section class="split-grid">
|
|
118
|
+
<div class="card">
|
|
119
|
+
<h2>Search and filtering</h2>
|
|
120
|
+
<label>
|
|
121
|
+
Search component/diagnostic
|
|
122
|
+
<input type="text" id="search" value="${c(p.filter.query)}" placeholder="e.g. redis.default or QUEUE_DEPENDENCY_NOT_READY" />
|
|
123
|
+
</label>
|
|
124
|
+
|
|
125
|
+
<div class="filter-row">
|
|
126
|
+
<span>Component readiness</span>
|
|
127
|
+
${te.map(r=>`<label><input type="checkbox" id="readiness-${r}" data-readiness="${r}" ${p.filter.readinessStatuses.includes(r)?"checked":""}/> ${r}</label>`).join("")}
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="filter-row">
|
|
131
|
+
<span>Diagnostic severity</span>
|
|
132
|
+
${oe.map(r=>`<label><input type="checkbox" id="severity-${r}" data-severity="${r}" ${p.filter.severities.includes(r)?"checked":""}/> ${r}</label>`).join("")}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="card">
|
|
137
|
+
<h2>Timing</h2>
|
|
138
|
+
${le()}
|
|
139
|
+
</div>
|
|
140
|
+
</section>
|
|
141
|
+
|
|
142
|
+
<section class="card">
|
|
143
|
+
<h2>Platform dependency graph</h2>
|
|
144
|
+
<p class="muted">Component dependencies are rendered directly from the shared platform snapshot schema. Select a node to inspect its dependency neighborhood.</p>
|
|
145
|
+
<div id="graph-host">${l}</div>
|
|
146
|
+
</section>
|
|
147
|
+
|
|
148
|
+
<section class="card inspector-card">
|
|
149
|
+
<h2>Connection explorer</h2>
|
|
150
|
+
<p class="muted">Studio owns snapshot inspection and rendering: use this panel to inspect incoming and outgoing component relationships without changing CLI export semantics.</p>
|
|
151
|
+
${ce(s,o==null?void 0:o.id)}
|
|
152
|
+
</section>
|
|
153
|
+
|
|
154
|
+
<section class="split-grid">
|
|
155
|
+
<div class="card" id="details-panel">
|
|
156
|
+
<h2>Component details</h2>
|
|
157
|
+
${de(o)}
|
|
158
|
+
</div>
|
|
159
|
+
<div class="card">
|
|
160
|
+
<h2>Mermaid output</h2>
|
|
161
|
+
<pre>${c(i||"No snapshot loaded.")}</pre>
|
|
162
|
+
</div>
|
|
163
|
+
</section>
|
|
164
|
+
|
|
165
|
+
<section class="card">
|
|
166
|
+
<h2>Diagnostics issues</h2>
|
|
167
|
+
<p class="muted">Fix hints and dependency chains are rendered from <code>diagnostics.fixHint</code> and <code>diagnostics.dependsOn</code>.</p>
|
|
168
|
+
${ee(s)}
|
|
169
|
+
</section>
|
|
170
|
+
</main>
|
|
171
|
+
`;const a=document.querySelector("#file-input"),d=document.querySelector("#drop-zone"),g=document.querySelector("#search"),y=document.querySelector("#copy-json"),v=document.querySelector("#download-json"),E=document.querySelector("#copy-mermaid"),u=async r=>{const f=await r.text();try{const m=B(f);p.payload=m.payload,p.rawJson=m.rawJson,x(),$("Diagnostics file loaded successfully.")}catch(m){p.payload=void 0,p.filteredSnapshot=void 0,p.selectedComponentId=void 0,p.rawJson=void 0,$(m instanceof Error?m.message:"Failed to parse diagnostics file.")}};a==null||a.addEventListener("change",async r=>{var S;const m=(S=r.target.files)==null?void 0:S[0];m&&await u(m)}),d==null||d.addEventListener("dragover",r=>{r.preventDefault(),d.classList.add("drag-active")}),d==null||d.addEventListener("dragleave",()=>{d.classList.remove("drag-active")}),d==null||d.addEventListener("drop",async r=>{var m,S;r.preventDefault(),d.classList.remove("drag-active");const f=(S=(m=r.dataTransfer)==null?void 0:m.files)==null?void 0:S[0];f&&await u(f)}),g==null||g.addEventListener("input",r=>{const f=r.target;p.filter.query=f.value,x(),$({preserveFocus:!0})}),document.querySelectorAll("input[data-readiness]").forEach(r=>{r.addEventListener("change",()=>{p.filter.readinessStatuses=M(p.filter.readinessStatuses,r.dataset.readiness),x(),$({preserveFocus:!0})})}),document.querySelectorAll("input[data-severity]").forEach(r=>{r.addEventListener("change",()=>{p.filter.severities=M(p.filter.severities,r.dataset.severity),x(),$({preserveFocus:!0})})}),y==null||y.addEventListener("click",async()=>{if(p.rawJson)try{await O(p.rawJson),$("Loaded JSON copied to clipboard.")}catch(r){$(r instanceof Error?r.message:"Failed to copy JSON.")}}),v==null||v.addEventListener("click",()=>{p.rawJson&&(se("fluo-diagnostics.json",p.rawJson),$("Loaded JSON downloaded."))}),E==null||E.addEventListener("click",async()=>{if(s)try{await O(N(s)),$("Mermaid copied to clipboard.")}catch(r){$(r instanceof Error?r.message:"Failed to copy Mermaid text.")}}),document.querySelectorAll("[data-component]").forEach(r=>{r.addEventListener("click",()=>{const f=r.dataset.component;f&&(p.selectedComponentId=f,$({preserveFocus:!0}))}),r.addEventListener("keydown",f=>{if(f.key!=="Enter"&&f.key!==" ")return;f.preventDefault();const m=r.dataset.component;m&&(p.selectedComponentId=m,$({preserveFocus:!0}))})}),document.querySelectorAll("[data-select-component]").forEach(r=>{r.addEventListener("click",()=>{const f=r.dataset.selectComponent;f&&(p.selectedComponentId=f,$())})}),ae(t)}x();$();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{color-scheme:light dark;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}body{margin:0;background:#0b1020;color:#e5e7eb}main{max-width:1200px;margin:0 auto;padding:24px}h1,h2,h3{margin-top:0}code{background:#1f2937;border-radius:4px;padding:2px 6px}.card{border:1px solid #334155;border-radius:10px;background:#111827;padding:16px;margin-bottom:16px}.split-grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}.actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}button{background:#2563eb;border:none;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer}button:disabled{background:#475569;cursor:not-allowed}label{display:block;margin-bottom:10px}input[type=text]{width:100%;margin-top:6px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#e5e7eb;padding:8px 10px}.filter-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px}.muted{color:#94a3b8}.chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}.chip{border:1px solid #334155;border-radius:999px;padding:2px 8px;font-size:12px}table{width:100%;border-collapse:collapse}th,td{border-bottom:1px solid #334155;text-align:left;font-size:13px;padding:6px}pre{overflow:auto;max-height:300px;background:#0f172a;border:1px solid #334155;padding:12px;border-radius:8px}.notice{margin-top:8px;color:#93c5fd}.uploader.drag-active{border-color:#60a5fa;box-shadow:0 0 0 2px #60a5fa59 inset}#graph-host{overflow:auto;border:1px solid #334155;border-radius:8px;background:#0f172a}.edge-line{stroke:#64748b;stroke-width:1.6}.edge-selected{stroke:#f59e0b;stroke-width:2.6}.edge-arrow{fill:#64748b}.module-node{fill:#1e293b;stroke:#64748b;stroke-width:1.5;cursor:pointer}.module-node:focus-visible{outline:none;stroke:#60a5fa;stroke-width:3}.module-root{stroke:#3b82f6;stroke-width:2.6}.component-ready{fill:#14532d}.component-degraded{fill:#7c2d12}.component-not-ready{fill:#7f1d1d}.component-external{fill:#312e81;cursor:default}.module-selected{stroke:#f59e0b;stroke-width:3}.module-neighbor{stroke:#38bdf8;stroke-width:2.6}.module-dependent{stroke:#a78bfa;stroke-width:2.6}.module-label{fill:#e5e7eb;font-size:11px;pointer-events:none}.diagnostics-list{display:grid;gap:12px}.issue{margin-bottom:0}.issue.severity-error{border-color:#ef4444}.issue.severity-warning{border-color:#f59e0b}.issue.severity-info{border-color:#3b82f6}a{color:#93c5fd}.inspector-card{background:linear-gradient(135deg,#0f172af5,#111827f5)}.connection-hero{display:flex;flex-wrap:wrap;gap:16px;align-items:flex-start;justify-content:space-between;border:1px solid #334155;border-radius:12px;background:#0f172a;padding:14px;margin-bottom:14px}.eyebrow{margin:0 0 6px;color:#93c5fd;font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.connection-metrics{display:flex;flex-wrap:wrap;gap:8px}.connection-metrics span,.connection-pill{border:1px solid #334155;border-radius:999px;background:#111827;color:#cbd5e1;padding:6px 10px;font-size:12px}.connection-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}.connection-group h4{margin:0 0 8px}.connection-button{display:block;width:100%;margin-bottom:8px;border:1px solid #334155;background:#0f172a;color:#e5e7eb;text-align:left}.connection-button:hover,.connection-button:focus-visible{border-color:#60a5fa;outline:none}.connection-button span,.connection-button small{display:block}.connection-button small{margin-top:4px;color:#94a3b8}.external-pill{display:inline-block;margin:0 6px 6px 0}.connection-diagnostic{border-left:3px solid #64748b;padding:8px 0 8px 10px;margin-bottom:8px}.connection-diagnostic.severity-error{border-left-color:#ef4444}.connection-diagnostic.severity-warning{border-left-color:#f59e0b}.connection-diagnostic.severity-info{border-left-color:#3b82f6}.connection-diagnostic span{display:block;color:#94a3b8;font-size:12px;margin-top:2px}.connection-diagnostic p{margin:6px 0 0}
|
package/dist/contracts.d.ts.map
CHANGED
|
@@ -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,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;
|
|
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;AA0MD;;;;;;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;AA8BD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAsErE"}
|
package/dist/contracts.js
CHANGED
|
@@ -113,6 +113,13 @@ function validateReportSummary(value) {
|
|
|
113
113
|
}
|
|
114
114
|
return value;
|
|
115
115
|
}
|
|
116
|
+
function validateReportSummaryConsistency(summary, snapshot, timing) {
|
|
117
|
+
const errorCount = snapshot.diagnostics.filter(diagnostic => diagnostic.severity === 'error').length;
|
|
118
|
+
const warningCount = snapshot.diagnostics.filter(diagnostic => diagnostic.severity === 'warning').length;
|
|
119
|
+
if (summary.componentCount !== snapshot.components.length || summary.diagnosticCount !== snapshot.diagnostics.length || summary.errorCount !== errorCount || summary.healthStatus !== snapshot.health.status || summary.readinessStatus !== snapshot.readiness.status || summary.timingTotalMs !== timing.totalMs || summary.warningCount !== warningCount) {
|
|
120
|
+
throw new Error('Inspect report summary does not match snapshot and timing payload data.');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
116
123
|
function isReportArtifactEnvelope(value) {
|
|
117
124
|
return value.summary !== undefined || hasOwn(value, 'snapshot') && hasOwn(value, 'timing') && (hasOwn(value, 'generatedAt') || hasOwn(value, 'version'));
|
|
118
125
|
}
|
|
@@ -123,10 +130,12 @@ function validateReport(value, snapshot, timing) {
|
|
|
123
130
|
if (value.summary === undefined || value.version !== 1 || typeof value.generatedAt !== 'string' || !snapshot || !timing) {
|
|
124
131
|
throw new Error('Invalid inspect report artifact payload.');
|
|
125
132
|
}
|
|
133
|
+
const summary = validateReportSummary(value.summary);
|
|
134
|
+
validateReportSummaryConsistency(summary, snapshot, timing);
|
|
126
135
|
return {
|
|
127
136
|
generatedAt: value.generatedAt,
|
|
128
137
|
snapshot,
|
|
129
|
-
summary
|
|
138
|
+
summary,
|
|
130
139
|
timing,
|
|
131
140
|
version: 1
|
|
132
141
|
};
|
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
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="
|
|
8
|
-
<link rel="stylesheet" crossorigin href="
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-BOSlyIRm.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-RQNFORxf.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"module-graph",
|
|
10
10
|
"devtools"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.
|
|
12
|
+
"version": "1.0.4",
|
|
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.1.
|
|
44
|
+
"@fluojs/runtime": "^1.1.2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"happy-dom": "^20.9.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{color-scheme:light dark;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}body{margin:0;background:#0b1020;color:#e5e7eb}main{max-width:1200px;margin:0 auto;padding:24px}h1,h2,h3{margin-top:0}code{background:#1f2937;border-radius:4px;padding:2px 6px}.card{border:1px solid #334155;border-radius:10px;background:#111827;padding:16px;margin-bottom:16px}.split-grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}.actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}button{background:#2563eb;border:none;color:#fff;padding:8px 12px;border-radius:8px;cursor:pointer}button:disabled{background:#475569;cursor:not-allowed}label{display:block;margin-bottom:10px}input[type=text]{width:100%;margin-top:6px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#e5e7eb;padding:8px 10px}.filter-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px}.muted{color:#94a3b8}.chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}.chip{border:1px solid #334155;border-radius:999px;padding:2px 8px;font-size:12px}table{width:100%;border-collapse:collapse}th,td{border-bottom:1px solid #334155;text-align:left;font-size:13px;padding:6px}pre{overflow:auto;max-height:300px;background:#0f172a;border:1px solid #334155;padding:12px;border-radius:8px}.notice{margin-top:8px;color:#93c5fd}.uploader.drag-active{border-color:#60a5fa;box-shadow:0 0 0 2px #60a5fa59 inset}#graph-host{overflow:auto;border:1px solid #334155;border-radius:8px;background:#0f172a}.edge-line{stroke:#64748b;stroke-width:1.6}.edge-arrow{fill:#64748b}.module-node{fill:#1e293b;stroke:#64748b;stroke-width:1.5;cursor:pointer}.module-root{stroke:#3b82f6;stroke-width:2.6}.component-ready{fill:#14532d}.component-degraded{fill:#7c2d12}.component-not-ready{fill:#7f1d1d}.component-external{fill:#312e81;cursor:default}.module-selected{stroke:#f59e0b;stroke-width:3}.module-label{fill:#e5e7eb;font-size:11px;pointer-events:none}.diagnostics-list{display:grid;gap:12px}.issue{margin-bottom:0}.issue.severity-error{border-color:#ef4444}.issue.severity-warning{border-color:#f59e0b}.issue.severity-info{border-color:#3b82f6}a{color:#93c5fd}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))o(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const c of r.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&o(c)}).observe(document,{childList:!0,subtree:!0});function n(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(s){if(s.ep)return;s.ep=!0;const r=n(s);fetch(s.href,r)}})();function h(e){return typeof e=="object"&&e!==null}function $(e,t){return Object.hasOwn(e,t)}function O(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function L(e){return e==="ready"||e==="not-ready"||e==="degraded"}function A(e){return e==="healthy"||e==="unhealthy"||e==="degraded"}function k(e){return e==="error"||e==="warning"||e==="info"}function R(e){if(!h(e))return null;if(typeof e.generatedAt!="string"||!h(e.readiness)||!h(e.health)||!Array.isArray(e.components)||!Array.isArray(e.diagnostics))throw new Error("Invalid platform snapshot payload.");if(!L(e.readiness.status)||typeof e.readiness.critical!="boolean")throw new Error("Invalid aggregate readiness in platform snapshot payload.");if(!A(e.health.status))throw new Error("Invalid aggregate health in platform snapshot payload.");for(const t of e.components){if(!h(t))throw new Error("Invalid component entry in platform snapshot payload.");if(typeof t.id!="string"||typeof t.kind!="string"||typeof t.state!="string"||!h(t.readiness)||!h(t.health)||!O(t.dependencies)||!h(t.telemetry)||!h(t.ownership)||!h(t.details))throw new Error("Invalid component shape in platform snapshot payload.");if(!L(t.readiness.status)||typeof t.readiness.critical!="boolean")throw new Error("Invalid component readiness in platform snapshot payload.");if(!A(t.health.status))throw new Error("Invalid component health in platform snapshot payload.");if(typeof t.telemetry.namespace!="string"||!h(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(!h(t))throw new Error("Invalid diagnostics issue entry in platform snapshot payload.");if(typeof t.code!="string"||!k(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&&!O(t.dependsOn))throw new Error("Invalid optional diagnostics issue fields in platform snapshot payload.")}return e}function F(e){if(!h(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(!h(t)||typeof t.name!="string"||typeof t.durationMs!="number")throw new Error("Invalid phase entry in bootstrap timing payload.");return e}function J(e){if(!h(e))throw new Error("Invalid inspect report summary payload.");if(typeof e.componentCount!="number"||typeof e.diagnosticCount!="number"||typeof e.errorCount!="number"||!A(e.healthStatus)||!L(e.readinessStatus)||typeof e.timingTotalMs!="number"||typeof e.warningCount!="number")throw new Error("Invalid inspect report summary payload.");return e}function q(e){return e.summary!==void 0||$(e,"snapshot")&&$(e,"timing")&&($(e,"generatedAt")||$(e,"version"))}function H(e,t,n){if(!h(e)||!q(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:J(e.summary),timing:n,version:1}}function P(e){const t=JSON.parse(e),n=h(t)?t:void 0,o=n!==void 0&&$(n,"snapshot"),s=n!==void 0&&$(n,"timing"),r=n!==void 0&&!o&&!s&&$(n,"version")&&$(n,"totalMs")&&$(n,"phases"),c=R(o?n.snapshot:r?void 0:t),l=F(s?n.timing:c?void 0:t),i=H(t,c,l);if(!c&&!l)throw new Error("Unsupported file format. Expected platform snapshot JSON or timing JSON.");return{payload:{...i?{report:i}:{},...c?{snapshot:c}:{},...l?{timing:l}:{}},rawJson:e}}function U(e,t){const n=t.query.trim().toLowerCase(),o=e.components.filter(r=>t.readinessStatuses.length>0&&!t.readinessStatuses.includes(r.readiness.status)?!1:n?r.id.toLowerCase().includes(n)||r.kind.toLowerCase().includes(n)||r.dependencies.some(c=>c.toLowerCase().includes(n)):!0),s=e.diagnostics.filter(r=>{var c,l,i,m;return t.severities.length>0&&!t.severities.includes(r.severity)?!1:n?r.code.toLowerCase().includes(n)||r.componentId.toLowerCase().includes(n)||r.message.toLowerCase().includes(n)||(((c=r.cause)==null?void 0:c.toLowerCase().includes(n))??!1)||(((l=r.fixHint)==null?void 0:l.toLowerCase().includes(n))??!1)||(((i=r.docsUrl)==null?void 0:i.toLowerCase().includes(n))??!1)||(((m=r.dependsOn)==null?void 0:m.some(f=>f.toLowerCase().includes(n)))??!1):!0});return{...e,components:o,diagnostics:s}}function b(e){return e.replaceAll("\\","\\\\").replaceAll('"','\\"').replaceAll(`\r
|
|
2
|
-
`,"\\n").replaceAll("\r","\\n").replaceAll(`
|
|
3
|
-
`,"\\n")}function _(e){return e.replaceAll(/[^a-zA-Z0-9_]/g,"_")}function z(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 Y(e){return`EXT_${_(e)}_${z(e)}`}function I(e){const t=["graph TD"],n=new Map,o=new Map;if(e.components.length===0)return t.push(' EMPTY["No registered platform components"]'),t.join(`
|
|
4
|
-
`);for(const[c,l]of e.components.entries()){const i=`C${String(c+1)}`;n.set(l.id,i),t.push(` ${i}["${b(l.id)}\\nkind: ${b(l.kind)}\\nreadiness: ${l.readiness.status}\\nhealth: ${l.health.status}"]`)}for(const c of e.components){const l=n.get(c.id);if(l)for(const i of c.dependencies){const m=n.get(i);if(m){t.push(` ${l} --> ${m}`);continue}let f=o.get(i);f||(f=Y(i),o.set(i,f),t.push(` ${f}["${b(i)}"]`)),t.push(` ${l} --> ${f}`)}}const s=[],r=[];for(const c of e.components){const l=n.get(c.id);l&&(c.readiness.status==="degraded"&&s.push(l),c.readiness.status==="not-ready"&&r.push(l))}return s.length>0&&(t.push(` class ${s.join(",")} degraded`),t.push(" classDef degraded stroke:#f59e0b,stroke-width:2px")),r.length>0&&(t.push(` class ${r.join(",")} notReady`),t.push(" classDef notReady stroke:#ef4444,stroke-width:2px")),t.join(`
|
|
5
|
-
`)}function p(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function B(e){try{const t=new URL(e);if(t.protocol==="https:"||t.protocol==="http:")return t.href}catch{return}}function X(e){const t=B(e),n=p(e);return t?`<p><strong>docs:</strong> <a href="${p(t)}" target="_blank" rel="noopener noreferrer">${n}</a></p>`:`<p><strong>docs:</strong> <span>${n}</span></p>`}function M(e){const t=new Set(e.map(s=>s.id)),n=[],o=new Set;for(const s of e)for(const r of s.dependencies)t.has(r)||o.has(r)||(o.add(r),n.push(r));return n}function V(e,t,n){const o=Math.min(t,n)/2-70,s=t/2,r=n/2,c=new Map,l=M(e.components),i=[...e.components.map(m=>m.id),...l];return i.forEach((m,f)=>{const u=Math.PI*2*f/Math.max(i.length,1);c.set(m,{x:s+o*Math.cos(u),y:r+o*Math.sin(u)})}),c}function G(e,t){const s=e.components,r=V(e,900,460),c=M(s),l=s.flatMap(f=>f.dependencies.map(u=>{const v=r.get(f.id),S=r.get(u);return!v||!S?"":`<line x1="${v.x}" y1="${v.y}" x2="${S.x}" y2="${S.y}" class="edge-line" marker-end="url(#arrow)" />`})).join(""),i=s.map(f=>{const u=r.get(f.id);if(!u)return"";const S=["module-node",f.readiness.status==="not-ready"?"component-not-ready":f.readiness.status==="degraded"?"component-degraded":"component-ready",f.id===t?"module-selected":""].filter(Boolean).join(" ");return`<g>
|
|
6
|
-
<circle cx="${u.x}" cy="${u.y}" r="34" class="${S}" data-component="${p(f.id)}" />
|
|
7
|
-
<text x="${u.x}" y="${u.y+4}" text-anchor="middle" class="module-label">${p(f.id)}</text>
|
|
8
|
-
</g>`}).join(""),m=c.map(f=>{const u=r.get(f);return u?`<g>
|
|
9
|
-
<rect x="${u.x-42}" y="${u.y-20}" width="84" height="40" rx="10" class="module-node component-external" />
|
|
10
|
-
<text x="${u.x}" y="${u.y+4}" text-anchor="middle" class="module-label">${p(f)}</text>
|
|
11
|
-
</g>`:""}).join("");return`<svg viewBox="0 0 900 460" role="img" aria-label="Platform component dependency graph">
|
|
12
|
-
<defs>
|
|
13
|
-
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
14
|
-
<polygon points="0 0, 10 3.5, 0 7" class="edge-arrow" />
|
|
15
|
-
</marker>
|
|
16
|
-
</defs>
|
|
17
|
-
${l}
|
|
18
|
-
${i}
|
|
19
|
-
${m}
|
|
20
|
-
</svg>`}function K(e){return e?e.diagnostics.length===0?'<p class="muted">No diagnostics issues.</p>':`<div class="diagnostics-list">
|
|
21
|
-
${e.diagnostics.map(t=>{const n=t.dependsOn&&t.dependsOn.length>0?`<div class="chips">${t.dependsOn.map(o=>`<span class="chip">dependsOn: ${p(o)}</span>`).join("")}</div>`:"";return`<article class="card issue severity-${p(t.severity)}">
|
|
22
|
-
<h3>${p(t.code)}</h3>
|
|
23
|
-
<p><strong>severity:</strong> ${p(t.severity)} · <strong>component:</strong> ${p(t.componentId)}</p>
|
|
24
|
-
<p>${p(t.message)}</p>
|
|
25
|
-
${t.cause?`<p><strong>cause:</strong> ${p(t.cause)}</p>`:""}
|
|
26
|
-
${t.fixHint?`<p><strong>fix hint:</strong> ${p(t.fixHint)}</p>`:""}
|
|
27
|
-
${t.docsUrl?X(t.docsUrl):""}
|
|
28
|
-
${n}
|
|
29
|
-
</article>`}).join("")}
|
|
30
|
-
</div>`:'<p class="muted">No platform snapshot loaded.</p>'}const j=document.querySelector("#app");if(!j)throw new Error("App root not found.");const Q=j,W=["ready","degraded","not-ready"],Z=["error","warning","info"],d={filter:{query:"",readinessStatuses:[],severities:[]}};function ee(e,t){const n=new Blob([t],{type:"application/json"}),o=URL.createObjectURL(n),s=document.createElement("a");s.href=o,s.download=e,s.click(),URL.revokeObjectURL(o)}async function N(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 D(e){return e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}function te(){const e=document.activeElement;if(!(!D(e)||!e.id))return{id:e.id,selectionEnd:e.selectionEnd,selectionStart:e.selectionStart}}function ne(e){if(!e)return;const t=document.getElementById(e.id);D(t)&&(t.focus({preventScroll:!0}),e.selectionStart!==null&&e.selectionEnd!==null&&t.setSelectionRange(e.selectionStart,e.selectionEnd))}function T(e,t){return!e||e.components.length===0?void 0:(t?e.components.find(o=>o.id===t):void 0)??e.components[0]}function x(){var n;const e=(n=d.payload)==null?void 0:n.snapshot;if(!e){d.filteredSnapshot=void 0,d.selectedComponentId=void 0;return}d.filteredSnapshot=U(e,d.filter);const t=T(d.filteredSnapshot,d.selectedComponentId);d.selectedComponentId=t==null?void 0:t.id}function re(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: ${p(n)}</span>`).join(""):'<span class="chip">dependsOn: none</span>';return`
|
|
31
|
-
<h3>${p(e.id)}</h3>
|
|
32
|
-
<p class="muted">kind: <strong>${p(e.kind)}</strong> · state: <strong>${p(e.state)}</strong></p>
|
|
33
|
-
<div class="chips">
|
|
34
|
-
<span class="chip">readiness: ${p(e.readiness.status)}</span>
|
|
35
|
-
<span class="chip">critical: ${e.readiness.critical?"true":"false"}</span>
|
|
36
|
-
<span class="chip">health: ${p(e.health.status)}</span>
|
|
37
|
-
<span class="chip">ownership: owns=${e.ownership.ownsResources?"true":"false"}/external=${e.ownership.externallyManaged?"true":"false"}</span>
|
|
38
|
-
${t}
|
|
39
|
-
</div>
|
|
40
|
-
<p class="muted">telemetry namespace: <code>${p(e.telemetry.namespace)}</code></p>
|
|
41
|
-
<h4>Sanitized details</h4>
|
|
42
|
-
<pre>${p(JSON.stringify(e.details,null,2))}</pre>
|
|
43
|
-
`}function se(){var t;const e=(t=d.payload)==null?void 0:t.timing;return e?`
|
|
44
|
-
<p><strong>Total:</strong> ${e.totalMs.toFixed(3)}ms</p>
|
|
45
|
-
<table>
|
|
46
|
-
<thead>
|
|
47
|
-
<tr><th>phase</th><th>duration (ms)</th></tr>
|
|
48
|
-
</thead>
|
|
49
|
-
<tbody>
|
|
50
|
-
${e.phases.map(n=>`<tr><td>${p(n.name)}</td><td>${n.durationMs.toFixed(3)}</td></tr>`).join("")}
|
|
51
|
-
</tbody>
|
|
52
|
-
</table>
|
|
53
|
-
`:'<p class="muted">Timing not collected.</p>'}function oe(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`
|
|
54
|
-
<div class="chips">
|
|
55
|
-
<span class="chip">generatedAt: ${p(e.generatedAt)}</span>
|
|
56
|
-
<span class="chip">aggregate readiness: ${p(e.readiness.status)}</span>
|
|
57
|
-
<span class="chip">aggregate health: ${p(e.health.status)}</span>
|
|
58
|
-
<span class="chip">components: ${String(e.components.length)}</span>
|
|
59
|
-
<span class="chip">diagnostics: ${String(e.diagnostics.length)}</span>
|
|
60
|
-
<span class="chip">ready/degraded/not-ready: ${t.ready}/${t.degraded}/${t.notReady}</span>
|
|
61
|
-
</div>
|
|
62
|
-
`}function g(e={}){const t=typeof e=="string"?e:e.message,n=typeof e=="string"||!e.preserveFocus?void 0:te(),o=d.filteredSnapshot,s=T(o,d.selectedComponentId),r=o?I(o):"",c=o&&o.components.length>0?G(o,s==null?void 0:s.id):'<p class="muted">No platform components loaded.</p>';Q.innerHTML=`
|
|
63
|
-
<main>
|
|
64
|
-
<header>
|
|
65
|
-
<h1>Fluo Studio Platform Snapshot Viewer</h1>
|
|
66
|
-
<p>Load JSON exported by <code>fluo inspect --json</code> (shared platform snapshot/diagnostic schema) and optionally timing JSON from <code>--timing</code>.</p>
|
|
67
|
-
</header>
|
|
68
|
-
|
|
69
|
-
<section class="card uploader" id="drop-zone">
|
|
70
|
-
<h2>Diagnostics file input</h2>
|
|
71
|
-
<p>Drag & drop a JSON file, or choose one manually.</p>
|
|
72
|
-
<input type="file" id="file-input" accept="application/json" />
|
|
73
|
-
<div class="actions">
|
|
74
|
-
<button id="download-json" ${d.rawJson?"":"disabled"}>Download loaded JSON</button>
|
|
75
|
-
<button id="copy-json" ${d.rawJson?"":"disabled"}>Copy loaded JSON</button>
|
|
76
|
-
<button id="copy-mermaid" ${o?"":"disabled"}>Copy Mermaid</button>
|
|
77
|
-
</div>
|
|
78
|
-
${t?`<p class="notice">${p(t)}</p>`:""}
|
|
79
|
-
</section>
|
|
80
|
-
|
|
81
|
-
<section class="card">
|
|
82
|
-
<h2>Snapshot summary</h2>
|
|
83
|
-
${oe(o)}
|
|
84
|
-
</section>
|
|
85
|
-
|
|
86
|
-
<section class="split-grid">
|
|
87
|
-
<div class="card">
|
|
88
|
-
<h2>Search and filtering</h2>
|
|
89
|
-
<label>
|
|
90
|
-
Search component/diagnostic
|
|
91
|
-
<input type="text" id="search" value="${p(d.filter.query)}" placeholder="e.g. redis.default or QUEUE_DEPENDENCY_NOT_READY" />
|
|
92
|
-
</label>
|
|
93
|
-
|
|
94
|
-
<div class="filter-row">
|
|
95
|
-
<span>Component readiness</span>
|
|
96
|
-
${W.map(a=>`<label><input type="checkbox" id="readiness-${a}" data-readiness="${a}" ${d.filter.readinessStatuses.includes(a)?"checked":""}/> ${a}</label>`).join("")}
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
<div class="filter-row">
|
|
100
|
-
<span>Diagnostic severity</span>
|
|
101
|
-
${Z.map(a=>`<label><input type="checkbox" id="severity-${a}" data-severity="${a}" ${d.filter.severities.includes(a)?"checked":""}/> ${a}</label>`).join("")}
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
<div class="card">
|
|
106
|
-
<h2>Timing</h2>
|
|
107
|
-
${se()}
|
|
108
|
-
</div>
|
|
109
|
-
</section>
|
|
110
|
-
|
|
111
|
-
<section class="card">
|
|
112
|
-
<h2>Platform dependency graph</h2>
|
|
113
|
-
<p class="muted">Component dependencies are rendered directly from the shared platform snapshot schema.</p>
|
|
114
|
-
<div id="graph-host">${c}</div>
|
|
115
|
-
</section>
|
|
116
|
-
|
|
117
|
-
<section class="split-grid">
|
|
118
|
-
<div class="card" id="details-panel">
|
|
119
|
-
<h2>Component details</h2>
|
|
120
|
-
${re(s)}
|
|
121
|
-
</div>
|
|
122
|
-
<div class="card">
|
|
123
|
-
<h2>Mermaid output</h2>
|
|
124
|
-
<pre>${p(r||"No snapshot loaded.")}</pre>
|
|
125
|
-
</div>
|
|
126
|
-
</section>
|
|
127
|
-
|
|
128
|
-
<section class="card">
|
|
129
|
-
<h2>Diagnostics issues</h2>
|
|
130
|
-
<p class="muted">Fix hints and dependency chains are rendered from <code>diagnostics.fixHint</code> and <code>diagnostics.dependsOn</code>.</p>
|
|
131
|
-
${K(o)}
|
|
132
|
-
</section>
|
|
133
|
-
</main>
|
|
134
|
-
`;const l=document.querySelector("#file-input"),i=document.querySelector("#drop-zone"),m=document.querySelector("#search"),f=document.querySelector("#copy-json"),u=document.querySelector("#download-json"),v=document.querySelector("#copy-mermaid"),S=async a=>{const w=await a.text();try{const y=P(w);d.payload=y.payload,d.rawJson=y.rawJson,x(),g("Diagnostics file loaded successfully.")}catch(y){d.payload=void 0,d.filteredSnapshot=void 0,d.selectedComponentId=void 0,d.rawJson=void 0,g(y instanceof Error?y.message:"Failed to parse diagnostics file.")}};l==null||l.addEventListener("change",async a=>{var E;const y=(E=a.target.files)==null?void 0:E[0];y&&await S(y)}),i==null||i.addEventListener("dragover",a=>{a.preventDefault(),i.classList.add("drag-active")}),i==null||i.addEventListener("dragleave",()=>{i.classList.remove("drag-active")}),i==null||i.addEventListener("drop",async a=>{var y,E;a.preventDefault(),i.classList.remove("drag-active");const w=(E=(y=a.dataTransfer)==null?void 0:y.files)==null?void 0:E[0];w&&await S(w)}),m==null||m.addEventListener("input",a=>{const w=a.target;d.filter.query=w.value,x(),g({preserveFocus:!0})}),document.querySelectorAll("input[data-readiness]").forEach(a=>{a.addEventListener("change",()=>{d.filter.readinessStatuses=C(d.filter.readinessStatuses,a.dataset.readiness),x(),g({preserveFocus:!0})})}),document.querySelectorAll("input[data-severity]").forEach(a=>{a.addEventListener("change",()=>{d.filter.severities=C(d.filter.severities,a.dataset.severity),x(),g({preserveFocus:!0})})}),f==null||f.addEventListener("click",async()=>{if(d.rawJson)try{await N(d.rawJson),g("Loaded JSON copied to clipboard.")}catch(a){g(a instanceof Error?a.message:"Failed to copy JSON.")}}),u==null||u.addEventListener("click",()=>{d.rawJson&&(ee("fluo-diagnostics.json",d.rawJson),g("Loaded JSON downloaded."))}),v==null||v.addEventListener("click",async()=>{if(o)try{await N(I(o)),g("Mermaid copied to clipboard.")}catch(a){g(a instanceof Error?a.message:"Failed to copy Mermaid text.")}}),document.querySelectorAll("[data-component]").forEach(a=>{a.addEventListener("click",()=>{const w=a.dataset.component;w&&(d.selectedComponentId=w,g())})}),ne(n)}x();g();
|