feat: redesign settings pages to match canonical flat UI patterns (#493)

* refactor(settings): new IA shell + projects section + skills catalog discoverability

* chore(settings): split providers list by scope; show user before project

* fix: navigation flow in mobile Settings

* feat: redesign settings pages to use modern elevated surface patterns

* feat: replace helper text with tooltips in settings

* ui: redesign update dialog and fix external link routing

- Restructures UpdateDialog to focus on changelog readability with a wider max-w-4xl canvas
- Highlights @username contributor mentions with theme primary color
- Strips excessive vertical padding and right-aligns compact action buttons
- Disables streamdown's internal link safety dialog in favor of direct Tauri shell routing

* feat: refactor Git identities into dedicated Git settings page

* feat: unify sidebar background styling across VS Code and web/mobile

* fix: adjust button styling and layout for mobile settings pages

* feat: add MCP settings page and sidebar

* feat: hide models in provider view (thanks to @nguyenngothuong)

* feat: add "Add new provider" option to model selector dropdown

* fix: local evroc logo + provider dropdown icons

* fix: increase width of provider menu

* fix: dark theme background color for better contrast

* feat: update @opencode-ai/sdk dependency to v1.2.10

* fix: restore session sorting to only use updated time

* fix: added settings for sessions deletion dialog

* fix: adjust padding on settings pages for better layout

* fix: standardize select dropdown height across UI

* fix: agent selector UI and notification settings

* fix: remove redundant helper text from settings pages

* fix: update UI layout for description fields

* fix: remove border-none and shadow-none from textarea classes

* fix: enable context menu on sidebar items

* feat: refactor UI controls and layout patterns across settings pages

* fix: use headerless blocks when page title already provides context

* fix: remove subtask option from command settings

* fix: refactor mcp page settings

* fix: reduce spacing in skills configuration pages

* feat: refactor voice settings

* feat: refactor settings sidebar sections
This commit is contained in:
Bohdan Triapitsyn
2026-02-24 03:28:30 +02:00
committed by GitHub
parent d2d39c48ac
commit d2358c2c03
90 changed files with 8062 additions and 7128 deletions
@@ -20,7 +20,6 @@ export interface CommandConfig {
agent?: string | null;
model?: string | null;
template?: string;
subtask?: boolean;
scope?: CommandScope;
}
@@ -74,7 +73,6 @@ export interface CommandDraft {
agent?: string | null;
model?: string | null;
template?: string;
subtask?: boolean;
}
interface CommandsStore {
@@ -204,7 +202,6 @@ export const useCommandsStore = create<CommandsStore>()(
if (config.description) commandConfig.description = config.description;
if (config.agent) commandConfig.agent = config.agent;
if (config.model) commandConfig.model = config.model;
if (config.subtask !== undefined) commandConfig.subtask = config.subtask;
if (config.scope) commandConfig.scope = config.scope;
console.log('[CommandsStore] Command config to save:', commandConfig);
@@ -267,7 +264,6 @@ export const useCommandsStore = create<CommandsStore>()(
if (config.agent !== undefined) commandConfig.agent = config.agent;
if (config.model !== undefined) commandConfig.model = config.model;
if (config.template !== undefined) commandConfig.template = config.template;
if (config.subtask !== undefined) commandConfig.subtask = config.subtask;
console.log('[CommandsStore] Command config to update:', commandConfig);
@@ -15,6 +15,7 @@ import type {
import { refreshSkillsAfterOpenCodeRestart, useSkillsStore } from '@/stores/useSkillsStore';
import { opencodeClient } from '@/lib/opencode/client';
import { startConfigUpdate, finishConfigUpdate, updateConfigUpdateMessage } from '@/lib/configUpdate';
const FALLBACK_SOURCES: SkillsCatalogSource[] = [
{
@@ -79,7 +80,7 @@ export interface SkillsCatalogState {
loadSource: (sourceId: string, options?: { refresh?: boolean }) => Promise<boolean>;
loadMoreClawdHub: () => Promise<boolean>;
scanRepo: (request: SkillsRepoScanRequest) => Promise<SkillsRepoScanResponse>;
installSkills: (request: SkillsInstallRequest) => Promise<SkillsInstallResponse>;
installSkills: (request: SkillsInstallRequest, options?: { directory?: string | null }) => Promise<SkillsInstallResponse>;
}
export const useSkillsCatalogStore = create<SkillsCatalogState>()(
@@ -348,10 +349,15 @@ export const useSkillsCatalogStore = create<SkillsCatalogState>()(
}
},
installSkills: async (request) => {
installSkills: async (request, options) => {
startConfigUpdate('Installing skills…');
set({ isInstalling: true, lastInstallError: null });
let requiresReload = false;
try {
const currentDirectory = getCurrentDirectory();
const directoryOverride = typeof options?.directory === 'string' && options.directory.trim().length > 0
? options.directory.trim()
: null;
const currentDirectory = directoryOverride ?? getCurrentDirectory();
const queryParams = currentDirectory ? `?directory=${encodeURIComponent(currentDirectory)}` : '';
const response = await fetch(`/api/config/skills/install${queryParams}`, {
@@ -364,21 +370,25 @@ export const useSkillsCatalogStore = create<SkillsCatalogState>()(
if (!payload) {
const error = { kind: 'unknown', message: 'Failed to install skills' } as SkillsInstallError;
set({ lastInstallError: error });
updateConfigUpdateMessage('Failed to install skills. Please retry.');
return { ok: false, error };
}
if (!response.ok || !payload.ok) {
const error = payload.error || ({ kind: 'unknown', message: 'Failed to install skills' } as SkillsInstallError);
set({ lastInstallError: error });
updateConfigUpdateMessage(error.message || 'Failed to install skills. Please retry.');
return { ok: false, error };
}
if (payload.requiresReload) {
requiresReload = true;
await refreshSkillsAfterOpenCodeRestart({
message: payload.message,
delayMs: payload.reloadDelayMs,
});
} else {
updateConfigUpdateMessage(payload.message || 'Refreshing skills…');
void useSkillsStore.getState().loadSkills();
}
@@ -386,9 +396,13 @@ export const useSkillsCatalogStore = create<SkillsCatalogState>()(
} catch (error) {
const err = { kind: 'unknown', message: error instanceof Error ? error.message : String(error) } as SkillsInstallError;
set({ lastInstallError: err });
updateConfigUpdateMessage('Failed to install skills. Please retry.');
return { ok: false, error: err };
} finally {
set({ isInstalling: false });
if (!requiresReload) {
finishConfigUpdate();
}
}
},
}),
+88 -1
View File
@@ -161,10 +161,16 @@ interface UIStore {
isSettingsDialogOpen: boolean;
isModelSelectorOpen: boolean;
sidebarSection: SidebarSection;
// Settings IA (new shell)
settingsPage: string;
settingsHasOpenedOnce: boolean;
settingsProjectsSelectedId: string | null;
eventStreamStatus: EventStreamStatus;
eventStreamHint: string | null;
showReasoningTraces: boolean;
showTextJustificationActivity: boolean;
showDeletionDialog: boolean;
autoDeleteEnabled: boolean;
autoDeleteAfterDays: number;
autoDeleteLastRunAt: number | null;
@@ -178,6 +184,7 @@ interface UIStore {
inputBarOffset: number;
favoriteModels: Array<{ providerID: string; modelID: string }>;
hiddenModels: Array<{ providerID: string; modelID: string }>;
recentModels: Array<{ providerID: string; modelID: string }>;
recentAgents: string[];
recentEfforts: Record<string, string[]>;
@@ -257,9 +264,12 @@ interface UIStore {
setModelSelectorOpen: (open: boolean) => void;
applyTheme: () => void;
setSidebarSection: (section: SidebarSection) => void;
setSettingsPage: (slug: string) => void;
setSettingsProjectsSelectedId: (projectId: string | null) => void;
setEventStreamStatus: (status: EventStreamStatus, hint?: string | null) => void;
setShowReasoningTraces: (value: boolean) => void;
setShowTextJustificationActivity: (value: boolean) => void;
setShowDeletionDialog: (value: boolean) => void;
setAutoDeleteEnabled: (value: boolean) => void;
setAutoDeleteAfterDays: (days: number) => void;
setAutoDeleteLastRunAt: (timestamp: number | null) => void;
@@ -275,6 +285,10 @@ interface UIStore {
applyPadding: () => void;
updateProportionalSidebarWidths: () => void;
toggleFavoriteModel: (providerID: string, modelID: string) => void;
toggleHiddenModel: (providerID: string, modelID: string) => void;
isHiddenModel: (providerID: string, modelID: string) => boolean;
hideAllModels: (providerID: string, modelIDs: string[]) => void;
showAllModels: (providerID: string) => void;
isFavoriteModel: (providerID: string, modelID: string) => boolean;
addRecentModel: (providerID: string, modelID: string) => void;
addRecentAgent: (agentName: string) => void;
@@ -346,10 +360,14 @@ export const useUIStore = create<UIStore>()(
isSettingsDialogOpen: false,
isModelSelectorOpen: false,
sidebarSection: 'sessions',
settingsPage: 'home',
settingsHasOpenedOnce: false,
settingsProjectsSelectedId: null,
eventStreamStatus: 'idle',
eventStreamHint: null,
showReasoningTraces: true,
showTextJustificationActivity: false,
showDeletionDialog: true,
autoDeleteEnabled: false,
autoDeleteAfterDays: 30,
autoDeleteLastRunAt: null,
@@ -361,6 +379,7 @@ export const useUIStore = create<UIStore>()(
cornerRadius: 12,
inputBarOffset: 0,
favoriteModels: [],
hiddenModels: [],
recentModels: [],
recentAgents: [],
recentEfforts: {},
@@ -785,7 +804,15 @@ export const useUIStore = create<UIStore>()(
},
setSettingsDialogOpen: (open) => {
set({ isSettingsDialogOpen: open });
set((state) => {
if (!open) {
return { isSettingsDialogOpen: false };
}
if (state.settingsHasOpenedOnce) {
return { isSettingsDialogOpen: true };
}
return { isSettingsDialogOpen: true, settingsHasOpenedOnce: true };
});
},
setModelSelectorOpen: (open) => {
@@ -796,6 +823,14 @@ export const useUIStore = create<UIStore>()(
set({ sidebarSection: section });
},
setSettingsPage: (slug) => {
set({ settingsPage: slug });
},
setSettingsProjectsSelectedId: (projectId) => {
set({ settingsProjectsSelectedId: projectId });
},
setEventStreamStatus: (status, hint) => {
set({
eventStreamStatus: status,
@@ -811,6 +846,10 @@ export const useUIStore = create<UIStore>()(
set({ showTextJustificationActivity: value });
},
setShowDeletionDialog: (value) => {
set({ showDeletionDialog: value });
},
setAutoDeleteEnabled: (value) => {
set({ autoDeleteEnabled: value });
},
@@ -965,6 +1004,49 @@ export const useUIStore = create<UIStore>()(
});
},
toggleHiddenModel: (providerID, modelID) => {
set((state) => {
const exists = state.hiddenModels.some(
(item) => item.providerID === providerID && item.modelID === modelID
);
if (exists) {
return {
hiddenModels: state.hiddenModels.filter(
(item) => !(item.providerID === providerID && item.modelID === modelID)
),
};
}
return {
hiddenModels: [{ providerID, modelID }, ...state.hiddenModels],
};
});
},
isHiddenModel: (providerID, modelID) => {
const { hiddenModels } = get();
return hiddenModels.some(
(item) => item.providerID === providerID && item.modelID === modelID
);
},
hideAllModels: (providerID, modelIDs) => {
set((state) => {
const current = state.hiddenModels.filter((item) => item.providerID !== providerID);
const additions = modelIDs
.filter((modelID) => typeof modelID === 'string' && modelID.length > 0)
.map((modelID) => ({ providerID, modelID }));
return { hiddenModels: [...additions, ...current] };
});
},
showAllModels: (providerID) => {
set((state) => ({
hiddenModels: state.hiddenModels.filter((item) => item.providerID !== providerID),
}));
},
isFavoriteModel: (providerID, modelID) => {
const { favoriteModels } = get();
return favoriteModels.some(
@@ -1227,10 +1309,14 @@ export const useUIStore = create<UIStore>()(
isSessionSwitcherOpen: state.isSessionSwitcherOpen,
activeMainTab: state.activeMainTab,
sidebarSection: state.sidebarSection,
settingsPage: state.settingsPage,
settingsHasOpenedOnce: state.settingsHasOpenedOnce,
settingsProjectsSelectedId: state.settingsProjectsSelectedId,
isSessionCreateDialogOpen: state.isSessionCreateDialogOpen,
// Note: isSettingsDialogOpen intentionally NOT persisted
showReasoningTraces: state.showReasoningTraces,
showTextJustificationActivity: state.showTextJustificationActivity,
showDeletionDialog: state.showDeletionDialog,
autoDeleteEnabled: state.autoDeleteEnabled,
autoDeleteAfterDays: state.autoDeleteAfterDays,
autoDeleteLastRunAt: state.autoDeleteLastRunAt,
@@ -1241,6 +1327,7 @@ export const useUIStore = create<UIStore>()(
padding: state.padding,
cornerRadius: state.cornerRadius,
favoriteModels: state.favoriteModels,
hiddenModels: state.hiddenModels,
recentModels: state.recentModels,
recentAgents: state.recentAgents,
recentEfforts: state.recentEfforts,