From f48c7a40cda7a449fb61db58f46f4c431193d1da Mon Sep 17 00:00:00 2001 From: jeremy Date: Sat, 11 Jul 2026 11:32:14 -0500 Subject: [PATCH] fix: make LifeOS actually render in local mode The container served a blank page. Four upstream assumptions broke a root-served, Supabase-less deployment; all are patched at build time with grep assertions so a silent upstream rename fails the build instead of shipping a blank page again. - React Router was pinned to basename '/app' (upstream is hosted under an /app path). At '/' it matched no route and rendered nothing -- silently, with only a console warning. This was the blank page. - supabase.ts calls createClient() at module scope, which throws 'supabaseUrl is required' on an empty string and killed the bundle before React mounted. Dummy values fix it; nothing reads Supabase in local mode. - initAuth() only booted the local user for Tauri/Electron, so a browser build fell through to a Supabase login screen the backend cannot serve (it has no /api/auth/login or /api/auth/signup -- it is single-user). - The local API adapter has no realtime layer, so supabase.channel() was undefined and every subscribing component tripped the ErrorBoundary ('Something went wrong'). Added no-op stubs; realtime is meaningless on a single-user instance. Verified in a browser: dashboard and routed pages render, 0 fatal errors. Known gap: the Flask backend implements only a subset of the cloud schema, so quests, messages, partnerships, grocery_lists, recurring_transactions, junction_traditions and habit_suggestions 404. Non-fatal; those features degrade. Core (tasks/habits/goals/health/finance/journal/XP/AI chat) works. Co-Authored-By: Claude Opus 4.8 --- compose/hf-pdocker-02/lifeos/Dockerfile | 31 +++++++++++++++++++ .../lifeos/local-api-realtime-shim.ts | 30 ++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 compose/hf-pdocker-02/lifeos/local-api-realtime-shim.ts diff --git a/compose/hf-pdocker-02/lifeos/Dockerfile b/compose/hf-pdocker-02/lifeos/Dockerfile index fe086b3..e67f5e1 100644 --- a/compose/hf-pdocker-02/lifeos/Dockerfile +++ b/compose/hf-pdocker-02/lifeos/Dockerfile @@ -15,10 +15,41 @@ RUN npm ci --ignore-scripts COPY . . +# Upstream is hosted under an /app base path and hardcodes it in two places. +# We serve at the root, so both must be rewritten or the app renders a blank page: +# - App.tsx pins React Router to basename "/app"; at "/" it matches no route and +# renders NOTHING (silent -- only a console warning, no error). +# - index.html points the PWA manifest and touch icons at /app/... +RUN sed -i 's|basename: "/app"|basename: "/"|' src/App.tsx \ + && sed -i 's|"/app/|"/|g' index.html \ + && grep -q 'basename: "/"' src/App.tsx \ + && ! grep -q '"/app/' index.html + +# initAuth() only boots the single local user for Tauri/Electron, so a browser +# build in local-api mode falls through to a Supabase login screen instead -- +# one the backend cannot serve (it has no /api/auth/login or /api/auth/signup +# routes, being single-user by design). Treat local-api like the desktop envs. +RUN sed -i "s@if (detectedEnv === 'tauri' || detectedEnv === 'electron') {@if (detectedEnv === 'tauri' || detectedEnv === 'electron' || detectedEnv === 'local-api') {@" src/stores/useUserStore.ts \ + && grep -q "detectedEnv === 'local-api') {" src/stores/useUserStore.ts + +# The local API adapter has no realtime layer, so supabase.channel() is undefined +# and every subscribing component crashes the app. Append no-op stubs. +COPY local-api-realtime-shim.ts /tmp/shim.ts +RUN cat /tmp/shim.ts >> src/lib/local-api.ts && rm /tmp/shim.ts + # Routes the data layer (src/lib/data-access.ts) to the local Flask API instead # of Supabase Cloud. VITE_API_BASE_URL is deliberately left unset: local-api.ts # then falls back to a relative /api, so the bundle works on any host and port. ENV VITE_USE_LOCAL_API=true + +# Dummy Supabase values. Nothing reads Supabase in local mode, but src/lib/supabase.ts +# calls createClient() at module scope, and createClient throws "supabaseUrl is +# required" on an empty string -- which kills the bundle before React mounts, leaving +# a blank page. Its own comment claims it only warns; it does not. A syntactically +# valid dummy URL constructs fine and is never called. +ENV VITE_SUPABASE_URL=https://noop.invalid +ENV VITE_SUPABASE_ANON_KEY=noop + ENV NODE_OPTIONS=--max-old-space-size=3072 RUN npm run build diff --git a/compose/hf-pdocker-02/lifeos/local-api-realtime-shim.ts b/compose/hf-pdocker-02/lifeos/local-api-realtime-shim.ts new file mode 100644 index 0000000..b9141dc --- /dev/null +++ b/compose/hf-pdocker-02/lifeos/local-api-realtime-shim.ts @@ -0,0 +1,30 @@ + +// ─── Realtime no-op shim (appended to local-api.ts at image build) ─── +// +// The local Flask API has no websocket/realtime layer -- upstream's LOCAL_MODE.md +// says so outright ("Realtime subscriptions -- NOT migrated"), but the adapter it +// ships still lacks `.channel()`, so every component that opens a subscription +// (useSubscription, useCurrentEvent, social, realm multiplayer) throws +// "TypeError: supabase.channel is not a function" and trips the ErrorBoundary -- +// the whole app renders "Something went wrong". +// +// These stubs satisfy the call shape and do nothing. Reads still work: the app +// fetches through the normal query path. What is lost is live push updates, which +// are meaningless on a single-user instance with no other clients to sync from. +Object.assign(supabase as any, { + channel: (_name?: string, _opts?: any) => { + const ch: any = { + on: () => ch, // chainable, never fires + subscribe: (cb?: (status: string) => void) => { cb?.('SUBSCRIBED'); return ch; }, + unsubscribe: async () => 'ok', + presenceState: () => ({}), // realm multiplayer: nobody else here + track: async () => 'ok', + untrack: async () => 'ok', + send: async () => 'ok', + }; + return ch; + }, + removeChannel: async () => 'ok', + removeAllChannels: async () => 'ok', + getChannels: () => [], +});