From f060a39acee208e89b605b181ef682df7e69db17 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Fri, 30 Oct 2026 12:28:52 -0700 Subject: [PATCH] fix(workspace): 18px gap between a two-column row and the surface after it (#530) Recovery policy touched the Backup & export cards, in the concept and on main. Measured for every System section and Tags at 1600 and 390 px. --- TESTING.md | 5 ++-- tests/browser/concept-inventory.mjs | 3 +- .../workspace-layout-tags-system.spec.mjs | 29 +++++++++++++++++++ web/js/workspace/workspace.css | 2 ++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/TESTING.md b/TESTING.md index 6a6b9ac3..df3ca4fe 100644 --- a/TESTING.md +++ b/TESTING.md @@ -2931,13 +2931,14 @@ hand-off and the comparisons) and `library-editor-visual.spec.mjs` with the rena ### Card tops, the Reusable tags window and System's width (#530) Found in the maintainer's test of the cutover branch; each departs from the concept, whose files are not edited. The -comparisons add `CONCEPT_LAYOUT_DEVIATIONS` (`concept-inventory.mjs`, three rules: card margin, System limit, tag header +comparisons add `CONCEPT_LAYOUT_DEVIATIONS` (`concept-inventory.mjs`, four rules: card margins, System limit, tag header background and line) to the opened concept page, so the boxes that follow from them are still compared. The new spec pins the real values and that the unmapped concept keeps its 18px offset. - **D182**: the second card of a `.two-col` starts level with the first. The concept's `.surface + .surface` 18px margin also applies inside its grid, which put Categories and Workspace preferences 18px lower. Stacked cards are now one 18px - gap apart instead of 36px. + gap apart instead of 36px. A surface after a two-column row (Backup & export's Recovery policy) touched the row in the + concept and on main; it now sits 18px below it. - **D183**: Reusable tags scrolls in its own window inside the card (`max(280px, 100dvh - 500px)`), so a long vocabulary no longer runs the page off screen. The column headers stick to the top of the window, with the surface background and a drawn line. Load more stays below the window; the end of the list inside the window (`.vocabulary-end`, observed with diff --git a/tests/browser/concept-inventory.mjs b/tests/browser/concept-inventory.mjs index 06a708d5..604980c2 100644 --- a/tests/browser/concept-inventory.mjs +++ b/tests/browser/concept-inventory.mjs @@ -116,9 +116,10 @@ async function selectTheme(page,theme) { * follows from them (wider columns, wrapped text, row heights) is compared rather than excused path by path. * Every other measurement still differs if production moves. workspace-layout-tags-system.spec.mjs pins the real values * and that the concept itself still has the old ones. - * D182: the second `.two-col` card starts level. D184: no 1000px limit on System. D183: the sticky tag header's background and drawn line. + * D182: the second `.two-col` card starts level, and a surface after a row is 18px below it. D184: no 1000px limit on System. D183: the sticky tag header's background and drawn line. */ export const CONCEPT_LAYOUT_DEVIATIONS=`.two-col > .surface + .surface { margin-top: 0; } +.two-col + .surface { margin-top: 18px; } .settings-layout { max-width: none; } .two-col > .surface > .table-wrap thead th { background: var(--ca-surface); border-bottom-color: transparent; }`; export const mapConceptLayout=page=>page.addStyleTag({content:CONCEPT_LAYOUT_DEVIATIONS}); diff --git a/tests/browser/workspace-layout-tags-system.spec.mjs b/tests/browser/workspace-layout-tags-system.spec.mjs index 100dfcba..bdbd8b2d 100644 --- a/tests/browser/workspace-layout-tags-system.spec.mjs +++ b/tests/browser/workspace-layout-tags-system.spec.mjs @@ -69,6 +69,35 @@ test('System sections use the full content width, as Inspect does (D184)',async( expect(inspect.width).toBeCloseTo(nav.width,0); }); +// Every System section and the Tags page: blocks stacked in one column are never closer than the concept's 18px surface +// gap. Between a `.two-col` and the surface after it the concept has no gap at all (D182). +const SECTIONS=['overview','appearance','sources','integrations','tagging','maintenance','backups','diagnostics']; +const stackGaps=host=>host.evaluate(node=>{ + const blocks=[...node.children].filter(child=>child.matches('.surface,.two-col')&&child.getClientRects().length); + return blocks.slice(1).map((block,index)=>[blocks[index].className,block.className,Math.round(block.getBoundingClientRect().top-blocks[index].getBoundingClientRect().bottom)]); +}); +test('every stacked card has one 18px gap, including after a two-column row (D182)',async({page,request})=>{ + for(const [width,height] of [[1600,1100],[390,844]]) { + await page.setViewportSize({width,height}); + await seedConcept(request);await expect(await request.post('/__test__/system/reset')).toBeOK(); + for(const section of SECTIONS) { + await page.goto('about:blank');await page.goto(`${workspace}#/system/${section}`); + const layout=main(page).locator('.settings-layout'); + await expect(layout.locator(':scope > .surface, :scope > .two-col').first()).toBeVisible(); + for(const gap of await stackGaps(layout)) expect([width,section,...gap]).toEqual([width,section,gap[0],gap[1],18]); + } + await seedTags(request); + await page.goto('about:blank');await page.goto(TAGS);await expect(rows(page).first()).toBeVisible(); + for(const gap of await stackGaps(main(page))) expect([width,'tags',...gap]).toEqual([width,'tags',gap[0],gap[1],18]); + } + // The deviation is real: the immutable concept has no gap after its Backup & export columns. + await page.setViewportSize({width:1600,height:1100}); + await page.goto('about:blank');await page.goto(`${CONCEPT_URL}#system`); + await page.locator('#main .section-nav').getByRole('button',{name:'Backup & export',exact:true}).click(); + const policy=page.locator('#main .settings-layout > section.surface');await expect(policy).toBeVisible(); + expect(await policy.evaluate(node=>Math.round(node.getBoundingClientRect().top-node.previousElementSibling.getBoundingClientRect().bottom))).toBe(0); +}); + test('a long tag list scrolls in its own window and the page does not grow (D183)',async({page,request})=>{ await page.setViewportSize({width:1600,height:1100}); await seedTags(request,{extra_tags:211}); diff --git a/web/js/workspace/workspace.css b/web/js/workspace/workspace.css index fcbd19d9..5ac2bdae 100644 --- a/web/js/workspace/workspace.css +++ b/web/js/workspace/workspace.css @@ -110,6 +110,8 @@ main:focus-visible { outline: 2px solid var(--ca-focus); outline-offset: 4px; } /* D182 (#530, the concept has the offset): the second card of a `.two-col` starts level with the first. The grid gap already spaces them; `.surface + .surface` is for surfaces stacked in plain flow. */ .two-col > .surface + .surface { margin-top: 0; /* D182 */ } +/* D182: a surface after a two-column row has the same gap as stacked surfaces; the concept has none there. */ +.two-col + .surface { margin-top: 18px; /* D182 */ } /* D184 (#530, the concept stops System at 1000px): every System section uses the full content width, as other pages do. */ .settings-layout { max-width: none; /* D184 */ }