fix(relays): keep document tab strip scroll position on tab switch

This commit is contained in:
Federico Jaramillo Martinez
2026-07-29 13:47:24 +02:00
parent 93249c8bc4
commit c3aeef23f3
3 changed files with 194 additions and 48 deletions
@@ -109,7 +109,7 @@ describe("multiple relays", () => {
const select = picker(panel);
if (select === null) throw new Error("relay picker missing");
select.value = `${RELAYS_ROOT}/older`;
select.dispatchEvent(new Event("change"));
select.dispatchEvent(new Event("change", { bubbles: true })); // Real change events bubble; the panel listens at the region container.
await flushAsync();
expect(fake.listFiles).toHaveBeenCalledWith(`${RELAYS_ROOT}/older`);
@@ -167,6 +167,77 @@ describe("document tabs", () => {
expect(children.map((child) => child.textContent)).toEqual(["status.md", "log.md", "notes.md"]);
});
it("keeps the tab strip mounted with its scroll and focus when switching documents", async () => {
const fake = workspaceFilesFake();
fake.addDirectory(RELAYS_ROOT, [relayDirectory("relay")]);
fake.addDirectory(`${RELAYS_ROOT}/relay`, [
relayDocument("relay", "notes.md"),
relayDocument("relay", "log.md"),
relayDocument("relay", "status.md"),
]);
fake.addDocument(`${RELAYS_ROOT}/relay/status.md`, "status body");
fake.addDocument(`${RELAYS_ROOT}/relay/log.md`, "log body");
const panel = await mountPanel(panelContext(fake));
const strip = tabStrip(panel);
strip.scrollLeft = 120;
const logTab = tabNamed(panel, "log.md");
logTab.click();
await flushAsync();
// Switching documents only re-renders the viewer: the strip element and
// its buttons stay mounted, so scroll position and button identity survive.
expect(documentText(panel)).toBe("log body");
expect(tabStrip(panel)).toBe(strip);
expect(tabStrip(panel).scrollLeft).toBe(120);
expect(tabNamed(panel, "log.md")).toBe(logTab);
expect(activeTab(panel)?.textContent).toBe("log.md");
});
it("scrolls the viewer back to the top when switching documents", async () => {
const fake = workspaceFilesFake();
fake.addDirectory(RELAYS_ROOT, [relayDirectory("relay")]);
fake.addDirectory(`${RELAYS_ROOT}/relay`, [relayDocument("relay", "log.md"), relayDocument("relay", "status.md")]);
fake.addDocument(`${RELAYS_ROOT}/relay/status.md`, "status body");
fake.addDocument(`${RELAYS_ROOT}/relay/log.md`, "log body");
const panel = await mountPanel(panelContext(fake));
const viewer = shadow(panel).querySelector("section.viewer");
if (!(viewer instanceof HTMLElement)) throw new Error("viewer missing");
viewer.scrollTop = 80;
tabNamed(panel, "log.md").click();
await flushAsync();
expect(documentText(panel)).toBe("log body");
expect(viewer.scrollTop).toBe(0);
});
it("starts a different relay's tab strip at the left edge", async () => {
const fake = workspaceFilesFake();
fake.addDirectory(RELAYS_ROOT, [
relayDirectory("alpha", "2026-01-01T00:00:00.000Z"),
relayDirectory("beta", "2026-02-01T00:00:00.000Z"),
]);
fake.addDirectory(`${RELAYS_ROOT}/alpha`, [relayDocument("alpha", "status.md")]);
fake.addDirectory(`${RELAYS_ROOT}/beta`, [relayDocument("beta", "status.md"), relayDocument("beta", "log.md")]);
fake.addDocument(`${RELAYS_ROOT}/beta/status.md`, "beta status");
fake.addDocument(`${RELAYS_ROOT}/alpha/status.md`, "alpha status");
const panel = await mountPanel(panelContext(fake));
tabStrip(panel).scrollLeft = 120;
const select = picker(panel);
if (select === null) throw new Error("relay picker missing");
select.value = `${RELAYS_ROOT}/alpha`;
select.dispatchEvent(new Event("change", { bubbles: true })); // Real change events bubble; the panel listens at the region container.
await flushAsync();
expect(documentText(panel)).toBe("alpha status");
expect(tabStrip(panel).scrollLeft).toBe(0);
});
it("shows a truncation notice when the open document is truncated", async () => {
const fake = workspaceFilesFake();
fake.addDirectory(RELAYS_ROOT, [relayDirectory("relay")]);
@@ -308,7 +379,7 @@ describe("refresh and context changes", () => {
const select = picker(panel);
if (select === null) throw new Error("relay picker missing");
select.value = `${RELAYS_ROOT}/alpha`;
select.dispatchEvent(new Event("change"));
select.dispatchEvent(new Event("change", { bubbles: true })); // Real change events bubble; the panel listens at the region container.
await flushAsync();
tabNamed(panel, "log.md").click();
await flushAsync();
@@ -463,6 +534,12 @@ function refreshButton(panel: RelaysPanelTestElement): HTMLElement {
return button;
}
function tabStrip(panel: RelaysPanelTestElement): HTMLElement {
const strip = shadow(panel).querySelector("nav.document-tabs");
if (!(strip instanceof HTMLElement)) throw new Error("tab strip missing");
return strip;
}
function tabNames(panel: RelaysPanelTestElement): string[] {
return [...shadow(panel).querySelectorAll("button[data-document-path]")].map((tab) => tab.textContent);
}