Compare commits

...
Author SHA1 Message Date
Cursor Agentand陈大猫 f8c220dbd1 Isolate notes and customAccent from TerminalLayer hot props
Route vault notes through notesStore (shellHistoryStore pattern) and
app accent through appearanceChromeStore so note edits and accent-drag
ticks no longer rebuild TerminalLayer. Notes/AI slots and Terminal leaves
subscribe locally; VaultView and open-note callbacks stay unchanged.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-08-07 03:33:25 +00:00
16 changed files with 420 additions and 48 deletions

No files matched your search

-4
View File
@@ -464,8 +464,6 @@ function AppViewInner({ domains }: AppViewProps) {
identities={identities}
snippets={snippets}
snippetPackages={snippetPackages}
notes={notes}
noteGroups={noteGroups}
sessions={sessions}
workspaces={workspaces}
knownHosts={effectiveKnownHosts}
@@ -529,8 +527,6 @@ function AppViewInner({ domains }: AppViewProps) {
updateHosts={updateTerminalHosts}
updateSnippets={updateSnippets}
updateSnippetPackages={updateSnippetPackages}
updateNotes={updateNotes}
updateNoteGroups={updateNoteGroups}
sftpDefaultViewMode={sftpDefaultViewMode}
sftpDoubleClickBehavior={sftpDoubleClickBehavior}
sftpAutoSync={sftpAutoSync}
@@ -0,0 +1,31 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
getAppearanceChromeSnapshot,
publishAppearanceChromeSnapshot,
subscribeAppearanceChrome,
} from './appearanceChromeStore.ts';
test('appearanceChromeStore notifies subscribers only when accent fields change', () => {
const events: string[] = [];
const unsubscribe = subscribeAppearanceChrome(() => {
const snap = getAppearanceChromeSnapshot();
events.push(`${snap.accentMode}:${snap.customAccent}`);
});
publishAppearanceChromeSnapshot({ accentMode: 'custom', customAccent: '#ff0000' });
assert.equal(events.at(-1), 'custom:#ff0000');
assert.equal(getAppearanceChromeSnapshot().customAccent, '#ff0000');
publishAppearanceChromeSnapshot({ accentMode: 'custom', customAccent: '#ff0000' });
assert.equal(events.length, 1);
publishAppearanceChromeSnapshot({ accentMode: 'custom', customAccent: '#00ff00' });
assert.equal(events.at(-1), 'custom:#00ff00');
publishAppearanceChromeSnapshot({ accentMode: 'theme', customAccent: '#00ff00' });
assert.equal(events.at(-1), 'theme:#00ff00');
unsubscribe();
});
@@ -0,0 +1,68 @@
import { useSyncExternalStore } from 'react';
type Listener = () => void;
export type AppearanceChromeSnapshot = {
accentMode: 'theme' | 'custom';
customAccent: string;
};
const DEFAULT_SNAPSHOT: AppearanceChromeSnapshot = Object.freeze({
accentMode: 'theme',
customAccent: '',
});
/**
* External store for app accent chrome so Terminal leaves can apply custom
* accent without TerminalLayer rebuilding on every color-picker drag tick.
*/
class AppearanceChromeStore {
private snapshot: AppearanceChromeSnapshot = DEFAULT_SNAPSHOT;
private listeners = new Set<Listener>();
getSnapshot = (): AppearanceChromeSnapshot => this.snapshot;
subscribe = (listener: Listener): (() => void) => {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
};
setSnapshot(next: AppearanceChromeSnapshot): void {
if (
this.snapshot.accentMode === next.accentMode
&& this.snapshot.customAccent === next.customAccent
) {
return;
}
this.snapshot = next;
for (const listener of this.listeners) {
listener();
}
}
}
export const appearanceChromeStore = new AppearanceChromeStore();
export function publishAppearanceChromeSnapshot(
snapshot: AppearanceChromeSnapshot,
): void {
appearanceChromeStore.setSnapshot(snapshot);
}
export function getAppearanceChromeSnapshot(): AppearanceChromeSnapshot {
return appearanceChromeStore.getSnapshot();
}
export function subscribeAppearanceChrome(listener: Listener): () => void {
return appearanceChromeStore.subscribe(listener);
}
export function useAppearanceChromeStore(): AppearanceChromeSnapshot {
return useSyncExternalStore(
subscribeAppearanceChrome,
getAppearanceChromeSnapshot,
getAppearanceChromeSnapshot,
);
}
+101
View File
@@ -0,0 +1,101 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
EMPTY_NOTES_SNAPSHOT,
getEmptyNotesSnapshot,
getNotesActions,
getNotesSnapshot,
publishNotesSnapshot,
registerNotesActions,
subscribeNotes,
subscribeNotesNoop,
} from './notesStore.ts';
test('notesStore notifies subscribers only when snapshot identity changes', () => {
const events: number[] = [];
const unsubscribe = subscribeNotes(() => {
events.push(getNotesSnapshot().notes.length);
});
const firstNotes = [{
id: 'n1',
title: 'One',
content: 'body',
tags: [],
createdAt: 1,
updatedAt: 1,
order: 1000,
}];
const firstGroups = ['Ops'];
publishNotesSnapshot({ notes: firstNotes, noteGroups: firstGroups });
assert.equal(events.at(-1), 1);
assert.equal(getNotesSnapshot().notes, firstNotes);
assert.equal(getNotesSnapshot().noteGroups, firstGroups);
publishNotesSnapshot({ notes: firstNotes, noteGroups: firstGroups });
assert.equal(events.length, 1);
const secondNotes = [
...firstNotes,
{
id: 'n2',
title: 'Two',
content: 'more',
tags: [],
createdAt: 2,
updatedAt: 2,
order: 2000,
},
];
publishNotesSnapshot({ notes: secondNotes, noteGroups: firstGroups });
assert.equal(events.at(-1), 2);
publishNotesSnapshot({ notes: secondNotes, noteGroups: ['Ops', 'DB'] });
assert.equal(events.length, 3);
assert.deepEqual(getNotesSnapshot().noteGroups, ['Ops', 'DB']);
unsubscribe();
});
test('notesStore gated helpers stay empty and never notify', () => {
let called = 0;
const unsub = subscribeNotesNoop(() => {
called += 1;
});
publishNotesSnapshot({
notes: [{
id: 'n1',
title: 'One',
content: 'body',
tags: [],
createdAt: 1,
updatedAt: 1,
order: 1000,
}],
noteGroups: ['Ops'],
});
assert.equal(called, 0);
assert.equal(getEmptyNotesSnapshot(), EMPTY_NOTES_SNAPSHOT);
assert.equal(getEmptyNotesSnapshot().notes.length, 0);
unsub();
});
test('registerNotesActions exposes update handlers', () => {
const calls: string[] = [];
registerNotesActions({
updateNotes: () => {
calls.push('notes');
},
updateNoteGroups: () => {
calls.push('groups');
},
});
const actions = getNotesActions();
assert.ok(actions);
actions!.updateNotes([]);
actions!.updateNoteGroups([]);
assert.deepEqual(calls, ['notes', 'groups']);
registerNotesActions(null);
assert.equal(getNotesActions(), null);
});
+138
View File
@@ -0,0 +1,138 @@
import { useSyncExternalStore } from 'react';
import type { VaultNote } from '../../domain/models';
type Listener = () => void;
export type NotesSnapshot = {
notes: readonly VaultNote[];
noteGroups: readonly string[];
};
export type NotesActions = {
updateNotes: (notes: VaultNote[]) => void;
updateNoteGroups: (groups: string[]) => void;
};
const EMPTY_NOTES: readonly VaultNote[] = Object.freeze([]);
const EMPTY_NOTE_GROUPS: readonly string[] = Object.freeze([]);
export const EMPTY_NOTES_SNAPSHOT: NotesSnapshot = Object.freeze({
notes: EMPTY_NOTES,
noteGroups: EMPTY_NOTE_GROUPS,
});
/**
* External store for vault notes so Notes / AI side-panel consumers can
* subscribe without forcing TerminalLayer re-renders on every note edit.
*/
class NotesStore {
private snapshot: NotesSnapshot = EMPTY_NOTES_SNAPSHOT;
private actions: NotesActions | null = null;
private listeners = new Set<Listener>();
private actionListeners = new Set<Listener>();
getSnapshot = (): NotesSnapshot => this.snapshot;
subscribe = (listener: Listener): (() => void) => {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
};
setSnapshot(next: NotesSnapshot): void {
if (
this.snapshot.notes === next.notes
&& this.snapshot.noteGroups === next.noteGroups
) {
return;
}
this.snapshot = next;
for (const listener of this.listeners) {
listener();
}
}
getActions = (): NotesActions | null => this.actions;
subscribeActions = (listener: Listener): (() => void) => {
this.actionListeners.add(listener);
return () => {
this.actionListeners.delete(listener);
};
};
setActions(next: NotesActions | null): void {
if (this.actions === next) return;
this.actions = next;
for (const listener of this.actionListeners) {
listener();
}
}
}
export const notesStore = new NotesStore();
export function publishNotesSnapshot(snapshot: NotesSnapshot): void {
notesStore.setSnapshot(snapshot);
}
export function getNotesSnapshot(): NotesSnapshot {
return notesStore.getSnapshot();
}
export function subscribeNotes(listener: Listener): () => void {
return notesStore.subscribe(listener);
}
/** No-op subscribe for gated (hidden) panel mounts. */
export function subscribeNotesNoop(_listener: Listener): () => void {
return () => {};
}
export function getEmptyNotesSnapshot(): NotesSnapshot {
return EMPTY_NOTES_SNAPSHOT;
}
export function registerNotesActions(actions: NotesActions | null): void {
notesStore.setActions(actions);
}
export function getNotesActions(): NotesActions | null {
return notesStore.getActions();
}
export function subscribeNotesActions(listener: Listener): () => void {
return notesStore.subscribeActions(listener);
}
const noopUpdateNotes: NotesActions['updateNotes'] = () => {};
const noopUpdateNoteGroups: NotesActions['updateNoteGroups'] = () => {};
/**
* Subscribe to notes catalog + vault mutation actions for Notes / AI panels.
*/
export function useNotesStore(): {
notes: VaultNote[];
noteGroups: string[];
updateNotes: NotesActions['updateNotes'];
updateNoteGroups: NotesActions['updateNoteGroups'];
} {
const snapshot = useSyncExternalStore(
subscribeNotes,
getNotesSnapshot,
getNotesSnapshot,
);
const actions = useSyncExternalStore(
subscribeNotesActions,
getNotesActions,
getNotesActions,
);
return {
notes: snapshot.notes as VaultNote[],
noteGroups: snapshot.noteGroups as string[],
updateNotes: actions?.updateNotes ?? noopUpdateNotes,
updateNoteGroups: actions?.updateNoteGroups ?? noopUpdateNoteGroups,
};
}
+3
View File
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type SetStateAction } from 'react';
import { runThemeTransition, type ThemeTransitionMode } from './themeTransition';
import { publishAppearanceChromeSnapshot } from './appearanceChromeStore';
import { SyncConfig, TerminalSettings, HotkeyScheme, CustomKeyBindings, DEFAULT_KEY_BINDINGS, KeyBinding, UILanguage, SessionLogFormat, normalizeTerminalSettings } from '../../domain/models';
import {
DEFAULT_HTTP_NETWORK_PROXY,
@@ -967,6 +968,8 @@ export const useSettingsState = (options: { enableSettingsSync?: boolean; enable
const persistedAppearanceChanged = previousAppearance === null
|| hasPersistedAppearanceChanged(previousAppearance, appearanceRender);
previousAppearanceRenderRef.current = appearanceRender;
// Terminal leaves subscribe here so accent drag does not rebuild TerminalLayer.
publishAppearanceChromeSnapshot({ accentMode, customAccent });
const tokens = getUiThemeById(resolvedTheme, resolvedTheme === 'dark' ? darkUiThemeId : lightUiThemeId).tokens;
const apply = () => applyThemeTokens(theme, resolvedTheme, tokens, accentMode, customAccent);
const transitionMode = appearanceTransitionModeRef.current;
+16
View File
@@ -65,6 +65,10 @@ import {
} from "../../domain/connectionLogTerminalData";
import { getNextVaultOrder, normalizeVaultOrder } from "../../domain/vaultOrder";
import { loadSanitizedShellHistory } from "./shellHistoryPersistence";
import {
publishNotesSnapshot,
registerNotesActions,
} from "./notesStore";
import { publishShellHistorySnapshot } from "./shellHistoryStore";
import { setVaultInitialized } from "./vaultInitStore";
import {
@@ -828,6 +832,18 @@ export const useVaultState = () => {
publishShellHistorySnapshot(shellHistory);
}, [shellHistory]);
// Notes catalog for Notes / AI side panels — keep TerminalLayer off the hot path.
useLayoutEffect(() => {
publishNotesSnapshot({ notes, noteGroups });
}, [notes, noteGroups]);
useLayoutEffect(() => {
registerNotesActions({ updateNotes, updateNoteGroups });
return () => {
registerNotesActions(null);
};
}, [updateNotes, updateNoteGroups]);
const addShellHistoryEntry = useCallback(
(entry: Omit<ShellHistoryEntry, "id" | "timestamp">) => {
setShellHistory((prev) => {
+3 -2
View File
@@ -66,6 +66,7 @@ import { useStoredBoolean } from "../application/state/useStoredBoolean";
import { readOptionalStoredStringValue, useStoredString } from "../application/state/useStoredString";
import { useSessionLogBackend } from "../application/state/useSessionLogBackend";
import { useTerminalLayoutSuppressActive } from "../application/state/terminalLayoutSuppressStore";
import { useAppearanceChromeStore } from "../application/state/appearanceChromeStore";
import {
shouldPublishPluginTerminalSessionMountLifecycle,
usePluginTerminalSessionLifecycle,
@@ -260,8 +261,6 @@ const TerminalComponent: React.FC<TerminalProps> = ({
fontSize,
terminalTheme,
followAppTerminalTheme = false,
accentMode = "theme",
customAccent = "",
terminalSettings,
sessionId,
workspaceId,
@@ -1264,6 +1263,8 @@ const TerminalComponent: React.FC<TerminalProps> = ({
});
}, [availableFonts, fontFamilyId, hasFontFamilyOverride, host.fontFamily, terminalSettings?.fallbackFont]);
const { accentMode, customAccent } = useAppearanceChromeStore();
const baseEffectiveTheme = useMemo(() => {
if (appearanceTheme) return appearanceTheme;
if (followAppTerminalTheme) {
+26
View File
@@ -217,6 +217,32 @@ test("TerminalLayer ignores shellHistory prop churn (reads shellHistoryStore ins
);
});
test("TerminalLayer ignores notes prop churn (reads notesStore instead)", () => {
assert.equal(
terminalLayerAreEqual(
baseProps as never,
{
...baseProps,
notes: [{ id: "n1", title: "One", content: "body", tags: [], createdAt: 1, updatedAt: 1, order: 1000 }],
noteGroups: ["Ops"],
updateNotes: () => {},
updateNoteGroups: () => {},
} as never,
),
true,
);
});
test("TerminalLayer ignores customAccent prop churn (reads appearanceChromeStore instead)", () => {
assert.equal(
terminalLayerAreEqual(
baseProps as never,
{ ...baseProps, accentMode: "custom", customAccent: "#ff00aa" } as never,
),
true,
);
});
test("TerminalLayer ignores sessions dynamicTitle-only updates", () => {
const sessionsA = [{
id: "s1",
-8
View File
@@ -173,8 +173,6 @@ const TerminalLayerInner: React.FC<TerminalLayerProps> = ({
identities,
snippets,
snippetPackages,
notes,
noteGroups,
openNoteRequest,
onOpenVaultNoteFromChat,
onOpenVaultHostFromChat,
@@ -237,8 +235,6 @@ const TerminalLayerInner: React.FC<TerminalLayerProps> = ({
updateHosts,
updateSnippets,
updateSnippetPackages,
updateNotes,
updateNoteGroups,
sftpDefaultViewMode,
sftpDoubleClickBehavior,
sftpAutoSync,
@@ -2165,8 +2161,6 @@ const TerminalLayerInner: React.FC<TerminalLayerProps> = ({
programmaticCommandLogRewriteHandlersRef,
snippetPackages,
snippets,
noteGroups,
notes,
onOpenVaultHostFromChat,
onOpenVaultNoteFromChat: handleOpenVaultNoteFromAiPanel,
onOpenVaultSectionFromChat,
@@ -2189,8 +2183,6 @@ const TerminalLayerInner: React.FC<TerminalLayerProps> = ({
TooltipContent,
TooltipTrigger,
updateHosts,
updateNoteGroups,
updateNotes,
updateSnippetPackages,
updateSnippets,
X,
+1 -2
View File
@@ -31,8 +31,7 @@ export const terminalPropsAreEqual = (
&& prev.fontFamilyId === next.fontFamilyId
&& prev.fontSize === next.fontSize
&& prev.followAppTerminalTheme === next.followAppTerminalTheme
&& prev.accentMode === next.accentMode
&& prev.customAccent === next.customAccent
// accentMode / customAccent intentionally omitted — Terminal reads appearanceChromeStore.
&& prev.terminalSettings === next.terminalSettings
&& prev.sessionId === next.sessionId
&& prev.restoreState === next.restoreState
@@ -511,8 +511,6 @@ export interface TerminalLayerProps {
identities: Identity[];
snippets: Snippet[];
snippetPackages: string[];
notes: VaultNote[];
noteGroups: string[];
openNoteRequest?: { tabId: string; noteId: string; requestId: number } | null;
onOpenVaultNoteFromChat?: (noteId: string) => void;
onOpenVaultHostFromChat?: (hostId: string) => void;
@@ -581,8 +579,6 @@ export interface TerminalLayerProps {
updateHosts: (hosts: Host[]) => void;
updateSnippets?: (snippets: Snippet[]) => void;
updateSnippetPackages?: (packages: string[]) => void;
updateNotes: (notes: VaultNote[]) => void;
updateNoteGroups: (groups: string[]) => void;
sftpDefaultViewMode: 'list' | 'tree';
sftpDoubleClickBehavior: 'open' | 'transfer';
sftpAutoSync: boolean;
@@ -761,8 +757,7 @@ const terminalPanePropsAreEqual = (
prev.fontSize === next.fontSize &&
prev.terminalTheme === next.terminalTheme &&
prev.followAppTerminalTheme === next.followAppTerminalTheme &&
prev.accentMode === next.accentMode &&
prev.customAccent === next.customAccent &&
// accentMode / customAccent intentionally omitted — Terminal reads appearanceChromeStore.
prev.terminalSettings === next.terminalSettings &&
prev.hotkeyScheme === next.hotkeyScheme &&
prev.disableTerminalFontZoom === next.disableTerminalFontZoom &&
@@ -1534,8 +1529,7 @@ const terminalPanesHostPropsAreEqual = (
if (prev.fontSize !== next.fontSize) return false;
if (prev.terminalTheme !== next.terminalTheme) return false;
if (prev.followAppTerminalTheme !== next.followAppTerminalTheme) return false;
if (prev.accentMode !== next.accentMode) return false;
if (prev.customAccent !== next.customAccent) return false;
// accentMode / customAccent intentionally omitted — Terminal reads appearanceChromeStore.
if (prev.terminalSettings !== next.terminalSettings) return false;
if (prev.hotkeyScheme !== next.hotkeyScheme) return false;
if (prev.disableTerminalFontZoom !== next.disableTerminalFontZoom) return false;
@@ -523,8 +523,6 @@ export function TerminalLayerTabBridge({ stableRef }: { stableRef: StableRef })
notesMountedTabIds: s.notesMountedTabIds,
notesOpenNoteByTab: s.notesOpenNoteByTab,
NotesManager: s.NotesManager,
noteGroups: s.noteGroups,
notes: s.notes,
scriptsMountedTabIds: s.scriptsMountedTabIds,
systemMountedTabIds: s.systemMountedTabIds,
themeMountedTabIds: s.themeMountedTabIds,
@@ -595,8 +593,6 @@ export function TerminalLayerTabBridge({ stableRef }: { stableRef: StableRef })
snippets: s.snippets,
updateSnippetPackages: s.updateSnippetPackages,
updateSnippets: s.updateSnippets,
updateNoteGroups: s.updateNoteGroups,
updateNotes: s.updateNotes,
splitHorizontalHandlersRef: s.splitHorizontalHandlersRef,
splitVerticalHandlersRef: s.splitVerticalHandlersRef,
sshDebugLogsEnabled: s.sshDebugLogsEnabled,
@@ -641,8 +637,6 @@ export function TerminalLayerTabBridge({ stableRef }: { stableRef: StableRef })
focusedHost,
focusedSessionId,
s.restoreTerminalCwd,
s.notes,
s.noteGroups,
handleWorkspaceDrop,
handleTerminalContextReaderChange,
hibernateHiddenTabs,
@@ -8,6 +8,13 @@ import {
getSidePanelLiveSnapshot,
subscribeSidePanelLiveSnapshot,
} from '../../application/state/sidePanelLiveStore';
import {
getEmptyNotesSnapshot,
getNotesSnapshot,
subscribeNotes,
subscribeNotesNoop,
useNotesStore,
} from '../../application/state/notesStore';
import {
getShellHistorySnapshot,
subscribeShellHistory,
@@ -399,14 +406,16 @@ function SidePanelNotesSlotInner({
isVisible: boolean;
}) {
const openNoteRequest = (ctx.notesOpenNoteByTab as Map<string, { noteId: string; requestId: number }>).get(tabId) ?? null;
const {
notes,
noteGroups,
updateNotes,
updateNoteGroups,
} = useNotesStore();
const {
NotesManager,
notes,
noteGroups,
hosts,
updateNotes,
updateNoteGroups,
handleOpenHostFromNotes,
} = ctx;
@@ -499,7 +508,6 @@ function SidePanelAiSlotInner({
resolveAIExecutorContext,
pendingTerminalSelectionForAI,
handlePendingTerminalSelectionConsumed,
notes,
hosts,
snippets,
onOpenVaultNoteFromChat,
@@ -509,6 +517,14 @@ function SidePanelAiSlotInner({
validAIScopeTargetIds,
} = ctx;
// Gate notes subscription while AI is hidden so note edits do not re-render
// retained AI mounts (panel still mounts for fast reopen).
const notesSnapshot = useSyncExternalStore(
isVisible ? subscribeNotes : subscribeNotesNoop,
isVisible ? getNotesSnapshot : getEmptyNotesSnapshot,
isVisible ? getNotesSnapshot : getEmptyNotesSnapshot,
);
if (mountedAiTabIds.length === 0) return null;
const activeLayout = activeTabId
? (ctx.sidePanelLayouts as Map<string, SidePanelLayout>).get(activeTabId)
@@ -522,6 +538,9 @@ function SidePanelAiSlotInner({
// Only the visible AI panel needs vault catalogs for artifact navigation.
// Hidden retained panels keep session state without re-binding huge hosts/notes.
const injectVaultCatalog = isVisible;
const notes = injectVaultCatalog
? (notesSnapshot.notes as import('../../domain/models').VaultNote[])
: EMPTY_VAULT_NOTES;
return (
<AISidePanelStateRoot validAIScopeTargetIds={validAIScopeTargetIds}>
@@ -533,7 +552,7 @@ function SidePanelAiSlotInner({
resolveExecutorContext={resolveAIExecutorContext}
pendingTerminalSelection={pendingTerminalSelectionForAI}
onPendingTerminalSelectionConsumed={handlePendingTerminalSelectionConsumed}
notes={injectVaultCatalog ? notes : EMPTY_VAULT_NOTES}
notes={notes}
hosts={injectVaultCatalog ? hosts : EMPTY_VAULT_HOSTS}
snippets={injectVaultCatalog ? snippets : EMPTY_VAULT_SNIPPETS}
onOpenVaultNoteFromChat={onOpenVaultNoteFromChat}
@@ -320,10 +320,7 @@ const SIDE_PANEL_STABLE_CTX_KEYS = [
'validAIScopeTargetIds',
'AISidePanelStateRoot',
'NotesManager',
'notes',
'noteGroups',
'updateNotes',
'updateNoteGroups',
// notes / noteGroups / updateNotes / updateNoteGroups come from notesStore.
'onOpenVaultNoteFromChat',
'onOpenVaultHostFromChat',
'onOpenVaultSectionFromChat',
@@ -362,8 +359,7 @@ const WORKSPACE_CTX_KEYS = [
'fontSize',
'terminalTheme',
'followAppTerminalTheme',
'accentMode',
'customAccent',
// accentMode / customAccent come from appearanceChromeStore (Terminal leaf).
'terminalSettings',
'hotkeyScheme',
'disableTerminalFontZoom',
+4 -6
View File
@@ -12,8 +12,8 @@ export const terminalLayerAreEqual = (
prev.keys === next.keys &&
prev.snippets === next.snippets &&
prev.snippetPackages === next.snippetPackages &&
prev.notes === next.notes &&
prev.noteGroups === next.noteGroups &&
// notes / noteGroups / updateNotes / updateNoteGroups intentionally omitted —
// Notes + AI panels read notesStore.
prev.openNoteRequest === next.openNoteRequest &&
prev.onOpenVaultNoteFromChat === next.onOpenVaultNoteFromChat &&
prev.onOpenVaultHostFromChat === next.onOpenVaultHostFromChat &&
@@ -32,8 +32,8 @@ export const terminalLayerAreEqual = (
prev.followAppTerminalTheme === next.followAppTerminalTheme &&
prev.pickTerminalTheme === next.pickTerminalTheme &&
prev.resolveSessionAppearance === next.resolveSessionAppearance &&
prev.accentMode === next.accentMode &&
prev.customAccent === next.customAccent &&
// accentMode / customAccent intentionally omitted — Terminal reads
// appearanceChromeStore so accent drag does not rebuild TerminalLayer.
prev.terminalSettings === next.terminalSettings &&
prev.fontSize === next.fontSize &&
prev.terminalFontFamilyId === next.terminalFontFamilyId &&
@@ -75,8 +75,6 @@ export const terminalLayerAreEqual = (
prev.onToggleBroadcast === next.onToggleBroadcast &&
prev.updateSnippets === next.updateSnippets &&
prev.updateSnippetPackages === next.updateSnippetPackages &&
prev.updateNotes === next.updateNotes &&
prev.updateNoteGroups === next.updateNoteGroups &&
prev.toggleScriptsSidePanelRef === next.toggleScriptsSidePanelRef &&
prev.toggleSidePanelRef === next.toggleSidePanelRef &&
prev.identities === next.identities