Files
kandev/apps/web/lib/ws/handlers/users.ts
T
2026-06-19 12:46:46 +01:00

151 lines
6.1 KiB
TypeScript

import type { StoreApi } from "zustand";
import type { AppState } from "@/lib/state/store";
import type { UserSettingsUpdatedPayload } from "@/lib/types/backend";
import type { WsHandlers } from "@/lib/ws/handlers/types";
import {
parseChangesPanelLayout,
parseSystemMetricsDisplay,
parseVoiceMode,
} from "@/lib/ssr/user-settings";
import { fromApiSidebarDraft, fromApiSidebarView } from "@/lib/state/slices/ui/sidebar-view-wire";
import { migrateView } from "@/lib/state/slices/ui/ui-slice";
export function registerUsersHandlers(store: StoreApi<AppState>): WsHandlers {
return {
"user.settings.updated": (message) => {
store.setState((state) => ({
...state,
sidebarViews: buildSidebarViewsState(state, message.payload),
sidebarTaskPrefs: buildSidebarTaskPrefsState(state, message.payload),
userSettings: buildUserSettingsState(state, message.payload),
}));
},
};
}
function buildUserSettingsState(state: AppState, payload: UserSettingsUpdatedPayload) {
return {
...state.userSettings,
...buildBehaviorSettings(payload),
...buildSidebarSettings(state, payload),
...buildLspSettings(payload),
...buildSyncedLocalSettings(payload),
defaultUtilityAgentId: payload.default_utility_agent_id || null,
keyboardShortcuts: payload.keyboard_shortcuts ?? {},
changesPanelLayout: parseChangesPanelLayout(payload.changes_panel_layout),
systemMetricsDisplay: parseSystemMetricsDisplay(payload.system_metrics_display),
voiceMode: parseVoiceMode(payload.voice_mode),
loaded: true,
};
}
function buildLspSettings(payload: UserSettingsUpdatedPayload) {
return {
lspAutoStartLanguages: payload.lsp_auto_start_languages ?? [],
lspAutoInstallLanguages: payload.lsp_auto_install_languages ?? [],
};
}
function buildSyncedLocalSettings(payload: UserSettingsUpdatedPayload) {
return {
savedLayouts: payload.saved_layouts ?? [],
taskCreateLastUsed: {
repositoryId: payload.task_create_last_used?.repository_id || null,
branch: payload.task_create_last_used?.branch || null,
agentProfileId: payload.task_create_last_used?.agent_profile_id || null,
executorProfileId: payload.task_create_last_used?.executor_profile_id || null,
},
jiraSavedViews: payload.jira_saved_views,
jiraTaskPresets: payload.jira_task_presets,
githubSavedPresets: payload.github_saved_presets,
githubDefaultQueryPresets: payload.github_default_query_presets,
gitlabSavedPresets: payload.gitlab_saved_presets,
};
}
function buildBehaviorSettings(payload: UserSettingsUpdatedPayload) {
return {
preferredShell: payload.preferred_shell || null,
defaultEditorId: payload.default_editor_id || null,
enablePreviewOnClick: payload.enable_preview_on_click ?? false,
chatSubmitKey: (payload.chat_submit_key as "enter" | "cmd_enter") ?? "cmd_enter",
reviewAutoMarkOnScroll: payload.review_auto_mark_on_scroll ?? true,
showReleaseNotification: payload.show_release_notification ?? true,
releaseNotesLastSeenVersion: (payload.release_notes_last_seen_version as string) || null,
terminalLinkBehavior:
payload.terminal_link_behavior === "browser_panel"
? ("browser_panel" as const)
: ("new_tab" as const),
};
}
function buildSidebarSettings(state: AppState, payload: UserSettingsUpdatedPayload) {
const sidebarViews =
payload.sidebar_views === undefined
? state.userSettings.sidebarViews
: (payload.sidebar_views ?? []).map(fromApiSidebarView).map(migrateView);
return {
sidebarViews,
sidebarActiveViewId:
payload.sidebar_active_view_id === undefined
? state.userSettings.sidebarActiveViewId
: payload.sidebar_active_view_id || null,
sidebarDraft: parseSidebarDraftForSettings(state, payload),
sidebarTaskPrefs:
payload.sidebar_task_prefs === undefined
? state.userSettings.sidebarTaskPrefs
: {
pinnedTaskIds: payload.sidebar_task_prefs.pinned_task_ids ?? [],
orderedTaskIds: payload.sidebar_task_prefs.ordered_task_ids ?? [],
subtaskOrderByParentId: payload.sidebar_task_prefs.subtask_order_by_parent_id ?? {},
},
};
}
function parseSidebarDraftForSettings(state: AppState, payload: UserSettingsUpdatedPayload) {
if (payload.sidebar_draft === undefined) return state.userSettings.sidebarDraft;
if (payload.sidebar_draft === null) return null;
return fromApiSidebarDraft(payload.sidebar_draft);
}
function buildSidebarTaskPrefsState(state: AppState, payload: UserSettingsUpdatedPayload) {
if (!payload.sidebar_task_prefs) return state.sidebarTaskPrefs;
if (state.sidebarTaskPrefs.syncPending) return state.sidebarTaskPrefs;
return {
pinnedTaskIds: payload.sidebar_task_prefs.pinned_task_ids ?? [],
orderedTaskIds: payload.sidebar_task_prefs.ordered_task_ids ?? [],
subtaskOrderByParentId: payload.sidebar_task_prefs.subtask_order_by_parent_id ?? {},
syncError: state.sidebarTaskPrefs.syncError,
};
}
function buildSidebarViewsState(state: AppState, payload: UserSettingsUpdatedPayload) {
const views = (payload.sidebar_views ?? []).map(fromApiSidebarView).map(migrateView);
const draft = parseSidebarDraftForViews(state, payload);
if (views.length === 0) return { ...state.sidebarViews, draft };
const collapsedById = new Map(
state.sidebarViews.views.map((view) => [view.id, view.collapsedGroups]),
);
const mergedViews = views.map((view) => ({
...view,
collapsedGroups: collapsedById.get(view.id) ?? view.collapsedGroups,
}));
const activeViewId =
payload.sidebar_active_view_id &&
mergedViews.some((v) => v.id === payload.sidebar_active_view_id)
? payload.sidebar_active_view_id
: state.sidebarViews.activeViewId;
return {
...state.sidebarViews,
views: mergedViews,
activeViewId: mergedViews.some((v) => v.id === activeViewId) ? activeViewId : mergedViews[0].id,
draft,
};
}
function parseSidebarDraftForViews(state: AppState, payload: UserSettingsUpdatedPayload) {
if (payload.sidebar_draft === undefined) return state.sidebarViews.draft;
if (payload.sidebar_draft === null) return null;
return fromApiSidebarDraft(payload.sidebar_draft);
}