039 — Unify useCaps on usePolledReader<Caps>
What to build
useCaps (src/hooks/useCaps.ts) is a one-shot reader pattern: returns Caps directly, silently falls back to DEFAULT_CAPS on error, no loading state. useFocuses and useProposals (now collapsed in 037/038) use the polled pattern: loading | ready | error. Two patterns for the same job.
Unify on usePolledReader<Caps>. Preserve current UX (silent default fallback, no loading flash) by mapping the polled state at the call site.
Changes
src/api/caps.ts — CapsReader becomes PolledReader<Caps>. get() renames to read(). Drop the subscribe? field if caps are not pushed by Tauri (currently they are not — caps are read once at startup).
src/api/caps.ts — createTauriCapsReader and createFixtureCapsReader return PolledReader<Caps>.
src/hooks/useCaps.ts — delete.
src/hooks/useAppState.ts (line 30) — replace useCaps(capsReader) with:
const capsState = usePolledReader<Caps>(capsReader);
const caps = capsState.status === "ready" ? capsState.value : DEFAULT_CAPS;
useAppState re-exports caps: Caps to match the current consumer contract — no downstream component needs to handle a loading/error variant.
Behavior contract
- Silent fallback to
DEFAULT_CAPS on error preserved.
- Caps surface to consumers as
Caps, not as a polled state. (Other catalogs surface the full polled state because they need to render loading/empty UI; caps drive only badge thresholds where defaults are correct.)
Out of scope
- Pushing live cap-config updates from Tauri (would need
subscribe); caps still load once at startup.
- Generic factory (040).
Completion promise
useCaps no longer exists. Caps load through usePolledReader<Caps>; the silent-default UX is preserved by collapsing the polled state to Caps inside useAppState.
Acceptance criteria
Blocked by
037 — establish the rename and call-site pattern in the simpler Focus pipeline first.
039 — Unify
useCapsonusePolledReader<Caps>What to build
useCaps(src/hooks/useCaps.ts) is a one-shot reader pattern: returnsCapsdirectly, silently falls back toDEFAULT_CAPSon error, no loading state.useFocusesanduseProposals(now collapsed in 037/038) use the polled pattern:loading | ready | error. Two patterns for the same job.Unify on
usePolledReader<Caps>. Preserve current UX (silent default fallback, no loading flash) by mapping the polled state at the call site.Changes
src/api/caps.ts—CapsReaderbecomesPolledReader<Caps>.get()renames toread(). Drop thesubscribe?field if caps are not pushed by Tauri (currently they are not — caps are read once at startup).src/api/caps.ts—createTauriCapsReaderandcreateFixtureCapsReaderreturnPolledReader<Caps>.src/hooks/useCaps.ts— delete.src/hooks/useAppState.ts(line 30) — replaceuseCaps(capsReader)with:useAppStatere-exportscaps: Capsto match the current consumer contract — no downstream component needs to handle a loading/error variant.Behavior contract
DEFAULT_CAPSon error preserved.Caps, not as a polled state. (Other catalogs surface the full polled state because they need to render loading/empty UI; caps drive only badge thresholds where defaults are correct.)Out of scope
subscribe); caps still load once at startup.Completion promise
useCapsno longer exists. Caps load throughusePolledReader<Caps>; the silent-default UX is preserved by collapsing the polled state toCapsinsideuseAppState.Acceptance criteria
CapsReaderisPolledReader<Caps>(or aliased to it).src/hooks/useCaps.tsdeleted.useAppStateusesusePolledReader<Caps>and falls back toDEFAULT_CAPSon non-ready states.task checkgreen.Blocked by
037 — establish the rename and call-site pattern in the simpler Focus pipeline first.