/.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(" | |