feat(settings): add a scrollbar minimap switch under Appearance
chatMinimapEnabled defaults to on; turning it off restores the native message scrollbar while keeping continuous history and DOM windowing.
This commit is contained in:
@@ -14,4 +14,4 @@
|
|||||||
|
|
||||||
- **历史窗口**:允许滚动访问的消息范围。第一阶段扩大为当前已加载会话的全部消息。
|
- **历史窗口**:允许滚动访问的消息范围。第一阶段扩大为当前已加载会话的全部消息。
|
||||||
- **DOM 窗口**:实际挂载消息组件的范围。继续由消息高度估算和最多约 60 条挂载消息控制。
|
- **DOM 窗口**:实际挂载消息组件的范围。继续由消息高度估算和最多约 60 条挂载消息控制。
|
||||||
- **minimap**:替代消息区原生滚动条的 canvas 全局概览,高度与滚动区一致,悬停时展开为较宽的缩略长图;不等于完整渲染所有消息。
|
- **minimap**:替代消息区原生滚动条的 canvas 全局概览,高度与滚动区一致,悬停时展开为较宽的缩略长图;不等于完整渲染所有消息。设置界面中称为“滚动条缩略图”(设置 › 外观),对应应用设置 `chatMinimapEnabled`,默认开启;关闭后恢复原生滚动条,历史窗口不受影响。
|
||||||
|
|||||||
@@ -73,3 +73,10 @@
|
|||||||
- 验证:minimap、历史窗口、时间线定向测试 69/69(新增“行测量不��发重画”和“滑块对齐可见行的概览区段”用例);web 类型检查与 lint 通过;`npm run build:bundle` 通过。
|
- 验证:minimap、历史窗口、时间线定向测试 69/69(新增“行测量不��发重画”和“滑块对齐可见行的概览区段”用例);web 类型检查与 lint 通过;`npm run build:bundle` 通过。
|
||||||
- `npm run perf:app` 连续两次通过。第二次结果:长会话打开、长会话滚动、`stream-seeded`、`stream-and-type` 均无长任务,帧 p95 8.5 ms;24 次切换后 DOM 2,449、堆 23.2 MB,后半段增长 0.2 MB。第一次运行中长会话滚动帧 p95 为 16.6 ms,`stream-seeded` 有 1 个 62 ms 长任务,第二次未复现,按单次噪声记录。
|
- `npm run perf:app` 连续两次通过。第二次结果:长会话打开、长会话滚动、`stream-seeded`、`stream-and-type` 均无长任务,帧 p95 8.5 ms;24 次切换后 DOM 2,449、堆 23.2 MB,后半段增长 0.2 MB。第一次运行中长会话滚动帧 p95 为 16.6 ms,`stream-seeded` 有 1 个 62 ms 长任务,第二次未复现,按单次噪声记录。
|
||||||
- 未验证:真实桌面端滚动时的视觉稳定性需人工确认。
|
- 未验证:真实桌面端滚动时的视觉稳定性需人工确认。
|
||||||
|
|
||||||
|
### 2026-10-12 外观设置开关
|
||||||
|
|
||||||
|
- 新增应用设置 `chatMinimapEnabled`(`z.boolean().default(true)`),旧设置文件缺省时读为开启,无需升级设置版本;`application.update` 配置工具复用同一更新 schema,自动接受该字段。
|
||||||
|
- 设置 › 外观新增开关“滚动条缩略图”/“Scrollbar minimap”,位于“透明磨砂特效”之后,保存后立即生效。
|
||||||
|
- 关闭时 `ChatHistoryPane` 不挂载 `ChatMinimap`,也不再设置 `data-minimap`,原生滚动条随之恢复;历史窗口(完整已加载消息)和约 60 条 DOM 窗口不变。
|
||||||
|
- 验证:设置存储(缺省默认、两种取值的持久化与重载、非法值拒绝)、外观开关(中英文)、窗格开关切换用例均通过;相关 8 个测试文件 410 项中修正配置快照夹具后全部通过;`App.test.tsx` 342/350,8 个失败与此前相同;web/node 类型检查(除既有 `desktop-storage-electron-fixture.ts`)与改动文件 lint 通过。
|
||||||
|
|||||||
@@ -84,6 +84,20 @@ describe('ApplicationSettingsStore', () => {
|
|||||||
expect(applicationSettingsSchema.parse({ ...defaultApplicationSettings, transparentFrostedEffectEnabled: undefined }).transparentFrostedEffectEnabled).toBe(true)
|
expect(applicationSettingsSchema.parse({ ...defaultApplicationSettings, transparentFrostedEffectEnabled: undefined }).transparentFrostedEffectEnabled).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('defaults a missing scrollbar minimap setting to on and persists both choices', async () => {
|
||||||
|
const { filePath, store } = await createStore()
|
||||||
|
const legacy: Record<string, unknown> = { ...defaultApplicationSettings, version: 12, lastSeenReleaseNotesVersion: null }
|
||||||
|
delete legacy.chatMinimapEnabled
|
||||||
|
await writeFile(filePath, JSON.stringify(legacy))
|
||||||
|
expect((await store.get()).chatMinimapEnabled).toBe(true)
|
||||||
|
for (const chatMinimapEnabled of [false, true]) {
|
||||||
|
expect((await store.update({ chatMinimapEnabled })).chatMinimapEnabled).toBe(chatMinimapEnabled)
|
||||||
|
await store.update({ checkUpdatesOnStartup: false })
|
||||||
|
expect((await createApplicationSettingsStore(filePath).get()).chatMinimapEnabled).toBe(chatMinimapEnabled)
|
||||||
|
}
|
||||||
|
await expect(store.update({ chatMinimapEnabled: 'false' })).rejects.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
it('persists and publishes both frosted glass choices across unrelated updates and reloads', async () => {
|
it('persists and publishes both frosted glass choices across unrelated updates and reloads', async () => {
|
||||||
const { filePath, store } = await createStore()
|
const { filePath, store } = await createStore()
|
||||||
expect((await store.get()).transparentFrostedEffectEnabled).toBe(true)
|
expect((await store.get()).transparentFrostedEffectEnabled).toBe(true)
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ export const defaultApplicationSettings: ApplicationSettings = {
|
|||||||
checkUpdatesOnStartup: true,
|
checkUpdatesOnStartup: true,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: true,
|
transparentFrostedEffectEnabled: true,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: 'github',
|
updateSource: 'github',
|
||||||
modelDownloadSource: 'modelscope',
|
modelDownloadSource: 'modelscope',
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -295,6 +296,7 @@ export class ApplicationSettingsStore {
|
|||||||
| 'conversationHtmlRenderingEnabled'
|
| 'conversationHtmlRenderingEnabled'
|
||||||
| 'desktopNotificationsEnabled'
|
| 'desktopNotificationsEnabled'
|
||||||
| 'transparentFrostedEffectEnabled'
|
| 'transparentFrostedEffectEnabled'
|
||||||
|
| 'chatMinimapEnabled'
|
||||||
| 'localToolEnvironment'
|
| 'localToolEnvironment'
|
||||||
| 'version'
|
| 'version'
|
||||||
| 'applicationNavigation'
|
| 'applicationNavigation'
|
||||||
@@ -532,6 +534,7 @@ export class ApplicationSettingsStore {
|
|||||||
checkUpdatesOnStartup: stored.checkUpdatesOnStartup,
|
checkUpdatesOnStartup: stored.checkUpdatesOnStartup,
|
||||||
desktopNotificationsEnabled: stored.desktopNotificationsEnabled,
|
desktopNotificationsEnabled: stored.desktopNotificationsEnabled,
|
||||||
transparentFrostedEffectEnabled: stored.transparentFrostedEffectEnabled,
|
transparentFrostedEffectEnabled: stored.transparentFrostedEffectEnabled,
|
||||||
|
chatMinimapEnabled: stored.chatMinimapEnabled,
|
||||||
updateSource: stored.updateSource,
|
updateSource: stored.updateSource,
|
||||||
modelDownloadSource: stored.modelDownloadSource,
|
modelDownloadSource: stored.modelDownloadSource,
|
||||||
localToolEnvironment: stored.localToolEnvironment,
|
localToolEnvironment: stored.localToolEnvironment,
|
||||||
@@ -581,6 +584,7 @@ export class ApplicationSettingsStore {
|
|||||||
checkUpdatesOnStartup: next.checkUpdatesOnStartup,
|
checkUpdatesOnStartup: next.checkUpdatesOnStartup,
|
||||||
desktopNotificationsEnabled: next.desktopNotificationsEnabled,
|
desktopNotificationsEnabled: next.desktopNotificationsEnabled,
|
||||||
transparentFrostedEffectEnabled: next.transparentFrostedEffectEnabled,
|
transparentFrostedEffectEnabled: next.transparentFrostedEffectEnabled,
|
||||||
|
chatMinimapEnabled: next.chatMinimapEnabled,
|
||||||
updateSource: next.updateSource,
|
updateSource: next.updateSource,
|
||||||
modelDownloadSource: next.modelDownloadSource,
|
modelDownloadSource: next.modelDownloadSource,
|
||||||
localToolEnvironment: next.localToolEnvironment,
|
localToolEnvironment: next.localToolEnvironment,
|
||||||
|
|||||||
@@ -1256,6 +1256,7 @@ function installRemoteProjectsSetting(enabled: boolean): {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github",
|
updateSource: "github",
|
||||||
modelDownloadSource: "modelscope",
|
modelDownloadSource: "modelscope",
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -3486,6 +3487,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -3591,6 +3593,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: true,
|
checkUpdatesOnStartup: true,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -3608,6 +3611,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: true,
|
checkUpdatesOnStartup: true,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -3711,6 +3715,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: true,
|
checkUpdatesOnStartup: true,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -3728,6 +3733,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: true,
|
checkUpdatesOnStartup: true,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -6430,6 +6436,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github",
|
updateSource: "github",
|
||||||
modelDownloadSource: "modelscope",
|
modelDownloadSource: "modelscope",
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -14663,6 +14670,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -14680,6 +14688,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -14734,6 +14743,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -14795,6 +14805,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -14833,6 +14844,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -14850,6 +14862,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github" as const,
|
updateSource: "github" as const,
|
||||||
modelDownloadSource: "modelscope" as const,
|
modelDownloadSource: "modelscope" as const,
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -14913,6 +14926,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github",
|
updateSource: "github",
|
||||||
modelDownloadSource: "modelscope",
|
modelDownloadSource: "modelscope",
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -14973,6 +14987,7 @@ describe("App", () => {
|
|||||||
checkUpdatesOnStartup: false,
|
checkUpdatesOnStartup: false,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: "github",
|
updateSource: "github",
|
||||||
modelDownloadSource: "modelscope",
|
modelDownloadSource: "modelscope",
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
|
|||||||
@@ -6511,6 +6511,8 @@ function App(): React.JSX.Element {
|
|||||||
const settingsPanelActions = useStableHandlers({
|
const settingsPanelActions = useStableHandlers({
|
||||||
onTransparentFrostedEffectEnabledChange: (enabled: boolean) =>
|
onTransparentFrostedEffectEnabledChange: (enabled: boolean) =>
|
||||||
updateApplicationSettings({ transparentFrostedEffectEnabled: enabled }),
|
updateApplicationSettings({ transparentFrostedEffectEnabled: enabled }),
|
||||||
|
onChatMinimapEnabledChange: (enabled: boolean) =>
|
||||||
|
updateApplicationSettings({ chatMinimapEnabled: enabled }),
|
||||||
onRetryApplicationSettings: () => void reloadApplicationSettings(),
|
onRetryApplicationSettings: () => void reloadApplicationSettings(),
|
||||||
onBrandingPreferencesChange: (preferences: Parameters<typeof saveBrandingPreferences>[0]) => {
|
onBrandingPreferencesChange: (preferences: Parameters<typeof saveBrandingPreferences>[0]) => {
|
||||||
if (!saveBrandingPreferences(preferences)) {
|
if (!saveBrandingPreferences(preferences)) {
|
||||||
@@ -7069,6 +7071,7 @@ function App(): React.JSX.Element {
|
|||||||
conversationHtmlRenderingEnabled={
|
conversationHtmlRenderingEnabled={
|
||||||
conversationHtmlRenderingEnabled
|
conversationHtmlRenderingEnabled
|
||||||
}
|
}
|
||||||
|
chatMinimapEnabled={applicationSettings?.chatMinimapEnabled ?? true}
|
||||||
conversationId={conversationId}
|
conversationId={conversationId}
|
||||||
key={conversationId}
|
key={conversationId}
|
||||||
loadHistory={ensureConversationHistory}
|
loadHistory={ensureConversationHistory}
|
||||||
@@ -7394,6 +7397,7 @@ function App(): React.JSX.Element {
|
|||||||
{...settingsPanelActions}
|
{...settingsPanelActions}
|
||||||
appearanceTheme={appearanceTheme}
|
appearanceTheme={appearanceTheme}
|
||||||
transparentFrostedEffectEnabled={applicationSettings?.transparentFrostedEffectEnabled ?? true}
|
transparentFrostedEffectEnabled={applicationSettings?.transparentFrostedEffectEnabled ?? true}
|
||||||
|
chatMinimapEnabled={applicationSettings?.chatMinimapEnabled ?? true}
|
||||||
applicationSettingsPending={applicationSettingsPending}
|
applicationSettingsPending={applicationSettingsPending}
|
||||||
applicationSettingsLocked={applicationSettingsUnconfirmed || !applicationSettings}
|
applicationSettingsLocked={applicationSettingsUnconfirmed || !applicationSettings}
|
||||||
applicationSettingsError={applicationSettingsError}
|
applicationSettingsError={applicationSettingsError}
|
||||||
|
|||||||
@@ -181,6 +181,19 @@ afterEach(() => {
|
|||||||
vi.unstubAllGlobals()
|
vi.unstubAllGlobals()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('ChatHistoryPane scrollbar minimap setting', () => {
|
||||||
|
it('replaces the native scrollbar only while the minimap is enabled', () => {
|
||||||
|
const messages = makeMessages(5)
|
||||||
|
const { container, rerender, props } = setup(messages)
|
||||||
|
const pane = container.querySelector<HTMLElement>('.chat-history-pane')!
|
||||||
|
expect(pane).toHaveAttribute('data-minimap', 'true')
|
||||||
|
expect(container.querySelector('.chat-minimap')).not.toBeNull()
|
||||||
|
rerender(<ChatHistoryPane {...props} chatMinimapEnabled={false} />)
|
||||||
|
expect(pane).not.toHaveAttribute('data-minimap')
|
||||||
|
expect(container.querySelector('.chat-minimap')).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('ChatHistoryPane windowing', () => {
|
describe('ChatHistoryPane windowing', () => {
|
||||||
it('restores disclosures after eviction by more than eight row interactions with bounded DOM', async () => {
|
it('restores disclosures after eviction by more than eight row interactions with bounded DOM', async () => {
|
||||||
const messages = makeMessages(2_000).map(message => ({ ...message, role: 'assistant' as const, reasoning: 'Reasoning' }))
|
const messages = makeMessages(2_000).map(message => ({ ...message, role: 'assistant' as const, reasoning: 'Reasoning' }))
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ export const ChatHistoryPane = memo(function ChatHistoryPane({
|
|||||||
active,
|
active,
|
||||||
artifactById,
|
artifactById,
|
||||||
conversationHtmlRenderingEnabled,
|
conversationHtmlRenderingEnabled,
|
||||||
|
chatMinimapEnabled = true,
|
||||||
conversation,
|
conversation,
|
||||||
locale,
|
locale,
|
||||||
onCopyMessage,
|
onCopyMessage,
|
||||||
@@ -177,6 +178,8 @@ export const ChatHistoryPane = memo(function ChatHistoryPane({
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
artifactById: ReadonlyMap<string, AssistantArtifact>;
|
artifactById: ReadonlyMap<string, AssistantArtifact>;
|
||||||
conversationHtmlRenderingEnabled: boolean;
|
conversationHtmlRenderingEnabled: boolean;
|
||||||
|
/** Replaces the native scrollbar with the minimap; off restores the scrollbar. */
|
||||||
|
chatMinimapEnabled?: boolean;
|
||||||
conversation: Conversation;
|
conversation: Conversation;
|
||||||
locale: TimeFormatLocale;
|
locale: TimeFormatLocale;
|
||||||
onCopyMessage: (content: string, kind?: 'tool') => Promise<boolean>;
|
onCopyMessage: (content: string, kind?: 'tool') => Promise<boolean>;
|
||||||
@@ -1071,6 +1074,7 @@ export const ChatHistoryPane = memo(function ChatHistoryPane({
|
|||||||
className="chat-history-pane"
|
className="chat-history-pane"
|
||||||
data-active={active ? "true" : "false"}
|
data-active={active ? "true" : "false"}
|
||||||
data-conversation-id={conversation.id}
|
data-conversation-id={conversation.id}
|
||||||
|
data-minimap={chatMinimapEnabled ? "true" : undefined}
|
||||||
hidden={!active}
|
hidden={!active}
|
||||||
inert={!active}
|
inert={!active}
|
||||||
>
|
>
|
||||||
@@ -1142,7 +1146,7 @@ export const ChatHistoryPane = memo(function ChatHistoryPane({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{active && (
|
{active && chatMinimapEnabled && (
|
||||||
<ChatMinimap
|
<ChatMinimap
|
||||||
messages={messages}
|
messages={messages}
|
||||||
rowHeights={heights}
|
rowHeights={heights}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ const otherTodo: MagicTodoItem = { ...todo, id: '00000000-0000-4000-8000-0000000
|
|||||||
const settings: ApplicationSettings = {
|
const settings: ApplicationSettings = {
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
checkUpdatesOnStartup: false, updateSource: 'github', modelDownloadSource: 'modelscope',
|
checkUpdatesOnStartup: false, updateSource: 'github', modelDownloadSource: 'modelscope',
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings, applicationNavigation: defaultApplicationNavigation,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings, applicationNavigation: defaultApplicationNavigation,
|
||||||
localInferenceEnabled: true, deviceSharingEnabled: false, conversationHtmlRenderingEnabled: true, remoteProjectsEnabled: false,
|
localInferenceEnabled: true, deviceSharingEnabled: false, conversationHtmlRenderingEnabled: true, remoteProjectsEnabled: false,
|
||||||
|
|||||||
@@ -520,6 +520,7 @@ let applicationSettings: ApplicationSettings = {
|
|||||||
checkUpdatesOnStartup: true,
|
checkUpdatesOnStartup: true,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: 'github',
|
updateSource: 'github',
|
||||||
modelDownloadSource: 'modelscope',
|
modelDownloadSource: 'modelscope',
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -815,6 +816,7 @@ describe('SettingsPanel runtime files', () => {
|
|||||||
checkUpdatesOnStartup: true,
|
checkUpdatesOnStartup: true,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: 'github',
|
updateSource: 'github',
|
||||||
modelDownloadSource: 'modelscope',
|
modelDownloadSource: 'modelscope',
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
@@ -1594,6 +1596,32 @@ describe('SettingsPanel runtime files', () => {
|
|||||||
expect(onChange).toHaveBeenLastCalledWith(true)
|
expect(onChange).toHaveBeenLastCalledWith(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['zh-CN', '外观', '滚动条缩略图'],
|
||||||
|
['en-US', 'Appearance', 'Scrollbar minimap']
|
||||||
|
] as const)('exposes the scrollbar minimap Switch under Appearance in %s', async (locale, category, label) => {
|
||||||
|
await changeUiLocale(locale)
|
||||||
|
const onChange = vi.fn(async () => true)
|
||||||
|
const props = {
|
||||||
|
...heartbeatSettingsProps,
|
||||||
|
open: true,
|
||||||
|
onClearLocalData: vi.fn(async () => {}),
|
||||||
|
onClose: vi.fn(),
|
||||||
|
onSaved: vi.fn(),
|
||||||
|
onChatMinimapEnabledChange: onChange
|
||||||
|
}
|
||||||
|
const { rerender } = render(<SettingsPanel {...props} />)
|
||||||
|
fireEvent.click(screen.getByRole('tab', { name: category }))
|
||||||
|
const toggle = screen.getByRole('switch', { name: label })
|
||||||
|
expect(toggle).toBeChecked()
|
||||||
|
fireEvent.click(toggle)
|
||||||
|
expect(onChange).toHaveBeenCalledExactlyOnceWith(false)
|
||||||
|
rerender(<SettingsPanel {...props} chatMinimapEnabled={false} />)
|
||||||
|
expect(toggle).not.toBeChecked()
|
||||||
|
fireEvent.click(toggle)
|
||||||
|
expect(onChange).toHaveBeenLastCalledWith(true)
|
||||||
|
})
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['zh-CN', '平台功能', '桌面通知'],
|
['zh-CN', '平台功能', '桌面通知'],
|
||||||
['en-US', 'Platform features', 'Desktop notifications']
|
['en-US', 'Platform features', 'Desktop notifications']
|
||||||
|
|||||||
@@ -243,6 +243,8 @@ type SettingsPanelProps = {
|
|||||||
applicationSettingsLocked?: boolean
|
applicationSettingsLocked?: boolean
|
||||||
applicationSettingsError?: string
|
applicationSettingsError?: string
|
||||||
onTransparentFrostedEffectEnabledChange?: (enabled: boolean) => Promise<boolean>
|
onTransparentFrostedEffectEnabledChange?: (enabled: boolean) => Promise<boolean>
|
||||||
|
chatMinimapEnabled?: boolean
|
||||||
|
onChatMinimapEnabledChange?: (enabled: boolean) => Promise<boolean>
|
||||||
onRetryApplicationSettings?: () => void
|
onRetryApplicationSettings?: () => void
|
||||||
onAppearanceThemeChange?: (theme: AppearanceTheme) => void
|
onAppearanceThemeChange?: (theme: AppearanceTheme) => void
|
||||||
brandingPreferences?: BrandingPreferences
|
brandingPreferences?: BrandingPreferences
|
||||||
@@ -658,6 +660,8 @@ function SettingsPanelView({
|
|||||||
applicationSettingsLocked = false,
|
applicationSettingsLocked = false,
|
||||||
applicationSettingsError,
|
applicationSettingsError,
|
||||||
onTransparentFrostedEffectEnabledChange,
|
onTransparentFrostedEffectEnabledChange,
|
||||||
|
chatMinimapEnabled = true,
|
||||||
|
onChatMinimapEnabledChange,
|
||||||
onRetryApplicationSettings,
|
onRetryApplicationSettings,
|
||||||
onAppearanceThemeChange = () => {},
|
onAppearanceThemeChange = () => {},
|
||||||
brandingPreferences = defaultBrandingPreferences,
|
brandingPreferences = defaultBrandingPreferences,
|
||||||
@@ -2380,6 +2384,22 @@ function SettingsPanelView({
|
|||||||
<p id="frosted-glass-description" className="settings-panel__description">
|
<p id="frosted-glass-description" className="settings-panel__description">
|
||||||
{t('appearance.frostedGlass.description')}
|
{t('appearance.frostedGlass.description')}
|
||||||
</p>
|
</p>
|
||||||
|
<label className="toggle-row">
|
||||||
|
<span>{t('appearance.chatMinimap.label')}</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
role="switch"
|
||||||
|
checked={chatMinimapEnabled}
|
||||||
|
disabled={applicationSettingsPending || applicationSettingsLocked || !onChatMinimapEnabledChange}
|
||||||
|
aria-describedby="chat-minimap-description"
|
||||||
|
onChange={(event) => {
|
||||||
|
void onChatMinimapEnabledChange?.(event.target.checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<p id="chat-minimap-description" className="settings-panel__description">
|
||||||
|
{t('appearance.chatMinimap.description')}
|
||||||
|
</p>
|
||||||
{applicationSettingsError && (
|
{applicationSettingsError && (
|
||||||
<div role="alert">
|
<div role="alert">
|
||||||
<p>{applicationSettingsError}</p>
|
<p>{applicationSettingsError}</p>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ describe('UpdateSettingsSection', () => {
|
|||||||
checkUpdatesOnStartup: true,
|
checkUpdatesOnStartup: true,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: 'github',
|
updateSource: 'github',
|
||||||
modelDownloadSource: 'modelscope',
|
modelDownloadSource: 'modelscope',
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
|
|||||||
@@ -876,6 +876,10 @@ export const settings = {
|
|||||||
description: 'Applies to the top bar and expanded runtime checklist. Takes effect as soon as it is saved.',
|
description: 'Applies to the top bar and expanded runtime checklist. Takes effect as soon as it is saved.',
|
||||||
retry: 'Retry'
|
retry: 'Retry'
|
||||||
},
|
},
|
||||||
|
chatMinimap: {
|
||||||
|
label: 'Scrollbar minimap',
|
||||||
|
description: 'Replaces the message scrollbar with an overview of the conversation that expands on hover; click or drag to jump. Takes effect as soon as it is saved.'
|
||||||
|
},
|
||||||
theme: {
|
theme: {
|
||||||
title: 'Interface theme',
|
title: 'Interface theme',
|
||||||
description: 'Applies immediately and is saved on this device',
|
description: 'Applies immediately and is saved on this device',
|
||||||
|
|||||||
@@ -811,6 +811,10 @@ export const settings = {
|
|||||||
description: '应用于顶栏和展开的运行待办卡片,保存后立即生效。',
|
description: '应用于顶栏和展开的运行待办卡片,保存后立即生效。',
|
||||||
retry: '重试'
|
retry: '重试'
|
||||||
},
|
},
|
||||||
|
chatMinimap: {
|
||||||
|
label: '滚动条缩略图',
|
||||||
|
description: '在消息区右侧用缩略图代替滚动条,悬停时展开,可点击或拖动定位。保存后立即生效。'
|
||||||
|
},
|
||||||
theme: {
|
theme: {
|
||||||
title: '界面主题',
|
title: '界面主题',
|
||||||
description: '选择后立即应用,并保存在此设备',
|
description: '选择后立即应用,并保存在此设备',
|
||||||
|
|||||||
@@ -7343,12 +7343,12 @@ button > svg {
|
|||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The minimap replaces the native scrollbar of the message scroller. */
|
/* When enabled, the minimap replaces the native scrollbar of the message scroller. */
|
||||||
.chat-history-pane > .chat {
|
.chat-history-pane[data-minimap='true'] > .chat {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-history-pane > .chat::-webkit-scrollbar {
|
.chat-history-pane[data-minimap='true'] > .chat::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ const applicationPreferencesSchema = z
|
|||||||
checkUpdatesOnStartup: z.boolean(),
|
checkUpdatesOnStartup: z.boolean(),
|
||||||
desktopNotificationsEnabled: z.boolean().default(true),
|
desktopNotificationsEnabled: z.boolean().default(true),
|
||||||
transparentFrostedEffectEnabled: z.boolean().default(true),
|
transparentFrostedEffectEnabled: z.boolean().default(true),
|
||||||
|
chatMinimapEnabled: z.boolean().default(true),
|
||||||
updateSource: updateSourceSchema,
|
updateSource: updateSourceSchema,
|
||||||
modelDownloadSource: modelDownloadSourceSchema,
|
modelDownloadSource: modelDownloadSourceSchema,
|
||||||
localToolEnvironment: localToolEnvironmentSettingsSchema,
|
localToolEnvironment: localToolEnvironmentSettingsSchema,
|
||||||
@@ -94,6 +95,7 @@ export const applicationSettingsUpdateSchema = applicationPreferencesSchema
|
|||||||
applicationNavigation: applicationNavigationSchema.optional(),
|
applicationNavigation: applicationNavigationSchema.optional(),
|
||||||
desktopNotificationsEnabled: z.boolean().optional(),
|
desktopNotificationsEnabled: z.boolean().optional(),
|
||||||
transparentFrostedEffectEnabled: z.boolean().optional(),
|
transparentFrostedEffectEnabled: z.boolean().optional(),
|
||||||
|
chatMinimapEnabled: z.boolean().optional(),
|
||||||
localInferenceEnabled: z.boolean().optional(),
|
localInferenceEnabled: z.boolean().optional(),
|
||||||
deviceSharingEnabled: z.boolean().optional(),
|
deviceSharingEnabled: z.boolean().optional(),
|
||||||
magicNotesEnabled: z.boolean().optional(),
|
magicNotesEnabled: z.boolean().optional(),
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ describe('GoodBuddy configuration contracts', () => {
|
|||||||
checkUpdatesOnStartup: true,
|
checkUpdatesOnStartup: true,
|
||||||
desktopNotificationsEnabled: true,
|
desktopNotificationsEnabled: true,
|
||||||
transparentFrostedEffectEnabled: false,
|
transparentFrostedEffectEnabled: false,
|
||||||
|
chatMinimapEnabled: true,
|
||||||
updateSource: 'github',
|
updateSource: 'github',
|
||||||
modelDownloadSource: 'modelscope',
|
modelDownloadSource: 'modelscope',
|
||||||
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
localToolEnvironment: defaultLocalToolEnvironmentSettings,
|
||||||
|
|||||||
Reference in New Issue
Block a user