fix(electrobun): eliminate remaining reactive cycles (tabs store + palette)
project-tabs-store: replaced Map reassignment (_tabs = new Map(_tabs)) with version counter pattern. Map reassignment created new object reference on every getActiveTab() call from $derived → infinite loop. CommandPalette: replaced $derived COMMANDS array with plain function call. $derived with .map() created new array every evaluation → infinite loop when any i18n state changed.
This commit is contained in:
parent
9d45caa8df
commit
86251f9d92
2 changed files with 22 additions and 21 deletions
|
|
@ -55,15 +55,17 @@
|
|||
{ id: 'zoom-out', labelKey: 'palette.zoomOut', shortcut: 'Ctrl+-', action: () => dispatch('zoom-out') },
|
||||
];
|
||||
|
||||
let COMMANDS = $derived<Command[]>(
|
||||
COMMAND_DEFS.map(d => ({
|
||||
// Build commands once — NOT $derived (creating new array per evaluation causes loops)
|
||||
function buildCommands(): Command[] {
|
||||
return COMMAND_DEFS.map(d => ({
|
||||
id: d.id,
|
||||
label: t(d.labelKey as any),
|
||||
description: d.descKey ? t(d.descKey as any) : undefined,
|
||||
shortcut: d.shortcut,
|
||||
action: d.action,
|
||||
}))
|
||||
);
|
||||
}));
|
||||
}
|
||||
let COMMANDS = buildCommands();
|
||||
|
||||
let query = $state('');
|
||||
let selectedIdx = $state(0);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
/**
|
||||
* Project tabs store — per-project tab state.
|
||||
*
|
||||
* Tracks which tab is active and which tabs have been activated (PERSISTED-LAZY
|
||||
* pattern) for each project card. This allows cross-component access — e.g.,
|
||||
* StatusBar can show which tab is active, palette commands can switch tabs.
|
||||
* Uses a version counter to signal changes instead of Map reassignment.
|
||||
* Map reassignment (`_tabs = new Map(_tabs)`) caused infinite reactive loops
|
||||
* in Svelte 5 because each call created a new object reference.
|
||||
*/
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────────
|
||||
|
||||
export type ProjectTab =
|
||||
| 'model' | 'docs' | 'context' | 'files'
|
||||
| 'ssh' | 'memory' | 'comms' | 'tasks';
|
||||
|
|
@ -23,7 +21,9 @@ interface TabState {
|
|||
|
||||
// ── State ────────────────────────────────────────────────────────────────
|
||||
|
||||
let _tabs = $state<Map<string, TabState>>(new Map());
|
||||
// Plain Map — NOT reactive. We use _version to signal changes.
|
||||
const _tabs = new Map<string, TabState>();
|
||||
let _version = $state(0);
|
||||
|
||||
// ── Internal helper ──────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -32,36 +32,35 @@ function ensureEntry(projectId: string): TabState {
|
|||
if (!entry) {
|
||||
entry = { activeTab: 'model', activatedTabs: new Set(['model']) };
|
||||
_tabs.set(projectId, entry);
|
||||
// Trigger reactivity by reassigning the map
|
||||
_tabs = new Map(_tabs);
|
||||
// Do NOT bump version here — this is a read-path side effect
|
||||
// that would cause infinite loops when called from $derived
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
// ── Getters ──────────────────────────────────────────────────────────────
|
||||
// ── Getters (read _version to subscribe to changes) ─────────────────────
|
||||
|
||||
export function getActiveTab(projectId: string): ProjectTab {
|
||||
void _version; // subscribe to version counter
|
||||
return _tabs.get(projectId)?.activeTab ?? 'model';
|
||||
}
|
||||
|
||||
export function isTabActivated(projectId: string, tab: ProjectTab): boolean {
|
||||
void _version;
|
||||
return _tabs.get(projectId)?.activatedTabs.has(tab) ?? (tab === 'model');
|
||||
}
|
||||
|
||||
// ── Actions ──────────────────────────────────────────────────────────────
|
||||
// ── Actions (bump version to notify subscribers) ────────────────────────
|
||||
|
||||
export function setActiveTab(projectId: string, tab: ProjectTab): void {
|
||||
const entry = ensureEntry(projectId);
|
||||
entry.activeTab = tab;
|
||||
entry.activatedTabs = new Set([...entry.activatedTabs, tab]);
|
||||
// Trigger reactivity
|
||||
_tabs = new Map(_tabs);
|
||||
entry.activatedTabs.add(tab);
|
||||
_version++; // signal change without creating new objects
|
||||
}
|
||||
|
||||
/** Remove tab state when a project is deleted. */
|
||||
export function removeProject(projectId: string): void {
|
||||
if (_tabs.has(projectId)) {
|
||||
_tabs.delete(projectId);
|
||||
_tabs = new Map(_tabs);
|
||||
if (_tabs.delete(projectId)) {
|
||||
_version++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue