mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
The Settings page PUT four keys (notifications_enabled, sound_enabled, auto_refresh, refresh_interval) the backend's settings allowlist never accepted — Save died on the first 422 and had never persisted these cards. Worse, nothing consumed the prefs anywhere: no auto-refresh timer, no notification toast, no sound system existed. Pure theater. - the four prefs move into the persisted UI store (client-only, same idiom as theme/sidebar) and the cards apply instantly; the dead server plumbing and the global Save button are gone — the backend allowlist stays strict and untouched - AutoRefreshDriver (new): when Auto Refresh is on, ticks the page-refresh registry every N seconds — skips while nothing is registered or a refresh is in flight; default-off so no background poller starts unasked - NotificationAlerts (new): toasts each newly-arrived WS notification (subject + priority) when notifications are enabled, with an optional ~120ms Web-Audio chime — initial backlog on connect never toasts, one chime per batch, autoplay blocks never throw - tests: settings page rewritten store-driven; fake-timer coverage for the driver; stream/store/sonner/AudioContext-mocked coverage for alerts Co-authored-by: Renn F <rennf93@users.noreply.github.com>
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { create } from "zustand";
|
|
import { persist } from "zustand/middleware";
|
|
import type { Team } from "@/types";
|
|
|
|
interface UIState {
|
|
// Sidebar
|
|
sidebarOpen: boolean;
|
|
sidebarCollapsed: boolean;
|
|
|
|
// Theme
|
|
theme: "light" | "dark" | "system";
|
|
|
|
// Current context
|
|
currentTeam: Team | null;
|
|
|
|
// A2A live view: xl:+ context pane collapse (conversation-first layout
|
|
// design doc §1) — same persisted-preference idiom as sidebar/theme.
|
|
a2aContextOpen: boolean;
|
|
|
|
// Client-only Settings-page prefs (never sent to the backend — the
|
|
// server's settings allowlist is transcript_retention_days + feature
|
|
// flags only). Same persisted-preference idiom as sidebar/theme.
|
|
notificationsEnabled: boolean;
|
|
soundEnabled: boolean;
|
|
autoRefresh: boolean;
|
|
refreshIntervalSeconds: number;
|
|
|
|
// Actions
|
|
toggleSidebar: () => void;
|
|
setSidebarCollapsed: (collapsed: boolean) => void;
|
|
setTheme: (theme: "light" | "dark" | "system") => void;
|
|
setCurrentTeam: (team: Team | null) => void;
|
|
toggleA2AContext: () => void;
|
|
setNotificationsEnabled: (enabled: boolean) => void;
|
|
setSoundEnabled: (enabled: boolean) => void;
|
|
setAutoRefresh: (enabled: boolean) => void;
|
|
setRefreshIntervalSeconds: (seconds: number) => void;
|
|
}
|
|
|
|
export const useUIStore = create<UIState>()(
|
|
persist(
|
|
(set) => ({
|
|
sidebarOpen: true,
|
|
sidebarCollapsed: false,
|
|
theme: "system",
|
|
currentTeam: null,
|
|
a2aContextOpen: true,
|
|
notificationsEnabled: true,
|
|
soundEnabled: true,
|
|
autoRefresh: false, // default-off: never start a background poller unasked
|
|
refreshIntervalSeconds: 30,
|
|
|
|
toggleSidebar: () =>
|
|
set((state) => ({ sidebarOpen: !state.sidebarOpen })),
|
|
setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
|
|
setTheme: (theme) => set({ theme }),
|
|
setCurrentTeam: (team) => set({ currentTeam: team }),
|
|
toggleA2AContext: () =>
|
|
set((state) => ({ a2aContextOpen: !state.a2aContextOpen })),
|
|
setNotificationsEnabled: (enabled) =>
|
|
set({ notificationsEnabled: enabled }),
|
|
setSoundEnabled: (enabled) => set({ soundEnabled: enabled }),
|
|
setAutoRefresh: (enabled) => set({ autoRefresh: enabled }),
|
|
setRefreshIntervalSeconds: (seconds) =>
|
|
set({ refreshIntervalSeconds: seconds }),
|
|
}),
|
|
{
|
|
name: "roboco-ui-storage",
|
|
partialize: (state) => ({
|
|
sidebarCollapsed: state.sidebarCollapsed,
|
|
theme: state.theme,
|
|
currentTeam: state.currentTeam,
|
|
a2aContextOpen: state.a2aContextOpen,
|
|
notificationsEnabled: state.notificationsEnabled,
|
|
soundEnabled: state.soundEnabled,
|
|
autoRefresh: state.autoRefresh,
|
|
refreshIntervalSeconds: state.refreshIntervalSeconds,
|
|
}),
|
|
},
|
|
),
|
|
);
|