From 706aa41955f86eeb6fb35af0b844048e2f53a788 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sun, 6 Sep 2026 16:00:12 +0900 Subject: fix(layout): draw diagnostic tile markers inset + frame the sidebar viewport, not its scrolled content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two renderer bugs made the diagnostic layout plugin's borders useless: 1. Borders went past the panels and were invisible at screen edges: CSS outlines draw OUTSIDE the border box by default, and tiles span their containers edge-to-edge, so the 4px ring overflowed the visible area and was clipped. The outline is now inset (outline-offset: -4px) so it draws inside the tile's box. 2. The green side border scrolled with the sidebar: the marker was drawn on the TilePanels root, which is the SCROLLED CONTENT of the split's collapsible child. The marker now lives on the split's scroll-viewport div — the sidebar's fixed, opaquely-painted frame: its box never moves (only its contents scroll), and its own outline is never clipped by its overflow. (The aside behind it can't carry the marker — the scroll viewport's bg-base-100 paints over an outline drawn on the aside.) markerStyle() centralizes the full declaration (outline + inset offset) and returns undefined for unmarked/unknown markers, so unmarked trees still render with zero outline CSS (pinned by the existing tests). Paint layering verified with a headless-Chromium screenshot of a probe page reproducing the exact DOM + computed styles. --- src/features/layout/ui/TileSplit.svelte | 40 ++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) (limited to 'src/features/layout/ui/TileSplit.svelte') diff --git a/src/features/layout/ui/TileSplit.svelte b/src/features/layout/ui/TileSplit.svelte index b41f9fc..b526ccd 100644 --- a/src/features/layout/ui/TileSplit.svelte +++ b/src/features/layout/ui/TileSplit.svelte @@ -6,9 +6,9 @@ * in-flow push vs `max-lg:absolute` overlay with a backdrop. Collapse state * is chrome (per-node-id), owned by the injected collapse store. */ - import type { LayoutOp, Path, SplitNode } from "@dispatch/layout-contract"; + import type { LayoutOp, Path, SplitNode, TileMarker } from "@dispatch/layout-contract"; import type { CollapseStore } from "../logic/collapse"; - import { markerOutline } from "../logic/marker"; + import { markerStyle } from "../logic/marker"; import TileNode from "./TileNode.svelte"; let { @@ -36,10 +36,31 @@ const key = keyOf(i, id); return state.openByNode[key] ?? state.defaultOpen; } + + /** + * The scroll container's inline style: the fixed sidebar width + slide + * transform + any diagnostic marker (inset) — none when unmarked. + * + * The marker goes on THIS div, not the aside: the inner div paints an + * OPAQUE full-height background (bg-base-100) that would cover an outline + * drawn on the aside. This div's own box never moves — only its contents + * scroll — and overflow-y-auto clips descendants, never an element's own + * outline, so the marker frames exactly the visible sidebar. + */ + function scrollViewportStyle( + basis: number | undefined, + open: boolean, + marker: TileMarker | undefined, + ): string { + const parts = [`width: ${basis ?? 320}px`, `transform: translateX(${open ? "0" : "100%"})`]; + const markerCss = markerStyle(marker); + if (markerCss !== undefined) parts.push(markerCss); + return parts.join("; "); + } {#if node.dir === "col"} -
+
{#each node.children as child, i (i)} {@const key = keyOf(i, child.node.id)}
{:else} -
+
{#each node.children as child, i (i)} {@const key = keyOf(i, child.node.id)} {#if child.collapsible} {@const open = isOpen(i, child.node.id)} +