From 9c7704663ba8194d02a04269c84628bdda5dcb7c Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 8 Nov 2026 15:05:28 +0800 Subject: [PATCH] fix(settings): contain long names in configuration lists --- e2e/settingsListLayout.spec.ts | 73 +++++++++++++++++++ .../settings/pages/Workflow.module.css | 16 ++++ src/features/settings/pages/runTargets.tsx | 9 ++- src/features/settings/pages/ssh.tsx | 8 +- src/features/settings/pages/tools.tsx | 8 +- 5 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 e2e/settingsListLayout.spec.ts diff --git a/e2e/settingsListLayout.spec.ts b/e2e/settingsListLayout.spec.ts new file mode 100644 index 000000000..9d81f61c6 --- /dev/null +++ b/e2e/settingsListLayout.spec.ts @@ -0,0 +1,73 @@ +import { expect, test } from "./fixtures"; + +for (const kind of ["external", "ssh", "target"] as const) { + test(`${kind} settings keep long names and details inside their selection row`, async ({ workbench: page }) => { + await page.keyboard.press("Meta+,"); + await expect(page.getByRole("dialog", { name: "Settings", exact: true })).toBeVisible(); + await page.evaluate(async (kind) => { + const path = "/src/features/settings/model/staging.ts"; + const { selectPage, stage } = await import(/* @vite-ignore */ path); + const remotePath = "/src/platform/remoteSettings.ts"; + const workflowPath = "/src/platform/workflowSettings.ts"; + const { DEFAULT_SSH_CONFIG, DEFAULT_RUN_TARGET } = await import(/* @vite-ignore */ remotePath); + const { DEFAULT_EXTERNAL_TOOL } = await import(/* @vite-ignore */ workflowPath); + const name = "Run integration tests for the current project with the configured environment"; + const detail = "Shared infrastructure for the staging environment"; + if (kind === "external") { + stage("tools.externalTools", "global", "externalTools", [ + { ...DEFAULT_EXTERNAL_TOOL, id: "qa", name, group: detail }, + ]); + selectPage("tools.externalTools"); + } else if (kind === "ssh") { + stage("tools.sshConfigurations", "global", "ssh.configs", [ + { ...DEFAULT_SSH_CONFIG, id: "qa", name, host: detail, user: "qa" }, + ]); + selectPage("tools.sshConfigurations"); + } else { + stage("build.runTargets", "global", "runTargets", [ + { ...DEFAULT_RUN_TARGET, id: "qa", name, kind: "docker", image: detail }, + ]); + selectPage("build.runTargets"); + } + }, kind); + const row = page.getByRole("dialog", { name: "Settings", exact: true }).getByRole("option").first(); + await expect(row).toBeVisible(); + for (const width of [1000, 640, 520]) { + await page.setViewportSize({ width, height: 760 }); + const fits = await row.evaluate((row) => { + const box = row.getBoundingClientRect(); + return [...row.querySelectorAll(":scope > span")].every((span) => { + const range = document.createRange(); + range.selectNodeContents(span); + return [...range.getClientRects()].every((r) => r.top >= box.top - 1 && r.bottom <= box.bottom + 1); + }); + }); + expect(fits).toBe(true); + } + await expect(row.locator(":scope > span").first()).toHaveAttribute( + "title", + "Run integration tests for the current project with the configured environment", + ); + await page.evaluate(async (kind) => { + const path = "/src/features/settings/model/staging.ts"; + const { stage } = await import(/* @vite-ignore */ path); + const [id, key] = + kind === "external" + ? ["tools.externalTools", "externalTools"] + : kind === "ssh" + ? ["tools.sshConfigurations", "ssh.configs"] + : ["build.runTargets", "runTargets"]; + stage(id, "global", key, []); + }, kind); + const empty = page.getByRole("dialog", { name: "Settings", exact: true }).getByRole("listbox"); + await expect(row).toHaveCount(0); + const hintFits = await empty.evaluate((list) => { + const hint = list.firstElementChild!; + const box = hint.getBoundingClientRect(); + const range = document.createRange(); + range.selectNodeContents(hint); + return [...range.getClientRects()].every((r) => r.top >= box.top - 1 && r.bottom <= box.bottom + 1); + }); + expect(hintFits).toBe(true); + }); +} diff --git a/src/features/settings/pages/Workflow.module.css b/src/features/settings/pages/Workflow.module.css index 5f004af6e..2a1f6d684 100644 --- a/src/features/settings/pages/Workflow.module.css +++ b/src/features/settings/pages/Workflow.module.css @@ -166,6 +166,21 @@ padding: 0 8px; cursor: default; } +.toolRow > span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.toolName { + flex: 1; +} +.toolRow.empty { + height: auto; + min-height: var(--h-list-row); + padding-block: 4px; + overflow-wrap: anywhere; +} .toolRow:hover { background: var(--bg-tree-hover); } @@ -174,6 +189,7 @@ background: var(--bg-tree-selected); } .toolGroup { + max-width: 45%; color: var(--fg-subtle); margin-left: auto; } diff --git a/src/features/settings/pages/runTargets.tsx b/src/features/settings/pages/runTargets.tsx index a19603a53..fd1a2c2b6 100644 --- a/src/features/settings/pages/runTargets.tsx +++ b/src/features/settings/pages/runTargets.tsx @@ -491,8 +491,13 @@ function RunTargetsEditor() { className={clsx(styles.toolRow, t.id === selected && styles.toolRowSelected)} onMouseDown={() => setSelected(t.id)} > - {t.name || t.id} - + + {t.name || t.id} + + {t.kind === "ssh" ? (t.sshConfig ? sshName(t.sshConfig) : "") : containerSummary(t)} diff --git a/src/features/settings/pages/ssh.tsx b/src/features/settings/pages/ssh.tsx index 0a5eca0fb..ee2b9f9cb 100644 --- a/src/features/settings/pages/ssh.tsx +++ b/src/features/settings/pages/ssh.tsx @@ -339,8 +339,12 @@ function SshConfigsEditor() { className={clsx(styles.toolRow, c.id === selected && styles.toolRowSelected)} onMouseDown={() => setSelected(c.id)} > - {c.name.trim() || sshLabel(c) || c.id} - {sshLabel(c)} + + {c.name.trim() || sshLabel(c) || c.id} + + + {sshLabel(c)} + ))} diff --git a/src/features/settings/pages/tools.tsx b/src/features/settings/pages/tools.tsx index 1b08c0c17..57ee4a765 100644 --- a/src/features/settings/pages/tools.tsx +++ b/src/features/settings/pages/tools.tsx @@ -480,8 +480,12 @@ function ExternalToolsEditor() { checked={t.enabled} onChange={(on) => setTools(tools.map((x) => (x.id === t.id ? { ...x, enabled: on } : x)))} /> - {t.name} - {t.group} + + {t.name} + + + {t.group} + ))}