Feature 06: PWA shell and offline (#58)
CI / ci (push) Successful in 1m40s

Co-authored-by: dev_agent <dev_agent@cubecraftlabs.com>
This commit was merged in pull request #58.
This commit is contained in:
2026-10-05 15:22:37 -04:00
committed by approver_agent
parent e6fd24db0a
commit 81c5085f9a
44 changed files with 7359 additions and 42 deletions
+19 -3
View File
@@ -1,4 +1,4 @@
import { useSyncExternalStore, type ReactNode } from 'react'
import { useEffect, useSyncExternalStore, type ReactNode } from 'react'
import { useQuery } from '@tanstack/react-query'
import { ApiError, ME_PATH, useApi } from './api/index.ts'
import { NotInHouseholdPage } from './pages/NotInHouseholdPage.tsx'
@@ -11,13 +11,29 @@ export const meQueryKey = [ME_PATH] as const
// AuthBootstrap loads GET /api/v1/me before rendering children. A 401 is
// already navigating to sign-in, so the skeleton stays up; an account outside
// the household sees only the "not in this household" screen.
//
// When /me cannot reach the server and it last answered with a member on this
// device, the saved pages open read-only: the service worker answers their
// reads from its cache, and every write is refused for want of a CSRF token.
// /me runs even while the browser reports offline, so that case fails fast
// instead of waiting, and it is fetched again when the browser is back online.
export function AuthBootstrap({ children }: { children: ReactNode }) {
const api = useApi()
const forbidden = useSyncExternalStore(api.subscribe, api.forbidden)
const me = useQuery({ queryKey: meQueryKey, queryFn: api.loadMe, staleTime: Infinity })
const me = useQuery({ queryKey: meQueryKey, queryFn: api.loadMe, staleTime: Infinity, networkMode: 'always' })
// fetch rejects with a TypeError only when the network fails.
const savedPages = me.isError && me.error instanceof TypeError && api.session.verified()
const { refetch } = me
useEffect(() => {
if (!savedPages) return
return api.network.subscribe(() => {
if (!api.network.offline()) void refetch()
})
}, [savedPages, api, refetch])
if (forbidden) return <NotInHouseholdPage />
if (me.isSuccess) return children
if (me.isSuccess || savedPages) return children
if (me.isError && !(me.error instanceof ApiError && me.error.status === 401)) {
return (
<main className={styles.screen}>