@fluojs/studio 1.0.0-beta.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fluo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ko.md ADDED
@@ -0,0 +1,97 @@
1
+ # @fluojs/studio
2
+
3
+ <p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
4
+
5
+ fluo 런타임 내보내기의 공유 플랫폼 snapshot을 파일 기반으로 확인하는 뷰어입니다.
6
+
7
+ ## 목차
8
+
9
+ - [설치](#설치)
10
+ - [릴리스 정책](#릴리스-정책)
11
+ - [사용 시점](#사용-시점)
12
+ - [빠른 시작](#빠른-시작)
13
+ - [주요 패턴](#주요-패턴)
14
+ - [공개 API](#공개-api)
15
+ - [관련 패키지](#관련-패키지)
16
+ - [예제 소스](#예제-소스)
17
+
18
+ ## 설치
19
+
20
+ ```bash
21
+ pnpm add @fluojs/studio
22
+ ```
23
+
24
+ 배포된 패키지는 두 가지 caller-facing entrypoint를 제공합니다.
25
+
26
+ - `@fluojs/studio` / `@fluojs/studio/contracts`: snapshot 파싱, 필터링, Mermaid 내보내기 헬퍼
27
+ - `@fluojs/studio/viewer`: 패키징된 브라우저 뷰어 HTML 진입 파일
28
+
29
+ ## 릴리스 정책
30
+
31
+ - `@fluojs/studio`는 fluo의 intended public publish surface에 포함되는 공개 배포 패키지입니다.
32
+ - Studio의 npm 설치 계약은 `pnpm add @fluojs/studio`이며, 저장소 내부 개발 경로는 계속 `pnpm --dir packages/studio dev`를 사용합니다.
33
+ - 이번 릴리스에서 지원하는 공개 패키지 표면은 파일 기반 뷰어와 문서화된 snapshot 소비 계약까지입니다. 내부 workspace 연결 방식은 지원되는 설치 경로가 아닙니다.
34
+
35
+ ## 사용 시점
36
+
37
+ - **시각화**: 애플리케이션의 모듈 그래프와 의존성 체인을 탐색할 때.
38
+ - **진단**: 가이드된 힌트를 사용하여 플랫폼 수준의 설정 문제를 식별하고 해결할 때.
39
+ - **성능 분석**: 부트스트랩 타이밍을 분석하고 초기화 병목 지점을 찾을 때.
40
+ - **문서화**: 애플리케이션 아키텍처의 Mermaid 다이어그램을 생성할 때.
41
+
42
+ ## 빠른 시작
43
+
44
+ Studio는 fluo CLI에서 내보낸 JSON 파일을 소비합니다.
45
+
46
+ 1. **Snapshot 내보내기**:
47
+ ```bash
48
+ fluo inspect ./src/app.module.ts --json > snapshot.json
49
+ ```
50
+
51
+ 2. **Studio 실행**:
52
+ ```bash
53
+ pnpm --dir packages/studio dev
54
+ ```
55
+
56
+ 3. **파일 로드**: Studio 웹 인터페이스에 `snapshot.json` 파일을 드래그 앤 드롭합니다.
57
+
58
+ ## 주요 패턴
59
+
60
+ ### 초기화 문제 해결
61
+ **Diagnostics** 탭을 사용하여 런타임 부트스트랩 과정에서 수집된 이슈들을 확인합니다.
62
+ - 심각도(Error, Warning)별로 필터링합니다.
63
+ - `fixHint`를 통해 문제를 해결하기 위한 구체적인 조치 방법을 확인합니다.
64
+ - `dependsOn`을 통해 어떤 컴포넌트가 실패 지점을 차단하고 있는지 확인합니다.
65
+
66
+ ### 아키텍처 다이어그램 내보내기
67
+ 1. **Graph** 뷰로 이동합니다.
68
+ 2. 시각화하려는 모듈이나 컴포넌트를 선택합니다.
69
+ 3. **Export to Mermaid** 버튼을 사용하여 문서에 사용할 수 있는 텍스트 기반 다이어그램을 가져옵니다.
70
+
71
+ ## 공개 API
72
+
73
+ Studio는 주로 웹 애플리케이션이지만, 배포된 패키지는 도구/자동화가 사용할 수 있는 snapshot 소비 헬퍼도 함께 공개합니다.
74
+
75
+ | 규격 | 설명 |
76
+ |---|---|
77
+ | `PlatformShellSnapshot` | 애플리케이션 상태를 나타내는 핵심 데이터 구조입니다. |
78
+ | `PlatformDiagnosticIssue` | 플랫폼 오류 보고 및 수정을 위한 스키마입니다. |
79
+ | `parseStudioPayload(rawJson)` | CLI/export JSON을 Studio snapshot/timing envelope로 검증합니다. |
80
+ | `applyFilters(snapshot, filter)` | 원본 snapshot을 변경하지 않고 readiness/severity/query 필터를 적용합니다. |
81
+ | `renderMermaid(snapshot)` | 로드된 플랫폼 그래프를 Mermaid 텍스트로 변환합니다. |
82
+
83
+ ### 배포 패키지 entrypoint
84
+
85
+ - `@fluojs/studio`: snapshot 파싱/필터링/렌더링용 루트 헬퍼 배럴
86
+ - `@fluojs/studio/contracts`: 계약 헬퍼를 직접 가져오고 싶은 도구용 명시적 서브패스
87
+ - `@fluojs/studio/viewer`: 브라우저 뷰어 번들의 `dist/index.html` 진입 파일
88
+
89
+ ## 관련 패키지
90
+
91
+ - **[@fluojs/cli](../cli/README.ko.md)**: Studio 호환 데이터를 생성하기 위한 `inspect` 명령을 제공합니다.
92
+ - **[@fluojs/runtime](../runtime/README.ko.md)**: 진단 및 snapshot 데이터를 생성하는 엔진입니다.
93
+
94
+ ## 예제 소스
95
+
96
+ - [main.ts](./src/main.ts) - 애플리케이션 진입점.
97
+ - [contracts.ts](./src/contracts.ts) - snapshot 소비를 위한 타입 정의.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # @fluojs/studio
2
+
3
+ <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
+
5
+ File-first shared platform snapshot viewer for fluo runtime exports.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [Release Policy](#release-policy)
11
+ - [When to Use](#when-to-use)
12
+ - [Quick Start](#quick-start)
13
+ - [Common Patterns](#common-patterns)
14
+ - [Public API](#public-api)
15
+ - [Related Packages](#related-packages)
16
+ - [Example Sources](#example-sources)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm add @fluojs/studio
22
+ ```
23
+
24
+ The published package serves two caller-facing entrypoints:
25
+
26
+ - `@fluojs/studio` / `@fluojs/studio/contracts` for snapshot parsing, filtering, and Mermaid export helpers.
27
+ - `@fluojs/studio/viewer` for the packaged browser viewer HTML entry file.
28
+
29
+ ## Release Policy
30
+
31
+ - `@fluojs/studio` is part of the intended public publish surface for fluo.
32
+ - The npm install contract for Studio is `pnpm add @fluojs/studio`; local repo development still uses `pnpm --dir packages/studio dev`.
33
+ - Studio's public package surface in this release is the file-first viewer and its documented snapshot-consumption contracts. Internal workspace wiring is not a supported install path.
34
+
35
+ ## When to Use
36
+
37
+ - **Visualization**: To explore your application's module graph and dependency chains.
38
+ - **Diagnostics**: To identify and fix platform-level configuration issues using guided hints.
39
+ - **Performance**: To analyze bootstrap timing and identify initialization bottlenecks.
40
+ - **Documentation**: To generate Mermaid diagrams of your application architecture.
41
+
42
+ ## Quick Start
43
+
44
+ Studio consumes JSON exports from the fluo CLI.
45
+
46
+ 1. **Export a snapshot**:
47
+ ```bash
48
+ fluo inspect ./src/app.module.ts --json > snapshot.json
49
+ ```
50
+
51
+ 2. **Open Studio**:
52
+ ```bash
53
+ pnpm --dir packages/studio dev
54
+ ```
55
+
56
+ 3. **Load the file**: Drag and drop `snapshot.json` into the Studio web interface.
57
+
58
+ ## Common Patterns
59
+
60
+ ### Troubleshooting Initialization
61
+ Use the **Diagnostics** tab to see issues collected during the runtime bootstrap process.
62
+ - Filter by severity (Error, Warning).
63
+ - Use `fixHint` to get actionable advice on how to resolve the issue.
64
+ - View `dependsOn` to see which components are blocking the failing one.
65
+
66
+ ### Exporting Architecture Diagrams
67
+ 1. Navigate to the **Graph** view.
68
+ 2. Select the modules or components you want to visualize.
69
+ 3. Use the **Export to Mermaid** button to get a text-based diagram for your documentation.
70
+
71
+ ## Public API
72
+
73
+ Studio is primarily a web application, but the published package also exposes the documented snapshot-consumption helpers used by tooling and automation.
74
+
75
+ | Contract | Description |
76
+ |---|---|
77
+ | `PlatformShellSnapshot` | The core data structure representing the application state. |
78
+ | `PlatformDiagnosticIssue` | Schema for reporting and fixing platform errors. |
79
+ | `parseStudioPayload(rawJson)` | Validates CLI/exported JSON into the Studio snapshot/timing envelope. |
80
+ | `applyFilters(snapshot, filter)` | Applies readiness/severity/query filters without mutating the source snapshot. |
81
+ | `renderMermaid(snapshot)` | Produces Mermaid graph text from the loaded platform graph. |
82
+
83
+ ### Published package entrypoints
84
+
85
+ - `@fluojs/studio`: root helper barrel for snapshot parsing/filtering/rendering.
86
+ - `@fluojs/studio/contracts`: explicit helper subpath for tooling that wants the contract helpers directly.
87
+ - `@fluojs/studio/viewer`: packaged `dist/index.html` entrypoint for the browser viewer bundle.
88
+
89
+ ## Related Packages
90
+
91
+ - **[@fluojs/cli](../cli/README.md)**: Provides the `inspect` command to generate Studio-compatible exports.
92
+ - **[@fluojs/runtime](../runtime/README.md)**: The engine that generates the diagnostic and snapshot data.
93
+
94
+ ## Example Sources
95
+
96
+ - [main.ts](./src/main.ts) - Application entry point.
97
+ - [contracts.ts](./src/contracts.ts) - Type definitions for snapshot consumption.
@@ -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-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}.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}
@@ -0,0 +1,128 @@
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();
@@ -0,0 +1,49 @@
1
+ import type { BootstrapTimingDiagnostics, PlatformDiagnosticIssue, PlatformShellSnapshot, PlatformSnapshot } from '@fluojs/runtime';
2
+ export type PlatformReadinessStatus = PlatformSnapshot['readiness']['status'];
3
+ export type PlatformDiagnosticSeverity = PlatformDiagnosticIssue['severity'];
4
+ /**
5
+ * Serializable Studio payload envelope built from inspect snapshot/timing exports.
6
+ */
7
+ export interface StudioPayload {
8
+ snapshot?: PlatformShellSnapshot;
9
+ timing?: BootstrapTimingDiagnostics;
10
+ }
11
+ /**
12
+ * Filter state applied to the loaded platform snapshot inside Studio.
13
+ */
14
+ export interface FilterState {
15
+ query: string;
16
+ readinessStatuses: PlatformReadinessStatus[];
17
+ severities: PlatformDiagnosticSeverity[];
18
+ }
19
+ /**
20
+ * Parsed Studio payload together with the original JSON source.
21
+ */
22
+ export interface ParsedPayload {
23
+ payload: StudioPayload;
24
+ rawJson: string;
25
+ }
26
+ /**
27
+ * Parses a Studio JSON file into the documented snapshot/timing envelope.
28
+ *
29
+ * @param rawJson - Raw JSON emitted by `fluo inspect` or a Studio-compatible producer.
30
+ * @returns The validated Studio payload plus the original JSON string.
31
+ * @throws Error when the JSON does not match the supported Studio file contracts.
32
+ */
33
+ export declare function parseStudioPayload(rawJson: string): ParsedPayload;
34
+ /**
35
+ * Applies Studio filter state to a platform snapshot without mutating the input.
36
+ *
37
+ * @param snapshot - The loaded platform snapshot.
38
+ * @param filter - Active readiness, severity, and query filters.
39
+ * @returns A filtered snapshot containing only the matching components and diagnostics.
40
+ */
41
+ export declare function applyFilters(snapshot: PlatformShellSnapshot, filter: FilterState): PlatformShellSnapshot;
42
+ /**
43
+ * Renders the loaded platform snapshot as a Mermaid dependency graph.
44
+ *
45
+ * @param snapshot - The platform snapshot to render.
46
+ * @returns Mermaid graph text suitable for docs or clipboard export.
47
+ */
48
+ export declare function renderMermaid(snapshot: PlatformShellSnapshot): string;
49
+ //# sourceMappingURL=contracts.d.ts.map
@@ -0,0 +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,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC9E,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;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAsErE"}
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Serializable Studio payload envelope built from inspect snapshot/timing exports.
3
+ */
4
+
5
+ /**
6
+ * Filter state applied to the loaded platform snapshot inside Studio.
7
+ */
8
+
9
+ /**
10
+ * Parsed Studio payload together with the original JSON source.
11
+ */
12
+
13
+ function isRecord(value) {
14
+ return typeof value === 'object' && value !== null;
15
+ }
16
+ function isStringArray(value) {
17
+ return Array.isArray(value) && value.every(entry => typeof entry === 'string');
18
+ }
19
+ function isReadinessStatus(value) {
20
+ return value === 'ready' || value === 'not-ready' || value === 'degraded';
21
+ }
22
+ function isHealthStatus(value) {
23
+ return value === 'healthy' || value === 'unhealthy' || value === 'degraded';
24
+ }
25
+ function isDiagnosticSeverity(value) {
26
+ return value === 'error' || value === 'warning' || value === 'info';
27
+ }
28
+ function validateSnapshot(value) {
29
+ if (!isRecord(value)) {
30
+ return null;
31
+ }
32
+ if (typeof value.generatedAt !== 'string' || !isRecord(value.readiness) || !isRecord(value.health) || !Array.isArray(value.components) || !Array.isArray(value.diagnostics)) {
33
+ throw new Error('Invalid platform snapshot payload.');
34
+ }
35
+ if (!isReadinessStatus(value.readiness.status) || typeof value.readiness.critical !== 'boolean') {
36
+ throw new Error('Invalid aggregate readiness in platform snapshot payload.');
37
+ }
38
+ if (!isHealthStatus(value.health.status)) {
39
+ throw new Error('Invalid aggregate health in platform snapshot payload.');
40
+ }
41
+ for (const component of value.components) {
42
+ if (!isRecord(component)) {
43
+ throw new Error('Invalid component entry in platform snapshot payload.');
44
+ }
45
+ if (typeof component.id !== 'string' || typeof component.kind !== 'string' || typeof component.state !== 'string' || !isRecord(component.readiness) || !isRecord(component.health) || !isStringArray(component.dependencies) || !isRecord(component.telemetry) || !isRecord(component.ownership) || !isRecord(component.details)) {
46
+ throw new Error('Invalid component shape in platform snapshot payload.');
47
+ }
48
+ if (!isReadinessStatus(component.readiness.status) || typeof component.readiness.critical !== 'boolean') {
49
+ throw new Error('Invalid component readiness in platform snapshot payload.');
50
+ }
51
+ if (!isHealthStatus(component.health.status)) {
52
+ throw new Error('Invalid component health in platform snapshot payload.');
53
+ }
54
+ if (typeof component.telemetry.namespace !== 'string' || !isRecord(component.telemetry.tags) || typeof component.ownership.ownsResources !== 'boolean' || typeof component.ownership.externallyManaged !== 'boolean') {
55
+ throw new Error('Invalid component telemetry/ownership in platform snapshot payload.');
56
+ }
57
+ }
58
+ for (const issue of value.diagnostics) {
59
+ if (!isRecord(issue)) {
60
+ throw new Error('Invalid diagnostics issue entry in platform snapshot payload.');
61
+ }
62
+ if (typeof issue.code !== 'string' || !isDiagnosticSeverity(issue.severity) || typeof issue.componentId !== 'string' || typeof issue.message !== 'string') {
63
+ throw new Error('Invalid diagnostics issue shape in platform snapshot payload.');
64
+ }
65
+ if (issue.cause !== undefined && typeof issue.cause !== 'string' || issue.fixHint !== undefined && typeof issue.fixHint !== 'string' || issue.docsUrl !== undefined && typeof issue.docsUrl !== 'string' || issue.dependsOn !== undefined && !isStringArray(issue.dependsOn)) {
66
+ throw new Error('Invalid optional diagnostics issue fields in platform snapshot payload.');
67
+ }
68
+ }
69
+ return value;
70
+ }
71
+ function validateTiming(value) {
72
+ if (!isRecord(value)) {
73
+ return null;
74
+ }
75
+ if (value.version !== 1) {
76
+ throw new Error('Unsupported bootstrap timing version. Expected version: 1.');
77
+ }
78
+ if (typeof value.totalMs !== 'number' || !Array.isArray(value.phases)) {
79
+ throw new Error('Invalid bootstrap timing payload.');
80
+ }
81
+ for (const phase of value.phases) {
82
+ if (!isRecord(phase) || typeof phase.name !== 'string' || typeof phase.durationMs !== 'number') {
83
+ throw new Error('Invalid phase entry in bootstrap timing payload.');
84
+ }
85
+ }
86
+ return value;
87
+ }
88
+
89
+ /**
90
+ * Parses a Studio JSON file into the documented snapshot/timing envelope.
91
+ *
92
+ * @param rawJson - Raw JSON emitted by `fluo inspect` or a Studio-compatible producer.
93
+ * @returns The validated Studio payload plus the original JSON string.
94
+ * @throws Error when the JSON does not match the supported Studio file contracts.
95
+ */
96
+ export function parseStudioPayload(rawJson) {
97
+ const parsed = JSON.parse(rawJson);
98
+ const envelope = isRecord(parsed) ? parsed : undefined;
99
+ const snapshot = validateSnapshot(envelope?.snapshot ?? parsed);
100
+ const timing = validateTiming(envelope?.timing ?? (!snapshot ? parsed : undefined));
101
+ if (!snapshot && !timing) {
102
+ throw new Error('Unsupported file format. Expected platform snapshot JSON or timing JSON.');
103
+ }
104
+ return {
105
+ payload: {
106
+ ...(snapshot ? {
107
+ snapshot
108
+ } : {}),
109
+ ...(timing ? {
110
+ timing
111
+ } : {})
112
+ },
113
+ rawJson
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Applies Studio filter state to a platform snapshot without mutating the input.
119
+ *
120
+ * @param snapshot - The loaded platform snapshot.
121
+ * @param filter - Active readiness, severity, and query filters.
122
+ * @returns A filtered snapshot containing only the matching components and diagnostics.
123
+ */
124
+ export function applyFilters(snapshot, filter) {
125
+ const query = filter.query.trim().toLowerCase();
126
+ const components = snapshot.components.filter(component => {
127
+ if (filter.readinessStatuses.length > 0 && !filter.readinessStatuses.includes(component.readiness.status)) {
128
+ return false;
129
+ }
130
+ if (!query) {
131
+ return true;
132
+ }
133
+ return component.id.toLowerCase().includes(query) || component.kind.toLowerCase().includes(query) || component.dependencies.some(dependency => dependency.toLowerCase().includes(query));
134
+ });
135
+ const diagnostics = snapshot.diagnostics.filter(issue => {
136
+ if (filter.severities.length > 0 && !filter.severities.includes(issue.severity)) {
137
+ return false;
138
+ }
139
+ if (!query) {
140
+ return true;
141
+ }
142
+ return issue.code.toLowerCase().includes(query) || issue.componentId.toLowerCase().includes(query) || issue.message.toLowerCase().includes(query) || (issue.cause?.toLowerCase().includes(query) ?? false) || (issue.fixHint?.toLowerCase().includes(query) ?? false) || (issue.docsUrl?.toLowerCase().includes(query) ?? false) || (issue.dependsOn?.some(dependency => dependency.toLowerCase().includes(query)) ?? false);
143
+ });
144
+ return {
145
+ ...snapshot,
146
+ components,
147
+ diagnostics
148
+ };
149
+ }
150
+ function escapeMermaidText(value) {
151
+ return value.replaceAll('"', '\\"');
152
+ }
153
+ function sanitizeMermaidNodeIdSegment(value) {
154
+ return value.replaceAll(/[^a-zA-Z0-9_]/g, '_');
155
+ }
156
+ function hashMermaidNodeId(value) {
157
+ let hash = 2_166_136_261;
158
+ for (const character of value) {
159
+ hash ^= character.codePointAt(0) ?? 0;
160
+ hash = Math.imul(hash, 16_777_619) >>> 0;
161
+ }
162
+ return hash.toString(16).padStart(8, '0');
163
+ }
164
+ function createExternalMermaidNodeId(value) {
165
+ return `EXT_${sanitizeMermaidNodeIdSegment(value)}_${hashMermaidNodeId(value)}`;
166
+ }
167
+
168
+ /**
169
+ * Renders the loaded platform snapshot as a Mermaid dependency graph.
170
+ *
171
+ * @param snapshot - The platform snapshot to render.
172
+ * @returns Mermaid graph text suitable for docs or clipboard export.
173
+ */
174
+ export function renderMermaid(snapshot) {
175
+ const lines = ['graph TD'];
176
+ const nodeByComponent = new Map();
177
+ const externalNodeByDependency = new Map();
178
+ if (snapshot.components.length === 0) {
179
+ lines.push(' EMPTY["No registered platform components"]');
180
+ return lines.join('\n');
181
+ }
182
+ for (const [index, component] of snapshot.components.entries()) {
183
+ const nodeId = `C${String(index + 1)}`;
184
+ nodeByComponent.set(component.id, nodeId);
185
+ lines.push(` ${nodeId}["${escapeMermaidText(component.id)}\\nkind: ${escapeMermaidText(component.kind)}\\nreadiness: ${component.readiness.status}\\nhealth: ${component.health.status}"]`);
186
+ }
187
+ for (const component of snapshot.components) {
188
+ const from = nodeByComponent.get(component.id);
189
+ if (!from) {
190
+ continue;
191
+ }
192
+ for (const dependency of component.dependencies) {
193
+ const to = nodeByComponent.get(dependency);
194
+ if (to) {
195
+ lines.push(` ${from} --> ${to}`);
196
+ continue;
197
+ }
198
+ let externalNode = externalNodeByDependency.get(dependency);
199
+ if (!externalNode) {
200
+ externalNode = createExternalMermaidNodeId(dependency);
201
+ externalNodeByDependency.set(dependency, externalNode);
202
+ lines.push(` ${externalNode}["${escapeMermaidText(dependency)}"]`);
203
+ }
204
+ lines.push(` ${from} --> ${externalNode}`);
205
+ }
206
+ }
207
+ const degradedNodes = [];
208
+ const notReadyNodes = [];
209
+ for (const component of snapshot.components) {
210
+ const nodeId = nodeByComponent.get(component.id);
211
+ if (!nodeId) {
212
+ continue;
213
+ }
214
+ if (component.readiness.status === 'degraded') {
215
+ degradedNodes.push(nodeId);
216
+ }
217
+ if (component.readiness.status === 'not-ready') {
218
+ notReadyNodes.push(nodeId);
219
+ }
220
+ }
221
+ if (degradedNodes.length > 0) {
222
+ lines.push(` class ${degradedNodes.join(',')} degraded`);
223
+ lines.push(' classDef degraded stroke:#f59e0b,stroke-width:2px');
224
+ }
225
+ if (notReadyNodes.length > 0) {
226
+ lines.push(` class ${notReadyNodes.join(',')} notReady`);
227
+ lines.push(' classDef notReady stroke:#ef4444,stroke-width:2px');
228
+ }
229
+ return lines.join('\n');
230
+ }
@@ -0,0 +1,2 @@
1
+ export { applyFilters, parseStudioPayload, renderMermaid, type FilterState, type ParsedPayload, type PlatformDiagnosticSeverity, type PlatformReadinessStatus, type StudioPayload, } from './contracts.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,0BAA0B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Fluo Studio</title>
7
+ <script type="module" crossorigin src="/assets/index-CUoyTA_q.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-BTqPuJus.css">
9
+ </head>
10
+ <body>
11
+ <div id="app"></div>
12
+ </body>
13
+ </html>
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { applyFilters, parseStudioPayload, renderMermaid } from './contracts.js';
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@fluojs/studio",
3
+ "description": "File-first diagnostics viewer for Fluo runtime platform snapshot and timing JSON exports.",
4
+ "keywords": [
5
+ "fluo",
6
+ "studio",
7
+ "diagnostics",
8
+ "viewer",
9
+ "module-graph",
10
+ "devtools"
11
+ ],
12
+ "version": "1.0.0-beta.1",
13
+ "private": false,
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/fluojs/fluo.git",
18
+ "directory": "packages/studio"
19
+ },
20
+ "engines": {
21
+ "node": ">=20.0.0"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./contracts": {
33
+ "types": "./dist/contracts.d.ts",
34
+ "import": "./dist/contracts.js"
35
+ },
36
+ "./viewer": "./dist/index.html"
37
+ },
38
+ "main": "./dist/index.js",
39
+ "types": "./dist/index.d.ts",
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "dependencies": {
44
+ "@fluojs/runtime": "^1.0.0-beta.1"
45
+ },
46
+ "devDependencies": {
47
+ "vitest": "^3.2.4",
48
+ "@fluojs-internal/tooling-vite": "^0.0.0"
49
+ },
50
+ "scripts": {
51
+ "prebuild": "node ../../tooling/scripts/clean-dist.mjs",
52
+ "build": "pnpm exec vite build && pnpm exec babel src/contracts.ts src/index.ts --extensions .ts --out-dir dist --config-file ../../tooling/babel/babel.config.cjs && pnpm exec tsc -p tsconfig.build.json",
53
+ "dev": "pnpm exec vite",
54
+ "preview": "pnpm exec vite preview",
55
+ "typecheck": "pnpm exec tsc -p tsconfig.json --noEmit",
56
+ "test": "pnpm exec vitest run -c vitest.config.ts",
57
+ "test:watch": "pnpm exec vitest -c vitest.config.ts"
58
+ }
59
+ }