Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit eeebd3a4190826f2b2b46008cc7580fd2de57ee5
parent 1d684d1b1216cbbc566e7ba68bd22587702a14f1
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu, 24 Sep 2026 19:21:36 -0400

channels: the rack is its own stacking context

Audit finding (mobile deck-over-thead, 390x640): the scroll region was
`relative` with no z-index, so it formed no stacking context and every
layer inside it — the z-30 thead, the pinned cells — competed with the
page itself. Below md the selection deck pins to the screen at z-20, and
the thead scrolling under it painted over the deck's first line
("18 selected", the tier select). Before-shot:
p-shots-c-before/mobile-deck-over-thead.png.

`isolate` on the region orders the whole ladder inside it; the deck,
later in the DOM and positioned, paints over the region as a unit.

Also in this commit, both rack specs wait for hydration (the stations
are disabled until mount; --thead-h, --rack-w and the popover's
onToggle only exist after it) — mid-run 2 measured the group header
and clicked Advanced on server HTML. The deck-over-thead shot uses a
640 px window so the header can reach the deck at all.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Meditor/app/channels/components/ChannelsRack.tsx | 9+++++++--
Meditor/e2e/channels-rack-audit.spec.ts | 21+++++++++++++++------
Meditor/e2e/channels-rack-layers.spec.ts | 17+++++++++++------
3 files changed, 33 insertions(+), 14 deletions(-)

diff --git a/editor/app/channels/components/ChannelsRack.tsx b/editor/app/channels/components/ChannelsRack.tsx @@ -209,11 +209,16 @@ export function ChannelsRack({ Slug cells pin to its left, and the sixteen columns move underneath them. The table itself must NOT clip (`overflow-hidden` would make it the sticky ancestor and nothing would pin) — the rounded corners are - the region's. */} + the region's. + `isolate` makes the region its own stacking context, so the whole + ladder (rackLayout.ts) is ordered INSIDE it: without it the pinned + cells and the z-30 thead competed with the page itself, and below + md the thead scrolling under the screen-pinned selection deck + (z-20) painted over it. */} <div ref={regionRef} data-testid="channels-rack" - className="relative -mx-4 min-h-0 flex-1 overflow-auto border-y border-border md:mx-0 md:rounded-md md:border" + className="relative isolate -mx-4 min-h-0 flex-1 overflow-auto border-y border-border md:mx-0 md:rounded-md md:border" > <ChannelsTable rows={channels} diff --git a/editor/e2e/channels-rack-audit.spec.ts b/editor/e2e/channels-rack-audit.spec.ts @@ -75,6 +75,10 @@ async function open(page: Page) { await seed(); await page.goto("/channels?site=alpha"); await expect(page.getByRole("link", { name: "rack-16" })).toBeVisible(); + // HYDRATED, not just painted: the stations are disabled until mount, and the + // rack's measured custom properties (--thead-h, --rack-w) and every + // onToggle exist only from then on. A click before it lands on server HTML. + await expect(page.getByLabel("sync group News")).toBeEnabled(); } for (const vp of VIEWPORTS) { @@ -109,16 +113,21 @@ for (const vp of VIEWPORTS) { }); // Below md the deck pins to the SCREEN while the document scrolls: park - // the column header just above the bottom edge, under the deck, to see - // which paints on top. + // the column header under the deck's top edge to see which paints on top. + // A shorter window, because at 844 px the header sits above where the + // deck starts before the page has scrolled at all. test(`${vp.name} deck open over the column header`, async ({ page }) => { + await page.setViewportSize({ width: vp.width, height: 640 }); await open(page); await page.getByLabel("select all channels").check(); - await expect(page.getByLabel("channel priority bulk")).toBeVisible(); + const deck = page.getByLabel("channel priority bulk"); + await expect(deck).toBeVisible(); await page.evaluate(() => { - const thead = document.querySelector("thead")!; - const top = thead.getBoundingClientRect().top + window.scrollY; - window.scrollTo(0, Math.max(0, top - window.innerHeight + 60)); + const deckTop = document + .querySelector('[aria-label="channel priority bulk"]')! + .getBoundingClientRect().top; + const theadTop = document.querySelector("thead")!.getBoundingClientRect().top; + window.scrollBy(0, theadTop - (deckTop + 12)); }); await page.waitForTimeout(250); await shot(page, `${vp.name}-deck-over-thead`); diff --git a/editor/e2e/channels-rack-layers.spec.ts b/editor/e2e/channels-rack-layers.spec.ts @@ -19,6 +19,14 @@ const EXTRA = Array.from( (_, i) => `rack-${String(i + 1).padStart(2, "0")}`, ); +// Seeded, loaded and HYDRATED: the stations are disabled until mount, and the +// measured --thead-h / --rack-w and the popover's onToggle exist only after it. +async function open(page: Page) { + await seed(); + await page.goto("/channels?site=alpha"); + await expect(page.getByLabel("sync group News")).toBeEnabled(); +} + async function seed() { await resetData("two-slow-channels"); for (const slug of EXTRA) await writeChannelConfig(slug); @@ -58,8 +66,7 @@ test.use({ viewport: { width: 1280, height: 720 } }); test("scrolled to the bottom-right, the pinned group header paints over the pinned identity cells", async ({ page, }) => { - await seed(); - await page.goto("/channels?site=alpha"); + await open(page); const region = page.getByTestId("channels-rack"); await expect(region).toBeVisible(); @@ -140,8 +147,7 @@ test("scrolled to the bottom-right, the pinned group header paints over the pinn test("the Advanced priority popover paints over the rows, pins and headers around it", async ({ page, }) => { - await seed(); - await page.goto("/channels?site=alpha"); + await open(page); // A row with rows under it: the panel overlays them, and their pinned // identity cells, which is the overlap the ladder orders. @@ -163,8 +169,7 @@ test("the Advanced priority popover paints over the rows, pins and headers aroun test("an Advanced popover opened on the last row scrolls itself into view", async ({ page, }) => { - await seed(); - await page.goto("/channels?site=alpha"); + await open(page); const region = page.getByTestId("channels-rack"); // slow-a sorts last: its panel opens past the region's bottom edge. await page.getByLabel("advanced priority for slow-a").click();