diff --git a/frontend/src/app/error.tsx b/frontend/src/app/error.tsx
new file mode 100644
index 0000000..6275525
--- /dev/null
+++ b/frontend/src/app/error.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { useEffect } from "react";
+
+export default function Error({
+ error,
+ reset,
+}: {
+ error: Error & { digest?: string };
+ reset: () => void;
+}) {
+ useEffect(() => {
+ console.error(error);
+ }, [error]);
+
+ return (
+
+
Something went wrong
+
+ {error.message || "An unexpected client-side error occurred."}
+
+
+
+ );
+}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
index 39acc92..402d60e 100644
--- a/frontend/src/app/page.tsx
+++ b/frontend/src/app/page.tsx
@@ -200,7 +200,7 @@ export default function Dashboard() {
{memory.pinned && }
- {memory.hscore === null ? "—" : memory.hscore.toFixed(2)}
+ {memory.hscore == null ? "—" : memory.hscore.toFixed(2)}
))}
@@ -233,7 +233,7 @@ export default function Dashboard() {
{people.map((person, index) => (
- {person.name.slice(0, 2).toUpperCase()}
+ {(person.name || "?").slice(0, 2).toUpperCase()}
{person.name}
{person.memory_count} memories
@@ -242,7 +242,7 @@ export default function Dashboard() {
))}
{people.length === 0 && organizations.slice(0, 4).map((org, index) => (
- {org.name.slice(0, 2).toUpperCase()}
+ {(org.name || "?").slice(0, 2).toUpperCase()}
{org.name}
{org.memory_count} memories
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 114f269..3d3f9d4 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -52,7 +52,11 @@ async function fetchApi(path: string, options?: RequestInit): Promise {
} catch {}
throw new Error(`API error ${detail}`);
}
- return res.json();
+ try {
+ return (await res.json()) as T;
+ } catch {
+ throw new Error(`API error: invalid response from ${path}`);
+ }
}
export function wsUrl(): string {
diff --git a/server/cli.py b/server/cli.py
index bca1e33..e6089c7 100644
--- a/server/cli.py
+++ b/server/cli.py
@@ -1229,6 +1229,29 @@ async def _run() -> dict:
return 0
+def cmd_remove_demo(args: argparse.Namespace) -> int:
+ """Remove all demo-tagged memories, leaving real data untouched."""
+ import asyncio
+
+ from server.core import engine_provider
+
+ async def _run() -> dict:
+ engine = engine_provider.get_engine()
+ await engine.initialize()
+ try:
+ return await engine.remove_demo_data()
+ finally:
+ await engine.shutdown()
+
+ result = asyncio.run(_run())
+ removed = result.get("removed", 0)
+ if removed == 0:
+ print(" No demo data found — nothing to remove.")
+ else:
+ print(f" Removed {removed} demo memories.")
+ return 0
+
+
# ── mcp config ───────────────────────────────────────────────────
def cmd_mcp_config(args: argparse.Namespace) -> int:
@@ -1576,6 +1599,12 @@ def main() -> int:
help="Seed even if the store already has memories",
)
+ # remove-demo (onboarding: strip the demo corpus back out)
+ sub.add_parser(
+ "remove-demo",
+ help="Remove demo-tagged memories, leaving real data untouched",
+ )
+
# entities (persistent entity knowledge graph)
ent_p = sub.add_parser("entities", help="Persistent entity knowledge graph")
ent_sub = ent_p.add_subparsers(dest="entities_command")
@@ -1654,6 +1683,8 @@ def main() -> int:
return cmd_purge(args)
elif args.command == "seed-demo":
return cmd_seed_demo(args)
+ elif args.command == "remove-demo":
+ return cmd_remove_demo(args)
elif args.command == "entities":
if args.entities_command in ("reindex", "list", "about"):
return cmd_entities(args)