Merge main (anchored-turn chat scrolling) into perf/switch-and-scroll
Main replaced the chat timeline scroll engine while this branch was in flight, which obsoletes two of its subareas and reshapes a third: - Chat timeline: main's LegendList-based MessageList/ChatContainer win; the activation-overscan staircase targeted the removed tanstack path (LegendList provides adaptive rendering natively) and is dropped along with its test. - Scroll shadows: main's hook-based masks stay (the virtualized list owns its scroll element — there is no wrapper to hand the styling to); the viewport-wrapper ScrollShadow rewrite, its index.css replacement, its test, and the call-site viewportClassName adaptations are reverted to main. The chat OverlayScrollbar keeps this branch's disableHorizontal. - OverlayScrollbar: the direct-DOM rewrite lands, but binding now follows the live container node instead of binding once per ref object — the chat scroller remounts on every session switch, and a bind-once contract left the scrollbar attached to a dead element. - Markdown renderer: the detached-DOM cache and warm-block fast path merge with main's block-commit reveal (enter cascade), streaming code highlighting, and gutter reservation; the per-block reconcile keeps both the decoration-refresh path and the reveal cascade.
This commit is contained in:
@@ -7,6 +7,7 @@ import type { TerminalShell } from '@/lib/api/types';
|
||||
|
||||
type AppearanceSlice = {
|
||||
showReasoningTraces: boolean;
|
||||
streamingAutoFollowEnabled: boolean;
|
||||
workStatusPanelEnabled: boolean;
|
||||
workStatusHiddenSections: string[];
|
||||
sessionRecapEnabled: boolean;
|
||||
@@ -62,6 +63,7 @@ export const startAppearanceAutoSave = (): void => {
|
||||
|
||||
let previous: AppearanceSlice = {
|
||||
showReasoningTraces: useUIStore.getState().showReasoningTraces,
|
||||
streamingAutoFollowEnabled: useUIStore.getState().streamingAutoFollowEnabled,
|
||||
workStatusPanelEnabled: useUIStore.getState().workStatusPanelEnabled,
|
||||
workStatusHiddenSections: useUIStore.getState().workStatusHiddenSections,
|
||||
sessionRecapEnabled: useUIStore.getState().sessionRecapEnabled,
|
||||
@@ -104,6 +106,7 @@ export const startAppearanceAutoSave = (): void => {
|
||||
useUIStore.subscribe((state) => {
|
||||
const current: AppearanceSlice = {
|
||||
showReasoningTraces: state.showReasoningTraces,
|
||||
streamingAutoFollowEnabled: state.streamingAutoFollowEnabled,
|
||||
workStatusPanelEnabled: state.workStatusPanelEnabled,
|
||||
workStatusHiddenSections: state.workStatusHiddenSections,
|
||||
sessionRecapEnabled: state.sessionRecapEnabled,
|
||||
@@ -156,6 +159,9 @@ export const startAppearanceAutoSave = (): void => {
|
||||
if (current.showReasoningTraces !== previous.showReasoningTraces) {
|
||||
diff.showReasoningTraces = current.showReasoningTraces;
|
||||
}
|
||||
if (current.streamingAutoFollowEnabled !== previous.streamingAutoFollowEnabled) {
|
||||
diff.streamingAutoFollowEnabled = current.streamingAutoFollowEnabled;
|
||||
}
|
||||
if (current.sessionRecapEnabled !== previous.sessionRecapEnabled) {
|
||||
diff.sessionRecapEnabled = current.sessionRecapEnabled;
|
||||
}
|
||||
|
||||
@@ -131,6 +131,7 @@ export type DesktopSettings = {
|
||||
defaultVariant?: string;
|
||||
defaultAgent?: string;
|
||||
smallModelUseDefault?: boolean;
|
||||
streamingAutoFollowEnabled?: boolean;
|
||||
sessionRecapEnabled?: boolean;
|
||||
sessionSuggestionEnabled?: boolean;
|
||||
sessionGoalEnabled?: boolean;
|
||||
|
||||
@@ -1856,6 +1856,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Werkzeuge standardmäßig geöffnet anzeigen:',
|
||||
'settings.openchamber.visual.section.sessionAssistance': 'Sitzungshilfe',
|
||||
'settings.openchamber.visual.section.reasoning': 'Reasoning',
|
||||
'settings.openchamber.visual.section.streaming': 'Streaming',
|
||||
'settings.openchamber.visual.field.streamingAutoFollow': 'Neuen Inhalten beim Streaming folgen',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowAria': 'Neuen Inhalten automatisch folgen, während eine Antwort gestreamt wird',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowInfo': 'Während eine Antwort eintrifft, folgt die Ansicht laufend dem neuesten Inhalt. Deaktivieren, um die Ansicht ruhig zu halten und manuell zu scrollen.',
|
||||
'settings.openchamber.visual.section.messageAppearance': 'Nachrichten-Erscheinungsbild',
|
||||
'settings.openchamber.visual.section.toolsAndFiles': 'Werkzeuge & Dateien',
|
||||
'settings.openchamber.visual.section.composer': 'Komponist',
|
||||
|
||||
@@ -1929,6 +1929,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Show tools opened by default',
|
||||
'settings.openchamber.visual.section.sessionAssistance': 'Session Assistance',
|
||||
'settings.openchamber.visual.section.reasoning': 'Reasoning',
|
||||
'settings.openchamber.visual.section.streaming': 'Streaming',
|
||||
'settings.openchamber.visual.field.streamingAutoFollow': 'Follow new content while streaming',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowAria': 'Automatically follow new content while a response streams',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowInfo': 'While a reply streams in, the view keeps gliding to the newest content. Turn this off to keep the view still and scroll manually.',
|
||||
'settings.openchamber.visual.section.messageAppearance': 'Message Appearance',
|
||||
'settings.openchamber.visual.section.toolsAndFiles': 'Tools & Files',
|
||||
'settings.openchamber.visual.section.composer': 'Composer',
|
||||
|
||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
||||
"settings.openchamber.visual.section.showToolsOpenedByDefault": "Mostrar herramientas abiertas por defecto",
|
||||
"settings.openchamber.visual.section.sessionAssistance": "Asistencia de sesión",
|
||||
"settings.openchamber.visual.section.reasoning": "Razonamiento",
|
||||
"settings.openchamber.visual.section.streaming": "Streaming",
|
||||
"settings.openchamber.visual.field.streamingAutoFollow": "Seguir el contenido nuevo durante el streaming",
|
||||
"settings.openchamber.visual.field.streamingAutoFollowAria": "Seguir automáticamente el contenido nuevo mientras se transmite una respuesta",
|
||||
"settings.openchamber.visual.field.streamingAutoFollowInfo": "Mientras llega una respuesta, la vista se desplaza hacia el contenido más reciente. Desactívalo para mantener la vista quieta y desplazarte manualmente.",
|
||||
"settings.openchamber.visual.section.messageAppearance": "Apariencia de los mensajes",
|
||||
"settings.openchamber.visual.section.toolsAndFiles": "Herramientas y archivos",
|
||||
"settings.openchamber.visual.section.composer": "Compositor",
|
||||
|
||||
@@ -1820,6 +1820,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Afficher les outils ouverts par défaut',
|
||||
'settings.openchamber.visual.section.sessionAssistance': 'Assistance de session',
|
||||
'settings.openchamber.visual.section.reasoning': 'Raisonnement',
|
||||
'settings.openchamber.visual.section.streaming': 'Streaming',
|
||||
'settings.openchamber.visual.field.streamingAutoFollow': 'Suivre le nouveau contenu pendant le streaming',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowAria': 'Suivre automatiquement le nouveau contenu pendant la diffusion d’une réponse',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowInfo': 'Pendant qu’une réponse arrive, la vue glisse vers le contenu le plus récent. Désactivez pour garder la vue immobile et défiler manuellement.',
|
||||
'settings.openchamber.visual.section.messageAppearance': 'Apparence des messages',
|
||||
'settings.openchamber.visual.section.toolsAndFiles': 'Outils et fichiers',
|
||||
'settings.openchamber.visual.section.composer': 'Zone de saisie',
|
||||
|
||||
@@ -1939,6 +1939,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'デフォルトで開くツールを表示',
|
||||
'settings.openchamber.visual.section.sessionAssistance': 'セッション支援',
|
||||
'settings.openchamber.visual.section.reasoning': '推論',
|
||||
'settings.openchamber.visual.section.streaming': 'ストリーミング',
|
||||
'settings.openchamber.visual.field.streamingAutoFollow': '応答のストリーミング中に新しい内容を追従',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowAria': '応答のストリーミング中に新しい内容へ自動スクロールする',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowInfo': '応答の受信中、ビューは常に最新の内容へスクロールします。オフにするとビューは動かず、手動でスクロールできます。',
|
||||
'settings.openchamber.visual.section.messageAppearance': 'メッセージの外観',
|
||||
'settings.openchamber.visual.section.toolsAndFiles': 'ツールとファイル',
|
||||
'settings.openchamber.visual.section.composer': '入力欄',
|
||||
|
||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': '도구를 기본으로 펼쳐 표시',
|
||||
'settings.openchamber.visual.section.sessionAssistance': '세션 지원',
|
||||
'settings.openchamber.visual.section.reasoning': '추론',
|
||||
'settings.openchamber.visual.section.streaming': '스트리밍',
|
||||
'settings.openchamber.visual.field.streamingAutoFollow': '스트리밍 중 새 내용 따라가기',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowAria': '응답 스트리밍 중 새 내용으로 자동 스크롤',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowInfo': '응답이 스트리밍되는 동안 화면이 최신 내용으로 계속 이동합니다. 끄면 화면이 고정되어 직접 스크롤할 수 있습니다.',
|
||||
'settings.openchamber.visual.section.messageAppearance': '메시지 모양',
|
||||
'settings.openchamber.visual.section.toolsAndFiles': '도구 및 파일',
|
||||
'settings.openchamber.visual.section.composer': '입력창',
|
||||
|
||||
@@ -1209,6 +1209,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': 'Pokaż narzędzia domyślnie otwarte',
|
||||
'settings.openchamber.visual.section.sessionAssistance': 'Wsparcie sesji',
|
||||
'settings.openchamber.visual.section.reasoning': 'Rozumowanie',
|
||||
'settings.openchamber.visual.section.streaming': 'Streaming',
|
||||
'settings.openchamber.visual.field.streamingAutoFollow': 'Podążaj za nową treścią podczas streamingu',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowAria': 'Automatycznie podążaj za nową treścią podczas streamowania odpowiedzi',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowInfo': 'Podczas napływania odpowiedzi widok płynnie podąża za najnowszą treścią. Wyłącz, aby widok pozostał nieruchomy i przewijać ręcznie.',
|
||||
'settings.openchamber.visual.section.messageAppearance': 'Wygląd wiadomości',
|
||||
'settings.openchamber.visual.section.toolsAndFiles': 'Narzędzia i pliki',
|
||||
'settings.openchamber.visual.section.composer': 'Pole wiadomości',
|
||||
|
||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
||||
"settings.openchamber.visual.section.showToolsOpenedByDefault": "Mostrar ferramentas abertas por padrão",
|
||||
"settings.openchamber.visual.section.sessionAssistance": "Assistência da sessão",
|
||||
"settings.openchamber.visual.section.reasoning": "Raciocínio",
|
||||
"settings.openchamber.visual.section.streaming": "Streaming",
|
||||
"settings.openchamber.visual.field.streamingAutoFollow": "Seguir o novo conteúdo durante o streaming",
|
||||
"settings.openchamber.visual.field.streamingAutoFollowAria": "Seguir automaticamente o novo conteúdo enquanto uma resposta é transmitida",
|
||||
"settings.openchamber.visual.field.streamingAutoFollowInfo": "Enquanto uma resposta chega, a visualização acompanha o conteúdo mais recente. Desative para manter a visualização parada e rolar manualmente.",
|
||||
"settings.openchamber.visual.section.messageAppearance": "Aparência das mensagens",
|
||||
"settings.openchamber.visual.section.toolsAndFiles": "Ferramentas e arquivos",
|
||||
"settings.openchamber.visual.section.composer": "Campo de mensagem",
|
||||
|
||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
||||
"settings.openchamber.visual.section.showToolsOpenedByDefault": "Показувати інструменти відкритими за замовчуванням",
|
||||
"settings.openchamber.visual.section.sessionAssistance": "Допомога із сесією",
|
||||
"settings.openchamber.visual.section.reasoning": "Міркування",
|
||||
"settings.openchamber.visual.section.streaming": "Стримінг",
|
||||
"settings.openchamber.visual.field.streamingAutoFollow": "Слідкувати за новим вмістом під час стримінгу",
|
||||
"settings.openchamber.visual.field.streamingAutoFollowAria": "Автоматично слідкувати за новим вмістом під час стримінгу відповіді",
|
||||
"settings.openchamber.visual.field.streamingAutoFollowInfo": "Поки відповідь надходить, вигляд плавно рухається до найновішого вмісту. Вимкніть, щоб вигляд залишався нерухомим і гортати вручну.",
|
||||
"settings.openchamber.visual.section.messageAppearance": "Вигляд повідомлень",
|
||||
"settings.openchamber.visual.section.toolsAndFiles": "Інструменти та файли",
|
||||
"settings.openchamber.visual.section.composer": "Поле вводу",
|
||||
|
||||
@@ -1906,6 +1906,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': '默认展开以下工具',
|
||||
'settings.openchamber.visual.section.sessionAssistance': '会话辅助',
|
||||
'settings.openchamber.visual.section.reasoning': '推理',
|
||||
'settings.openchamber.visual.section.streaming': '流式输出',
|
||||
'settings.openchamber.visual.field.streamingAutoFollow': '流式输出时跟随新内容',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowAria': '在回复流式输出时自动跟随新内容',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowInfo': '回复流式输出时,视图会持续滚动到最新内容。关闭后视图保持不动,可手动滚动。',
|
||||
'settings.openchamber.visual.section.messageAppearance': '消息外观',
|
||||
'settings.openchamber.visual.section.toolsAndFiles': '工具和文件',
|
||||
'settings.openchamber.visual.section.composer': '输入框',
|
||||
|
||||
@@ -1813,6 +1813,10 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.section.showToolsOpenedByDefault': '預設展開以下工具',
|
||||
'settings.openchamber.visual.section.sessionAssistance': '工作階段輔助',
|
||||
'settings.openchamber.visual.section.reasoning': '推理',
|
||||
'settings.openchamber.visual.section.streaming': '串流',
|
||||
'settings.openchamber.visual.field.streamingAutoFollow': '串流時跟隨新內容',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowAria': '回覆串流時自動跟隨新內容',
|
||||
'settings.openchamber.visual.field.streamingAutoFollowInfo': '回覆串流時,畫面會持續捲動到最新內容。關閉後畫面保持不動,可手動捲動。',
|
||||
'settings.openchamber.visual.section.messageAppearance': '訊息外觀',
|
||||
'settings.openchamber.visual.section.toolsAndFiles': '工具與檔案',
|
||||
'settings.openchamber.visual.section.composer': '輸入框',
|
||||
|
||||
@@ -531,6 +531,7 @@ const materializeAuthoritativeUiSettings = (settings: DesktopSettings): DesktopS
|
||||
darkThemeId: DEFAULT_DARK_THEME_ID,
|
||||
openInAppId: DEFAULT_OPEN_IN_APP_ID,
|
||||
showReasoningTraces: defaults.showReasoningTraces,
|
||||
streamingAutoFollowEnabled: defaults.streamingAutoFollowEnabled,
|
||||
workStatusPanelEnabled: defaults.workStatusPanelEnabled,
|
||||
workStatusHiddenSections: defaults.workStatusHiddenSections,
|
||||
sessionRecapEnabled: defaults.sessionRecapEnabled,
|
||||
@@ -637,6 +638,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
||||
if (typeof settings.showReasoningTraces === 'boolean' && settings.showReasoningTraces !== store.showReasoningTraces) {
|
||||
store.setShowReasoningTraces(settings.showReasoningTraces);
|
||||
}
|
||||
if (typeof settings.streamingAutoFollowEnabled === 'boolean' && settings.streamingAutoFollowEnabled !== store.streamingAutoFollowEnabled) {
|
||||
store.setStreamingAutoFollowEnabled(settings.streamingAutoFollowEnabled);
|
||||
}
|
||||
if (typeof settings.sessionRecapEnabled === 'boolean' && settings.sessionRecapEnabled !== store.sessionRecapEnabled) {
|
||||
store.setSessionRecapEnabled(settings.sessionRecapEnabled);
|
||||
}
|
||||
@@ -1158,6 +1162,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
if (typeof candidate.showReasoningTraces === 'boolean') {
|
||||
result.showReasoningTraces = candidate.showReasoningTraces;
|
||||
}
|
||||
if (typeof candidate.streamingAutoFollowEnabled === 'boolean') {
|
||||
result.streamingAutoFollowEnabled = candidate.streamingAutoFollowEnabled;
|
||||
}
|
||||
if (typeof candidate.sessionRecapEnabled === 'boolean') {
|
||||
result.sessionRecapEnabled = candidate.sessionRecapEnabled;
|
||||
}
|
||||
|
||||
@@ -240,6 +240,19 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||
titleKey: 'settings.openchamber.visual.section.reasoning',
|
||||
keywords: ['thinking', 'traces'],
|
||||
},
|
||||
{
|
||||
id: 'chat.streaming',
|
||||
page: 'chat',
|
||||
titleKey: 'settings.openchamber.visual.section.streaming',
|
||||
keywords: ['stream', 'scroll'],
|
||||
},
|
||||
{
|
||||
id: 'chat.streaming-auto-follow',
|
||||
page: 'chat',
|
||||
titleKey: 'settings.openchamber.visual.field.streamingAutoFollow',
|
||||
descriptionKey: 'settings.openchamber.visual.field.streamingAutoFollowInfo',
|
||||
keywords: ['autoscroll', 'auto-scroll', 'follow', 'stick to bottom', 'streaming'],
|
||||
},
|
||||
{
|
||||
id: 'chat.sticky-user-header',
|
||||
page: 'chat',
|
||||
|
||||
Reference in New Issue
Block a user