From ea3cfeeac4cdcad5d09ee7d4ca25e503f664d23c Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 25 Jun 2026 19:29:49 -0700 Subject: [PATCH] FN-7027: constrain skill list row text Keep discovered skill rows within the left pane by truncating long text values. - Wrap skill names in a truncatable text span while keeping chevrons fixed-size. - Add ellipsis rules and compact font sizing for skill names, paths, and sources. - Cover long, short, and empty metadata skill rows with a dashboard test. - Add a patch changeset for the published CLI package. Files changed: .changeset/fn-7027-skill-list-truncation.md | 7 +++ packages/dashboard/app/components/SkillsView.css | 28 ++++++++++ packages/dashboard/app/components/SkillsView.tsx | 2 +- .../app/components/__tests__/SkillsView.test.tsx | 64 ++++++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7027 Fusion-Task-Lineage: ae50f1aa-e58b-4374-abae-38ca28e4073a --- .changeset/fn-7027-skill-list-truncation.md | 7 ++ .../dashboard/app/components/SkillsView.css | 28 ++++++++ .../dashboard/app/components/SkillsView.tsx | 2 +- .../components/__tests__/SkillsView.test.tsx | 64 +++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 .changeset/fn-7027-skill-list-truncation.md diff --git a/.changeset/fn-7027-skill-list-truncation.md b/.changeset/fn-7027-skill-list-truncation.md new file mode 100644 index 0000000000..898eb2b6c4 --- /dev/null +++ b/.changeset/fn-7027-skill-list-truncation.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Prevent long Skills list rows from overflowing the left pane. +category: fix +dev: Constrains SkillsView discovered-skill name, path, and source rows with ellipsis truncation. diff --git a/packages/dashboard/app/components/SkillsView.css b/packages/dashboard/app/components/SkillsView.css index 249dd584a9..2ae49ba676 100644 --- a/packages/dashboard/app/components/SkillsView.css +++ b/packages/dashboard/app/components/SkillsView.css @@ -192,7 +192,17 @@ The search controls at the top of the Skills list need breathing room below the flex: 1; } +/* +FNXC:Skills 2026-06-25-00:00: +Left Discovered Skills rows must keep compact, intentional text sizing and truncate every text line with ellipsis so long names, paths, or sources never force horizontal scroll in wide clamped, narrow dock, or mobile list layouts. The chevron remains fixed-size while only the skill-name text truncates. +*/ .skills-view-item-name { + display: flex; + align-items: center; + gap: var(--space-xs); + max-width: 100%; + min-width: 0; + font-size: 0.9em; font-weight: 500; color: var(--text); overflow: hidden; @@ -200,7 +210,20 @@ The search controls at the top of the Skills list need breathing room below the white-space: nowrap; } +.skills-view-item-name svg { + flex-shrink: 0; +} + +.skills-view-item-name-text { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .skills-view-item-path { + max-width: 100%; + font-size: 0.85em; color: var(--text-muted); font-family: var(--font-mono); overflow: hidden; @@ -209,9 +232,14 @@ The search controls at the top of the Skills list need breathing room below the } .skills-view-item-source { + max-width: 100%; + font-size: 0.85em; color: var(--text-dim); text-transform: uppercase; letter-spacing: calc(var(--space-xs) / 8); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } /* Toggle switch */ diff --git a/packages/dashboard/app/components/SkillsView.tsx b/packages/dashboard/app/components/SkillsView.tsx index c818f990af..b314ed99b0 100644 --- a/packages/dashboard/app/components/SkillsView.tsx +++ b/packages/dashboard/app/components/SkillsView.tsx @@ -563,7 +563,7 @@ export function SkillsView({ projectId, addToast, onClose }: SkillsViewProps) {
{isSelected ? : } - {skill.name} + {skill.name} {skill.relativePath} {skill.metadata.source} diff --git a/packages/dashboard/app/components/__tests__/SkillsView.test.tsx b/packages/dashboard/app/components/__tests__/SkillsView.test.tsx index ef74086244..0fb913c2f1 100644 --- a/packages/dashboard/app/components/__tests__/SkillsView.test.tsx +++ b/packages/dashboard/app/components/__tests__/SkillsView.test.tsx @@ -607,6 +607,70 @@ describe("SkillsView", () => { }); }); + it("declares ellipsis truncation for long and short discovered-skill row text", async () => { + const longToken = "skill".repeat(30); + const longSkill: DiscoveredSkill = { + id: "github::skills/" + longToken, + name: longToken, + path: "/project/.fusion/skills/" + longToken, + relativePath: "skills/" + longToken + "/nested/" + longToken, + enabled: true, + metadata: { + source: "github.com/example/" + longToken + "/" + longToken, + scope: "project", + origin: "package", + }, + }; + const emptyMetadataSkill = { + id: "local::skills/empty-metadata", + name: "empty-metadata-skill", + path: "/project/.fusion/skills/empty-metadata", + relativePath: "", + enabled: false, + metadata: { + scope: "project", + origin: "top-level", + }, + } as unknown as DiscoveredSkill; + mockFetchDiscoveredSkills.mockResolvedValue([ + mockDiscoveredSkills[0], + longSkill, + emptyMetadataSkill, + ]); + + render(); + + await waitFor(() => { + expect(screen.getByText(longSkill.name)).toBeTruthy(); + expect(screen.getByText("test-skill")).toBeTruthy(); + expect(screen.getByText("empty-metadata-skill")).toBeTruthy(); + }); + + const assertTruncation = (element: Element | null) => { + expect(element).toBeTruthy(); + const styles = getComputedStyle(element as Element); + expect(styles.overflow).toBe("hidden"); + expect(styles.textOverflow).toBe("ellipsis"); + expect(styles.whiteSpace).toBe("nowrap"); + }; + + const longRow = screen.getByText(longSkill.name).closest(".skills-view-item"); + const shortRow = screen.getByText("test-skill").closest(".skills-view-item"); + const emptyMetadataRow = screen.getByText("empty-metadata-skill").closest(".skills-view-item"); + + assertTruncation(longRow?.querySelector(".skills-view-item-name-text") ?? null); + assertTruncation(longRow?.querySelector(".skills-view-item-path") ?? null); + assertTruncation(longRow?.querySelector(".skills-view-item-source") ?? null); + assertTruncation(shortRow?.querySelector(".skills-view-item-name-text") ?? null); + assertTruncation(shortRow?.querySelector(".skills-view-item-path") ?? null); + assertTruncation(shortRow?.querySelector(".skills-view-item-source") ?? null); + + expect(longRow?.querySelector(".skills-view-item-name svg")).toBeTruthy(); + expect(longRow?.querySelector(".skills-view-item-toggle")).toBeTruthy(); + expect(emptyMetadataRow?.querySelector(".skills-view-item-path")?.textContent).toBe(""); + expect(emptyMetadataRow?.querySelector(".skills-view-item-source")?.textContent).toBe(""); + }); + it("renders toggle sliders with .skills-view-toggle-slider class", async () => { render();