diff --git a/apps/dashboard/.gitignore b/apps/dashboard/.gitignore
new file mode 100644
index 00000000..361bf2de
--- /dev/null
+++ b/apps/dashboard/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+dist/
+.vite/
+coverage/
+*.tsbuildinfo
+.vite-dev.*.log
+tmp-screenshots/
+tmp-chrome-profile/
diff --git a/apps/dashboard/README.md b/apps/dashboard/README.md
new file mode 100644
index 00000000..d0b3daf7
--- /dev/null
+++ b/apps/dashboard/README.md
@@ -0,0 +1,77 @@
+# FlowMemory Dashboard V0
+
+Local operator/explorer app for inspecting FlowMemory V0 fixture output. It is intentionally fixture-backed and does not claim production live data, wallet support, token pricing, or hosted deployment.
+
+## Run Locally
+
+From this directory:
+
+```powershell
+npm install
+npm run dev
+```
+
+Default Vite URL:
+
+```text
+http://127.0.0.1:5173/
+```
+
+Useful checks:
+
+```powershell
+npm run typecheck
+npm test
+npm run build
+```
+
+## Data Boundary
+
+The canonical dashboard fixture lives at:
+
+```text
+fixtures/dashboard/flowmemory-dashboard-v0.json
+```
+
+The app loads its runtime copy from:
+
+```text
+apps/dashboard/public/data/flowmemory-dashboard-v0.json
+```
+
+The `dev` and `build` scripts run `npm run sync:fixtures`, which copies the canonical fixture into the Vite public data folder before the app starts or builds.
+
+Future generated local outputs should land under the fixture boundary first:
+
+```text
+fixtures/dashboard/generated/indexer-state.json
+fixtures/dashboard/generated/verifier-reports.json
+fixtures/dashboard/generated/devnet-state.json
+fixtures/dashboard/generated/hardware-heartbeats.json
+```
+
+The dashboard schema can then be updated to merge those generated files into `flowmemory-dashboard-v0.json` or replace the loader with a local API once that boundary exists.
+
+## Current Views
+
+- Overview
+- FlowPulse stream
+- Rootfields
+- Work lanes and receipts
+- Verifier reports
+- Devnet blocks
+- Hardware nodes
+- Alerts
+- Raw JSON inspector
+
+Every displayed record carries source subsystem, fixture/local origin, chain context, ID/hash, status, and last-updated metadata when available.
+
+## Status Vocabulary
+
+Dashboard V0 visually distinguishes:
+
+```text
+observed, pending, finalized, verified, unresolved, invalid, unsupported, reorged, offline, stale
+```
+
+These are app-facing display states for local fixture inspection. They should stay aligned with indexer and verifier terminology as those packages mature.
diff --git a/apps/dashboard/index.html b/apps/dashboard/index.html
new file mode 100644
index 00000000..f705964a
--- /dev/null
+++ b/apps/dashboard/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ FlowMemory Dashboard V0
+
+
+
+
+
+
diff --git a/apps/dashboard/package-lock.json b/apps/dashboard/package-lock.json
new file mode 100644
index 00000000..684afda4
--- /dev/null
+++ b/apps/dashboard/package-lock.json
@@ -0,0 +1,1486 @@
+{
+ "name": "@flowmemory/dashboard",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@flowmemory/dashboard",
+ "version": "0.0.0",
+ "dependencies": {
+ "lucide-react": "^0.468.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router-dom": "^6.28.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.18",
+ "@types/react-dom": "^18.3.5",
+ "@vitejs/plugin-react": "^6.0.1",
+ "typescript": "^5.6.3",
+ "vite": "^8.0.12",
+ "vitest": "^4.1.6"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
+ "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.129.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz",
+ "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.23.2",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
+ "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz",
+ "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz",
+ "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz",
+ "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz",
+ "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz",
+ "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz",
+ "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz",
+ "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz",
+ "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz",
+ "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz",
+ "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz",
+ "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz",
+ "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz",
+ "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz",
+ "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz",
+ "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.7",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
+ "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
+ "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "1.0.0-rc.7"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz",
+ "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz",
+ "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.6",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz",
+ "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz",
+ "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.6",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz",
+ "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz",
+ "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz",
+ "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.6",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.468.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz",
+ "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
+ "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
+ "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.2",
+ "react-router": "6.30.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz",
+ "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.129.0",
+ "@rolldown/pluginutils": "1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.0",
+ "@rolldown/binding-darwin-arm64": "1.0.0",
+ "@rolldown/binding-darwin-x64": "1.0.0",
+ "@rolldown/binding-freebsd-x64": "1.0.0",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0",
+ "@rolldown/binding-linux-x64-musl": "1.0.0",
+ "@rolldown/binding-openharmony-arm64": "1.0.0",
+ "@rolldown/binding-wasm32-wasi": "1.0.0",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0"
+ }
+ },
+ "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz",
+ "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
+ "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.0.12",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz",
+ "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.14",
+ "rolldown": "1.0.0",
+ "tinyglobby": "^0.2.16"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.18",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz",
+ "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.6",
+ "@vitest/mocker": "4.1.6",
+ "@vitest/pretty-format": "4.1.6",
+ "@vitest/runner": "4.1.6",
+ "@vitest/snapshot": "4.1.6",
+ "@vitest/spy": "4.1.6",
+ "@vitest/utils": "4.1.6",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.6",
+ "@vitest/browser-preview": "4.1.6",
+ "@vitest/browser-webdriverio": "4.1.6",
+ "@vitest/coverage-istanbul": "4.1.6",
+ "@vitest/coverage-v8": "4.1.6",
+ "@vitest/ui": "4.1.6",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ }
+ }
+}
diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json
new file mode 100644
index 00000000..cda2a83a
--- /dev/null
+++ b/apps/dashboard/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "@flowmemory/dashboard",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "npm run sync:fixtures && vite --host 127.0.0.1",
+ "build": "npm run sync:fixtures && tsc -b && vite build",
+ "test": "vitest run",
+ "typecheck": "tsc -b",
+ "sync:fixtures": "node scripts/sync-fixtures.mjs"
+ },
+ "dependencies": {
+ "lucide-react": "^0.468.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router-dom": "^6.28.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.18",
+ "@types/react-dom": "^18.3.5",
+ "@vitejs/plugin-react": "^6.0.1",
+ "typescript": "^5.6.3",
+ "vite": "^8.0.12",
+ "vitest": "^4.1.6"
+ }
+}
diff --git a/apps/dashboard/public/data/flowmemory-dashboard-v0.json b/apps/dashboard/public/data/flowmemory-dashboard-v0.json
new file mode 100644
index 00000000..dcd99f4b
--- /dev/null
+++ b/apps/dashboard/public/data/flowmemory-dashboard-v0.json
@@ -0,0 +1,802 @@
+{
+ "metadata": {
+ "schema": "flowmemory.dashboard.fixture.v0",
+ "generatedAt": "2026-05-13T16:00:00.000Z",
+ "mode": "fixture",
+ "description": "Synthetic local FlowMemory Dashboard V0 fixture. Values are operator/explorer test vectors only and do not claim live production data.",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "runtimeDataPath": "apps/dashboard/public/data/flowmemory-dashboard-v0.json",
+ "futureGeneratedPaths": {
+ "indexer": "fixtures/dashboard/generated/indexer-state.json",
+ "verifier": "fixtures/dashboard/generated/verifier-reports.json",
+ "devnet": "fixtures/dashboard/generated/devnet-state.json",
+ "hardware": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ },
+ "chain": {
+ "chainId": "31337",
+ "name": "Anvil local FlowMemory devnet",
+ "environment": "local-devnet",
+ "settlementContext": "Base-first project path; this fixture is not Base mainnet or Base Sepolia data.",
+ "currentBlock": 123482,
+ "finalizedBlock": 123450,
+ "source": "fixture",
+ "lastUpdated": "2026-05-13T15:58:42.000Z"
+ },
+ "flowPulseObservations": [
+ {
+ "id": "0x9717b7bad57bd0fa089e672e75cede4ae0bf3c86321dc0525ba00e9e0cc2da91",
+ "observationId": "0x9717b7bad57bd0fa089e672e75cede4ae0bf3c86321dc0525ba00e9e0cc2da91",
+ "pulseId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123456",
+ "blockHash": "0x2222222222222222222222222222222222222222222222222222222222222222",
+ "txHash": "0x3333333333333333333333333333333333333333333333333333333333333333",
+ "transactionIndex": "7",
+ "logIndex": "2",
+ "receiptStatus": "success",
+ "actor": "0x4444444444444444444444444444444444444444",
+ "pulseType": "1",
+ "subject": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "commitment": "0x4122209ff672fc04b2ec3af31ab1af79813971f86de000aa6534038cc79de6b5",
+ "parentPulseId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "sequence": "1",
+ "occurredAt": "2026-05-13T14:30:00.000Z",
+ "uri": "ipfs://bafy-flowmemory-example",
+ "summary": "Rootfield registration observed from fixture receipt log.",
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:51:00.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:51:00.000Z",
+ "localPathHint": "services/indexer/fixtures/flowpulse-logs.json"
+ }
+ },
+ {
+ "id": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "observationId": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "pulseId": "0xa3f9000000000000000000000000000000000000000000000000000000000112",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123468",
+ "blockHash": "0x2468246824682468246824682468246824682468246824682468246824682468",
+ "txHash": "0x5301530153015301530153015301530153015301530153015301530153015301",
+ "transactionIndex": "3",
+ "logIndex": "0",
+ "receiptStatus": "success",
+ "actor": "0x5555555555555555555555555555555555555555",
+ "pulseType": "2",
+ "subject": "0x6f6d65706174682d726f6f742d303030303030303030303030303030303031",
+ "commitment": "0x81d30d3de2b29b44f23745289dbf214026ed1789983ff7aac135d74531402031",
+ "parentPulseId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "sequence": "2",
+ "occurredAt": "2026-05-13T14:42:25.000Z",
+ "uri": "file://fixtures/rootfields/omega-path/root-0001.json",
+ "summary": "Root commitment finalized after local block threshold.",
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:53:12.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:53:12.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "observationId": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "pulseId": "0xa3f9000000000000000000000000000000000000000000000000000000000113",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123481",
+ "blockHash": "0x9595959595959595959595959595959595959595959595959595959595959595",
+ "txHash": "0x6060606060606060606060606060606060606060606060606060606060606060",
+ "transactionIndex": "1",
+ "logIndex": "1",
+ "receiptStatus": "success",
+ "actor": "0x5555555555555555555555555555555555555555",
+ "pulseType": "2",
+ "subject": "0x6f6d65706174682d726f6f742d303030303030303030303030303030303032",
+ "commitment": "0x61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff",
+ "parentPulseId": "0xa3f9000000000000000000000000000000000000000000000000000000000112",
+ "sequence": "3",
+ "occurredAt": "2026-05-13T15:55:28.000Z",
+ "uri": "file://fixtures/rootfields/omega-path/root-0002.json",
+ "summary": "Recent root commitment observed below finality threshold.",
+ "status": "observed",
+ "lastUpdated": "2026-05-13T15:56:02.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:02.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0x7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e",
+ "observationId": "0x7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e",
+ "pulseId": "0xc0ffee0000000000000000000000000000000000000000000000000000000417",
+ "rootfieldId": "0xdededededededededededededededededededededededededededededededede",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123449",
+ "blockHash": "0x1717171717171717171717171717171717171717171717171717171717171717",
+ "txHash": "0x1818181818181818181818181818181818181818181818181818181818181818",
+ "transactionIndex": "9",
+ "logIndex": "3",
+ "receiptStatus": "success",
+ "actor": "0x9999999999999999999999999999999999999999",
+ "pulseType": "9",
+ "subject": "0xdededededededededededededededededededededededededededededededede",
+ "commitment": "0x9090909090909090909090909090909090909090909090909090909090909090",
+ "parentPulseId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "sequence": "1",
+ "occurredAt": "2026-05-13T13:12:14.000Z",
+ "uri": "file://fixtures/rootfields/unsupported-pulse.json",
+ "summary": "Unsupported pulse type preserved for verifier policy review.",
+ "status": "unsupported",
+ "lastUpdated": "2026-05-13T15:18:00.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:18:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0xabababababababababababababababababababababababababababababababab",
+ "observationId": "0xabababababababababababababababababababababababababababababababab",
+ "pulseId": "0xa3f90000000000000000000000000000000000000000000000000000000000ff",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123438",
+ "blockHash": "0xffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000",
+ "txHash": "0x4242424242424242424242424242424242424242424242424242424242424242",
+ "transactionIndex": "2",
+ "logIndex": "4",
+ "receiptStatus": "success",
+ "actor": "0x5555555555555555555555555555555555555555",
+ "pulseType": "2",
+ "subject": "0x72656f72672d726f6f742d3030303030303030303030303030303030303030",
+ "commitment": "0xe5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5",
+ "parentPulseId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "sequence": "2",
+ "occurredAt": "2026-05-13T12:07:44.000Z",
+ "uri": "file://fixtures/rootfields/omega-path/reorged-root.json",
+ "summary": "Observation retained as noncanonical after local reorg fixture.",
+ "status": "reorged",
+ "lastUpdated": "2026-05-13T12:31:10.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T12:31:10.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "observationId": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "pulseId": "0x1212121212121212121212121212121212121212121212121212121212121212",
+ "rootfieldId": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123482",
+ "blockHash": "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
+ "txHash": "0xb1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1",
+ "transactionIndex": "0",
+ "logIndex": "0",
+ "receiptStatus": "unknown",
+ "actor": "0x7777777777777777777777777777777777777777",
+ "pulseType": "2",
+ "subject": "0x70656e64696e672d726f6f742d3030303030303030303030303030303030",
+ "commitment": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
+ "parentPulseId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "sequence": "0",
+ "occurredAt": "2026-05-13T15:58:20.000Z",
+ "uri": "file://fixtures/rootfields/pending-root.json",
+ "summary": "Pending candidate from local scan window before receipt finality.",
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ }
+ ],
+ "rootfields": [
+ {
+ "id": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "owner": "0x4444444444444444444444444444444444444444",
+ "schemaHash": "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
+ "metadataHash": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+ "latestRoot": "0x6f6d65706174682d726f6f742d303030303030303030303030303030303032",
+ "latestObservationId": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "pulseCount": 4,
+ "workLaneIds": ["lane-indexer-a", "lane-verifier-a"],
+ "evidenceUri": "file://fixtures/rootfields/omega-path/",
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:56:02.000Z",
+ "provenance": {
+ "subsystem": "contracts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:02.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0xdededededededededededededededededededededededededededededededede",
+ "rootfieldId": "0xdededededededededededededededededededededededededededededededede",
+ "owner": "0x9999999999999999999999999999999999999999",
+ "schemaHash": "0x1919191919191919191919191919191919191919191919191919191919191919",
+ "metadataHash": "0x2929292929292929292929292929292929292929292929292929292929292929",
+ "latestRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "latestObservationId": "0x7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e",
+ "pulseCount": 1,
+ "workLaneIds": ["lane-verifier-b"],
+ "evidenceUri": "file://fixtures/rootfields/unsupported/",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T15:18:00.000Z",
+ "provenance": {
+ "subsystem": "contracts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:18:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "rootfieldId": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "owner": "0x7777777777777777777777777777777777777777",
+ "schemaHash": "0x3838383838383838383838383838383838383838383838383838383838383838",
+ "metadataHash": "0x3939393939393939393939393939393939393939393939393939393939393939",
+ "latestRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "latestObservationId": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "pulseCount": 0,
+ "workLaneIds": ["lane-indexer-a"],
+ "evidenceUri": "file://fixtures/rootfields/pending-root.json",
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "contracts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ }
+ ],
+ "workLanes": [
+ {
+ "id": "lane-indexer-a",
+ "laneId": "lane-indexer-a",
+ "name": "Indexer receipt ingest",
+ "queueDepth": 3,
+ "inflight": 1,
+ "completed24h": 37,
+ "p95LatencyMs": 840,
+ "operator": "local-codex-fixture",
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "lane-verifier-a",
+ "laneId": "lane-verifier-a",
+ "name": "Verifier policy v0",
+ "queueDepth": 1,
+ "inflight": 1,
+ "completed24h": 22,
+ "p95LatencyMs": 1280,
+ "operator": "local-codex-fixture",
+ "status": "observed",
+ "lastUpdated": "2026-05-13T15:56:37.000Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:37.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "lane-verifier-b",
+ "laneId": "lane-verifier-b",
+ "name": "Unsupported pulse review",
+ "queueDepth": 0,
+ "inflight": 0,
+ "completed24h": 4,
+ "p95LatencyMs": 2120,
+ "operator": "local-codex-fixture",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T15:19:05.000Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:19:05.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ }
+ ],
+ "workReceipts": [
+ {
+ "id": "0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f",
+ "receiptId": "0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f",
+ "laneId": "lane-verifier-a",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "observationId": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "reportId": "0x8080808080808080808080808080808080808080808080808080808080808080",
+ "workType": "verify-root-commitment",
+ "artifactUri": "file://fixtures/rootfields/omega-path/root-0001.json",
+ "startedAt": "2026-05-13T14:43:00.000Z",
+ "completedAt": "2026-05-13T14:43:01.120Z",
+ "resultHash": "0x7100710071007100710071007100710071007100710071007100710071007100",
+ "status": "verified",
+ "lastUpdated": "2026-05-13T14:43:01.120Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T14:43:01.120Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x0101010101010101010101010101010101010101010101010101010101010101",
+ "receiptId": "0x0101010101010101010101010101010101010101010101010101010101010101",
+ "laneId": "lane-indexer-a",
+ "rootfieldId": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "observationId": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "workType": "scan-receipt-window",
+ "artifactUri": "file://fixtures/devnet/block-123482.json",
+ "startedAt": "2026-05-13T15:58:20.000Z",
+ "resultHash": "0x1111110000001111110000001111110000001111110000001111110000001111",
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0x5656565656565656565656565656565656565656565656565656565656565656",
+ "receiptId": "0x5656565656565656565656565656565656565656565656565656565656565656",
+ "laneId": "lane-verifier-a",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "observationId": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "reportId": "0x5959595959595959595959595959595959595959595959595959595959595959",
+ "workType": "verify-root-commitment",
+ "artifactUri": "file://fixtures/rootfields/omega-path/root-0002.json",
+ "startedAt": "2026-05-13T15:56:04.000Z",
+ "completedAt": "2026-05-13T15:56:04.640Z",
+ "resultHash": "0x6161616161616161616161616161616161616161616161616161616161616161",
+ "status": "invalid",
+ "lastUpdated": "2026-05-13T15:56:04.640Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:04.640Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ }
+ ],
+ "verifierReports": [
+ {
+ "id": "0xe0ac708c933782ef6797b3ab4b6550cdd1a993fb1170361e5e05641ab801e66e",
+ "reportId": "0xe0ac708c933782ef6797b3ab4b6550cdd1a993fb1170361e5e05641ab801e66e",
+ "observationId": "0x9717b7bad57bd0fa089e672e75cede4ae0bf3c86321dc0525ba00e9e0cc2da91",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 3,
+ "checksTotal": 3,
+ "reasonCodes": [],
+ "reportHash": "0xe0ac708c933782ef6797b3ab4b6550cdd1a993fb1170361e5e05641ab801e66e",
+ "status": "verified",
+ "lastUpdated": "2026-05-13T15:51:31.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:51:31.000Z",
+ "localPathHint": "services/verifier/fixtures/artifacts.json"
+ }
+ },
+ {
+ "id": "0x8080808080808080808080808080808080808080808080808080808080808080",
+ "reportId": "0x8080808080808080808080808080808080808080808080808080808080808080",
+ "observationId": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 4,
+ "checksTotal": 4,
+ "reasonCodes": [],
+ "reportHash": "0x8080808080808080808080808080808080808080808080808080808080808080",
+ "status": "verified",
+ "lastUpdated": "2026-05-13T14:43:01.120Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T14:43:01.120Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x5959595959595959595959595959595959595959595959595959595959595959",
+ "reportId": "0x5959595959595959595959595959595959595959595959595959595959595959",
+ "observationId": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 3,
+ "checksTotal": 4,
+ "reasonCodes": ["commitment.mismatch"],
+ "reportHash": "0x5959595959595959595959595959595959595959595959595959595959595959",
+ "status": "invalid",
+ "lastUpdated": "2026-05-13T15:56:04.640Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:04.640Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x6464646464646464646464646464646464646464646464646464646464646464",
+ "reportId": "0x6464646464646464646464646464646464646464646464646464646464646464",
+ "observationId": "0x7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e",
+ "rootfieldId": "0xdededededededededededededededededededededededededededededededede",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 1,
+ "checksTotal": 1,
+ "reasonCodes": ["pulse.type.unsupported"],
+ "reportHash": "0x6464646464646464646464646464646464646464646464646464646464646464",
+ "status": "unsupported",
+ "lastUpdated": "2026-05-13T15:18:11.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:18:11.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x7373737373737373737373737373737373737373737373737373737373737373",
+ "reportId": "0x7373737373737373737373737373737373737373737373737373737373737373",
+ "observationId": "0xabababababababababababababababababababababababababababababababab",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 1,
+ "checksTotal": 1,
+ "reasonCodes": ["observation.reorged"],
+ "reportHash": "0x7373737373737373737373737373737373737373737373737373737373737373",
+ "status": "reorged",
+ "lastUpdated": "2026-05-13T12:31:15.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T12:31:15.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x7474747474747474747474747474747474747474747474747474747474747474",
+ "reportId": "0x7474747474747474747474747474747474747474747474747474747474747474",
+ "observationId": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "rootfieldId": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 1,
+ "checksTotal": 2,
+ "reasonCodes": ["artifact.unavailable"],
+ "reportHash": "0x7474747474747474747474747474747474747474747474747474747474747474",
+ "status": "unresolved",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x7575757575757575757575757575757575757575757575757575757575757575",
+ "reportId": "0x7575757575757575757575757575757575757575757575757575757575757575",
+ "observationId": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.previous-fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 2,
+ "checksTotal": 4,
+ "reasonCodes": ["report.stale_policy"],
+ "reportHash": "0x7575757575757575757575757575757575757575757575757575757575757575",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T14:12:00.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T14:12:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ }
+ ],
+ "devnetBlocks": [
+ {
+ "id": "0x1717171717171717171717171717171717171717171717171717171717171717",
+ "blockNumber": 123449,
+ "blockHash": "0x1717171717171717171717171717171717171717171717171717171717171717",
+ "parentHash": "0x1616161616161616161616161616161616161616161616161616161616161616",
+ "stateRoot": "0x5050505050505050505050505050505050505050505050505050505050505050",
+ "receiptsRoot": "0x5151515151515151515151515151515151515151515151515151515151515151",
+ "timestamp": "2026-05-13T13:12:14.000Z",
+ "observationCount": 1,
+ "reportCount": 1,
+ "finalityDistance": 33,
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:45:00.000Z",
+ "provenance": {
+ "subsystem": "devnet",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:45:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0x2468246824682468246824682468246824682468246824682468246824682468",
+ "blockNumber": 123468,
+ "blockHash": "0x2468246824682468246824682468246824682468246824682468246824682468",
+ "parentHash": "0x2358235823582358235823582358235823582358235823582358235823582358",
+ "stateRoot": "0x6262626262626262626262626262626262626262626262626262626262626262",
+ "receiptsRoot": "0x6363636363636363636363636363636363636363636363636363636363636363",
+ "timestamp": "2026-05-13T14:42:25.000Z",
+ "observationCount": 1,
+ "reportCount": 1,
+ "finalityDistance": 14,
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:45:00.000Z",
+ "provenance": {
+ "subsystem": "devnet",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:45:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0x9595959595959595959595959595959595959595959595959595959595959595",
+ "blockNumber": 123481,
+ "blockHash": "0x9595959595959595959595959595959595959595959595959595959595959595",
+ "parentHash": "0x9494949494949494949494949494949494949494949494949494949494949494",
+ "stateRoot": "0x7070707070707070707070707070707070707070707070707070707070707070",
+ "receiptsRoot": "0x7171717171717171717171717171717171717171717171717171717171717171",
+ "timestamp": "2026-05-13T15:55:28.000Z",
+ "observationCount": 1,
+ "reportCount": 1,
+ "finalityDistance": 1,
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "devnet",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0xffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000",
+ "blockNumber": 123438,
+ "blockHash": "0xffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000",
+ "parentHash": "0x4343434343434343434343434343434343434343434343434343434343434343",
+ "stateRoot": "0x8888000088880000888800008888000088880000888800008888000088880000",
+ "receiptsRoot": "0x9999000099990000999900009999000099990000999900009999000099990000",
+ "timestamp": "2026-05-13T12:07:44.000Z",
+ "observationCount": 1,
+ "reportCount": 1,
+ "finalityDistance": 0,
+ "status": "reorged",
+ "lastUpdated": "2026-05-13T12:31:10.000Z",
+ "provenance": {
+ "subsystem": "devnet",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T12:31:10.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ }
+ ],
+ "hardwareNodes": [
+ {
+ "id": "fm-node-lab-01",
+ "nodeId": "fm-node-lab-01",
+ "callsign": "Lab Gateway 01",
+ "role": "gateway",
+ "firmware": "flowrouter-v0.1.0-fixture",
+ "transport": "ethernet + meshtastic sidecar",
+ "lastHeartbeatAt": "2026-05-13T15:57:58.000Z",
+ "batteryPercent": 100,
+ "signalDbm": -51,
+ "temperatureC": 39,
+ "linkedWorkLaneId": "lane-indexer-a",
+ "locationHint": "bench-east",
+ "status": "observed",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "hardware",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ },
+ {
+ "id": "fm-node-router-03",
+ "nodeId": "fm-node-router-03",
+ "callsign": "Router Shelf 03",
+ "role": "router",
+ "firmware": "flowrouter-v0.1.0-fixture",
+ "transport": "wifi + lora control",
+ "lastHeartbeatAt": "2026-05-13T15:11:08.000Z",
+ "batteryPercent": 72,
+ "signalDbm": -88,
+ "temperatureC": 44,
+ "linkedWorkLaneId": "lane-verifier-b",
+ "locationHint": "workshop-shelf",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T15:11:08.000Z",
+ "provenance": {
+ "subsystem": "hardware",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:11:08.000Z",
+ "localPathHint": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ },
+ {
+ "id": "fm-node-field-07",
+ "nodeId": "fm-node-field-07",
+ "callsign": "Field Kit 07",
+ "role": "field-kit",
+ "firmware": "flowrouter-v0.1.0-fixture",
+ "transport": "meshtastic control only",
+ "locationHint": "packed-case",
+ "status": "offline",
+ "lastUpdated": "2026-05-13T14:20:00.000Z",
+ "provenance": {
+ "subsystem": "hardware",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T14:20:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ }
+ ],
+ "alerts": [
+ {
+ "id": "incident-2026-05-13-001",
+ "incidentId": "incident-2026-05-13-001",
+ "severity": "critical",
+ "title": "Verifier commitment mismatch",
+ "summary": "Report 0x5959 records a deterministic commitment mismatch for the latest omega-path root fixture.",
+ "openedAt": "2026-05-13T15:56:04.640Z",
+ "linkedObjectIds": [
+ "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "0x5959595959595959595959595959595959595959595959595959595959595959"
+ ],
+ "recommendedAction": "Compare the generated root artifact with the emitted commitment before promoting this fixture to verified.",
+ "status": "unresolved",
+ "lastUpdated": "2026-05-13T15:56:04.640Z",
+ "provenance": {
+ "subsystem": "alerts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:04.640Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "incident-2026-05-13-002",
+ "incidentId": "incident-2026-05-13-002",
+ "severity": "warning",
+ "title": "Hardware heartbeat stale",
+ "summary": "Router Shelf 03 has not reported a fresh heartbeat inside the fixture freshness window.",
+ "openedAt": "2026-05-13T15:41:00.000Z",
+ "linkedObjectIds": ["fm-node-router-03", "lane-verifier-b"],
+ "recommendedAction": "Check local power and radio sidecar telemetry before assigning verifier work to the node.",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T15:41:00.000Z",
+ "provenance": {
+ "subsystem": "alerts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:41:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ },
+ {
+ "id": "incident-2026-05-13-003",
+ "incidentId": "incident-2026-05-13-003",
+ "severity": "info",
+ "title": "Reorg replacement recorded",
+ "summary": "A noncanonical block and observation remain in the fixture so the explorer can show reorg history.",
+ "openedAt": "2026-05-13T12:31:10.000Z",
+ "linkedObjectIds": [
+ "0xffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000",
+ "0xabababababababababababababababababababababababababababababababab"
+ ],
+ "recommendedAction": "Keep the reorged records visible, but exclude them from current verified root summaries.",
+ "status": "verified",
+ "lastUpdated": "2026-05-13T13:02:00.000Z",
+ "provenance": {
+ "subsystem": "alerts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T13:02:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ }
+ ]
+}
diff --git a/apps/dashboard/scripts/sync-fixtures.mjs b/apps/dashboard/scripts/sync-fixtures.mjs
new file mode 100644
index 00000000..46a84a45
--- /dev/null
+++ b/apps/dashboard/scripts/sync-fixtures.mjs
@@ -0,0 +1,14 @@
+import { copyFileSync, mkdirSync } from "node:fs";
+import { dirname, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const scriptDir = dirname(fileURLToPath(import.meta.url));
+const repoRoot = resolve(scriptDir, "../../..");
+const source = resolve(repoRoot, "fixtures/dashboard/flowmemory-dashboard-v0.json");
+const destinationDir = resolve(repoRoot, "apps/dashboard/public/data");
+const destination = resolve(destinationDir, "flowmemory-dashboard-v0.json");
+
+mkdirSync(destinationDir, { recursive: true });
+copyFileSync(source, destination);
+
+console.log(`Synced dashboard fixture: ${destination}`);
diff --git a/apps/dashboard/src/App.tsx b/apps/dashboard/src/App.tsx
new file mode 100644
index 00000000..df5df928
--- /dev/null
+++ b/apps/dashboard/src/App.tsx
@@ -0,0 +1,101 @@
+import { useEffect, useState } from "react";
+import { Route, Routes } from "react-router-dom";
+import { AlertTriangle, RefreshCw } from "lucide-react";
+import { AppShell } from "./components/AppShell";
+import { fetchDashboardData } from "./data/loadDashboardData";
+import type { DashboardData } from "./data/types";
+import { AlertsView } from "./views/AlertsView";
+import { DevnetBlocksView } from "./views/DevnetBlocksView";
+import { FlowPulseStreamView } from "./views/FlowPulseStreamView";
+import { HardwareNodesView } from "./views/HardwareNodesView";
+import { OverviewView } from "./views/OverviewView";
+import { RawJsonInspectorView } from "./views/RawJsonInspectorView";
+import { RootfieldsView } from "./views/RootfieldsView";
+import { VerifierReportsView } from "./views/VerifierReportsView";
+import { WorkReceiptsView } from "./views/WorkReceiptsView";
+
+function LoadingState() {
+ return (
+
+ );
+}
+
+function ErrorState({ message, onRetry }: { message: string; onRetry: () => void }) {
+ return (
+
+
+
+
+
Dashboard fixture failed to load
+
{message}
+
+
+ Retry fixture load
+
+
+
+
+ );
+}
+
+export default function App() {
+ const [data, setData] = useState(null);
+ const [error, setError] = useState(null);
+ const [version, setVersion] = useState(0);
+
+ useEffect(() => {
+ let cancelled = false;
+
+ fetchDashboardData()
+ .then((nextData) => {
+ if (!cancelled) {
+ setData(nextData);
+ setError(null);
+ }
+ })
+ .catch((nextError: unknown) => {
+ if (!cancelled) {
+ setError(nextError instanceof Error ? nextError.message : "Unknown fixture load error.");
+ }
+ });
+
+ return () => {
+ cancelled = true;
+ };
+ }, [version]);
+
+ if (error) {
+ return setVersion((current) => current + 1)} />;
+ }
+
+ if (data === null) {
+ return ;
+ }
+
+ return (
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+ );
+}
diff --git a/apps/dashboard/src/components/AppShell.tsx b/apps/dashboard/src/components/AppShell.tsx
new file mode 100644
index 00000000..fb354c16
--- /dev/null
+++ b/apps/dashboard/src/components/AppShell.tsx
@@ -0,0 +1,86 @@
+import type { ReactNode } from "react";
+import { NavLink } from "react-router-dom";
+import {
+ Activity,
+ Bell,
+ Binary,
+ Braces,
+ Boxes,
+ ClipboardCheck,
+ LayoutDashboard,
+ Network,
+ RadioTower,
+ ShieldCheck,
+} from "lucide-react";
+import type { DashboardData } from "../data/types";
+import { StatusBadge } from "./StatusBadge";
+
+interface AppShellProps {
+ data: DashboardData;
+ children: ReactNode;
+}
+
+const NAV_ITEMS = [
+ { to: "/", label: "Overview", icon: LayoutDashboard },
+ { to: "/flowpulse", label: "FlowPulse", icon: Activity },
+ { to: "/rootfields", label: "Rootfields", icon: Boxes },
+ { to: "/work", label: "Work lanes", icon: ClipboardCheck },
+ { to: "/verifier", label: "Verifier", icon: ShieldCheck },
+ { to: "/devnet", label: "Devnet", icon: Network },
+ { to: "/hardware", label: "Hardware", icon: RadioTower },
+ { to: "/alerts", label: "Alerts", icon: Bell },
+ { to: "/raw", label: "Raw JSON", icon: Braces },
+];
+
+export function AppShell({ data, children }: AppShellProps) {
+ return (
+
+
+
+
+
+
+
+ FlowMemory
+ Dashboard V0
+
+
+
+ {NAV_ITEMS.map(({ to, label, icon: Icon }) => (
+
+
+ {label}
+
+ ))}
+
+
+
+ {data.metadata.mode} data
+ {data.chain.name}
+
+
+
+
+
+
+ local operator surface
+
{data.chain.name}
+
+
+ chain {data.chain.chainId}
+ {data.chain.source}
+ updated {new Date(data.chain.lastUpdated).toLocaleString()}
+
+
+
+ Fixture/local data only.
+
+ Runtime JSON is loaded from {data.metadata.runtimeDataPath}; future generated outputs should land in the
+ documented fixture boundary before becoming live APIs.
+
+
+
{children}
+
+
+ );
+}
diff --git a/apps/dashboard/src/components/EmptyState.tsx b/apps/dashboard/src/components/EmptyState.tsx
new file mode 100644
index 00000000..13f6f5e7
--- /dev/null
+++ b/apps/dashboard/src/components/EmptyState.tsx
@@ -0,0 +1,18 @@
+import { CircleDashed } from "lucide-react";
+
+interface EmptyStateProps {
+ title: string;
+ detail: string;
+}
+
+export function EmptyState({ title, detail }: EmptyStateProps) {
+ return (
+
+ );
+}
diff --git a/apps/dashboard/src/components/HashValue.tsx b/apps/dashboard/src/components/HashValue.tsx
new file mode 100644
index 00000000..f4ce7d2e
--- /dev/null
+++ b/apps/dashboard/src/components/HashValue.tsx
@@ -0,0 +1,23 @@
+interface HashValueProps {
+ value: string;
+ label?: string;
+ trim?: "short" | "medium" | "none";
+}
+
+function formatHash(value: string, trim: HashValueProps["trim"]): string {
+ if (trim === "none" || value.length <= 18) {
+ return value;
+ }
+
+ const head = trim === "short" ? 8 : 12;
+ const tail = trim === "short" ? 6 : 10;
+ return `${value.slice(0, head)}...${value.slice(-tail)}`;
+}
+
+export function HashValue({ value, label, trim = "medium" }: HashValueProps) {
+ return (
+
+ {formatHash(value, trim)}
+
+ );
+}
diff --git a/apps/dashboard/src/components/ProvenanceLine.tsx b/apps/dashboard/src/components/ProvenanceLine.tsx
new file mode 100644
index 00000000..1a608236
--- /dev/null
+++ b/apps/dashboard/src/components/ProvenanceLine.tsx
@@ -0,0 +1,41 @@
+import { Database, HardDrive, Radio, ServerCog } from "lucide-react";
+import type { Provenance } from "../data/types";
+
+const ORIGIN_LABELS: Record = {
+ fixture: "fixture",
+ local: "local",
+ live: "live",
+};
+
+function OriginIcon({ origin }: { origin: Provenance["origin"] }) {
+ if (origin === "fixture") {
+ return ;
+ }
+ if (origin === "local") {
+ return ;
+ }
+ return ;
+}
+
+export function ProvenanceLine({ provenance, lastUpdated }: { provenance: Provenance; lastUpdated?: string }) {
+ return (
+
+
+
+ {provenance.subsystem}
+
+
+
+ {ORIGIN_LABELS[provenance.origin]}
+
+
+ {provenance.chainContext}
+
+ {lastUpdated ? (
+
+ {new Date(lastUpdated).toLocaleString()}
+
+ ) : null}
+
+ );
+}
diff --git a/apps/dashboard/src/components/SectionHeader.tsx b/apps/dashboard/src/components/SectionHeader.tsx
new file mode 100644
index 00000000..9d1036fd
--- /dev/null
+++ b/apps/dashboard/src/components/SectionHeader.tsx
@@ -0,0 +1,21 @@
+import type { ReactNode } from "react";
+
+interface SectionHeaderProps {
+ eyebrow: string;
+ title: string;
+ detail: string;
+ action?: ReactNode;
+}
+
+export function SectionHeader({ eyebrow, title, detail, action }: SectionHeaderProps) {
+ return (
+
+
+
{eyebrow}
+
{title}
+
{detail}
+
+ {action ? {action}
: null}
+
+ );
+}
diff --git a/apps/dashboard/src/components/StatusBadge.tsx b/apps/dashboard/src/components/StatusBadge.tsx
new file mode 100644
index 00000000..ddfa7e9e
--- /dev/null
+++ b/apps/dashboard/src/components/StatusBadge.tsx
@@ -0,0 +1,16 @@
+import { STATUS_LABELS, statusClassName } from "../data/status";
+import type { DashboardStatus } from "../data/types";
+
+interface StatusBadgeProps {
+ status: DashboardStatus;
+ compact?: boolean;
+}
+
+export function StatusBadge({ status, compact = false }: StatusBadgeProps) {
+ return (
+
+
+ {compact ? status : STATUS_LABELS[status]}
+
+ );
+}
diff --git a/apps/dashboard/src/data/format.ts b/apps/dashboard/src/data/format.ts
new file mode 100644
index 00000000..d34b0109
--- /dev/null
+++ b/apps/dashboard/src/data/format.ts
@@ -0,0 +1,20 @@
+export function formatDateTime(value?: string): string {
+ if (!value) {
+ return "not recorded";
+ }
+ return new Date(value).toLocaleString();
+}
+
+export function formatMs(value: number): string {
+ if (value >= 1000) {
+ return `${(value / 1000).toFixed(1)}s`;
+ }
+ return `${value}ms`;
+}
+
+export function formatPercent(value?: number): string {
+ if (value === undefined) {
+ return "n/a";
+ }
+ return `${value}%`;
+}
diff --git a/apps/dashboard/src/data/loadDashboardData.ts b/apps/dashboard/src/data/loadDashboardData.ts
new file mode 100644
index 00000000..55790b98
--- /dev/null
+++ b/apps/dashboard/src/data/loadDashboardData.ts
@@ -0,0 +1,48 @@
+import type { DashboardData } from "./types";
+
+export const DEFAULT_DASHBOARD_DATA_PATH = "/data/flowmemory-dashboard-v0.json";
+
+function assertArray(value: unknown, label: string): void {
+ if (!Array.isArray(value)) {
+ throw new Error(`Dashboard fixture field "${label}" must be an array.`);
+ }
+}
+
+export function validateDashboardData(payload: unknown): DashboardData {
+ if (payload === null || typeof payload !== "object") {
+ throw new Error("Dashboard fixture must be a JSON object.");
+ }
+
+ const candidate = payload as Partial;
+ if (candidate.metadata?.schema !== "flowmemory.dashboard.fixture.v0") {
+ throw new Error("Unsupported dashboard fixture schema.");
+ }
+ if (candidate.metadata.mode !== "fixture") {
+ throw new Error("Dashboard V0 expects fixture mode data.");
+ }
+ if (candidate.chain === undefined) {
+ throw new Error("Dashboard fixture is missing chain context.");
+ }
+
+ assertArray(candidate.flowPulseObservations, "flowPulseObservations");
+ assertArray(candidate.rootfields, "rootfields");
+ assertArray(candidate.workLanes, "workLanes");
+ assertArray(candidate.workReceipts, "workReceipts");
+ assertArray(candidate.verifierReports, "verifierReports");
+ assertArray(candidate.devnetBlocks, "devnetBlocks");
+ assertArray(candidate.hardwareNodes, "hardwareNodes");
+ assertArray(candidate.alerts, "alerts");
+
+ return candidate as DashboardData;
+}
+
+export async function fetchDashboardData(
+ path = DEFAULT_DASHBOARD_DATA_PATH,
+): Promise {
+ const response = await fetch(path, { cache: "no-store" });
+ if (!response.ok) {
+ throw new Error(`Unable to load dashboard fixture at ${path}: ${response.status}`);
+ }
+
+ return validateDashboardData(await response.json());
+}
diff --git a/apps/dashboard/src/data/selectors.ts b/apps/dashboard/src/data/selectors.ts
new file mode 100644
index 00000000..4ab94a19
--- /dev/null
+++ b/apps/dashboard/src/data/selectors.ts
@@ -0,0 +1,107 @@
+import type {
+ AlertIncident,
+ DashboardData,
+ DashboardStatus,
+ DevnetBlock,
+ FlowPulseObservation,
+ HardwareNode,
+ ProvenancedRecord,
+ VerifierReport,
+} from "./types";
+
+export interface OverviewMetric {
+ label: string;
+ value: string;
+ detail: string;
+ status: DashboardStatus;
+}
+
+export interface SearchableRecord extends ProvenancedRecord {
+ [key: string]: unknown;
+}
+
+export function countByStatus(records: ProvenancedRecord[]): Partial> {
+ return records.reduce>>((counts, record) => {
+ counts[record.status] = (counts[record.status] ?? 0) + 1;
+ return counts;
+ }, {});
+}
+
+export function getLatestFlowPulses(data: DashboardData, limit = 6): FlowPulseObservation[] {
+ return [...data.flowPulseObservations]
+ .sort((left, right) => Number(right.blockNumber) - Number(left.blockNumber))
+ .slice(0, limit);
+}
+
+export function getLatestBlocks(data: DashboardData, limit = 5): DevnetBlock[] {
+ return [...data.devnetBlocks]
+ .sort((left, right) => right.blockNumber - left.blockNumber)
+ .slice(0, limit);
+}
+
+export function getOpenAlerts(data: DashboardData): AlertIncident[] {
+ return data.alerts.filter((alert) => alert.status !== "verified");
+}
+
+export function getVerifierRiskReports(data: DashboardData): VerifierReport[] {
+ const riskStatuses: DashboardStatus[] = ["invalid", "unresolved", "unsupported", "reorged", "stale"];
+ return data.verifierReports.filter((report) => riskStatuses.includes(report.status));
+}
+
+export function getHardwareRiskNodes(data: DashboardData): HardwareNode[] {
+ return data.hardwareNodes.filter((node) => node.status === "offline" || node.status === "stale");
+}
+
+export function computeOverviewMetrics(data: DashboardData): OverviewMetric[] {
+ const observations = data.flowPulseObservations.length;
+ const verifiedReports = data.verifierReports.filter((report) => report.status === "verified").length;
+ const openAlerts = getOpenAlerts(data).length;
+ const hardwareRisk = getHardwareRiskNodes(data).length;
+ const latestBlock = data.chain.currentBlock;
+ const headStatus: DashboardStatus = data.chain.currentBlock > data.chain.finalizedBlock ? "pending" : "finalized";
+
+ return [
+ {
+ label: "FlowPulse observations",
+ value: observations.toString(),
+ detail: `${data.rootfields.length} rootfields represented`,
+ status: observations > 0 ? "observed" : "pending",
+ },
+ {
+ label: "Verifier reports",
+ value: `${verifiedReports}/${data.verifierReports.length}`,
+ detail: "verified in fixture set",
+ status: verifiedReports === data.verifierReports.length ? "verified" : "unresolved",
+ },
+ {
+ label: "Devnet head",
+ value: latestBlock.toString(),
+ detail: `finalized through ${data.chain.finalizedBlock} / chain ${data.chain.chainId}`,
+ status: headStatus,
+ },
+ {
+ label: "Hardware risk",
+ value: hardwareRisk.toString(),
+ detail: "offline or stale nodes",
+ status: hardwareRisk > 0 ? "stale" : "verified",
+ },
+ {
+ label: "Open incidents",
+ value: openAlerts.toString(),
+ detail: "unresolved local alerts",
+ status: openAlerts > 0 ? "unresolved" : "verified",
+ },
+ ];
+}
+
+export function searchRecords(records: T[], query: string): T[] {
+ const normalizedQuery = query.trim().toLowerCase();
+ if (normalizedQuery.length === 0) {
+ return records;
+ }
+
+ return records.filter((record) => {
+ const searchableText = JSON.stringify(record).toLowerCase();
+ return searchableText.includes(normalizedQuery);
+ });
+}
diff --git a/apps/dashboard/src/data/status.ts b/apps/dashboard/src/data/status.ts
new file mode 100644
index 00000000..762e1c11
--- /dev/null
+++ b/apps/dashboard/src/data/status.ts
@@ -0,0 +1,48 @@
+import type { DashboardStatus } from "./types";
+
+export const DASHBOARD_STATUSES: DashboardStatus[] = [
+ "observed",
+ "pending",
+ "finalized",
+ "verified",
+ "unresolved",
+ "invalid",
+ "unsupported",
+ "reorged",
+ "offline",
+ "stale",
+];
+
+export const STATUS_LABELS: Record = {
+ observed: "Observed",
+ pending: "Pending",
+ finalized: "Finalized",
+ verified: "Verified",
+ unresolved: "Unresolved",
+ invalid: "Invalid",
+ unsupported: "Unsupported",
+ reorged: "Reorged",
+ offline: "Offline",
+ stale: "Stale",
+};
+
+export const STATUS_DESCRIPTIONS: Record = {
+ observed: "Indexed or received locally without a stronger finality or verifier claim.",
+ pending: "Queued, in-flight, or below the configured local finality threshold.",
+ finalized: "Past the local devnet finality threshold in this fixture.",
+ verified: "Fixture report says supported deterministic checks passed.",
+ unresolved: "Required evidence is absent or not resolvable from fixture inputs.",
+ invalid: "Supported checks ran and a deterministic mismatch was recorded.",
+ unsupported: "Current V0 rules do not evaluate this object type or payload.",
+ reorged: "The fixture marks the prior observation or block as noncanonical.",
+ offline: "No usable heartbeat is present in the fixture window.",
+ stale: "A newer local record exists or the object is outside freshness policy.",
+};
+
+export function isDashboardStatus(value: string): value is DashboardStatus {
+ return DASHBOARD_STATUSES.includes(value as DashboardStatus);
+}
+
+export function statusClassName(status: DashboardStatus): string {
+ return `status-badge status-${status}`;
+}
diff --git a/apps/dashboard/src/data/types.ts b/apps/dashboard/src/data/types.ts
new file mode 100644
index 00000000..2fb690bb
--- /dev/null
+++ b/apps/dashboard/src/data/types.ts
@@ -0,0 +1,182 @@
+export type DashboardStatus =
+ | "observed"
+ | "pending"
+ | "finalized"
+ | "verified"
+ | "unresolved"
+ | "invalid"
+ | "unsupported"
+ | "reorged"
+ | "offline"
+ | "stale";
+
+export type DataOrigin = "fixture" | "local" | "live";
+
+export type SourceSubsystem =
+ | "contracts"
+ | "indexer"
+ | "verifier"
+ | "devnet"
+ | "worker"
+ | "hardware"
+ | "alerts";
+
+export interface Provenance {
+ subsystem: SourceSubsystem;
+ origin: DataOrigin;
+ chainContext: string;
+ fixturePath?: string;
+ capturedAt?: string;
+ localPathHint?: string;
+}
+
+export interface ProvenancedRecord {
+ id: string;
+ status: DashboardStatus;
+ lastUpdated?: string;
+ provenance: Provenance;
+}
+
+export interface DashboardChainContext {
+ chainId: string;
+ name: string;
+ environment: "local-devnet" | "testnet" | "mainnet" | "unknown";
+ settlementContext: string;
+ currentBlock: number;
+ finalizedBlock: number;
+ source: DataOrigin;
+ lastUpdated: string;
+}
+
+export interface FixtureMetadata {
+ schema: "flowmemory.dashboard.fixture.v0";
+ generatedAt: string;
+ mode: "fixture";
+ description: string;
+ fixturePath: string;
+ runtimeDataPath: string;
+ futureGeneratedPaths: {
+ indexer: string;
+ verifier: string;
+ devnet: string;
+ hardware: string;
+ };
+}
+
+export interface FlowPulseObservation extends ProvenancedRecord {
+ observationId: string;
+ pulseId: string;
+ rootfieldId: string;
+ eventSignature: string;
+ blockNumber: string;
+ blockHash: string;
+ txHash: string;
+ transactionIndex: string;
+ logIndex: string;
+ receiptStatus: "success" | "reverted" | "unknown";
+ actor: string;
+ pulseType: string;
+ subject: string;
+ commitment: string;
+ parentPulseId: string;
+ sequence: string;
+ occurredAt: string;
+ uri: string;
+ summary: string;
+}
+
+export interface RootfieldState extends ProvenancedRecord {
+ rootfieldId: string;
+ owner: string;
+ schemaHash: string;
+ metadataHash: string;
+ latestRoot: string;
+ latestObservationId: string;
+ pulseCount: number;
+ workLaneIds: string[];
+ evidenceUri: string;
+}
+
+export interface WorkLane extends ProvenancedRecord {
+ laneId: string;
+ name: string;
+ queueDepth: number;
+ inflight: number;
+ completed24h: number;
+ p95LatencyMs: number;
+ operator: string;
+}
+
+export interface WorkReceipt extends ProvenancedRecord {
+ receiptId: string;
+ laneId: string;
+ rootfieldId: string;
+ observationId?: string;
+ reportId?: string;
+ workType: string;
+ artifactUri: string;
+ startedAt: string;
+ completedAt?: string;
+ resultHash: string;
+}
+
+export interface VerifierReport extends ProvenancedRecord {
+ reportId: string;
+ observationId: string;
+ rootfieldId: string;
+ resolverPolicyId: string;
+ verifierSpecVersion: string;
+ checksPassed: number;
+ checksTotal: number;
+ reasonCodes: string[];
+ reportHash: string;
+}
+
+export interface DevnetBlock extends ProvenancedRecord {
+ blockNumber: number;
+ blockHash: string;
+ parentHash: string;
+ stateRoot: string;
+ receiptsRoot: string;
+ timestamp: string;
+ observationCount: number;
+ reportCount: number;
+ finalityDistance: number;
+}
+
+export interface HardwareNode extends ProvenancedRecord {
+ nodeId: string;
+ callsign: string;
+ role: "router" | "gateway" | "sidecar" | "field-kit";
+ firmware: string;
+ transport: string;
+ lastHeartbeatAt?: string;
+ batteryPercent?: number;
+ signalDbm?: number;
+ temperatureC?: number;
+ linkedWorkLaneId?: string;
+ locationHint: string;
+}
+
+export interface AlertIncident extends ProvenancedRecord {
+ incidentId: string;
+ severity: "info" | "warning" | "critical";
+ title: string;
+ summary: string;
+ openedAt: string;
+ linkedObjectIds: string[];
+ recommendedAction: string;
+}
+
+export interface DashboardData {
+ metadata: FixtureMetadata;
+ chain: DashboardChainContext;
+ flowPulseObservations: FlowPulseObservation[];
+ rootfields: RootfieldState[];
+ workLanes: WorkLane[];
+ workReceipts: WorkReceipt[];
+ verifierReports: VerifierReport[];
+ devnetBlocks: DevnetBlock[];
+ hardwareNodes: HardwareNode[];
+ alerts: AlertIncident[];
+}
diff --git a/apps/dashboard/src/main.tsx b/apps/dashboard/src/main.tsx
new file mode 100644
index 00000000..04e310d9
--- /dev/null
+++ b/apps/dashboard/src/main.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { HashRouter } from "react-router-dom";
+import App from "./App";
+import "./styles.css";
+
+ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
+
+
+
+
+ ,
+);
diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css
new file mode 100644
index 00000000..915fdbbc
--- /dev/null
+++ b/apps/dashboard/src/styles.css
@@ -0,0 +1,891 @@
+:root {
+ color-scheme: light;
+ font-family: Geist, Satoshi, "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
+ background: #f4f5f1;
+ color: #1d211e;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ --ink: #1d211e;
+ --muted: #687068;
+ --subtle: #8b9289;
+ --line: #d9ded5;
+ --line-strong: #b9c1b7;
+ --surface: #fbfcf8;
+ --surface-2: #eef1eb;
+ --sidebar: #1d211e;
+ --sidebar-muted: #aab3a7;
+ --accent: #2f7d6b;
+ --accent-strong: #195b4d;
+ --warning: #a46b16;
+ --danger: #aa3f3f;
+ --info: #326b89;
+ --violet: #6b5d8c;
+ --shadow: 0 18px 40px rgba(29, 33, 30, 0.08);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-width: 320px;
+ min-height: 100dvh;
+ background: #f4f5f1;
+}
+
+button,
+input,
+select {
+ font: inherit;
+}
+
+button,
+select {
+ border: 1px solid var(--line-strong);
+ border-radius: 7px;
+ background: var(--surface);
+ color: var(--ink);
+}
+
+button {
+ cursor: pointer;
+}
+
+button:active,
+.nav-link:active {
+ transform: translateY(1px);
+}
+
+input {
+ min-width: 0;
+ width: 100%;
+ border: 0;
+ outline: 0;
+ background: transparent;
+ color: var(--ink);
+}
+
+select {
+ min-height: 38px;
+ padding: 0 12px;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ table-layout: fixed;
+}
+
+th,
+td {
+ border-bottom: 1px solid var(--line);
+ padding: 12px 14px;
+ text-align: left;
+ vertical-align: top;
+}
+
+th {
+ color: #555d55;
+ font-size: 0.72rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+td {
+ font-size: 0.86rem;
+}
+
+th,
+td {
+ overflow-wrap: anywhere;
+}
+
+small {
+ color: var(--muted);
+}
+
+.app-shell {
+ display: grid;
+ grid-template-columns: 252px minmax(0, 1fr);
+ min-height: 100dvh;
+}
+
+.sidebar {
+ position: sticky;
+ top: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 22px;
+ height: 100dvh;
+ padding: 18px 14px;
+ background: var(--sidebar);
+ color: #f6f8f2;
+ border-right: 1px solid #32382f;
+}
+
+.brand-block {
+ display: grid;
+ grid-template-columns: 42px 1fr;
+ gap: 10px;
+ align-items: center;
+ padding: 4px 4px 14px;
+ border-bottom: 1px solid #343a33;
+}
+
+.brand-mark {
+ display: grid;
+ place-items: center;
+ width: 38px;
+ height: 38px;
+ border: 1px solid #556054;
+ border-radius: 8px;
+ background: #252b26;
+ color: #d8efe5;
+}
+
+.brand-kicker,
+.eyebrow {
+ display: block;
+ margin-bottom: 4px;
+ color: var(--subtle);
+ font-size: 0.71rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.brand-block strong {
+ font-size: 1rem;
+}
+
+.nav-list {
+ display: grid;
+ gap: 4px;
+}
+
+.nav-link {
+ display: grid;
+ grid-template-columns: 22px 1fr;
+ gap: 9px;
+ align-items: center;
+ min-height: 38px;
+ padding: 8px 10px;
+ color: var(--sidebar-muted);
+ text-decoration: none;
+ border: 1px solid transparent;
+ border-radius: 7px;
+ transition: background 140ms ease, border-color 140ms ease, color 140ms ease, transform 140ms ease;
+}
+
+.nav-link:hover,
+.nav-link.active {
+ background: #29302b;
+ border-color: #3c463e;
+ color: #f6f8f2;
+}
+
+.sidebar-footer {
+ display: grid;
+ gap: 7px;
+ margin-top: auto;
+ padding: 12px;
+ color: var(--sidebar-muted);
+ border: 1px solid #3a433b;
+ border-radius: 8px;
+ background: #232923;
+}
+
+.workspace {
+ min-width: 0;
+}
+
+.topbar {
+ position: sticky;
+ top: 0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 18px;
+ padding: 14px 24px;
+ background: rgba(244, 245, 241, 0.92);
+ border-bottom: 1px solid var(--line);
+ backdrop-filter: blur(12px);
+}
+
+.topbar h2 {
+ margin: 0;
+ font-size: 1.05rem;
+}
+
+.topbar-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.topbar-meta span,
+.provenance-chip,
+.severity-label {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ min-height: 24px;
+ padding: 4px 7px;
+ color: #4f584f;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ background: #f9faf6;
+ font-size: 0.72rem;
+ white-space: nowrap;
+}
+
+.fixture-banner {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ margin: 18px 24px 0;
+ padding: 10px 12px;
+ color: #394139;
+ border: 1px solid #cfd7cd;
+ border-radius: 8px;
+ background: #f9faf6;
+ font-size: 0.86rem;
+}
+
+.content {
+ width: min(100%, 1480px);
+ margin: 0 auto;
+ padding: 24px;
+}
+
+.view-stack {
+ display: grid;
+ gap: 22px;
+}
+
+.section-header {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 18px;
+ align-items: end;
+}
+
+.section-header h1 {
+ margin: 0;
+ font-size: 1.8rem;
+ line-height: 1.15;
+}
+
+.section-header p {
+ max-width: 76ch;
+ margin: 8px 0 0;
+ color: var(--muted);
+ line-height: 1.5;
+}
+
+.section-action {
+ min-width: min(520px, 100%);
+}
+
+.filter-row {
+ display: grid;
+ grid-template-columns: minmax(240px, 1fr) 150px;
+ gap: 10px;
+}
+
+.search-box {
+ display: grid;
+ grid-template-columns: 18px 1fr;
+ gap: 8px;
+ align-items: center;
+ min-height: 38px;
+ padding: 0 10px;
+ border: 1px solid var(--line-strong);
+ border-radius: 7px;
+ background: var(--surface);
+}
+
+.metric-grid {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 10px;
+}
+
+.metric-tile,
+.panel,
+.table-panel,
+.rootfield-tile,
+.lane-tile,
+.node-tile,
+.alert-row,
+.json-panel {
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--surface);
+ box-shadow: var(--shadow);
+}
+
+.metric-tile {
+ display: grid;
+ gap: 8px;
+ padding: 14px;
+}
+
+.metric-tile > span {
+ color: var(--muted);
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.metric-tile strong {
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 1.65rem;
+}
+
+.metric-tile div {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 7px;
+ align-items: center;
+}
+
+.overview-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.4fr) minmax(290px, 0.6fr);
+ gap: 14px;
+}
+
+.panel {
+ min-width: 0;
+ padding: 14px;
+}
+
+.panel-wide {
+ grid-column: span 1;
+}
+
+.panel-heading {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--line);
+}
+
+.panel-heading div,
+.tile-heading,
+.record-title,
+.json-panel-header {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ min-width: 0;
+}
+
+.panel-heading h2 {
+ margin: 0;
+ font-size: 0.98rem;
+}
+
+.panel-heading > span {
+ color: var(--muted);
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.78rem;
+}
+
+.record-list,
+.compact-list,
+.alert-list {
+ display: grid;
+}
+
+.record-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 270px;
+ gap: 16px;
+ padding: 14px 0;
+ border-bottom: 1px solid var(--line);
+}
+
+.record-row:last-child {
+ border-bottom: 0;
+}
+
+.record-row p,
+.alert-row p {
+ margin: 8px 0;
+ color: #3f483f;
+ line-height: 1.45;
+}
+
+.record-facts,
+.definition-grid,
+.lane-stats,
+.alert-facts {
+ display: grid;
+ gap: 10px;
+ margin: 0;
+}
+
+.record-facts {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+.record-facts div,
+.definition-grid div,
+.lane-stats div,
+.alert-facts div {
+ min-width: 0;
+}
+
+dt {
+ color: var(--subtle);
+ font-size: 0.68rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+dd {
+ margin: 3px 0 0;
+ overflow-wrap: anywhere;
+}
+
+.compact-list article {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ gap: 9px;
+ align-items: start;
+ padding: 12px 0;
+ border-bottom: 1px solid var(--line);
+}
+
+.compact-list article:last-child {
+ border-bottom: 0;
+}
+
+.compact-list strong,
+.compact-list small {
+ display: block;
+ overflow-wrap: anywhere;
+}
+
+.block-strip {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 10px;
+ padding-top: 14px;
+}
+
+.block-strip article {
+ display: grid;
+ gap: 7px;
+ padding: 12px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: #f7f8f4;
+}
+
+.table-panel {
+ min-width: 0;
+ overflow: hidden;
+}
+
+.table-scroll {
+ overflow-x: auto;
+}
+
+.table-scroll table {
+ min-width: 1120px;
+}
+
+.cell-stack {
+ display: grid;
+ gap: 4px;
+ min-width: 0;
+}
+
+.hash-value {
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.82rem;
+ overflow-wrap: anywhere;
+}
+
+.provenance-line {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 5px;
+}
+
+.rootfield-grid,
+.hardware-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+}
+
+.rootfield-tile,
+.node-tile,
+.lane-tile {
+ display: grid;
+ gap: 14px;
+ padding: 14px;
+}
+
+.definition-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.lane-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.lane-stats {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.alert-list {
+ gap: 12px;
+}
+
+.alert-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 300px;
+ gap: 16px;
+ padding: 14px;
+ border-left-width: 4px;
+}
+
+.severity-critical {
+ border-left-color: var(--danger);
+}
+
+.severity-warning {
+ border-left-color: var(--warning);
+}
+
+.severity-info {
+ border-left-color: var(--info);
+}
+
+.alert-action {
+ padding: 12px;
+ border-left: 1px solid var(--line);
+}
+
+.alert-action span {
+ color: var(--subtle);
+ font-size: 0.7rem;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.alert-action p {
+ margin-bottom: 0;
+}
+
+.json-panel {
+ overflow: hidden;
+}
+
+.json-panel-header {
+ justify-content: space-between;
+ padding: 12px 14px;
+ border-bottom: 1px solid var(--line);
+}
+
+.json-panel pre {
+ max-height: 68dvh;
+ margin: 0;
+ padding: 16px;
+ overflow: auto;
+ background: #1f241f;
+ color: #eef4ec;
+ font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
+ font-size: 0.78rem;
+ line-height: 1.5;
+}
+
+.status-badge {
+ display: inline-flex;
+ gap: 6px;
+ align-items: center;
+ width: fit-content;
+ max-width: 100%;
+ min-height: 24px;
+ padding: 4px 8px;
+ border: 1px solid var(--status-border);
+ border-radius: 999px;
+ background: var(--status-bg);
+ color: var(--status-fg);
+ font-size: 0.72rem;
+ font-weight: 800;
+ text-transform: uppercase;
+ white-space: nowrap;
+}
+
+.status-dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 999px;
+ background: currentColor;
+}
+
+.status-observed {
+ --status-bg: #e7f0f3;
+ --status-border: #b9d0d8;
+ --status-fg: #326b89;
+}
+
+.status-pending {
+ --status-bg: #f5eddb;
+ --status-border: #d9c393;
+ --status-fg: #8a5e14;
+}
+
+.status-finalized {
+ --status-bg: #e9eee8;
+ --status-border: #bdcabc;
+ --status-fg: #3e6541;
+}
+
+.status-verified {
+ --status-bg: #e2f0e8;
+ --status-border: #a8ceb7;
+ --status-fg: #1f6a45;
+}
+
+.status-unresolved {
+ --status-bg: #f3e7dc;
+ --status-border: #d4b495;
+ --status-fg: #8c5527;
+}
+
+.status-invalid {
+ --status-bg: #f5e2e2;
+ --status-border: #d4a0a0;
+ --status-fg: #963b3b;
+}
+
+.status-unsupported {
+ --status-bg: #ece7f1;
+ --status-border: #c1b6d0;
+ --status-fg: #665283;
+}
+
+.status-reorged {
+ --status-bg: #eee6dd;
+ --status-border: #c6b19e;
+ --status-fg: #714f35;
+}
+
+.status-offline {
+ --status-bg: #e7e8e3;
+ --status-border: #b9beb5;
+ --status-fg: #555d55;
+}
+
+.status-stale {
+ --status-bg: #f0ecdc;
+ --status-border: #cfc38c;
+ --status-fg: #766a23;
+}
+
+.empty-state {
+ display: flex;
+ gap: 12px;
+ align-items: flex-start;
+ padding: 22px;
+ color: var(--muted);
+ border: 1px dashed var(--line-strong);
+ border-radius: 8px;
+ background: #f8f9f4;
+}
+
+.empty-state h3 {
+ margin: 0 0 5px;
+ color: var(--ink);
+ font-size: 1rem;
+}
+
+.empty-state p {
+ margin: 0;
+ line-height: 1.45;
+}
+
+.boot-screen {
+ display: grid;
+ place-items: center;
+ min-height: 100dvh;
+ padding: 24px;
+ background: #f4f5f1;
+}
+
+.boot-panel,
+.error-panel {
+ width: min(620px, 100%);
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: var(--surface);
+ box-shadow: var(--shadow);
+}
+
+.boot-panel {
+ display: grid;
+ gap: 14px;
+ padding: 24px;
+}
+
+.skeleton-line {
+ height: 14px;
+ border-radius: 999px;
+ background: linear-gradient(90deg, #e1e6de 0%, #f5f6f0 45%, #e1e6de 100%);
+ background-size: 220% 100%;
+ animation: shimmer 1.4s ease-in-out infinite;
+}
+
+.skeleton-title {
+ width: 70%;
+ height: 24px;
+}
+
+.skeleton-short {
+ width: 46%;
+}
+
+.boot-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 10px;
+ margin-top: 8px;
+}
+
+.boot-grid div {
+ height: 92px;
+ border-radius: 8px;
+ background: #edf0e9;
+}
+
+.error-panel {
+ display: grid;
+ grid-template-columns: 42px 1fr;
+ gap: 14px;
+ padding: 22px;
+ color: #873a3a;
+}
+
+.error-panel h1 {
+ margin: 0 0 8px;
+ color: var(--ink);
+ font-size: 1.25rem;
+}
+
+.error-panel p {
+ margin: 0 0 14px;
+ color: #6f4c4c;
+}
+
+.button {
+ display: inline-flex;
+ gap: 8px;
+ align-items: center;
+ min-height: 36px;
+ padding: 0 12px;
+}
+
+.button-primary {
+ border-color: #1f6a45;
+ background: #2f7d6b;
+ color: #f9fcf7;
+}
+
+@keyframes shimmer {
+ 0% {
+ background-position: 100% 0;
+ }
+ 100% {
+ background-position: -100% 0;
+ }
+}
+
+@media (max-width: 1180px) {
+ .metric-grid,
+ .block-strip {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .overview-grid,
+ .rootfield-grid,
+ .hardware-grid,
+ .lane-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 860px) {
+ .app-shell {
+ grid-template-columns: 1fr;
+ }
+
+ .sidebar {
+ position: static;
+ height: auto;
+ }
+
+ .nav-list {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .topbar,
+ .section-header,
+ .fixture-banner,
+ .record-row,
+ .alert-row {
+ display: grid;
+ grid-template-columns: 1fr;
+ }
+
+ .content {
+ padding: 18px 14px 28px;
+ }
+
+ .section-action,
+ .filter-row {
+ min-width: 0;
+ grid-template-columns: 1fr;
+ }
+
+ .fixture-banner {
+ margin: 14px 14px 0;
+ }
+
+ .alert-action {
+ border-left: 0;
+ border-top: 1px solid var(--line);
+ }
+}
+
+@media (max-width: 560px) {
+ .nav-list,
+ .metric-grid,
+ .block-strip,
+ .definition-grid,
+ .lane-stats,
+ .record-facts {
+ grid-template-columns: 1fr;
+ }
+
+ .topbar {
+ padding: 12px 14px;
+ }
+
+ th,
+ td {
+ padding: 10px;
+ }
+}
diff --git a/apps/dashboard/src/test/dashboardData.test.ts b/apps/dashboard/src/test/dashboardData.test.ts
new file mode 100644
index 00000000..45e2997c
--- /dev/null
+++ b/apps/dashboard/src/test/dashboardData.test.ts
@@ -0,0 +1,60 @@
+import { describe, expect, it } from "vitest";
+import fixture from "../../../../fixtures/dashboard/flowmemory-dashboard-v0.json";
+import { validateDashboardData } from "../data/loadDashboardData";
+import { DASHBOARD_STATUSES } from "../data/status";
+import { computeOverviewMetrics, searchRecords } from "../data/selectors";
+import type { DashboardData, ProvenancedRecord } from "../data/types";
+
+describe("dashboard fixture", () => {
+ const data = validateDashboardData(fixture) as DashboardData;
+
+ it("loads the V0 dashboard fixture shape", () => {
+ expect(data.metadata.schema).toBe("flowmemory.dashboard.fixture.v0");
+ expect(data.metadata.mode).toBe("fixture");
+ expect(data.flowPulseObservations.length).toBeGreaterThan(0);
+ expect(data.verifierReports.length).toBeGreaterThan(0);
+ });
+
+ it("covers every required dashboard status", () => {
+ const records: ProvenancedRecord[] = [
+ ...data.flowPulseObservations,
+ ...data.rootfields,
+ ...data.workLanes,
+ ...data.workReceipts,
+ ...data.verifierReports,
+ ...data.devnetBlocks,
+ ...data.hardwareNodes,
+ ...data.alerts,
+ ];
+ const statuses = new Set(records.map((record) => record.status));
+
+ for (const status of DASHBOARD_STATUSES) {
+ expect(statuses.has(status), `${status} should appear in fixture data`).toBe(true);
+ }
+ });
+
+ it("keeps provenance on every displayed record", () => {
+ const records: ProvenancedRecord[] = [
+ ...data.flowPulseObservations,
+ ...data.rootfields,
+ ...data.workLanes,
+ ...data.workReceipts,
+ ...data.verifierReports,
+ ...data.devnetBlocks,
+ ...data.hardwareNodes,
+ ...data.alerts,
+ ];
+
+ expect(records.every((record) => record.id && record.status && record.provenance.subsystem)).toBe(true);
+ expect(records.every((record) => record.provenance.origin === "fixture")).toBe(true);
+ expect(records.every((record) => record.provenance.chainContext === "anvil-31337")).toBe(true);
+ });
+
+ it("computes overview metrics and searches records", () => {
+ const metrics = computeOverviewMetrics(data);
+ const matches = searchRecords(data.verifierReports, "commitment.mismatch");
+
+ expect(metrics).toHaveLength(5);
+ expect(matches.map((match) => match.status)).toContain("invalid");
+ });
+});
diff --git a/apps/dashboard/src/views/AlertsView.tsx b/apps/dashboard/src/views/AlertsView.tsx
new file mode 100644
index 00000000..041f1e3d
--- /dev/null
+++ b/apps/dashboard/src/views/AlertsView.tsx
@@ -0,0 +1,71 @@
+import { useMemo, useState } from "react";
+import { Search } from "lucide-react";
+import { EmptyState } from "../components/EmptyState";
+import { ProvenanceLine } from "../components/ProvenanceLine";
+import { SectionHeader } from "../components/SectionHeader";
+import { StatusBadge } from "../components/StatusBadge";
+import { formatDateTime } from "../data/format";
+import { searchRecords } from "../data/selectors";
+import type { DashboardData } from "../data/types";
+
+export function AlertsView({ data }: { data: DashboardData }) {
+ const [query, setQuery] = useState("");
+ const alerts = useMemo(() => searchRecords(data.alerts, query), [data.alerts, query]);
+
+ return (
+
+
+
+ setQuery(event.target.value)} placeholder="Search alerts" />
+
+ }
+ />
+
+ {alerts.length > 0 ? (
+
+ {alerts.map((alert) => (
+
+
+
+
+ {alert.severity}
+ {alert.title}
+
+
{alert.summary}
+
+
+
incident id
+ {alert.incidentId}
+
+
+
opened
+ {formatDateTime(alert.openedAt)}
+
+
+
linked objects
+ {alert.linkedObjectIds.join(", ")}
+
+
+
+
+
+
+ ))}
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/apps/dashboard/src/views/DevnetBlocksView.tsx b/apps/dashboard/src/views/DevnetBlocksView.tsx
new file mode 100644
index 00000000..e80b2fe3
--- /dev/null
+++ b/apps/dashboard/src/views/DevnetBlocksView.tsx
@@ -0,0 +1,99 @@
+import { useMemo, useState } from "react";
+import { Search } from "lucide-react";
+import { EmptyState } from "../components/EmptyState";
+import { HashValue } from "../components/HashValue";
+import { ProvenanceLine } from "../components/ProvenanceLine";
+import { SectionHeader } from "../components/SectionHeader";
+import { StatusBadge } from "../components/StatusBadge";
+import { formatDateTime } from "../data/format";
+import { searchRecords } from "../data/selectors";
+import type { DashboardData } from "../data/types";
+
+export function DevnetBlocksView({ data }: { data: DashboardData }) {
+ const [query, setQuery] = useState("");
+ const blocks = useMemo(() => searchRecords(data.devnetBlocks, query), [data.devnetBlocks, query]);
+
+ return (
+
+
+
+ setQuery(event.target.value)} placeholder="Search blocks" />
+
+ }
+ />
+
+
+ {blocks.length > 0 ? (
+
+
+
+
+ Status
+ Block
+ Hashes
+ Roots
+ Counts
+ Provenance
+
+
+
+ {blocks.map((block) => (
+
+
+
+
+
+
+ {block.blockNumber}
+ {formatDateTime(block.timestamp)}
+
+
+
+
+
+ block
+
+
+ parent
+
+
+
+
+
+
+ state
+
+
+ receipts
+
+
+
+
+
+ {block.observationCount} observations
+ {block.reportCount} reports / distance {block.finalityDistance}
+
+
+
+
+
+
+ ))}
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/apps/dashboard/src/views/FlowPulseStreamView.tsx b/apps/dashboard/src/views/FlowPulseStreamView.tsx
new file mode 100644
index 00000000..ce655bc8
--- /dev/null
+++ b/apps/dashboard/src/views/FlowPulseStreamView.tsx
@@ -0,0 +1,122 @@
+import { useMemo, useState } from "react";
+import { Search } from "lucide-react";
+import { EmptyState } from "../components/EmptyState";
+import { HashValue } from "../components/HashValue";
+import { ProvenanceLine } from "../components/ProvenanceLine";
+import { SectionHeader } from "../components/SectionHeader";
+import { StatusBadge } from "../components/StatusBadge";
+import { DASHBOARD_STATUSES } from "../data/status";
+import { searchRecords } from "../data/selectors";
+import type { DashboardData, DashboardStatus } from "../data/types";
+
+export function FlowPulseStreamView({ data }: { data: DashboardData }) {
+ const [query, setQuery] = useState("");
+ const [status, setStatus] = useState("all");
+
+ const observations = useMemo(() => {
+ const statusFiltered =
+ status === "all"
+ ? data.flowPulseObservations
+ : data.flowPulseObservations.filter((observation) => observation.status === status);
+ return searchRecords(statusFiltered, query);
+ }, [data.flowPulseObservations, query, status]);
+
+ return (
+
+
+
+
+ setQuery(event.target.value)}
+ placeholder="Search observations, hashes, URIs"
+ />
+
+ setStatus(event.target.value as DashboardStatus | "all")}>
+ All statuses
+ {DASHBOARD_STATUSES.map((item) => (
+
+ {item}
+
+ ))}
+
+
+ }
+ />
+
+
+ {observations.length > 0 ? (
+
+
+
+
+ Status
+ Observation
+ Rootfield / pulse
+ Receipt context
+ Commitment
+ Provenance
+
+
+
+ {observations.map((observation) => (
+
+
+
+
+
+
+
+ {observation.summary}
+
+
+
+
+
+ root
+
+
+ pulse
+
+
+
+
+
+ block {observation.blockNumber}
+
+ tx
+
+
+ index {observation.transactionIndex}:{observation.logIndex} / {observation.receiptStatus}
+
+
+
+
+
+
+ type {observation.pulseType} / seq {observation.sequence}
+
+
+
+
+
+
+ ))}
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/apps/dashboard/src/views/HardwareNodesView.tsx b/apps/dashboard/src/views/HardwareNodesView.tsx
new file mode 100644
index 00000000..b70ba8ea
--- /dev/null
+++ b/apps/dashboard/src/views/HardwareNodesView.tsx
@@ -0,0 +1,91 @@
+import { useMemo, useState } from "react";
+import { Search } from "lucide-react";
+import { EmptyState } from "../components/EmptyState";
+import { ProvenanceLine } from "../components/ProvenanceLine";
+import { SectionHeader } from "../components/SectionHeader";
+import { StatusBadge } from "../components/StatusBadge";
+import { formatDateTime, formatPercent } from "../data/format";
+import { searchRecords } from "../data/selectors";
+import type { DashboardData } from "../data/types";
+
+export function HardwareNodesView({ data }: { data: DashboardData }) {
+ const [query, setQuery] = useState("");
+ const nodes = useMemo(() => searchRecords(data.hardwareNodes, query), [data.hardwareNodes, query]);
+
+ return (
+
+
+
+ setQuery(event.target.value)} placeholder="Search nodes" />
+
+ }
+ />
+
+ {nodes.length > 0 ? (
+
+ {nodes.map((node) => (
+
+
+
+ {node.callsign}
+
+
+
+
node id
+ {node.nodeId}
+
+
+
role
+ {node.role}
+
+
+
transport
+ {node.transport}
+
+
+
firmware
+ {node.firmware}
+
+
+
heartbeat
+ {formatDateTime(node.lastHeartbeatAt)}
+
+
+
battery
+ {formatPercent(node.batteryPercent)}
+
+
+
signal
+ {node.signalDbm === undefined ? "n/a" : `${node.signalDbm} dBm`}
+
+
+
temperature
+ {node.temperatureC === undefined ? "n/a" : `${node.temperatureC}C`}
+
+
+
lane
+ {node.linkedWorkLaneId ?? "none"}
+
+
+
location
+ {node.locationHint}
+
+
+
+
+ ))}
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/apps/dashboard/src/views/OverviewView.tsx b/apps/dashboard/src/views/OverviewView.tsx
new file mode 100644
index 00000000..369ea80f
--- /dev/null
+++ b/apps/dashboard/src/views/OverviewView.tsx
@@ -0,0 +1,166 @@
+import { AlertTriangle, Cpu, DatabaseZap, RadioTower } from "lucide-react";
+import { EmptyState } from "../components/EmptyState";
+import { HashValue } from "../components/HashValue";
+import { ProvenanceLine } from "../components/ProvenanceLine";
+import { SectionHeader } from "../components/SectionHeader";
+import { StatusBadge } from "../components/StatusBadge";
+import { formatDateTime } from "../data/format";
+import {
+ computeOverviewMetrics,
+ getHardwareRiskNodes,
+ getLatestBlocks,
+ getLatestFlowPulses,
+ getVerifierRiskReports,
+} from "../data/selectors";
+import type { DashboardData } from "../data/types";
+
+export function OverviewView({ data }: { data: DashboardData }) {
+ const metrics = computeOverviewMetrics(data);
+ const latestPulses = getLatestFlowPulses(data, 5);
+ const latestBlocks = getLatestBlocks(data, 4);
+ const verifierRisk = getVerifierRiskReports(data);
+ const hardwareRisk = getHardwareRiskNodes(data);
+
+ return (
+
+
+
+
+ {metrics.map((metric) => (
+
+ {metric.label}
+ {metric.value}
+
+
+ {metric.detail}
+
+
+ ))}
+
+
+
+
+
+
+
+
Recent FlowPulse observations
+
+
{data.flowPulseObservations.length} total
+
+ {latestPulses.length > 0 ? (
+
+ {latestPulses.map((pulse) => (
+
+
+
+
+
+
+
{pulse.summary}
+
+
+
+
+
block
+ {pulse.blockNumber}
+
+
+
sequence
+ {pulse.sequence}
+
+
+
tx
+
+
+
+
+
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+
+
+
+
{verifierRisk.length} reports
+
+ {verifierRisk.length > 0 ? (
+
+ {verifierRisk.map((report) => (
+
+
+
+
+
+
+ {report.reasonCodes.join(", ") || "no reason code"}
+
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
Hardware risk
+
+
{hardwareRisk.length} nodes
+
+ {hardwareRisk.length > 0 ? (
+
+ {hardwareRisk.map((node) => (
+
+
+
+ {node.callsign}
+ {node.lastHeartbeatAt ? formatDateTime(node.lastHeartbeatAt) : "no heartbeat"}
+
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
Devnet block window
+
+
{data.chain.finalizedBlock} finalized
+
+
+ {latestBlocks.map((block) => (
+
+
+ {block.blockNumber}
+
+
+
+ {block.observationCount} observations
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/dashboard/src/views/RawJsonInspectorView.tsx b/apps/dashboard/src/views/RawJsonInspectorView.tsx
new file mode 100644
index 00000000..6a4f00ce
--- /dev/null
+++ b/apps/dashboard/src/views/RawJsonInspectorView.tsx
@@ -0,0 +1,57 @@
+import { useMemo, useState } from "react";
+import { Braces } from "lucide-react";
+import { SectionHeader } from "../components/SectionHeader";
+import type { DashboardData } from "../data/types";
+
+const DATASET_LABELS = [
+ "all",
+ "metadata",
+ "chain",
+ "flowPulseObservations",
+ "rootfields",
+ "workLanes",
+ "workReceipts",
+ "verifierReports",
+ "devnetBlocks",
+ "hardwareNodes",
+ "alerts",
+] as const;
+
+type DatasetKey = (typeof DATASET_LABELS)[number];
+
+export function RawJsonInspectorView({ data }: { data: DashboardData }) {
+ const [dataset, setDataset] = useState("all");
+
+ const rawJson = useMemo(() => {
+ const value = dataset === "all" ? data : data[dataset];
+ return JSON.stringify(value, null, 2);
+ }, [data, dataset]);
+
+ return (
+
+
setDataset(event.target.value as DatasetKey)}>
+ {DATASET_LABELS.map((label) => (
+
+ {label}
+
+ ))}
+
+ }
+ />
+
+
+
+
+ {data.metadata.runtimeDataPath}
+ {dataset}
+
+ {rawJson}
+
+
+ );
+}
diff --git a/apps/dashboard/src/views/RootfieldsView.tsx b/apps/dashboard/src/views/RootfieldsView.tsx
new file mode 100644
index 00000000..8f4656bc
--- /dev/null
+++ b/apps/dashboard/src/views/RootfieldsView.tsx
@@ -0,0 +1,93 @@
+import { useMemo, useState } from "react";
+import { Search } from "lucide-react";
+import { EmptyState } from "../components/EmptyState";
+import { HashValue } from "../components/HashValue";
+import { ProvenanceLine } from "../components/ProvenanceLine";
+import { SectionHeader } from "../components/SectionHeader";
+import { StatusBadge } from "../components/StatusBadge";
+import { searchRecords } from "../data/selectors";
+import type { DashboardData } from "../data/types";
+
+export function RootfieldsView({ data }: { data: DashboardData }) {
+ const [query, setQuery] = useState("");
+ const rootfields = useMemo(() => searchRecords(data.rootfields, query), [data.rootfields, query]);
+
+ return (
+
+
+
+ setQuery(event.target.value)} placeholder="Search rootfields" />
+
+ }
+ />
+
+ {rootfields.length > 0 ? (
+
+ {rootfields.map((rootfield) => (
+
+
+
+
+
+
+
+
owner
+
+
+
+
+
+
latest root
+
+
+
+
+
+
schema hash
+
+
+
+
+
+
metadata hash
+
+
+
+
+
+
pulse count
+ {rootfield.pulseCount}
+
+
+
lanes
+ {rootfield.workLaneIds.join(", ")}
+
+
+
latest observation
+
+
+
+
+
+
evidence URI
+ {rootfield.evidenceUri}
+
+
+
+
+ ))}
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/apps/dashboard/src/views/VerifierReportsView.tsx b/apps/dashboard/src/views/VerifierReportsView.tsx
new file mode 100644
index 00000000..402c0c5f
--- /dev/null
+++ b/apps/dashboard/src/views/VerifierReportsView.tsx
@@ -0,0 +1,115 @@
+import { useMemo, useState } from "react";
+import { Search } from "lucide-react";
+import { EmptyState } from "../components/EmptyState";
+import { HashValue } from "../components/HashValue";
+import { ProvenanceLine } from "../components/ProvenanceLine";
+import { SectionHeader } from "../components/SectionHeader";
+import { StatusBadge } from "../components/StatusBadge";
+import { DASHBOARD_STATUSES } from "../data/status";
+import { searchRecords } from "../data/selectors";
+import type { DashboardData, DashboardStatus } from "../data/types";
+
+export function VerifierReportsView({ data }: { data: DashboardData }) {
+ const [query, setQuery] = useState("");
+ const [status, setStatus] = useState("all");
+
+ const reports = useMemo(() => {
+ const statusFiltered =
+ status === "all" ? data.verifierReports : data.verifierReports.filter((report) => report.status === status);
+ return searchRecords(statusFiltered, query);
+ }, [data.verifierReports, query, status]);
+
+ return (
+
+
+
+
+ setQuery(event.target.value)} placeholder="Search reports" />
+
+ setStatus(event.target.value as DashboardStatus | "all")}>
+ All statuses
+ {DASHBOARD_STATUSES.map((item) => (
+
+ {item}
+
+ ))}
+
+
+ }
+ />
+
+
+ {reports.length > 0 ? (
+
+
+
+
+ Status
+ Report
+ Observation / rootfield
+ Policy
+ Checks
+ Provenance
+
+
+
+ {reports.map((report) => (
+
+
+
+
+
+
+
+
+ hash
+
+
+
+
+
+
+ obs
+
+
+ root
+
+
+
+
+
+ {report.resolverPolicyId}
+ spec {report.verifierSpecVersion}
+
+
+
+
+
+ {report.checksPassed}/{report.checksTotal}
+
+ {report.reasonCodes.join(", ") || "none"}
+
+
+
+
+
+
+ ))}
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/apps/dashboard/src/views/WorkReceiptsView.tsx b/apps/dashboard/src/views/WorkReceiptsView.tsx
new file mode 100644
index 00000000..80c81475
--- /dev/null
+++ b/apps/dashboard/src/views/WorkReceiptsView.tsx
@@ -0,0 +1,121 @@
+import { useMemo, useState } from "react";
+import { Search } from "lucide-react";
+import { EmptyState } from "../components/EmptyState";
+import { HashValue } from "../components/HashValue";
+import { ProvenanceLine } from "../components/ProvenanceLine";
+import { SectionHeader } from "../components/SectionHeader";
+import { StatusBadge } from "../components/StatusBadge";
+import { formatDateTime, formatMs } from "../data/format";
+import { searchRecords } from "../data/selectors";
+import type { DashboardData } from "../data/types";
+
+export function WorkReceiptsView({ data }: { data: DashboardData }) {
+ const [query, setQuery] = useState("");
+ const receipts = useMemo(() => searchRecords(data.workReceipts, query), [data.workReceipts, query]);
+
+ return (
+
+
+
+ setQuery(event.target.value)} placeholder="Search receipts" />
+
+ }
+ />
+
+
+ {data.workLanes.map((lane) => (
+
+
+
+ {lane.name}
+
+
+
+
queued
+ {lane.queueDepth}
+
+
+
in flight
+ {lane.inflight}
+
+
+
24h done
+ {lane.completed24h}
+
+
+
p95
+ {formatMs(lane.p95LatencyMs)}
+
+
+
+
+ ))}
+
+
+
+ {receipts.length > 0 ? (
+
+
+
+
+ Status
+ Receipt
+ Lane / rootfield
+ Work result
+ Timing
+ Provenance
+
+
+
+ {receipts.map((receipt) => (
+
+
+
+
+
+
+
+ {receipt.workType}
+
+
+
+
+ {receipt.laneId}
+
+
+
+
+
+
+ {receipt.artifactUri}
+
+
+
+
+ start {formatDateTime(receipt.startedAt)}
+ done {formatDateTime(receipt.completedAt)}
+
+
+
+
+
+
+ ))}
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/apps/dashboard/tsconfig.json b/apps/dashboard/tsconfig.json
new file mode 100644
index 00000000..e73de76d
--- /dev/null
+++ b/apps/dashboard/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "types": ["vitest/globals"]
+ },
+ "include": ["src", "vite.config.ts"]
+}
diff --git a/apps/dashboard/vite.config.ts b/apps/dashboard/vite.config.ts
new file mode 100644
index 00000000..bd068e16
--- /dev/null
+++ b/apps/dashboard/vite.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from "vitest/config";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ test: {
+ environment: "node",
+ globals: true,
+ },
+});
diff --git a/docs/DASHBOARD_MVP.md b/docs/DASHBOARD_MVP.md
new file mode 100644
index 00000000..6b4d52fc
--- /dev/null
+++ b/docs/DASHBOARD_MVP.md
@@ -0,0 +1,79 @@
+# Dashboard MVP
+
+FlowMemory Dashboard V0 is a local React/Vite operator app under `apps/dashboard/`. It visualizes fixture data for the first app-facing explorer surface without introducing production APIs, wallet flows, token data, or live network claims.
+
+## Scope
+
+The MVP covers local inspection of:
+
+- FlowPulse observations from indexer-style receipt/log data
+- Rootfield registry state
+- Work lanes and work receipts
+- Verifier reports
+- Devnet blocks and state roots
+- Hardware node heartbeats
+- Alerts and incidents
+- Raw JSON fixture data
+
+## Fixture Contract
+
+Canonical fixture:
+
+```text
+fixtures/dashboard/flowmemory-dashboard-v0.json
+```
+
+Runtime copy loaded by Vite:
+
+```text
+apps/dashboard/public/data/flowmemory-dashboard-v0.json
+```
+
+Copy command:
+
+```powershell
+npm run sync:fixtures --prefix apps/dashboard
+```
+
+Each displayed object should include:
+
+- `id` or hash
+- `status`
+- `lastUpdated` when available
+- `provenance.subsystem`
+- `provenance.origin`
+- `provenance.chainContext`
+- fixture or generated local path hints when known
+
+## Future Generated Inputs
+
+Future local jobs can write generated data here before a live API exists:
+
+```text
+fixtures/dashboard/generated/indexer-state.json
+fixtures/dashboard/generated/verifier-reports.json
+fixtures/dashboard/generated/devnet-state.json
+fixtures/dashboard/generated/hardware-heartbeats.json
+```
+
+The app should keep treating those files as local/fixture data until a separate API decision defines authentication, caching, freshness, and failure semantics.
+
+## Non-Goals
+
+- No backend service required for V0
+- No wallet connect
+- No token price, TVL, rewards, staking, or market data
+- No production monitoring claims
+- No secrets or RPC credentials
+- No contract, service, or hardware behavior changes
+
+## Local Checks
+
+From `apps/dashboard/`:
+
+```powershell
+npm install
+npm run typecheck
+npm test
+npm run build
+```
diff --git a/fixtures/dashboard/flowmemory-dashboard-v0.json b/fixtures/dashboard/flowmemory-dashboard-v0.json
new file mode 100644
index 00000000..dcd99f4b
--- /dev/null
+++ b/fixtures/dashboard/flowmemory-dashboard-v0.json
@@ -0,0 +1,802 @@
+{
+ "metadata": {
+ "schema": "flowmemory.dashboard.fixture.v0",
+ "generatedAt": "2026-05-13T16:00:00.000Z",
+ "mode": "fixture",
+ "description": "Synthetic local FlowMemory Dashboard V0 fixture. Values are operator/explorer test vectors only and do not claim live production data.",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "runtimeDataPath": "apps/dashboard/public/data/flowmemory-dashboard-v0.json",
+ "futureGeneratedPaths": {
+ "indexer": "fixtures/dashboard/generated/indexer-state.json",
+ "verifier": "fixtures/dashboard/generated/verifier-reports.json",
+ "devnet": "fixtures/dashboard/generated/devnet-state.json",
+ "hardware": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ },
+ "chain": {
+ "chainId": "31337",
+ "name": "Anvil local FlowMemory devnet",
+ "environment": "local-devnet",
+ "settlementContext": "Base-first project path; this fixture is not Base mainnet or Base Sepolia data.",
+ "currentBlock": 123482,
+ "finalizedBlock": 123450,
+ "source": "fixture",
+ "lastUpdated": "2026-05-13T15:58:42.000Z"
+ },
+ "flowPulseObservations": [
+ {
+ "id": "0x9717b7bad57bd0fa089e672e75cede4ae0bf3c86321dc0525ba00e9e0cc2da91",
+ "observationId": "0x9717b7bad57bd0fa089e672e75cede4ae0bf3c86321dc0525ba00e9e0cc2da91",
+ "pulseId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123456",
+ "blockHash": "0x2222222222222222222222222222222222222222222222222222222222222222",
+ "txHash": "0x3333333333333333333333333333333333333333333333333333333333333333",
+ "transactionIndex": "7",
+ "logIndex": "2",
+ "receiptStatus": "success",
+ "actor": "0x4444444444444444444444444444444444444444",
+ "pulseType": "1",
+ "subject": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "commitment": "0x4122209ff672fc04b2ec3af31ab1af79813971f86de000aa6534038cc79de6b5",
+ "parentPulseId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "sequence": "1",
+ "occurredAt": "2026-05-13T14:30:00.000Z",
+ "uri": "ipfs://bafy-flowmemory-example",
+ "summary": "Rootfield registration observed from fixture receipt log.",
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:51:00.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:51:00.000Z",
+ "localPathHint": "services/indexer/fixtures/flowpulse-logs.json"
+ }
+ },
+ {
+ "id": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "observationId": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "pulseId": "0xa3f9000000000000000000000000000000000000000000000000000000000112",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123468",
+ "blockHash": "0x2468246824682468246824682468246824682468246824682468246824682468",
+ "txHash": "0x5301530153015301530153015301530153015301530153015301530153015301",
+ "transactionIndex": "3",
+ "logIndex": "0",
+ "receiptStatus": "success",
+ "actor": "0x5555555555555555555555555555555555555555",
+ "pulseType": "2",
+ "subject": "0x6f6d65706174682d726f6f742d303030303030303030303030303030303031",
+ "commitment": "0x81d30d3de2b29b44f23745289dbf214026ed1789983ff7aac135d74531402031",
+ "parentPulseId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "sequence": "2",
+ "occurredAt": "2026-05-13T14:42:25.000Z",
+ "uri": "file://fixtures/rootfields/omega-path/root-0001.json",
+ "summary": "Root commitment finalized after local block threshold.",
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:53:12.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:53:12.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "observationId": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "pulseId": "0xa3f9000000000000000000000000000000000000000000000000000000000113",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123481",
+ "blockHash": "0x9595959595959595959595959595959595959595959595959595959595959595",
+ "txHash": "0x6060606060606060606060606060606060606060606060606060606060606060",
+ "transactionIndex": "1",
+ "logIndex": "1",
+ "receiptStatus": "success",
+ "actor": "0x5555555555555555555555555555555555555555",
+ "pulseType": "2",
+ "subject": "0x6f6d65706174682d726f6f742d303030303030303030303030303030303032",
+ "commitment": "0x61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff61ff",
+ "parentPulseId": "0xa3f9000000000000000000000000000000000000000000000000000000000112",
+ "sequence": "3",
+ "occurredAt": "2026-05-13T15:55:28.000Z",
+ "uri": "file://fixtures/rootfields/omega-path/root-0002.json",
+ "summary": "Recent root commitment observed below finality threshold.",
+ "status": "observed",
+ "lastUpdated": "2026-05-13T15:56:02.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:02.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0x7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e",
+ "observationId": "0x7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e",
+ "pulseId": "0xc0ffee0000000000000000000000000000000000000000000000000000000417",
+ "rootfieldId": "0xdededededededededededededededededededededededededededededededede",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123449",
+ "blockHash": "0x1717171717171717171717171717171717171717171717171717171717171717",
+ "txHash": "0x1818181818181818181818181818181818181818181818181818181818181818",
+ "transactionIndex": "9",
+ "logIndex": "3",
+ "receiptStatus": "success",
+ "actor": "0x9999999999999999999999999999999999999999",
+ "pulseType": "9",
+ "subject": "0xdededededededededededededededededededededededededededededededede",
+ "commitment": "0x9090909090909090909090909090909090909090909090909090909090909090",
+ "parentPulseId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "sequence": "1",
+ "occurredAt": "2026-05-13T13:12:14.000Z",
+ "uri": "file://fixtures/rootfields/unsupported-pulse.json",
+ "summary": "Unsupported pulse type preserved for verifier policy review.",
+ "status": "unsupported",
+ "lastUpdated": "2026-05-13T15:18:00.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:18:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0xabababababababababababababababababababababababababababababababab",
+ "observationId": "0xabababababababababababababababababababababababababababababababab",
+ "pulseId": "0xa3f90000000000000000000000000000000000000000000000000000000000ff",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123438",
+ "blockHash": "0xffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000",
+ "txHash": "0x4242424242424242424242424242424242424242424242424242424242424242",
+ "transactionIndex": "2",
+ "logIndex": "4",
+ "receiptStatus": "success",
+ "actor": "0x5555555555555555555555555555555555555555",
+ "pulseType": "2",
+ "subject": "0x72656f72672d726f6f742d3030303030303030303030303030303030303030",
+ "commitment": "0xe5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5",
+ "parentPulseId": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "sequence": "2",
+ "occurredAt": "2026-05-13T12:07:44.000Z",
+ "uri": "file://fixtures/rootfields/omega-path/reorged-root.json",
+ "summary": "Observation retained as noncanonical after local reorg fixture.",
+ "status": "reorged",
+ "lastUpdated": "2026-05-13T12:31:10.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T12:31:10.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "observationId": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "pulseId": "0x1212121212121212121212121212121212121212121212121212121212121212",
+ "rootfieldId": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "eventSignature": "0x5d07190b9ae441b4d7b16259a48424acd451492b12f5f99a29f5bfd992c13e43",
+ "blockNumber": "123482",
+ "blockHash": "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
+ "txHash": "0xb1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1",
+ "transactionIndex": "0",
+ "logIndex": "0",
+ "receiptStatus": "unknown",
+ "actor": "0x7777777777777777777777777777777777777777",
+ "pulseType": "2",
+ "subject": "0x70656e64696e672d726f6f742d3030303030303030303030303030303030",
+ "commitment": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
+ "parentPulseId": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "sequence": "0",
+ "occurredAt": "2026-05-13T15:58:20.000Z",
+ "uri": "file://fixtures/rootfields/pending-root.json",
+ "summary": "Pending candidate from local scan window before receipt finality.",
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "indexer",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ }
+ ],
+ "rootfields": [
+ {
+ "id": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "owner": "0x4444444444444444444444444444444444444444",
+ "schemaHash": "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
+ "metadataHash": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+ "latestRoot": "0x6f6d65706174682d726f6f742d303030303030303030303030303030303032",
+ "latestObservationId": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "pulseCount": 4,
+ "workLaneIds": ["lane-indexer-a", "lane-verifier-a"],
+ "evidenceUri": "file://fixtures/rootfields/omega-path/",
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:56:02.000Z",
+ "provenance": {
+ "subsystem": "contracts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:02.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0xdededededededededededededededededededededededededededededededede",
+ "rootfieldId": "0xdededededededededededededededededededededededededededededededede",
+ "owner": "0x9999999999999999999999999999999999999999",
+ "schemaHash": "0x1919191919191919191919191919191919191919191919191919191919191919",
+ "metadataHash": "0x2929292929292929292929292929292929292929292929292929292929292929",
+ "latestRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "latestObservationId": "0x7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e",
+ "pulseCount": 1,
+ "workLaneIds": ["lane-verifier-b"],
+ "evidenceUri": "file://fixtures/rootfields/unsupported/",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T15:18:00.000Z",
+ "provenance": {
+ "subsystem": "contracts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:18:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "rootfieldId": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "owner": "0x7777777777777777777777777777777777777777",
+ "schemaHash": "0x3838383838383838383838383838383838383838383838383838383838383838",
+ "metadataHash": "0x3939393939393939393939393939393939393939393939393939393939393939",
+ "latestRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "latestObservationId": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "pulseCount": 0,
+ "workLaneIds": ["lane-indexer-a"],
+ "evidenceUri": "file://fixtures/rootfields/pending-root.json",
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "contracts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ }
+ ],
+ "workLanes": [
+ {
+ "id": "lane-indexer-a",
+ "laneId": "lane-indexer-a",
+ "name": "Indexer receipt ingest",
+ "queueDepth": 3,
+ "inflight": 1,
+ "completed24h": 37,
+ "p95LatencyMs": 840,
+ "operator": "local-codex-fixture",
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "lane-verifier-a",
+ "laneId": "lane-verifier-a",
+ "name": "Verifier policy v0",
+ "queueDepth": 1,
+ "inflight": 1,
+ "completed24h": 22,
+ "p95LatencyMs": 1280,
+ "operator": "local-codex-fixture",
+ "status": "observed",
+ "lastUpdated": "2026-05-13T15:56:37.000Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:37.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "lane-verifier-b",
+ "laneId": "lane-verifier-b",
+ "name": "Unsupported pulse review",
+ "queueDepth": 0,
+ "inflight": 0,
+ "completed24h": 4,
+ "p95LatencyMs": 2120,
+ "operator": "local-codex-fixture",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T15:19:05.000Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:19:05.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ }
+ ],
+ "workReceipts": [
+ {
+ "id": "0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f",
+ "receiptId": "0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f",
+ "laneId": "lane-verifier-a",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "observationId": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "reportId": "0x8080808080808080808080808080808080808080808080808080808080808080",
+ "workType": "verify-root-commitment",
+ "artifactUri": "file://fixtures/rootfields/omega-path/root-0001.json",
+ "startedAt": "2026-05-13T14:43:00.000Z",
+ "completedAt": "2026-05-13T14:43:01.120Z",
+ "resultHash": "0x7100710071007100710071007100710071007100710071007100710071007100",
+ "status": "verified",
+ "lastUpdated": "2026-05-13T14:43:01.120Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T14:43:01.120Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x0101010101010101010101010101010101010101010101010101010101010101",
+ "receiptId": "0x0101010101010101010101010101010101010101010101010101010101010101",
+ "laneId": "lane-indexer-a",
+ "rootfieldId": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "observationId": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "workType": "scan-receipt-window",
+ "artifactUri": "file://fixtures/devnet/block-123482.json",
+ "startedAt": "2026-05-13T15:58:20.000Z",
+ "resultHash": "0x1111110000001111110000001111110000001111110000001111110000001111",
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/indexer-state.json"
+ }
+ },
+ {
+ "id": "0x5656565656565656565656565656565656565656565656565656565656565656",
+ "receiptId": "0x5656565656565656565656565656565656565656565656565656565656565656",
+ "laneId": "lane-verifier-a",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "observationId": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "reportId": "0x5959595959595959595959595959595959595959595959595959595959595959",
+ "workType": "verify-root-commitment",
+ "artifactUri": "file://fixtures/rootfields/omega-path/root-0002.json",
+ "startedAt": "2026-05-13T15:56:04.000Z",
+ "completedAt": "2026-05-13T15:56:04.640Z",
+ "resultHash": "0x6161616161616161616161616161616161616161616161616161616161616161",
+ "status": "invalid",
+ "lastUpdated": "2026-05-13T15:56:04.640Z",
+ "provenance": {
+ "subsystem": "worker",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:04.640Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ }
+ ],
+ "verifierReports": [
+ {
+ "id": "0xe0ac708c933782ef6797b3ab4b6550cdd1a993fb1170361e5e05641ab801e66e",
+ "reportId": "0xe0ac708c933782ef6797b3ab4b6550cdd1a993fb1170361e5e05641ab801e66e",
+ "observationId": "0x9717b7bad57bd0fa089e672e75cede4ae0bf3c86321dc0525ba00e9e0cc2da91",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 3,
+ "checksTotal": 3,
+ "reasonCodes": [],
+ "reportHash": "0xe0ac708c933782ef6797b3ab4b6550cdd1a993fb1170361e5e05641ab801e66e",
+ "status": "verified",
+ "lastUpdated": "2026-05-13T15:51:31.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:51:31.000Z",
+ "localPathHint": "services/verifier/fixtures/artifacts.json"
+ }
+ },
+ {
+ "id": "0x8080808080808080808080808080808080808080808080808080808080808080",
+ "reportId": "0x8080808080808080808080808080808080808080808080808080808080808080",
+ "observationId": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 4,
+ "checksTotal": 4,
+ "reasonCodes": [],
+ "reportHash": "0x8080808080808080808080808080808080808080808080808080808080808080",
+ "status": "verified",
+ "lastUpdated": "2026-05-13T14:43:01.120Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T14:43:01.120Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x5959595959595959595959595959595959595959595959595959595959595959",
+ "reportId": "0x5959595959595959595959595959595959595959595959595959595959595959",
+ "observationId": "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 3,
+ "checksTotal": 4,
+ "reasonCodes": ["commitment.mismatch"],
+ "reportHash": "0x5959595959595959595959595959595959595959595959595959595959595959",
+ "status": "invalid",
+ "lastUpdated": "2026-05-13T15:56:04.640Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:04.640Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x6464646464646464646464646464646464646464646464646464646464646464",
+ "reportId": "0x6464646464646464646464646464646464646464646464646464646464646464",
+ "observationId": "0x7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e",
+ "rootfieldId": "0xdededededededededededededededededededededededededededededededede",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 1,
+ "checksTotal": 1,
+ "reasonCodes": ["pulse.type.unsupported"],
+ "reportHash": "0x6464646464646464646464646464646464646464646464646464646464646464",
+ "status": "unsupported",
+ "lastUpdated": "2026-05-13T15:18:11.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:18:11.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x7373737373737373737373737373737373737373737373737373737373737373",
+ "reportId": "0x7373737373737373737373737373737373737373737373737373737373737373",
+ "observationId": "0xabababababababababababababababababababababababababababababababab",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 1,
+ "checksTotal": 1,
+ "reasonCodes": ["observation.reorged"],
+ "reportHash": "0x7373737373737373737373737373737373737373737373737373737373737373",
+ "status": "reorged",
+ "lastUpdated": "2026-05-13T12:31:15.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T12:31:15.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x7474747474747474747474747474747474747474747474747474747474747474",
+ "reportId": "0x7474747474747474747474747474747474747474747474747474747474747474",
+ "observationId": "0xfedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210",
+ "rootfieldId": "0x3434343434343434343434343434343434343434343434343434343434343434",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 1,
+ "checksTotal": 2,
+ "reasonCodes": ["artifact.unavailable"],
+ "reportHash": "0x7474747474747474747474747474747474747474747474747474747474747474",
+ "status": "unresolved",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "0x7575757575757575757575757575757575757575757575757575757575757575",
+ "reportId": "0x7575757575757575757575757575757575757575757575757575757575757575",
+ "observationId": "0x2ad24d8e9df0a17f1f6a6427c1ea3b401dc2f16d9a5bb64c3c96206cb3f5e712",
+ "rootfieldId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "resolverPolicyId": "flowmemory.resolver.policy.v0.previous-fixture",
+ "verifierSpecVersion": "0",
+ "checksPassed": 2,
+ "checksTotal": 4,
+ "reasonCodes": ["report.stale_policy"],
+ "reportHash": "0x7575757575757575757575757575757575757575757575757575757575757575",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T14:12:00.000Z",
+ "provenance": {
+ "subsystem": "verifier",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T14:12:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ }
+ ],
+ "devnetBlocks": [
+ {
+ "id": "0x1717171717171717171717171717171717171717171717171717171717171717",
+ "blockNumber": 123449,
+ "blockHash": "0x1717171717171717171717171717171717171717171717171717171717171717",
+ "parentHash": "0x1616161616161616161616161616161616161616161616161616161616161616",
+ "stateRoot": "0x5050505050505050505050505050505050505050505050505050505050505050",
+ "receiptsRoot": "0x5151515151515151515151515151515151515151515151515151515151515151",
+ "timestamp": "2026-05-13T13:12:14.000Z",
+ "observationCount": 1,
+ "reportCount": 1,
+ "finalityDistance": 33,
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:45:00.000Z",
+ "provenance": {
+ "subsystem": "devnet",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:45:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0x2468246824682468246824682468246824682468246824682468246824682468",
+ "blockNumber": 123468,
+ "blockHash": "0x2468246824682468246824682468246824682468246824682468246824682468",
+ "parentHash": "0x2358235823582358235823582358235823582358235823582358235823582358",
+ "stateRoot": "0x6262626262626262626262626262626262626262626262626262626262626262",
+ "receiptsRoot": "0x6363636363636363636363636363636363636363636363636363636363636363",
+ "timestamp": "2026-05-13T14:42:25.000Z",
+ "observationCount": 1,
+ "reportCount": 1,
+ "finalityDistance": 14,
+ "status": "finalized",
+ "lastUpdated": "2026-05-13T15:45:00.000Z",
+ "provenance": {
+ "subsystem": "devnet",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:45:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0x9595959595959595959595959595959595959595959595959595959595959595",
+ "blockNumber": 123481,
+ "blockHash": "0x9595959595959595959595959595959595959595959595959595959595959595",
+ "parentHash": "0x9494949494949494949494949494949494949494949494949494949494949494",
+ "stateRoot": "0x7070707070707070707070707070707070707070707070707070707070707070",
+ "receiptsRoot": "0x7171717171717171717171717171717171717171717171717171717171717171",
+ "timestamp": "2026-05-13T15:55:28.000Z",
+ "observationCount": 1,
+ "reportCount": 1,
+ "finalityDistance": 1,
+ "status": "pending",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "devnet",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ },
+ {
+ "id": "0xffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000",
+ "blockNumber": 123438,
+ "blockHash": "0xffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000",
+ "parentHash": "0x4343434343434343434343434343434343434343434343434343434343434343",
+ "stateRoot": "0x8888000088880000888800008888000088880000888800008888000088880000",
+ "receiptsRoot": "0x9999000099990000999900009999000099990000999900009999000099990000",
+ "timestamp": "2026-05-13T12:07:44.000Z",
+ "observationCount": 1,
+ "reportCount": 1,
+ "finalityDistance": 0,
+ "status": "reorged",
+ "lastUpdated": "2026-05-13T12:31:10.000Z",
+ "provenance": {
+ "subsystem": "devnet",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T12:31:10.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ }
+ ],
+ "hardwareNodes": [
+ {
+ "id": "fm-node-lab-01",
+ "nodeId": "fm-node-lab-01",
+ "callsign": "Lab Gateway 01",
+ "role": "gateway",
+ "firmware": "flowrouter-v0.1.0-fixture",
+ "transport": "ethernet + meshtastic sidecar",
+ "lastHeartbeatAt": "2026-05-13T15:57:58.000Z",
+ "batteryPercent": 100,
+ "signalDbm": -51,
+ "temperatureC": 39,
+ "linkedWorkLaneId": "lane-indexer-a",
+ "locationHint": "bench-east",
+ "status": "observed",
+ "lastUpdated": "2026-05-13T15:58:42.000Z",
+ "provenance": {
+ "subsystem": "hardware",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:58:42.000Z",
+ "localPathHint": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ },
+ {
+ "id": "fm-node-router-03",
+ "nodeId": "fm-node-router-03",
+ "callsign": "Router Shelf 03",
+ "role": "router",
+ "firmware": "flowrouter-v0.1.0-fixture",
+ "transport": "wifi + lora control",
+ "lastHeartbeatAt": "2026-05-13T15:11:08.000Z",
+ "batteryPercent": 72,
+ "signalDbm": -88,
+ "temperatureC": 44,
+ "linkedWorkLaneId": "lane-verifier-b",
+ "locationHint": "workshop-shelf",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T15:11:08.000Z",
+ "provenance": {
+ "subsystem": "hardware",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:11:08.000Z",
+ "localPathHint": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ },
+ {
+ "id": "fm-node-field-07",
+ "nodeId": "fm-node-field-07",
+ "callsign": "Field Kit 07",
+ "role": "field-kit",
+ "firmware": "flowrouter-v0.1.0-fixture",
+ "transport": "meshtastic control only",
+ "locationHint": "packed-case",
+ "status": "offline",
+ "lastUpdated": "2026-05-13T14:20:00.000Z",
+ "provenance": {
+ "subsystem": "hardware",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T14:20:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ }
+ ],
+ "alerts": [
+ {
+ "id": "incident-2026-05-13-001",
+ "incidentId": "incident-2026-05-13-001",
+ "severity": "critical",
+ "title": "Verifier commitment mismatch",
+ "summary": "Report 0x5959 records a deterministic commitment mismatch for the latest omega-path root fixture.",
+ "openedAt": "2026-05-13T15:56:04.640Z",
+ "linkedObjectIds": [
+ "0x82a7c8ef9931b361451efc99d932f615e251b3320ac395545971fe2e64fd14ca",
+ "0x5959595959595959595959595959595959595959595959595959595959595959"
+ ],
+ "recommendedAction": "Compare the generated root artifact with the emitted commitment before promoting this fixture to verified.",
+ "status": "unresolved",
+ "lastUpdated": "2026-05-13T15:56:04.640Z",
+ "provenance": {
+ "subsystem": "alerts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:56:04.640Z",
+ "localPathHint": "fixtures/dashboard/generated/verifier-reports.json"
+ }
+ },
+ {
+ "id": "incident-2026-05-13-002",
+ "incidentId": "incident-2026-05-13-002",
+ "severity": "warning",
+ "title": "Hardware heartbeat stale",
+ "summary": "Router Shelf 03 has not reported a fresh heartbeat inside the fixture freshness window.",
+ "openedAt": "2026-05-13T15:41:00.000Z",
+ "linkedObjectIds": ["fm-node-router-03", "lane-verifier-b"],
+ "recommendedAction": "Check local power and radio sidecar telemetry before assigning verifier work to the node.",
+ "status": "stale",
+ "lastUpdated": "2026-05-13T15:41:00.000Z",
+ "provenance": {
+ "subsystem": "alerts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T15:41:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/hardware-heartbeats.json"
+ }
+ },
+ {
+ "id": "incident-2026-05-13-003",
+ "incidentId": "incident-2026-05-13-003",
+ "severity": "info",
+ "title": "Reorg replacement recorded",
+ "summary": "A noncanonical block and observation remain in the fixture so the explorer can show reorg history.",
+ "openedAt": "2026-05-13T12:31:10.000Z",
+ "linkedObjectIds": [
+ "0xffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000",
+ "0xabababababababababababababababababababababababababababababababab"
+ ],
+ "recommendedAction": "Keep the reorged records visible, but exclude them from current verified root summaries.",
+ "status": "verified",
+ "lastUpdated": "2026-05-13T13:02:00.000Z",
+ "provenance": {
+ "subsystem": "alerts",
+ "origin": "fixture",
+ "chainContext": "anvil-31337",
+ "fixturePath": "fixtures/dashboard/flowmemory-dashboard-v0.json",
+ "capturedAt": "2026-05-13T13:02:00.000Z",
+ "localPathHint": "fixtures/dashboard/generated/devnet-state.json"
+ }
+ }
+ ]
+}