diff --git a/apps/web/app/settings/general/editors/page.tsx b/apps/web/app/settings/general/editors/page.tsx
index d5deab02b7..2ff417ca11 100644
--- a/apps/web/app/settings/general/editors/page.tsx
+++ b/apps/web/app/settings/general/editors/page.tsx
@@ -1,5 +1,40 @@
import { EditorsSettings } from '@/components/settings/editors-settings';
+import { StateProvider } from '@/components/state-provider';
+import { fetchUserSettings, listEditors } from '@/lib/http';
-export default function GeneralEditorsPage() {
- return ;
+export default async function GeneralEditorsPage() {
+ let initialState = {};
+ try {
+ const [editorsResponse, settingsResponse] = await Promise.all([
+ listEditors({ cache: 'no-store' }),
+ fetchUserSettings({ cache: 'no-store' }),
+ ]);
+ const settings = settingsResponse.settings;
+ initialState = {
+ editors: {
+ items: editorsResponse.editors ?? [],
+ loaded: true,
+ loading: false,
+ },
+ userSettings: settings
+ ? {
+ workspaceId: settings.workspace_id ?? null,
+ boardId: settings.board_id ?? null,
+ repositoryIds: settings.repository_ids ?? [],
+ preferredShell: settings.preferred_shell ?? null,
+ shellOptions: settingsResponse.shell_options ?? [],
+ defaultEditorId: settings.default_editor_id ?? null,
+ loaded: true,
+ }
+ : undefined,
+ };
+ } catch {
+ initialState = {};
+ }
+
+ return (
+
+
+
+ );
}
diff --git a/apps/web/app/settings/general/notifications/page.tsx b/apps/web/app/settings/general/notifications/page.tsx
index e1b5e192e7..a8a1363283 100644
--- a/apps/web/app/settings/general/notifications/page.tsx
+++ b/apps/web/app/settings/general/notifications/page.tsx
@@ -1,5 +1,27 @@
import { NotificationsSettings } from '@/components/settings/notifications-settings';
+import { StateProvider } from '@/components/state-provider';
+import { listNotificationProviders } from '@/lib/http';
-export default function GeneralNotificationsPage() {
- return ;
+export default async function GeneralNotificationsPage() {
+ let initialState = {};
+ try {
+ const response = await listNotificationProviders({ cache: 'no-store' });
+ initialState = {
+ notificationProviders: {
+ items: response.providers ?? [],
+ events: response.events ?? [],
+ appriseAvailable: response.apprise_available ?? false,
+ loaded: true,
+ loading: false,
+ },
+ };
+ } catch {
+ initialState = {};
+ }
+
+ return (
+
+
+
+ );
}
diff --git a/apps/web/app/settings/general/page.tsx b/apps/web/app/settings/general/page.tsx
index 95ac3bcde3..c29eb7a1d3 100644
--- a/apps/web/app/settings/general/page.tsx
+++ b/apps/web/app/settings/general/page.tsx
@@ -1,5 +1,32 @@
import { GeneralSettings } from '@/components/settings/general-settings';
+import { StateProvider } from '@/components/state-provider';
+import { fetchUserSettings } from '@/lib/http';
-export default function GeneralSettingsPage() {
- return ;
+export default async function GeneralSettingsPage() {
+ let initialState = {};
+ try {
+ const response = await fetchUserSettings({ cache: 'no-store' });
+ const settings = response.settings;
+ initialState = {
+ userSettings: settings
+ ? {
+ workspaceId: settings.workspace_id ?? null,
+ boardId: settings.board_id ?? null,
+ repositoryIds: settings.repository_ids ?? [],
+ preferredShell: settings.preferred_shell ?? null,
+ shellOptions: response.shell_options ?? [],
+ defaultEditorId: settings.default_editor_id ?? null,
+ loaded: true,
+ }
+ : undefined,
+ };
+ } catch {
+ initialState = {};
+ }
+
+ return (
+
+
+
+ );
}
diff --git a/apps/web/app/settings/layout.tsx b/apps/web/app/settings/layout.tsx
index 21dd14ae28..eb8d235126 100644
--- a/apps/web/app/settings/layout.tsx
+++ b/apps/web/app/settings/layout.tsx
@@ -6,10 +6,6 @@ import {
listEnvironments,
listExecutors,
listWorkspaces,
- fetchUserSettings,
- listPrompts,
- listNotificationProviders,
- listEditors,
} from '@/lib/http';
export default function SettingsLayout({
@@ -31,26 +27,13 @@ async function SettingsLayoutServer({ children }: { children: React.ReactNode })
environments,
agents,
discovery,
- userSettings,
- promptsResponse,
- notificationProviders,
- editorsResponse,
] = await Promise.all([
listWorkspaces({ cache: 'no-store' }),
listExecutors({ cache: 'no-store' }),
listEnvironments({ cache: 'no-store' }),
listAgents({ cache: 'no-store' }),
listAgentDiscovery({ cache: 'no-store' }),
- fetchUserSettings({ cache: 'no-store' }).catch(() => null),
- listPrompts({ cache: 'no-store' }).catch(() => ({ prompts: [] })),
- listNotificationProviders({ cache: 'no-store' }).catch(() => ({
- providers: [],
- apprise_available: false,
- events: [],
- })),
- listEditors({ cache: 'no-store' }).catch(() => ({ editors: [] })),
]);
- const settings = userSettings?.settings;
initialState = {
workspaces: {
items: workspaces.workspaces.map((workspace) => ({
@@ -88,32 +71,6 @@ async function SettingsLayoutServer({ children }: { children: React.ReactNode })
environmentsLoaded: true,
agentsLoaded: true,
},
- prompts: {
- items: promptsResponse.prompts ?? [],
- loaded: true,
- loading: false,
- },
- editors: {
- items: editorsResponse.editors ?? [],
- loaded: true,
- loading: false,
- },
- notificationProviders: {
- items: notificationProviders.providers ?? [],
- events: notificationProviders.events ?? [],
- appriseAvailable: notificationProviders.apprise_available ?? false,
- loaded: true,
- loading: false,
- },
- userSettings: {
- workspaceId: settings?.workspace_id ?? null,
- boardId: settings?.board_id ?? null,
- repositoryIds: settings?.repository_ids ?? [],
- preferredShell: settings?.preferred_shell ?? null,
- shellOptions: userSettings?.shell_options ?? [],
- defaultEditorId: settings?.default_editor_id ?? null,
- loaded: Boolean(settings),
- },
};
} catch {
initialState = {};
diff --git a/apps/web/app/settings/prompts/page.tsx b/apps/web/app/settings/prompts/page.tsx
index 323415dce6..1e3010b5f3 100644
--- a/apps/web/app/settings/prompts/page.tsx
+++ b/apps/web/app/settings/prompts/page.tsx
@@ -1,5 +1,25 @@
import { PromptsSettings } from '@/components/settings/prompts-settings';
+import { StateProvider } from '@/components/state-provider';
+import { listPrompts } from '@/lib/http';
export default async function PromptsSettingsPage() {
- return ;
+ let initialState = {};
+ try {
+ const response = await listPrompts({ cache: 'no-store' });
+ initialState = {
+ prompts: {
+ items: response.prompts ?? [],
+ loaded: true,
+ loading: false,
+ },
+ };
+ } catch {
+ initialState = {};
+ }
+
+ return (
+
+
+
+ );
}
diff --git a/apps/web/components/settings/notifications-settings.tsx b/apps/web/components/settings/notifications-settings.tsx
index bfc9258b41..c92bce919b 100644
--- a/apps/web/components/settings/notifications-settings.tsx
+++ b/apps/web/components/settings/notifications-settings.tsx
@@ -1,6 +1,6 @@
'use client';
-import { useMemo, useState, type FormEvent } from 'react';
+import { useMemo, useReducer, useState, useSyncExternalStore, type FormEvent } from 'react';
import { IconBell, IconRefresh } from '@tabler/icons-react';
import { Button } from '@kandev/ui/button';
import { Checkbox } from '@kandev/ui/checkbox';
@@ -105,14 +105,17 @@ export function NotificationsSettings() {
const [appriseFormMode, setAppriseFormMode] = useState('create');
const [activeAppriseId, setActiveAppriseId] = useState(null);
const [pendingDeletes, setPendingDeletes] = useState>(new Set());
- const [notificationPermission, setNotificationPermission] = useState<
- NotificationPermission | 'unsupported'
- >(() => {
- if (typeof Notification === 'undefined') {
- return 'unsupported';
- }
- return Notification.permission;
- });
+ const mounted = useSyncExternalStore(
+ () => () => undefined,
+ () => true,
+ () => false
+ );
+ const [, bumpPermission] = useReducer((value) => value + 1, 0);
+ const notificationPermission: NotificationPermission | 'unsupported' = !mounted
+ ? 'default'
+ : typeof Notification === 'undefined'
+ ? 'unsupported'
+ : Notification.permission;
const saveRequest = useRequest(async () => {
const updates: Array> = [];
@@ -215,19 +218,17 @@ export function NotificationsSettings() {
const handleRequestPermission = async () => {
if (typeof Notification === 'undefined') {
- setNotificationPermission('unsupported');
return;
}
- const result = await Notification.requestPermission();
- setNotificationPermission(result);
+ await Notification.requestPermission();
+ bumpPermission();
};
const handleRefreshPermission = () => {
if (typeof Notification === 'undefined') {
- setNotificationPermission('unsupported');
return;
}
- setNotificationPermission(Notification.permission);
+ bumpPermission();
};
const handleTestNotification = async () => {
@@ -237,7 +238,7 @@ export function NotificationsSettings() {
let permission = Notification.permission;
if (permission === 'default') {
permission = await Notification.requestPermission();
- setNotificationPermission(permission);
+ bumpPermission();
}
if (permission !== 'granted') {
return;
diff --git a/apps/web/components/settings/settings-app-sidebar.tsx b/apps/web/components/settings/settings-app-sidebar.tsx
index 822cbc3dc0..494e2eefa3 100644
--- a/apps/web/components/settings/settings-app-sidebar.tsx
+++ b/apps/web/components/settings/settings-app-sidebar.tsx
@@ -28,6 +28,7 @@ import {
SidebarMenuAction,
SidebarHeader,
} from '@kandev/ui/sidebar';
+import { ScrollArea } from '@kandev/ui/scroll-area';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@kandev/ui/collapsible';
import { useAppStore } from '@/components/state-provider';
import { useAvailableAgents } from '@/hooks/use-available-agents';
@@ -59,245 +60,187 @@ export function SettingsAppSidebar() {
-
-
- Settings
-
-
+
+
+
+ Settings
+
+
{/* General */}
-
-
-
-
-
- General
-
-
-
-
-
- Toggle
-
-
-
-
-
-
-
-
- Notifications
-
-
-
-
-
-
-
- Editors
-
-
-
-
-
-
-
+
+
+
+
+ General
+
+
+
+
+
+
+
+ Notifications
+
+
+
+
+
+
+
+ Editors
+
+
+
+
+
{/* Workspaces */}
-
-
-
-
-
- Workspaces
-
-
- {workspaces.length > 0 && (
- <>
-
-
-
- Toggle
-
-
-
-
- {workspaces.map((workspace) => {
- const workspacePath = `/settings/workspace/${workspace.id}`;
- const boardsPath = `${workspacePath}/boards`;
- const repositoriesPath = `${workspacePath}/repositories`;
+
+
+
+
+ Workspaces
+
+
+ {workspaces.length > 0 && (
+
+ {workspaces.map((workspace) => {
+ const workspacePath = `/settings/workspace/${workspace.id}`;
+ const boardsPath = `${workspacePath}/boards`;
+ const repositoriesPath = `${workspacePath}/repositories`;
- return (
-
-
-
- {workspace.name}
-
-
-
-
-
-
- Repositories
-
-
-
-
-
-
- Boards
-
-
-
-
-
- );
- })}
-
-
- >
- )}
-
-
+ return (
+
+
+
+ {workspace.name}
+
+
+
+
+
+
+ Repositories
+
+
+
+
+
+
+ Boards
+
+
+
+
+
+ );
+ })}
+
+ )}
+
{/* Environments */}
-
-
-
-
-
- Environments
-
-
- {environments.length > 0 && (
- <>
-
-
-
- Toggle
-
-
-
-
- {environments.map((env) => (
-
-
-
- {env.name}
-
-
-
- ))}
-
-
- >
- )}
-
-
+
+
+
+
+ Environments
+
+
+ {environments.length > 0 && (
+
+ {environments.map((env) => (
+
+
+
+ {env.name}
+
+
+
+ ))}
+
+ )}
+
{/* Executors */}
-
-
-
-
-
- Executors
-
-
- {executors.length > 0 && (
- <>
-
-
-
- Toggle
-
-
-
-
- {executors.map((executor) => (
-
-
-
- {executor.name}
-
-
-
- ))}
-
-
- >
- )}
-
-
+
+
+
+
+ Executors
+
+
+ {executors.length > 0 && (
+
+ {executors.map((executor) => (
+
+
+
+ {executor.name}
+
+
+
+ ))}
+
+ )}
+
{/* Agents */}
-
-
-
-
-
- Agents
-
-
- {agents.length > 0 && (
- <>
-
-
-
- Toggle
-
-
-
-
- {agents.flatMap((agent) =>
- agent.profiles.map((profile) => {
- const encodedAgent = encodeURIComponent(agent.name);
- const profilePath = `/settings/agents/${encodedAgent}/profiles/${profile.id}`;
- const agentLabel = profile.agent_display_name || agent.name;
- return (
-
-
-
- {agentLabel} • {profile.name}
-
-
-
- );
- })
- )}
-
-
- >
- )}
-
-
+
+
+
+
+ Agents
+
+
+ {agents.length > 0 && (
+
+ {agents.flatMap((agent) =>
+ agent.profiles.map((profile) => {
+ const encodedAgent = encodeURIComponent(agent.name);
+ const profilePath = `/settings/agents/${encodedAgent}/profiles/${profile.id}`;
+ const agentLabel = profile.agent_display_name || agent.name;
+ return (
+
+
+
+
+ {agentLabel} • {profile.name}
+
+
+
+
+ );
+ })
+ )}
+
+ )}
+
{/* Prompts */}
@@ -308,9 +251,10 @@ export function SettingsAppSidebar() {
-
-
-
+
+
+
+
);