diff --git a/apps/web/src/cockpit-extras.css b/apps/web/src/cockpit-extras.css index aa6a44c5..9b8f90ba 100644 --- a/apps/web/src/cockpit-extras.css +++ b/apps/web/src/cockpit-extras.css @@ -644,9 +644,11 @@ } .workspace-card-right-control-spacer { - width: 38px; + /* Reserve the full absolute-control lane: right offset plus the widest + * current rightControl set (two 18px buttons with a 2px gap). */ + width: 66px; height: 18px; - flex: 0 0 38px; + flex: 0 0 66px; } .workspace-card-collapse { diff --git a/apps/web/src/workspace-card-layout.test.ts b/apps/web/src/workspace-card-layout.test.ts new file mode 100644 index 00000000..b2f25dc4 --- /dev/null +++ b/apps/web/src/workspace-card-layout.test.ts @@ -0,0 +1,43 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); + +function readCss(): string { + return fs.readFileSync(path.join(HERE, "cockpit-extras.css"), "utf8"); +} + +function declarationBlock(css: string, selector: string): string { + const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = css.match(new RegExp(`${escaped}\\s*\\{([^}]*)\\}`, "m")); + if (!match?.[1]) throw new Error(`Missing CSS block for ${selector}`); + return match[1]; +} + +function pxDeclaration(block: string, property: string): number { + const escaped = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = block.match(new RegExp(`${escaped}\\s*:\\s*(\\d+(?:\\.\\d+)?)px\\b`)); + if (!match?.[1]) throw new Error(`Missing px declaration for ${property}`); + return Number(match[1]); +} + +describe("workspace-card control lane", () => { + it("keeps PR diff totals clear of the structured workspace action buttons", () => { + const css = readCss(); + const controlBlock = declarationBlock(css, ".workspace-card-right-control"); + const spacerBlock = declarationBlock(css, ".workspace-card-right-control-spacer"); + const buttonBlock = declarationBlock(css, ".workspace-card-collapse"); + + const rightOffset = pxDeclaration(controlBlock, "right"); + const controlGap = pxDeclaration(controlBlock, "gap"); + const buttonWidth = pxDeclaration(buttonBlock, "width"); + const spacerWidth = pxDeclaration(spacerBlock, "width"); + + const maxVisibleControls = 2; + const requiredLaneWidth = rightOffset + buttonWidth * maxVisibleControls + controlGap * (maxVisibleControls - 1); + + expect(spacerWidth).toBeGreaterThanOrEqual(requiredLaneWidth); + }); +});