feat: expose recompute diagnostic repair tree
This commit is contained in:
22
src/App.tsx
22
src/App.tsx
@@ -54,7 +54,7 @@ import {
|
||||
ZoomOut,
|
||||
} from 'lucide-react'
|
||||
import { menuDefinitions, pinnedWorkbenches, workbenchDefinitions, type MenuName, type WorkbenchId } from './freecadManifest'
|
||||
import { createMockFacade, type BitBybitViewportAdapter, type BitBybitWebCadFacade, type DocumentSnapshot, type FcstdInspection, type ModelTreeItem, type ObjectPropertySnapshot, type ProjectSummary, type PropertyValue, type ShapeHandle } from './facade'
|
||||
import { createMockFacade, type BitBybitViewportAdapter, type BitBybitWebCadFacade, type DiagnosticTreeNode, type DocumentSnapshot, type FcstdInspection, type ModelTreeItem, type ObjectPropertySnapshot, type ProjectSummary, type PropertyValue, type ShapeHandle } from './facade'
|
||||
|
||||
type Page = 'start' | 'projects' | 'workspace' | 'import' | 'export' | 'settings' | 'help' | 'diagnostics' | 'sync'
|
||||
type Workbench = WorkbenchId
|
||||
@@ -586,6 +586,7 @@ function DiagnosticsPage({ onNavigate, showNotice, facade }: { onNavigate: (page
|
||||
const [geometry, setGeometry] = useState(() => facade.geometry.capabilities())
|
||||
const document = facade.getState().document
|
||||
const persistence = facade.project.capabilities()
|
||||
const diagnosticTree = facade.diagnostics.tree()
|
||||
const geometryTone = geometry.status === 'ready' ? 'green' : geometry.status === 'failed' || geometry.status === 'unavailable' ? 'amber' : 'cyan'
|
||||
const geometryValue = geometry.status === 'ready' ? 'Ready' : geometry.status === 'initializing' ? 'Loading' : geometry.status
|
||||
useEffect(() => { void facade.project.recovery(facade.getState().document.id).then(setRecovery).catch((error: unknown) => showNotice(`Recovery report unavailable: ${error instanceof Error ? error.message : String(error)}`)) }, [facade, showNotice])
|
||||
@@ -594,7 +595,24 @@ function DiagnosticsPage({ onNavigate, showNotice, facade }: { onNavigate: (page
|
||||
void facade.geometry.initialize().then((capabilities) => { if (!disposed) setGeometry(capabilities) }).catch((error: unknown) => { if (!disposed) showNotice(`Geometry capability probe failed: ${error instanceof Error ? error.message : String(error)}`) })
|
||||
return () => { disposed = true }
|
||||
}, [facade, showNotice])
|
||||
return <div className="diagnostics-page"><PageHeader eyebrow="System status" title="Runtime checks." description="A concise view of browser capabilities, local storage and the current document runtime." onBack={() => onNavigate('start')} actions={<button className="button button-outline" onClick={() => showNotice('Diagnostic package prepared')}><Download size={16} />Export report</button>} /><div className="health-grid"><HealthCard label="WebAssembly" value={geometryValue} detail={geometry.reason || 'BitBybit OCCT package loads on demand'} tone={geometryTone} icon={Code2} /><HealthCard label="Local storage" value={persistence.mode === 'sqlite-opfs' ? 'SQLite + OPFS' : persistence.mode === 'sqlite-memory' ? 'Memory fallback' : 'Unavailable'} detail={`Schema v${persistence.schemaVersion || 'n/a'} · ${persistence.crossTabWriteLock || 'local queue'}`} tone={persistence.mode === 'unavailable' ? 'amber' : 'cyan'} icon={HardDrive} /><HealthCard label="Viewport" value="WebGL2" detail="Three.js 0.185.1" tone="cyan" icon={Rotate3D} /><HealthCard label="Document" value={`${document.recompute?.errors.length || 0} warnings`} detail={`${document.label} · v${document.version}`} tone={document.recompute?.errors.length ? 'amber' : 'green'} icon={AlertTriangle} /></div><section className="diagnostic-table panel-surface"><div className="section-title"><div><span className="section-kicker">Runtime</span><h3>Capability checks</h3></div><span className="last-checked">Last checked just now</span></div><DiagnosticRow name="BitBybit Facade" value="Connected" detail="API v0.1 · single public entry" tone="green" /><DiagnosticRow name="Geometry Worker" value={geometryValue} detail={`${geometry.provider} ${geometry.version} · Box, Boolean, feature and export boundaries`} tone={geometryTone} /><DiagnosticRow name="SQLite WASM" value={persistence.sqliteWasm ? 'Worker ready' : 'Memory fallback'} detail={`Schema v${persistence.schemaVersion || 'n/a'} · ${persistence.reason || 'SQLite worker configured'}`} tone={persistence.sqliteWasm ? 'green' : 'cyan'} /><DiagnosticRow name="OPFS" value={persistence.opfs ? 'Available' : 'Fallback'} detail="Cross-origin isolation and browser OPFS capability" tone={persistence.opfs ? 'green' : 'cyan'} /><DiagnosticRow name="Recovery" value={recovery?.integrity || 'Checking'} detail={recovery ? `Last saved v${recovery.lastSavedVersion ?? 'none'}${recovery.warnings.length ? ` · ${recovery.warnings.length} warning(s)` : ''}` : 'Checking SQLite integrity and saved snapshot'} tone={recovery?.integrity === 'ok' ? 'green' : 'cyan'} /><DiagnosticRow name="FreeCAD baseline" value="1.1.1" detail="Compatibility manifest loaded; unsupported commands remain disabled" tone="cyan" /><DiagnosticRow name="Document warnings" value={String(document.recompute?.errors.length || 0)} detail="Recompute diagnostics from the current document" tone={document.recompute?.errors.length ? 'amber' : 'green'} onClick={() => showNotice('Document diagnostics opened')} /></section></div>
|
||||
return <div className="diagnostics-page">
|
||||
<PageHeader eyebrow="System status" title="Runtime checks." description="A concise view of browser capabilities, local storage and the current document runtime." onBack={() => onNavigate('start')} actions={<button className="button button-outline" onClick={() => showNotice('Diagnostic package prepared')}><Download size={16} />Export report</button>} />
|
||||
<div className="health-grid"><HealthCard label="WebAssembly" value={geometryValue} detail={geometry.reason || 'BitBybit OCCT package loads on demand'} tone={geometryTone} icon={Code2} /><HealthCard label="Local storage" value={persistence.mode === 'sqlite-opfs' ? 'SQLite + OPFS' : persistence.mode === 'sqlite-memory' ? 'Memory fallback' : 'Unavailable'} detail={`Schema v${persistence.schemaVersion || 'n/a'} · ${persistence.crossTabWriteLock || 'local queue'}`} tone={persistence.mode === 'unavailable' ? 'amber' : 'cyan'} icon={HardDrive} /><HealthCard label="Viewport" value="WebGL2" detail="Three.js 0.185.1" tone="cyan" icon={Rotate3D} /><HealthCard label="Document" value={`${document.recompute?.errors.length || 0} warnings`} detail={`${document.label} · v${document.version}`} tone={document.recompute?.errors.length ? 'amber' : 'green'} icon={AlertTriangle} /></div>
|
||||
<section className="diagnostic-table panel-surface"><div className="section-title"><div><span className="section-kicker">Runtime</span><h3>Capability checks</h3></div><span className="last-checked">Last checked just now</span></div><DiagnosticRow name="BitBybit Facade" value="Connected" detail="API v0.1 · single public entry" tone="green" /><DiagnosticRow name="Geometry Worker" value={geometryValue} detail={`${geometry.provider} ${geometry.version} · Box, Boolean, feature and export boundaries`} tone={geometryTone} /><DiagnosticRow name="SQLite WASM" value={persistence.sqliteWasm ? 'Worker ready' : 'Memory fallback'} detail={`Schema v${persistence.schemaVersion || 'n/a'} · ${persistence.reason || 'SQLite worker configured'}`} tone={persistence.sqliteWasm ? 'green' : 'cyan'} /><DiagnosticRow name="OPFS" value={persistence.opfs ? 'Available' : 'Fallback'} detail="Cross-origin isolation and browser OPFS capability" tone={persistence.opfs ? 'green' : 'cyan'} /><DiagnosticRow name="Recovery" value={recovery?.integrity || 'Checking'} detail={recovery ? `Last saved v${recovery.lastSavedVersion ?? 'none'}${recovery.warnings.length ? ` · ${recovery.warnings.length} warning(s)` : ''}` : 'Checking SQLite integrity and saved snapshot'} tone={recovery?.integrity === 'ok' ? 'green' : 'cyan'} /><DiagnosticRow name="FreeCAD baseline" value="1.1.1" detail="Compatibility manifest loaded; unsupported commands remain disabled" tone="cyan" /></section>
|
||||
<section className="diagnostic-tree-panel panel-surface"><div className="section-title"><div><span className="section-kicker">Document</span><h3>Diagnostic tree</h3></div><strong className={diagnosticTree.length ? 'icon-amber' : 'icon-green'}>{diagnosticTree.length} root causes</strong></div>{diagnosticTree.length === 0 ? <div className="diagnostic-empty"><CheckCircle2 size={16} />No active document diagnostics</div> : <div className="diagnostic-tree">{diagnosticTree.map((node) => <DocumentDiagnosticNode key={node.diagnostic.id} node={node} facade={facade} showNotice={showNotice} />)}</div>}</section>
|
||||
</div>
|
||||
}
|
||||
|
||||
function DocumentDiagnosticNode({ node, facade, showNotice, child = false }: { node: DiagnosticTreeNode; facade: BitBybitWebCadFacade; showNotice: (message: string) => void; child?: boolean }) {
|
||||
const diagnostic = node.diagnostic
|
||||
const runRepair = (actionId: 'select-object' | 'recompute-root' | 'suppress-root') => {
|
||||
void facade.diagnostics.repair(diagnostic.id, actionId).then((result) => showNotice(result.message))
|
||||
}
|
||||
return <div className={`document-diagnostic ${child ? 'is-child' : ''}`}>
|
||||
<div className="document-diagnostic-main"><span className={`diagnostic-severity ${diagnostic.severity}`}><AlertTriangle size={14} /></span><div><strong>{diagnostic.code}</strong><span>{diagnostic.message}</span><small>{diagnostic.objectId || diagnostic.source}{diagnostic.dependencyPath && diagnostic.dependencyPath.length > 1 ? ` · ${diagnostic.dependencyPath.join(' → ')}` : ''}{diagnostic.generation ? ` · generation ${diagnostic.generation}` : ''}</small></div></div>
|
||||
{diagnostic.repairActions?.length ? <div className="diagnostic-actions">{diagnostic.repairActions.map((action) => <button key={action.id} className="button button-quiet" disabled={!action.enabled} title={action.reason || action.label} onClick={() => runRepair(action.id)}>{action.id === 'select-object' ? <Search size={13} /> : action.id === 'recompute-root' ? <RefreshCw size={13} /> : <Pause size={13} />}{action.label}</button>)}</div> : null}
|
||||
{node.children.length > 0 ? <div className="diagnostic-children">{node.children.map((entry) => <DocumentDiagnosticNode key={entry.diagnostic.id} node={entry} facade={facade} showNotice={showNotice} child />)}</div> : null}
|
||||
</div>
|
||||
}
|
||||
|
||||
function HealthCard({ label, value, detail, tone, icon: HealthIcon }: { label: string; value: string; detail: string; tone: 'green' | 'cyan' | 'amber'; icon: Icon }) {
|
||||
|
||||
Reference in New Issue
Block a user