diff --git a/internal/serve/store/cost_store.go b/internal/serve/store/cost_store.go index 8056fd7..d5b6daa 100644 --- a/internal/serve/store/cost_store.go +++ b/internal/serve/store/cost_store.go @@ -34,6 +34,8 @@ import ( "errors" "fmt" "net/url" + "os" + "path/filepath" "sync" "time" @@ -92,6 +94,18 @@ type sqliteCostStore struct { // keeps the handler-side Writer from erroring under light contention // with the quota-subscriber goroutine. func OpenCostStore(path string) (CostStore, error) { + // Ensure the parent directory exists. Production opens the DB at + // ~/.config/ctm/ctm.db; on a fresh install (or in CI runners + // without a pre-existing config dir) the parent might not exist + // yet, and mattn/go-sqlite3 surfaces that as + // "unable to open database file". Cheap and idempotent — no-op + // for ":memory:" (filepath.Dir returns "."). Errors here are + // non-fatal: if mkdir fails we let sql.Open surface the real + // problem. + if path != ":memory:" { + _ = os.MkdirAll(filepath.Dir(path), 0o700) + } + // DSN tuning: ?_busy_timeout=5000 waits out brief writer locks; // ?_journal=WAL enables concurrent readers; ?_sync=NORMAL pairs // with WAL for an acceptable durability-vs-speed trade. diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 1d38e53..06cdb86 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -26,6 +26,7 @@ import { AuthGate } from "@/routes/AuthGate"; const router = createBrowserRouter([ { path: "/", element: }, { path: "/s/:name", element: }, + { path: "/s/:name/feed", element: }, { path: "/s/:name/checkpoints", element: }, { path: "/s/:name/pane", element: }, { path: "/s/:name/subagents", element: }, diff --git a/ui/src/routes/SessionDetail.tsx b/ui/src/routes/SessionDetail.tsx index 339fe92..2356d62 100644 --- a/ui/src/routes/SessionDetail.tsx +++ b/ui/src/routes/SessionDetail.tsx @@ -41,6 +41,7 @@ type TabKey = | "meta"; function tabFromPath(pathname: string): TabKey { + if (pathname.endsWith("/feed")) return "feed"; if (pathname.endsWith("/checkpoints")) return "checkpoints"; if (pathname.endsWith("/pane")) return "pane"; if (pathname.endsWith("/subagents")) return "subagents";