From 1b0d2085bb41fbfa7656fb4ca20c10565a83ff7b Mon Sep 17 00:00:00 2001 From: Menghuan1918 Date: Tue, 11 Aug 2026 14:19:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(settings):=20=E5=BC=80=E5=85=B3=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8F=AF=E7=82=B9=E5=87=BB=E5=8D=A1=E7=89=87=20?= =?UTF-8?q?=E2=80=94=20=E9=AB=98=E4=BA=AE=E5=8D=B3=E5=90=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 清单项(侧边栏内容/文件预览)、嵌套相关设置与「新会话默认打开」 全部从复选框行改为 ) return (
- +
{t('settingsGeneralTitle')}
+ {renderCard({ + title: t('settingsOpenTitle'), + desc: t('settingsOpenDesc'), + icon: , + enabled: prefs.openByDefault, + onToggle, + })}
{t('settingsWidthTitle')} @@ -277,27 +282,25 @@ export function SideCardSection({ store, service }: SideCardSectionProps) {
- {/* 侧边栏内容: one row per registered tab type, icon + type id + switch, - plus each tab's declaratively-declared nested toggles (shown only - while the tab itself is enabled). */} + {/* 侧边栏内容: one card per registered tab type, icon + type id; the + card's `settings.toggles` render as nested smaller cards while the + tab itself is enabled. */}
{t('settingsTabsTitle')}
{tabs.map(tab => ( - {renderRow({ + {renderCard({ title: textOf(tab.title), desc: tab.id, icon: iconOf(tab.icon, 16), - checked: prefs.tabsEnabled[tab.id] !== false, - label: textOf(tab.title), + enabled: prefs.tabsEnabled[tab.id] !== false, onToggle: (next) => { onToggleTab(tab.id, next) }, })} {prefs.tabsEnabled[tab.id] !== false && (tab.settings?.toggles ?? []).map(toggle => ( - {renderRow({ + {renderCard({ title: textOf(toggle.title), desc: textOf(toggle.desc), - checked: prefBool(prefs, toggle.key), - label: textOf(toggle.title), + enabled: prefBool(prefs, toggle.key), onToggle: (next) => { onToggleSetting(toggle, next) }, sub: true, })} @@ -306,16 +309,15 @@ export function SideCardSection({ store, service }: SideCardSectionProps) { ))} - {/* 文件预览: one row per registered file viewer, icon + title + exts + switch. */} + {/* 文件预览: one card per registered file viewer, icon + title + exts. */}
{t('settingsViewersTitle')}
{viewers.map(viewer => ( - {renderRow({ + {renderCard({ title: textOf(viewer.title) || viewer.id, desc: viewer.exts.length === 0 ? t('settingsViewerCatchAll') : viewer.exts.join(' · '), icon: iconOf(viewer.icon, 16), - checked: prefs.viewersEnabled[viewer.id] !== false, - label: textOf(viewer.title) || viewer.id, + enabled: prefs.viewersEnabled[viewer.id] !== false, onToggle: (next) => { onToggleViewer(viewer.id, next) }, })} diff --git a/src/client/locales.ts b/src/client/locales.ts index 6850a31..a6140cc 100644 --- a/src/client/locales.ts +++ b/src/client/locales.ts @@ -123,6 +123,7 @@ const zh = { settingsToolsDesc: '开启后,模型可通过 terminal_create 等 8 个工具创建并操作侧边栏终端(默认关闭)', settingsTabsTitle: '侧边栏内容', settingsViewersTitle: '文件预览', + settingsGeneralTitle: '常规', settingsViewerCatchAll: '兜底:任意文件', viewerImage: '图片', viewerPdf: 'PDF', @@ -267,6 +268,7 @@ const en: Record = { settingsToolsDesc: 'When enabled, the model can create and drive sidebar terminals through the 8 terminal_* tools (off by default)', settingsTabsTitle: 'Sidebar content', settingsViewersTitle: 'File viewers', + settingsGeneralTitle: 'General', settingsViewerCatchAll: 'Catch-all: any file', viewerImage: 'Image', viewerPdf: 'PDF', diff --git a/tests/side-card-section.spec.tsx b/tests/side-card-section.spec.tsx index 8c11774..3b70119 100644 --- a/tests/side-card-section.spec.tsx +++ b/tests/side-card-section.spec.tsx @@ -1,11 +1,13 @@ /** * Side card settings section render tests: the section is DECLARATIVE — - * every row (icon, title, type id, extensions, enable switch) derives from + * every card (icon, title, type id, extensions, on/off state) derives from * the sidebar service's tab/viewer registries instead of hardcoded copy. - * These specs pin that derivation with a fake store + a small registry: + * The toggles are CARDS: the whole card is the switch, the visual state IS + * the state (highlighted = enabled), announced via `aria-pressed`. These + * specs pin that derivation with a fake store + a small registry: * registered tabs/viewers appear with their declared icon/title/settings, - * disabled states render unchecked, and a disabled tab hides its declared - * nested toggles. + * disabled states render pressed=false, and a disabled tab hides its + * declared nested cards. * * Rendered with renderToString (mount effects — the settings RPC sync — do * not run in SSR; the initial store prefs are the render input). @@ -60,58 +62,61 @@ function renderSection(store: SidebarStore, service: BetterSidebarService): stri )) } +/** Count `aria-pressed` occurrences of one value in the rendered HTML. */ +function pressedCount(html: string, value: string): number { + return html.match(new RegExp(`aria-pressed="${value}"`, 'g'))?.length ?? 0 +} + describe('SideCardSection declarative inventory', () => { - it('renders one row per registered tab: icon + title + type id + switch', () => { + it('renders one card per registered tab: icon + title + type id + pressed state', () => { const { store, service } = mount() const html = renderSection(store, service) expect(html).toContain('data-icon="explorer"') expect(html).toContain('>Explorer<') - // The type id is the row's desc (the declarative "type" surface). + // The type id is the card's desc (the declarative "type" surface). expect(html).toContain('>explorer<') expect(html).toContain('data-icon="subagent"') expect(html).toContain('>Subagents<') - // Both tabs are enabled by default: their switches render checked. - expect(html.match(/type="checkbox"/g)?.length).toBeGreaterThanOrEqual(4) + // Default prefs: openByDefault + both tabs + the nested auto-open + the + // image viewer are all enabled → 5 cards pressed, none pressed=false. + expect(pressedCount(html, 'true')).toBe(5) + expect(pressedCount(html, 'false')).toBe(0) }) - it('renders the declared nested toggle under an enabled tab', () => { + it('renders the declared nested card under an enabled tab', () => { const { store, service } = mount() const html = renderSection(store, service) // The subagent tab's declared related setting appears while enabled, - // bound to its prefs key (default true → checked). + // bound to its prefs key (default true → pressed). expect(html).toContain('Auto-open Subagents') expect(html).toContain('Expand on new subagent') }) - it('hides the nested toggles while the parent tab is disabled', () => { + it('hides the nested cards while the parent tab is disabled', () => { const { store, service } = mount() store.setPrefs({ ...store.getPrefs(), tabsEnabled: { subagent: false } }) const html = renderSection(store, service) - // The parent row stays (so the user can re-enable it) but unchecked. + // The parent card stays (so the user can re-enable it) but pressed=false. expect(html).toContain('>Subagents<') expect(html).not.toContain('Auto-open Subagents') + expect(pressedCount(html, 'false')).toBe(1) }) - it('renders one row per registered viewer: icon + title + exts + switch', () => { + it('renders one card per registered viewer: icon + title + exts', () => { const { store, service } = mount() const html = renderSection(store, service) expect(html).toContain('data-icon="image"') expect(html).toContain('>Image<') - // The covered extensions are the row's desc. + // The covered extensions are the card's desc. expect(html).toContain('png · jpg') }) - it('a disabled viewer renders unchecked', () => { + it('a disabled viewer renders pressed=false', () => { const { store, service } = mount() store.setPrefs({ ...store.getPrefs(), viewersEnabled: { image: false } }) const html = renderSection(store, service) expect(html).toContain('>Image<') - }) - - it('a disabled tab renders unchecked (its switch reflects the prefs map)', () => { - const { store, service } = mount() - store.setPrefs({ ...store.getPrefs(), tabsEnabled: { explorer: false } }) - const html = renderSection(store, service) - expect(html).toContain('>explorer<') + expect(pressedCount(html, 'false')).toBe(1) + expect(pressedCount(html, 'true')).toBe(4) }) })