From 0064decd4043e5320c4bfe05603ad16476c94d65 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 3 Sep 2026 00:52:46 -0700 Subject: [PATCH 1/2] Add tests for settings Actions column clipping (CL-7378) Roles and People Actions cells must not inherit page-fill nowrap+ellipsis, or system-role notes and Rename/Delete/Remove controls get truncated. --- .../settings-ui/test/people-section.test.tsx | 58 +++++++++++++++- .../settings-ui/test/roles-section.test.tsx | 69 ++++++++++++++++++- 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/packages/settings-ui/test/people-section.test.tsx b/packages/settings-ui/test/people-section.test.tsx index 56715fa79..747ad61ad 100644 --- a/packages/settings-ui/test/people-section.test.tsx +++ b/packages/settings-ui/test/people-section.test.tsx @@ -7,11 +7,16 @@ // which only works for existing accounts), role changes go through the // native role-assignment routes, the last owner can't be demoted, and a // pending invite can be cancelled. +// +// CL-7378: the People table's Actions cells must not inherit page-fill's +// nowrap+ellipsis clip, or Suspend/Remove controls get truncated. import { afterEach, describe, expect, mock, test } from "bun:test"; +import { readFileSync } from "node:fs"; import { act } from "react"; import { createRoot } from "react-dom/client"; import type { Root } from "react-dom/client"; +import { renderToStaticMarkup } from "react-dom/server"; const reportErrorCalls: { error: unknown; @@ -24,7 +29,7 @@ mock.module("@corbits/error-sink", () => ({ }, })); -const { PeopleSection } = await import("../src/people-section"); +const { PeopleSection, PeopleTable } = await import("../src/people-section"); const realFetch = globalThis.fetch; afterEach(() => { @@ -632,3 +637,54 @@ describe("PeopleSection", () => { }); } }); + +function actionsCellOpenTag(markup: string): string { + const heads = [...markup.matchAll(/]*>[\s\S]*?<\/th>/g)]; + const index = heads.findIndex((match) => /Actions/.test(match[0])); + if (index === -1) throw new Error("no Actions column"); + const firstRow = //.exec(markup); + if (firstRow === null) throw new Error("no body row"); + const cells = [...firstRow[0].matchAll(/]*>/g)]; + const cell = cells[index]; + if (cell === undefined) throw new Error("no Actions td"); + return cell[0]; +} + +function ruleFor(css: string, className: string): string { + const selector = new RegExp(`\\.${className}\\s*[,{]`); + const block = css.split("}").find((candidate) => selector.test(candidate)); + if (block === undefined) throw new Error(`no rule for .${className}`); + return block.slice(block.indexOf("{")); +} + +describe("PeopleTable Actions column", () => { + test("Actions cells opt out of page-fill nowrap-ellipsis clipping", () => { + const markup = renderToStaticMarkup( + undefined} + onReactivate={() => undefined} + onRemove={() => undefined} + onRoleChange={() => undefined} + />, + ); + + expect(actionsCellOpenTag(markup)).toContain("settings-actions-cell"); + expect(markup).toContain("Remove"); + + const css = readFileSync( + new URL("../src/styles.css", import.meta.url), + "utf8", + ); + const cell = ruleFor(css, "settings-actions-cell"); + expect(cell).toContain("overflow: visible"); + expect(cell).toContain("min-width"); + expect(cell).not.toContain("text-overflow: ellipsis"); + expect(cell).not.toContain("white-space: nowrap"); + expect(cell).not.toContain("overflow: hidden"); + + const actions = ruleFor(css, "settings-row-actions"); + expect(actions).toContain("min-width"); + }); +}); diff --git a/packages/settings-ui/test/roles-section.test.tsx b/packages/settings-ui/test/roles-section.test.tsx index ec94723a9..b04de79d2 100644 --- a/packages/settings-ui/test/roles-section.test.tsx +++ b/packages/settings-ui/test/roles-section.test.tsx @@ -2,11 +2,16 @@ // principals — the same roster as the People section. Agent/workflow // machine identities are excluded to prevent placeholder-named garbage // accounts from polluting the picker. +// +// CL-7378: the Roles table's Actions cells must not inherit page-fill's +// nowrap+ellipsis clip, or system-role notes and Rename/Delete controls +// get truncated. import { describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; import { renderToStaticMarkup } from "react-dom/server"; -import { RoleAssignments } from "../src/roles-section"; +import { RoleAssignments, RolesTable } from "../src/roles-section"; const timestamps = { createdAt: "2026-01-01T00:00:00.000Z", @@ -30,6 +35,25 @@ function principal( }; } +function actionsCellOpenTag(markup: string): string { + const heads = [...markup.matchAll(/]*>[\s\S]*?<\/th>/g)]; + const index = heads.findIndex((match) => /Actions/.test(match[0])); + if (index === -1) throw new Error("no Actions column"); + const firstRow = //.exec(markup); + if (firstRow === null) throw new Error("no body row"); + const cells = [...firstRow[0].matchAll(/]*>/g)]; + const cell = cells[index]; + if (cell === undefined) throw new Error("no Actions td"); + return cell[0]; +} + +function ruleFor(css: string, className: string): string { + const selector = new RegExp(`\\.${className}\\s*[,{]`); + const block = css.split("}").find((candidate) => selector.test(candidate)); + if (block === undefined) throw new Error(`no rule for .${className}`); + return block.slice(block.indexOf("{")); +} + describe("RoleAssignments picker", () => { test("shows only user-kind principals, not agents or workflows", () => { const markup = renderToStaticMarkup( @@ -106,3 +130,46 @@ describe("RoleAssignments picker", () => { expect(markup).not.toContain(" { + test("Actions cells opt out of page-fill nowrap-ellipsis clipping", () => { + const markup = renderToStaticMarkup( + undefined} + onRename={() => undefined} + />, + ); + + expect(actionsCellOpenTag(markup)).toContain("settings-actions-cell"); + + const css = readFileSync( + new URL("../src/styles.css", import.meta.url), + "utf8", + ); + const cell = ruleFor(css, "settings-actions-cell"); + expect(cell).toContain("overflow: visible"); + expect(cell).toContain("min-width"); + expect(cell).not.toContain("text-overflow: ellipsis"); + expect(cell).not.toContain("white-space: nowrap"); + expect(cell).not.toContain("overflow: hidden"); + + const actions = ruleFor(css, "settings-row-actions"); + expect(actions).toContain("min-width"); + }); +}); From 9cbc4d1e34db753356c3ec130e0f6ab9e5644e06 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 3 Sep 2026 00:52:55 -0700 Subject: [PATCH 2/2] Keep settings Actions cells from clipping under page-fill (CL-7378) .page-fill td nowrap+ellipsis truncated Roles notes and People Remove controls. Settings-scoped override: Actions cells overflow visible with min-width, no nowrap ellipsis. Agents and other page-fill tables unchanged. --- packages/settings-ui/src/people-section.tsx | 4 ++-- packages/settings-ui/src/roles-section.tsx | 4 ++-- packages/settings-ui/src/styles.css | 10 ++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/settings-ui/src/people-section.tsx b/packages/settings-ui/src/people-section.tsx index 58d3799de..5d9785bf7 100644 --- a/packages/settings-ui/src/people-section.tsx +++ b/packages/settings-ui/src/people-section.tsx @@ -348,7 +348,7 @@ export function PeopleTable({ Kind Status Roles - Actions + Actions @@ -402,7 +402,7 @@ export function PeopleTable({ )) )} - +
{person.status === "suspended" ? (