Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
891 changes: 891 additions & 0 deletions apps/dashboard/public/data/flowmemory-dashboard-base-canary-v0.json

Large diffs are not rendered by default.

18 changes: 13 additions & 5 deletions apps/dashboard/scripts/sync-fixtures.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { copyFileSync, mkdirSync } from "node:fs";
import { copyFileSync, existsSync, 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");
const fixtures = [
"flowmemory-dashboard-v0.json",
"flowmemory-dashboard-base-canary-v0.json",
];

mkdirSync(destinationDir, { recursive: true });
copyFileSync(source, destination);

console.log(`Synced dashboard fixture: ${destination}`);
for (const fixture of fixtures) {
const source = resolve(repoRoot, "fixtures/dashboard", fixture);
const destination = resolve(destinationDir, fixture);
if (existsSync(source)) {
copyFileSync(source, destination);
console.log(`Synced dashboard fixture: ${destination}`);
}
}
19 changes: 13 additions & 6 deletions apps/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ 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 { DEFAULT_CANARY_DASHBOARD_DATA_PATH, fetchDashboardData } from "./data/loadDashboardData";
import type { DashboardData } from "./data/types";
import { AlertsView } from "./views/AlertsView";
import { CanaryDeploymentView } from "./views/CanaryDeploymentView";
import { DevnetBlocksView } from "./views/DevnetBlocksView";
import { FlowMemoryView } from "./views/FlowMemoryView";
import { FlowPulseStreamView } from "./views/FlowPulseStreamView";
Expand Down Expand Up @@ -52,22 +53,27 @@ function ErrorState({ message, onRetry }: { message: string; onRetry: () => void

export default function App() {
const [data, setData] = useState<DashboardData | null>(null);
const [canaryData, setCanaryData] = useState<DashboardData | null>(null);
const [error, setError] = useState<string | null>(null);
const [version, setVersion] = useState(0);

useEffect(() => {
let cancelled = false;

fetchDashboardData()
.then((nextData) => {
Promise.all([
fetchDashboardData(),
fetchDashboardData(DEFAULT_CANARY_DASHBOARD_DATA_PATH),
])
.then(([nextData, nextCanaryData]) => {
if (!cancelled) {
setData(nextData);
setCanaryData(nextCanaryData);
setError(null);
}
})
.catch((nextError: unknown) => {
if (!cancelled) {
setError(nextError instanceof Error ? nextError.message : "Unknown fixture load error.");
setError(nextError instanceof Error ? nextError.message : "Unknown dashboard data load error.");
}
});

Expand All @@ -80,14 +86,15 @@ export default function App() {
return <ErrorState message={error} onRetry={() => setVersion((current) => current + 1)} />;
}

if (data === null) {
if (data === null || canaryData === null) {
return <LoadingState />;
}

return (
<AppShell data={data}>
<AppShell data={data} canaryData={canaryData}>
<Routes>
<Route path="/" element={<OverviewView data={data} />} />
<Route path="/canary" element={<CanaryDeploymentView data={canaryData} />} />
<Route path="/flowmemory" element={<FlowMemoryView data={data} />} />
<Route path="/flowpulse" element={<FlowPulseStreamView data={data} />} />
<Route path="/rootfields" element={<RootfieldsView data={data} />} />
Expand Down
6 changes: 5 additions & 1 deletion apps/dashboard/src/components/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
BrainCircuit,
Boxes,
ClipboardCheck,
RadioReceiver,
LayoutDashboard,
Network,
RadioTower,
Expand All @@ -18,11 +19,13 @@ import { StatusBadge } from "./StatusBadge";

interface AppShellProps {
data: DashboardData;
canaryData?: DashboardData;
children: ReactNode;
}

const NAV_ITEMS = [
{ to: "/", label: "Overview", icon: LayoutDashboard },
{ to: "/canary", label: "Base canary", icon: RadioReceiver },
{ to: "/flowmemory", label: "Flow Memory", icon: BrainCircuit },
{ to: "/flowpulse", label: "FlowPulse", icon: Activity },
{ to: "/rootfields", label: "Rootfields", icon: Boxes },
Expand All @@ -34,7 +37,7 @@ const NAV_ITEMS = [
{ to: "/raw", label: "Raw JSON", icon: Braces },
];

export function AppShell({ data, children }: AppShellProps) {
export function AppShell({ data, canaryData, children }: AppShellProps) {
return (
<div className="app-shell">
<aside className="sidebar">
Expand All @@ -58,6 +61,7 @@ export function AppShell({ data, children }: AppShellProps) {
<div className="sidebar-footer">
<StatusBadge status="observed" compact />
<span>{data.metadata.mode} data</span>
{canaryData ? <span>{canaryData.metadata.mode} data ready</span> : null}
<small>{data.chain.name}</small>
</div>
</aside>
Expand Down
5 changes: 3 additions & 2 deletions apps/dashboard/src/data/loadDashboardData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { DashboardData } from "./types";

export const DEFAULT_DASHBOARD_DATA_PATH = "/data/flowmemory-dashboard-v0.json";
export const DEFAULT_CANARY_DASHBOARD_DATA_PATH = "/data/flowmemory-dashboard-base-canary-v0.json";

function assertArray(value: unknown, label: string): void {
if (!Array.isArray(value)) {
Expand All @@ -17,8 +18,8 @@ export function validateDashboardData(payload: unknown): DashboardData {
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.metadata.mode !== "fixture" && candidate.metadata.mode !== "canary") {
throw new Error("Dashboard V0 expects fixture or canary mode data.");
}
if (candidate.chain === undefined) {
throw new Error("Dashboard fixture is missing chain context.");
Expand Down
31 changes: 29 additions & 2 deletions apps/dashboard/src/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,37 @@ export interface DashboardChainContext {
export interface FixtureMetadata {
schema: "flowmemory.dashboard.fixture.v0";
generatedAt: string;
mode: "fixture";
mode: "fixture" | "canary";
description: string;
fixturePath: string;
runtimeDataPath: string;
canary?: {
deploymentArtifactPath: string;
indexerStatePath: string;
checkpointPath: string;
docsPath: string;
productionReady: false;
readWindow: {
fromBlock: string;
toBlock: string;
finalizedBlock?: string;
};
counts: {
observations: number;
rejectedLogs: number;
duplicates: number;
contracts: number;
};
contracts: Array<{
name: string;
sourceName: string;
address: string;
deployTx: string;
block: string;
emitsFlowPulse: boolean;
}>;
boundaries: string[];
};
futureGeneratedPaths: {
indexer: string;
verifier: string;
Expand Down Expand Up @@ -276,7 +303,7 @@ export interface AgentMemoryView extends ProvenancedRecord {
receiptIds: string[];
transitionIds: string[];
warnings: string[];
localOnly: true;
localOnly: boolean;
}

export interface DevnetBlock extends ProvenancedRecord {
Expand Down
61 changes: 61 additions & 0 deletions apps/dashboard/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,66 @@ small {
background: rgba(251, 252, 248, 0.72);
}

.canary-hero {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(300px, 0.36fr);
gap: 14px;
align-items: stretch;
padding: 16px;
border: 1px solid #c9d5ce;
border-radius: 8px;
background:
linear-gradient(135deg, rgba(251, 252, 248, 0.94), rgba(229, 239, 235, 0.92)),
linear-gradient(90deg, rgba(47, 125, 107, 0.18), transparent);
box-shadow: var(--shadow);
}

.canary-hero h2 {
max-width: 760px;
margin: 0;
font-size: 1.52rem;
line-height: 1.14;
}

.canary-hero p {
max-width: 84ch;
margin: 10px 0 0;
color: #465047;
line-height: 1.5;
}

.canary-read-window {
display: grid;
grid-template-columns: 28px 1fr;
gap: 10px;
align-items: start;
padding: 12px;
border: 1px solid #bfd1c7;
border-radius: 8px;
background: rgba(251, 252, 248, 0.78);
}

.canary-read-window dl {
display: grid;
gap: 9px;
margin: 0;
}

.canary-boundaries {
display: grid;
gap: 8px;
padding-top: 12px;
}

.canary-boundaries span {
padding: 9px 10px;
color: #4d564d;
border: 1px solid var(--line);
border-radius: 7px;
background: #f8f9f4;
line-height: 1.35;
}

.metric-tile,
.panel,
.table-panel,
Expand Down Expand Up @@ -976,6 +1036,7 @@ dd {
}

.flowmemory-hero,
.canary-hero,
.flowmemory-spine,
.contract-event-list dl,
.bundle-grid {
Expand Down
14 changes: 14 additions & 0 deletions apps/dashboard/src/test/dashboardData.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import canaryFixture from "../../../../fixtures/dashboard/flowmemory-dashboard-base-canary-v0.json";
import fixture from "../../../../fixtures/dashboard/flowmemory-dashboard-v0.json";
import { validateDashboardData } from "../data/loadDashboardData";
import { DASHBOARD_STATUSES } from "../data/status";
Expand All @@ -7,6 +8,7 @@ import type { DashboardData, ProvenancedRecord } from "../data/types";

describe("dashboard fixture", () => {
const data = validateDashboardData(fixture) as DashboardData;
const canaryData = validateDashboardData(canaryFixture) as DashboardData;

it("loads the V0 dashboard fixture shape", () => {
expect(data.metadata.schema).toBe("flowmemory.dashboard.fixture.v0");
Expand All @@ -19,6 +21,18 @@ describe("dashboard fixture", () => {
expect(data.rootflowTransitions.every((transition) => transition.contractEventRef.signalId === transition.memorySignalId)).toBe(true);
});

it("loads the Base canary dashboard mode separately from local fixtures", () => {
expect(canaryData.metadata.schema).toBe("flowmemory.dashboard.fixture.v0");
expect(canaryData.metadata.mode).toBe("canary");
expect(canaryData.metadata.canary?.productionReady).toBe(false);
expect(canaryData.chain.environment).toBe("mainnet");
expect(canaryData.chain.source).toBe("live");
expect(canaryData.flowPulseObservations).toHaveLength(4);
expect(canaryData.verifierReports).toHaveLength(0);
expect(canaryData.memorySignals.some((signal) => signal.signalType === "swap_memory_signal")).toBe(true);
expect(canaryData.agentMemoryViews.every((view) => view.localOnly === false)).toBe(true);
});

it("covers every required dashboard status", () => {
const records: ProvenancedRecord[] = [
...data.flowPulseObservations,
Expand Down
Loading
Loading