mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-13 18:21:00 +00:00
Several everyday interactions re-rendered the entire shell (AppView, TopTabs,
TerminalLayer and every pane) even though nothing they show had changed:
- Clicking or focusing a split pane always replaced the workspaces array
(twice per click: onClick + onFocusCapture), cascading through the whole
app and re-saving the session restore snapshot. setWorkspaceFocusedSession
and moveFocusInWorkspace now keep the same array when focus is unchanged
(domain/workspace.ts setWorkspaceFocusedSessionId).
- Every window focus/visibility change resynced app lock and produced a new
runtime state object even when nothing changed, re-rendering the provider
tree. Equal states keep the current object and useAppLockState's result is
memoized.
- Quick switcher keystrokes went through AppSideEffects state and the
app-local UI store, re-rendering AppSideEffects, every host island, AppShell,
AppView and TopTabs per character. QuickSwitcherOverlay now owns the query
and host results; the overlay unmounts on close, which resets the query
exactly where setQuickSearch('') used to.
- Pressing/releasing Ctrl/Cmd (every ⌘C/⌘V/Ctrl+C) re-rendered the whole tab
bar to toggle shortcut number badges. The held flag now lives in a small
store read only by each tab's TabIconSlot.
- AppView passed fresh inline callbacks that defeated TopTabs' memo comparator
and every open editor tab; TextEditorTabView is now memoized. TopTabs
memoizes per-workspace session lists and hoists the root tab icons.
- Workspace drag previews only update state when the drop hint actually moves.
React commits / render time for 10 repetitions in the real app (profiling
build, 2 split panes, 300 hosts), main -> this change:
window focus 42 / 17.1 ms -> 2 / 0.4 ms
press/release Cmd 50 / 41.9 ms -> 22 / 6.6 ms
click focused pane 42 / 69.6 ms -> 3 / 0.4 ms
quick switcher typing 41 / 61.3 ms -> 16 / 34.8 ms
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
2.9 KiB
TypeScript
113 lines
2.9 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
normalizeRuntimeAppLockState,
|
|
selectPreferredRuntimeAppLockState,
|
|
} from "./useAppLockRuntime.ts";
|
|
|
|
test("normalizeRuntimeAppLockState defaults safely", () => {
|
|
assert.deepEqual(normalizeRuntimeAppLockState(null), {
|
|
initialized: false,
|
|
locked: false,
|
|
reason: null,
|
|
version: 0,
|
|
lastLockedAt: null,
|
|
lastUnlockedAt: null,
|
|
lastActivityAt: null,
|
|
});
|
|
});
|
|
|
|
test("normalizeRuntimeAppLockState preserves a valid runtime payload", () => {
|
|
assert.deepEqual(
|
|
normalizeRuntimeAppLockState({
|
|
initialized: true,
|
|
locked: true,
|
|
reason: "startup",
|
|
version: 3,
|
|
lastLockedAt: 123,
|
|
lastUnlockedAt: null,
|
|
lastActivityAt: 456,
|
|
}),
|
|
{
|
|
initialized: true,
|
|
locked: true,
|
|
reason: "startup",
|
|
version: 3,
|
|
lastLockedAt: 123,
|
|
lastUnlockedAt: null,
|
|
lastActivityAt: 456,
|
|
},
|
|
);
|
|
});
|
|
|
|
test("selectPreferredRuntimeAppLockState keeps the newer runtime version", () => {
|
|
const current = normalizeRuntimeAppLockState({
|
|
initialized: true,
|
|
locked: false,
|
|
reason: null,
|
|
version: 4,
|
|
lastLockedAt: 100,
|
|
lastUnlockedAt: 200,
|
|
lastActivityAt: 300,
|
|
});
|
|
const staleIncoming = normalizeRuntimeAppLockState({
|
|
initialized: true,
|
|
locked: true,
|
|
reason: "startup",
|
|
version: 3,
|
|
lastLockedAt: 50,
|
|
lastUnlockedAt: null,
|
|
lastActivityAt: 60,
|
|
});
|
|
|
|
assert.deepEqual(
|
|
selectPreferredRuntimeAppLockState(current, staleIncoming),
|
|
current,
|
|
);
|
|
});
|
|
|
|
test("selectPreferredRuntimeAppLockState prefers initialized state when versions tie", () => {
|
|
const current = normalizeRuntimeAppLockState({
|
|
initialized: true,
|
|
locked: false,
|
|
reason: null,
|
|
version: 0,
|
|
lastLockedAt: null,
|
|
lastUnlockedAt: 200,
|
|
lastActivityAt: 300,
|
|
});
|
|
const uninitializedIncoming = normalizeRuntimeAppLockState({
|
|
initialized: false,
|
|
locked: false,
|
|
reason: null,
|
|
version: 0,
|
|
lastLockedAt: null,
|
|
lastUnlockedAt: null,
|
|
lastActivityAt: null,
|
|
});
|
|
|
|
assert.deepEqual(
|
|
selectPreferredRuntimeAppLockState(current, uninitializedIncoming),
|
|
current,
|
|
);
|
|
});
|
|
|
|
test("selectPreferredRuntimeAppLockState keeps the current object when a resync changes nothing", () => {
|
|
const current = normalizeRuntimeAppLockState({
|
|
initialized: true,
|
|
locked: false,
|
|
reason: null,
|
|
version: 4,
|
|
lastUnlockedAt: 1000,
|
|
lastActivityAt: 2000,
|
|
});
|
|
const resynced = normalizeRuntimeAppLockState({ ...current });
|
|
assert.notEqual(resynced, current);
|
|
// Window focus resyncs must not hand React a new state object.
|
|
assert.equal(selectPreferredRuntimeAppLockState(current, resynced), current);
|
|
|
|
const changed = normalizeRuntimeAppLockState({ ...current, locked: true, reason: "idle" });
|
|
assert.equal(selectPreferredRuntimeAppLockState(current, changed), changed);
|
|
});
|