with one click
bedrud-fe-state
Frontend state management — all 10 Zustand 5 stores.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Frontend state management — all 10 Zustand 5 stores.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Admin endpoints — users, rooms, queue, settings, invite tokens, webhooks, overview, recordings.
Auth endpoints — JWT flow, local/OAuth/passkey/verify/password-reset/avatar/preferences.
Room endpoints — CRUD, join, moderation, presence, chat upload, stage stubs, recording.
Complete Bedrud API endpoint reference. Dense index of every route, middleware, status code; deep detail in leaf skills.
All DTO definitions, source file index, Swagger reference.
Auth service + middleware — JWT, passkeys, OAuth, session store, rate limiting, recordings gate.
| name | bedrud-fe-state |
| description | Frontend state management — all 10 Zustand 5 stores. |
| license | Apache License |
React SPA. apps/web/. Zustand 5. All stores live in apps/web/src/lib/*.store.ts.
Related (not stores): user-preferences.ts deep-merges server-side prefs blobs (audio / video / experimental / interface) via PATCH-style put to /api/auth/preferences.
| File | Hook | Persist key | Notes |
|---|---|---|---|
auth.store.ts | useAuthStore | manual: auth_remember, auth_at | Not zustand/persist |
user.store.ts | useUserStore | — | In-memory only |
theme.store.ts | useThemeStore | theme | DOM dark class |
audio-preferences.store.ts | useAudioPreferencesStore | audio-preferences | PTT + NS + gain |
video-preferences.store.ts | useVideoPreferencesStore | video-preferences | Webcam mirror |
experimental-preferences.store.ts | useExperimentalPreferencesStore | experimental-preferences | Whiteboard / YouTube |
interface-preferences.store.ts | useInterfacePreferencesStore | interface-preferences | Welcome screen |
recent-rooms.store.ts | useRecentRoomsStore | bedrud-recent-rooms | Cap 20, dedupe on rehydrate |
participant-overrides.store.ts | useParticipantOverridesStore | — | Map/Set, session only |
profile-sync.store.ts | useProfileSyncStore | — | Version bump signal |
src/lib/auth.store.ts — useAuthStoreState: { tokens: AuthTokens | null, initialized: boolean }
AuthTokens: { accessToken: string, refreshToken: string | null }
| Action | Purpose |
|---|---|
setTokens(tokens, remember?) | Store tokens. remember=true → localStorage; false or 'ephemeral' → sessionStorage |
updateAccessToken(accessToken) | Update access token in state + same storage as remember flag |
clear() | Clear tokens, reset initialized, wipe both storages' keys, reset init promise |
initialize() | Restore session: cookie POST /api/auth/refresh first → fallback persisted AT validated via GET /api/auth/me. Deduplicates concurrent calls via module _init.promise |
Storage keys: auth_remember, auth_at (localStorage and/or sessionStorage). Not zustand/persist.
src/lib/user.store.ts — useUserStoreState: { user: User | null }
User: {
id, email, name, provider: string
isSuperAdmin: boolean
isAdmin: boolean
accesses: string[] | null
avatarUrl?: string
}
Actions: setUser(user), clear(). Not persisted.
src/lib/theme.store.ts — useThemeStoreState: { theme: Theme } // default 'system'
Theme: 'light' | 'dark' | 'system'
Persist key: theme.
Actions: setTheme(theme) — update store; apply DOM dark class only if resolved theme differs (avoids fighting view-transitions).
Helpers (exported, not on store):
resolveTheme(theme) — 'system' → OS preference; SSR → 'light'applyTheme(theme) — toggle <html class="dark">; SSR no-opsrc/lib/audio-preferences.store.ts — useAudioPreferencesStoreState: AudioPreferences // persisted
AudioPreferences: {
noiseSuppressionMode: NoiseSuppressionMode // 'none'|'browser'|'rnnoise'|'krisp'; default 'browser'
echoCancellation: boolean // default true
autoGainControl: boolean // default true
inputGain: number // 0–300, default 100
noiseGate: number // 0–100, default 0
mutedBeepEnabled: boolean // default true
mutedBeepInterval: number // ms, default 3000
pushToTalkEnabled: boolean // default false
pushToTalkKey: string // KeyboardEvent.code, default 'Space' (DEFAULT_PUSH_TO_TALK_KEY)
}
Persist key: audio-preferences.
| Action | Notes |
|---|---|
setMode(mode) | NS mode |
setEchoCancellation(v) | |
setAutoGainControl(v) | |
setInputGain(v) | clamped 0–300 |
setNoiseGate(v) | clamped 0–100 |
setMutedBeepEnabled(v) | |
setMutedBeepInterval(v) | |
setPushToTalkEnabled(v) | |
setPushToTalkKey(key) | via normalizePushToTalkKey |
merge(partial) | field-wise; clamps gain/gate; normalizes PTT key |
src/lib/video-preferences.store.ts — useVideoPreferencesStoreState: VideoPreferences // persisted
VideoPreferences: { mirrorWebcam: boolean } // default true
Persist key: video-preferences.
Actions: setMirrorWebcam(enabled), merge(partial).
src/lib/experimental-preferences.store.ts — useExperimentalPreferencesStoreState: ExperimentalPreferences // persisted
ExperimentalPreferences: {
whiteboardEnabled: boolean // default false
youtubeEnabled: boolean // default false
whiteboardDisclaimerAcknowledged: boolean // default false; one-time disclaimer
}
Persist key: experimental-preferences.
| Action | Purpose |
|---|---|
setWhiteboardEnabled(enabled) | Toggle whiteboard feature |
setYoutubeEnabled(enabled) | Toggle YouTube feature |
acknowledgeWhiteboardDisclaimer() | Set disclaimer acknowledged true |
merge(partial) | Field-wise partial update |
src/lib/interface-preferences.store.ts — useInterfacePreferencesStoreState: InterfacePreferences // persisted
InterfacePreferences: { showWelcomeScreen: boolean } // default true
Persist key: interface-preferences.
Actions: setShowWelcomeScreen(enabled), merge(partial).
src/lib/recent-rooms.store.ts — useRecentRoomsStoreState: { rooms: RecentRoom[] } // default []
RecentRoom: { name: string, joinedAt: number }
Persist key: bedrud-recent-rooms.
| Action | Purpose |
|---|---|
add(name) | Prepend with joinedAt: Date.now(), dedupe by name, cap 20 |
remove(name) | Filter out by name |
clear() | Empty list |
Rehydrate: onRehydrateStorage dedupes rooms by name (first wins).
src/lib/participant-overrides.store.ts — useParticipantOverridesStoreState: {
volumes: Map<string, number> // identity → 0–2
muted: Set<string> // muted identities
}
Not persisted (session/in-memory only).
| Action | Purpose |
|---|---|
setVolume(identity, vol) | Clamped 0–2 |
toggleMute(identity) | Add/remove from muted set |
Selectors (factories — use in components for granular re-renders):
selectIsMuted(identity) → booleanselectVolume(identity) → 0 if muted, else map value or default 1const isMuted = useParticipantOverridesStore(selectIsMuted(identity))
const volume = useParticipantOverridesStore(selectVolume(identity))
src/lib/profile-sync.store.ts — useProfileSyncStoreState: { version: number } // default 0
Not persisted. Lightweight pub/sub for profile refresh.
Actions: bump() — version++. Consumers re-fetch profile when version changes.