Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion plugins/web-ui/src/split.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,7 +879,13 @@ class PaneContent implements IContentRenderer {
this.syncZones();
p.api.onDidDimensionsChange(() => this.syncDensity());
p.api.onDidVisibilityChange((e) => {
if (e.isVisible) void this.load();
if (!e.isVisible) return;
if (!this.loaded) {
void this.load();
return;
}
this.syncDensity();
this.conversation.drawActiveChat(undefined, { forceScroll: true });
});
if (p.api.isVisible) void this.load();
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/web-ui/test/split-canvas-entry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ test("a pane is an element in this document — never a second copy of the app",
"a pane loads its transcript once, and never after it closes",
);
assert.match(load, /if \(this\.disposed\) return;/, "and drops the continuation if the pane closed mid-load");
assert.match(split, /onDidVisibilityChange\(\(e\) => \{\s*\n\s*if \(e\.isVisible\) void this\.load\(\);/);
assert.match(split, /onDidVisibilityChange\(\(e\) => \{\s*\n\s*if \(!e\.isVisible\) return;/);
});

test("a conversation dropped on a pane's tab strip joins that pane — and only there", () => {
Expand Down
26 changes: 26 additions & 0 deletions plugins/web-ui/test/tab-switch-scroll-snap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";

const split = readFileSync(new URL("../src/split.ts", import.meta.url), "utf8");
const chat = readFileSync(new URL("../src/chat.ts", import.meta.url), "utf8");

test("re-activating a pane tab force-scrolls the transcript (already-loaded panes snap to bottom)", () => {
const handler = split.match(/onDidVisibilityChange\(\(e\) => \{[\s\S]*?\}\);/)?.[0] ?? "";
assert.match(handler, /drawActiveChat\(undefined, \{ forceScroll: true \}\)/);
assert.match(handler, /if \(!this\.loaded\)/, "only already-loaded panes redraw; first show still loads");
const density = handler.indexOf("this.syncDensity()");
const draw = handler.indexOf("drawActiveChat(undefined, { forceScroll: true })");
assert.ok(
density >= 0 && density < draw,
"density resyncs before the forced redraw — a hidden pane measured 0\u00d70 and would render scroller-less glance UI",
);
});

test("a forced transcript scroll bypasses the smooth scroll-behavior (snaps instantly)", () => {
const fn = chat.match(/function scrollTranscript\(force = false\): void \{[\s\S]*?\n {2}\}/)?.[0] ?? "";
const setAuto = fn.indexOf('scroller.style.scrollBehavior = "auto"');
const write = fn.indexOf("scroller.scrollTop = scroller.scrollHeight");
assert.ok(setAuto >= 0, "forced path must set scroll-behavior:auto");
assert.ok(write > setAuto, "the snap write must come after behavior is set to auto");
});