f85b648d97
* feat: notify user when a kandev update is detected Adds a background-poller-driven update notification with a saved preference for delivery channel (desktop / in-app banner / both) and an enable/disable switch on the Updates settings page. Backend: - internal/system/updates: NotifySettings + NotifyStore persist the enable/channel preference on the existing install-wide settings table; notifyIfNewUpdate broadcasts a WS event the first time a newer release is observed, deduped durably via kandev_meta so a restart or repeat poll doesn't re-announce the same version. - New GET/PUT /api/v1/system/updates/notification-settings endpoints. - pkg/websocket: new system.update_available action. Desktop (Tauri): - native_notifications.rs: task_id is now optional (Option<String>, required only for session-scoped events) and the event allowlist accepts system.update_available: alongside the existing session events, so the update notification can use the native bridge. Frontend: - New UpdateNotificationsCard on the Updates settings page (Switch + channel Select), wired through the shared settings-save-provider floating Save/Discard action. - useUpdateAvailableToast bridges the WS event to an in-app toast and/or the native/browser desktop notification, per the saved channel, deduped per version. * docs: document the update notification channel setting * fix: hydrate update notification settings and broadcast delivery ack - Only mark a release notification as delivered when the broadcaster reports at least one connected client, closing a race where an update could be silently dropped before the first client connects. - Hydrate updateNotificationSettings through the settings boot payload (loadSettingsInitialState -> hydrateState) instead of a component- level fetch, per the boot-payload -> store -> component data flow. Widen the hydration types (HydrationState) so callers can hydrate a partial system slice without fabricating the other system fields. UpdateNotificationsCard now only reads/writes the subscribed store state. - Fix module-scope settings-fetch cache leaking across tests in use-update-available-toast.test.ts so the fetch-failure test actually exercises the catch/fallback path. - Add API-client contract tests for fetchUpdateNotificationSettings and saveUpdateNotificationSettings (GET cache policy, PUT method and body). * test: cover delayed hydration and boot-payload mapping for update notifications
61 lines
2.7 KiB
TypeScript
61 lines
2.7 KiB
TypeScript
import { AppSidebar } from "@/components/app-sidebar/app-sidebar";
|
|
import { AppStatusSurfaceProvider } from "@/components/app-status-bar/app-status-surface-provider";
|
|
import { CommandPanel } from "@/components/command-panel";
|
|
import { ConfigChatProvider } from "@/components/config-chat/config-chat-provider";
|
|
import { DiffWorkerPoolProvider } from "@/components/diff-worker-pool-provider";
|
|
import { DesktopCommandHost } from "@/components/desktop-command-host";
|
|
import { GlobalCommands } from "@/components/global-commands";
|
|
import { LogBufferBridge } from "@/components/log-buffer-bridge";
|
|
import { QuickChatProvider } from "@/components/quick-chat/quick-chat-provider";
|
|
import { RecentTaskSwitcher } from "@/components/task/recent-task-switcher";
|
|
import { SessionFailureToastBridge } from "@/components/session-failure-toast-bridge";
|
|
import { TaskDeletedToastBridge } from "@/components/task-deleted-toast-bridge";
|
|
import { UpdateAvailableToastBridge } from "@/components/update-available-toast-bridge";
|
|
import { SidebarViewsSyncBridge } from "@/components/sidebar-views-sync-bridge";
|
|
import { ThemeProvider } from "@/components/theme-provider";
|
|
import { ToastProvider } from "@/components/toast-provider";
|
|
import { WebSocketConnector } from "@/components/ws-connector";
|
|
import { CommandRegistryProvider } from "@/lib/commands/command-registry";
|
|
import { Toaster as SonnerToaster } from "@kandev/ui/sonner";
|
|
import { TooltipProvider } from "@kandev/ui/tooltip";
|
|
|
|
type AppShellProps = {
|
|
children: React.ReactNode;
|
|
};
|
|
|
|
export function AppShell({ children }: AppShellProps) {
|
|
return (
|
|
<ThemeProvider>
|
|
<DiffWorkerPoolProvider>
|
|
<TooltipProvider>
|
|
<ToastProvider>
|
|
<SonnerToaster richColors position="top-right" />
|
|
<SessionFailureToastBridge />
|
|
<TaskDeletedToastBridge />
|
|
<UpdateAvailableToastBridge />
|
|
<SidebarViewsSyncBridge />
|
|
<LogBufferBridge />
|
|
<CommandRegistryProvider>
|
|
<DesktopCommandHost />
|
|
<WebSocketConnector />
|
|
<GlobalCommands />
|
|
<CommandPanel />
|
|
<RecentTaskSwitcher />
|
|
<ConfigChatProvider>
|
|
<QuickChatProvider>
|
|
<AppStatusSurfaceProvider>
|
|
<div className="flex min-h-0 flex-1 overflow-hidden">
|
|
<AppSidebar />
|
|
<main className="flex min-h-0 min-w-0 flex-1 flex-col">{children}</main>
|
|
</div>
|
|
</AppStatusSurfaceProvider>
|
|
</QuickChatProvider>
|
|
</ConfigChatProvider>
|
|
</CommandRegistryProvider>
|
|
</ToastProvider>
|
|
</TooltipProvider>
|
|
</DiffWorkerPoolProvider>
|
|
</ThemeProvider>
|
|
);
|
|
}
|