From 34ed2d1ffb643aaed7f4681d4034ad4d7f745f42 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 2 Aug 2026 21:50:26 -0700 Subject: [PATCH] FN-8750: redesign resolved GitHub issues table Improve the resolved GitHub issues table across desktop, tablet, and mobile layouts. - Add responsive table styling with semantic links and readable metadata. - Add table hooks and regression coverage for layout and token use. - Capture desktop and mobile layout smoke screenshots. Files changed: ...mmand-center-css-token-canonicalization.test.ts | 15 ++ .../components/command-center/areas/GithubArea.tsx | 37 +++-- .../areas/__tests__/areas.github-signals.test.tsx | 27 ++-- .../app/components/command-center/areas/areas.css | 151 +++++++++++++++++++++ .../dashboard/scripts/browser-layout-smoke.mjs | 120 +++++++++++++++- screenshots/fn-8750-resolved-github-desktop.png | 3 + screenshots/fn-8750-resolved-github-mobile.png | 3 + 7 files changed, 333 insertions(+), 23 deletions(-) Fusion-Task-Id: FN-8750 Fusion-Task-Lineage: b229bad6-7d32-4a04-9681-c4184c46c015 Co-authored-by: Fusion (runfusion.ai) --- ...-center-css-token-canonicalization.test.ts | 15 ++ .../command-center/areas/GithubArea.tsx | 37 +++-- .../__tests__/areas.github-signals.test.tsx | 27 +++- .../components/command-center/areas/areas.css | 151 ++++++++++++++++++ .../scripts/browser-layout-smoke.mjs | 120 +++++++++++++- .../fn-8750-resolved-github-desktop.png | 3 + .../fn-8750-resolved-github-mobile.png | 3 + 7 files changed, 333 insertions(+), 23 deletions(-) create mode 100644 screenshots/fn-8750-resolved-github-desktop.png create mode 100644 screenshots/fn-8750-resolved-github-mobile.png diff --git a/packages/dashboard/app/components/command-center/__tests__/command-center-css-token-canonicalization.test.ts b/packages/dashboard/app/components/command-center/__tests__/command-center-css-token-canonicalization.test.ts index 13b0c720f5..111d393981 100644 --- a/packages/dashboard/app/components/command-center/__tests__/command-center-css-token-canonicalization.test.ts +++ b/packages/dashboard/app/components/command-center/__tests__/command-center-css-token-canonicalization.test.ts @@ -3,6 +3,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; const COMMAND_CENTER_ROOT = path.resolve(__dirname, ".."); +const AREAS_CSS_PATH = path.join(COMMAND_CENTER_ROOT, "areas", "areas.css"); function collectCssFiles(dir: string): string[] { const out: string[] = []; @@ -37,4 +38,18 @@ describe("Command Center CSS token canonicalization", () => { expect(chartsCss).toContain("var(--accent)"); expect(chartsCss).toContain("var(--text)"); }); + + it("scopes the resolved GitHub table contract to tokenized responsive selectors", () => { + const areasCss = readFileSync(AREAS_CSS_PATH, "utf8"); + const resolvedTableCss = areasCss.match(/\.cc-github-resolved-table-wrap[\s\S]*?(?=\.cc-sort-caret)/)?.[0]; + + expect(resolvedTableCss).toBeTruthy(); + expect(resolvedTableCss).toContain(".cc-github-resolved-table"); + expect(resolvedTableCss).toContain("table-layout: fixed"); + expect(resolvedTableCss).toContain("overflow-wrap: anywhere"); + expect(resolvedTableCss).toContain("var(--accent)"); + expect(resolvedTableCss).toContain("var(--focus-ring)"); + expect(resolvedTableCss).toContain("@media (max-width: 768px)"); + expect(resolvedTableCss).not.toMatch(/\b\d+px\s*;|#[0-9a-f]{3,8}\b|rgba\(/i); + }); }); diff --git a/packages/dashboard/app/components/command-center/areas/GithubArea.tsx b/packages/dashboard/app/components/command-center/areas/GithubArea.tsx index d09693404c..d506c46119 100644 --- a/packages/dashboard/app/components/command-center/areas/GithubArea.tsx +++ b/packages/dashboard/app/components/command-center/areas/GithubArea.tsx @@ -4,6 +4,9 @@ The GitHub Command Center area visualizes only locally persisted task-store data FNXC:CommandCenterGithub 2026-06-21-03:28: FN-6722 adds a resolved-issues detail list so operators can see which imported GitHub source issues were completed in the selected range. The UI must render only rows returned by the local task-store analytics endpoint, link out only when `sourceIssueUrl` exists, and mark `updatedAt` fallback dates as approximate. + +FNXC:CommandCenterGithub 2026-08-03-04:08: +FN-8750 gives the resolved table its own responsive visual contract. Semantic outbound links retain their safe attributes while using the dashboard accent/focus treatment; long task titles wrap independently from compact task IDs and resolution metadata at desktop, tablet, and mobile widths. */ import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -269,8 +272,8 @@ export function GithubArea({ range, projectId }: { range: DateRange; projectId?: {hasResolvedIssues ? (

{t("commandCenter.github.resolvedTitle", "Resolved issues")}

-
- +
+
@@ -285,11 +288,13 @@ export function GithubArea({ range, projectId }: { range: DateRange; projectId?: issue.resolvedAt, t("commandCenter.github.resolvedAtUnknown", "Unknown"), ); + const hasTaskTitle = Boolean(issue.taskTitle); return ( - - - ); diff --git a/packages/dashboard/app/components/command-center/areas/__tests__/areas.github-signals.test.tsx b/packages/dashboard/app/components/command-center/areas/__tests__/areas.github-signals.test.tsx index accc48b8e7..fc6fd45890 100644 --- a/packages/dashboard/app/components/command-center/areas/__tests__/areas.github-signals.test.tsx +++ b/packages/dashboard/app/components/command-center/areas/__tests__/areas.github-signals.test.tsx @@ -114,14 +114,15 @@ describe("GithubArea", () => { expect(within(repoChart).getByLabelText("acme/alpha: 4 filed / 1 fixed")).toBeTruthy(); }); - it("renders resolved issues with safe outbound links and approximation labels", async () => { + it("renders long resolved content with safe links, title fallback, and exact or approximate metadata", async () => { + const longTitle = "Resolve a deliberately long imported GitHub issue title without forcing the Command Center table beyond its responsive container"; apiMock.mockResolvedValue({ ...githubFixture(), resolved: [ { taskId: "FN-100", - taskTitle: "Fix alpha crash", - repo: "acme/alpha", + taskTitle: longTitle, + repo: "acme/a-deliberately-long-repository-reference", issueNumber: 123, url: "https://github.com/acme/alpha/issues/123", resolvedAt: "2026-06-10T12:34:56.000Z", @@ -129,7 +130,7 @@ describe("GithubArea", () => { }, { taskId: "FN-101", - taskTitle: "Patch unknown import", + taskTitle: "", repo: "(unknown)", issueNumber: null, url: null, @@ -142,20 +143,30 @@ describe("GithubArea", () => { render(); const section = await screen.findByTestId("cc-github-resolved"); + const table = within(section).getByTestId("cc-github-resolved-table"); expect(section.textContent).toContain("Resolved issues"); - expect(section.textContent).toContain("acme/alpha#123"); - expect(section.textContent).toContain("Fix alpha crash"); + expect(section.textContent).toContain("acme/a-deliberately-long-repository-reference#123"); + expect(section.textContent).toContain(longTitle); expect(section.textContent).toContain("FN-100"); expect(section.textContent).toContain("(unknown)"); - expect(section.textContent).toContain("Patch unknown import"); expect(section.textContent).toContain("approx"); expect(section.textContent).toContain("2026"); + expect(table.className).toContain("cc-github-resolved-table"); - const linkedIssue = within(section).getByRole("link", { name: "Open GitHub issue acme/alpha#123" }); + const linkedIssue = within(section).getByRole("link", { name: "Open GitHub issue acme/a-deliberately-long-repository-reference#123" }); + expect(linkedIssue.className).toContain("cc-github-resolved-issue-link"); expect(linkedIssue.getAttribute("href")).toBe("https://github.com/acme/alpha/issues/123"); expect(linkedIssue.getAttribute("target")).toBe("_blank"); expect(linkedIssue.getAttribute("rel")).toBe("noopener noreferrer"); expect(within(section).queryByRole("link", { name: /unknown/i })).toBeNull(); + + const title = within(section).getByText(longTitle); + expect(title.className).toContain("cc-github-resolved-task-title"); + expect(within(section).getByText("FN-100").className).toContain("cc-github-resolved-task-id"); + const fallback = within(section).getByText("FN-101"); + expect(fallback.className).toContain("cc-github-resolved-task-title"); + expect(within(section).queryByText("(FN-101)")).toBeNull(); + expect(within(section).getByText("approx").className).toContain("cc-github-resolved-date-approx"); }); it("omits the resolved issues section for an empty resolved list", async () => { diff --git a/packages/dashboard/app/components/command-center/areas/areas.css b/packages/dashboard/app/components/command-center/areas/areas.css index 70f45794ef..4bd37d3cf9 100644 --- a/packages/dashboard/app/components/command-center/areas/areas.css +++ b/packages/dashboard/app/components/command-center/areas/areas.css @@ -174,6 +174,157 @@ FN-6680 keeps table-heavy chart areas on the same --border-width/--border-subtle color: var(--text); } +/* +FNXC:CommandCenterGithub 2026-08-03-04:08: +FN-8750 scopes the resolved-issues redesign below its GitHub-specific classes so Team, Workflow, and Tokens retain their compact analytics-table contract. A fixed table layout and wrapping task title preserve readable rows without page-level overflow; issue links stay visibly actionable with the shared accent and focus ring in every theme. +*/ +.cc-github-resolved-table-wrap { + box-shadow: var(--shadow-sm); +} + +.cc-github-resolved-table { + table-layout: fixed; +} + +.cc-github-resolved-table th, +.cc-github-resolved-table td { + border-color: var(--border-subtle); + white-space: normal; +} + +.cc-github-resolved-table thead th { + background: var(--surface-2); +} + +.cc-github-resolved-table th:first-child, +.cc-github-resolved-table td:first-child { + inline-size: 28%; +} + +.cc-github-resolved-table th:nth-child(2), +.cc-github-resolved-table td:nth-child(2) { + inline-size: 48%; + text-align: left; +} + +.cc-github-resolved-table th:last-child, +.cc-github-resolved-table td:last-child { + inline-size: 24%; +} + +.cc-github-resolved-table tbody tr { + cursor: default; + transition: background-color var(--duration-fast) ease; +} + +.cc-github-resolved-table tbody tr:hover { + background: color-mix(in srgb, var(--accent) 8%, var(--surface-1)); +} + +.cc-github-resolved-table tbody tr:last-child td { + border-block-end: 0; +} + +.cc-github-resolved-issue-cell, +.cc-github-resolved-task-cell, +.cc-github-resolved-date-cell { + vertical-align: top; +} + +.cc-github-resolved-issue-link { + color: var(--accent); + font-weight: 600; + overflow-wrap: anywhere; + text-decoration: underline; + text-decoration-color: color-mix(in srgb, var(--accent) 55%, transparent); + text-underline-offset: var(--space-xs); + transition: color var(--duration-fast) ease, text-decoration-color var(--duration-fast) ease; +} + +.cc-github-resolved-issue-link:hover { + color: var(--text); + text-decoration-color: currentColor; +} + +.cc-github-resolved-issue-link:focus-visible { + outline: none; + box-shadow: var(--focus-ring); +} + +.cc-github-resolved-issue-ref { + color: var(--text-muted); + overflow-wrap: anywhere; +} + +.cc-github-resolved-task { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--space-xs); + min-inline-size: 0; +} + +.cc-github-resolved-task-title { + color: var(--text); + font-weight: 600; + overflow-wrap: anywhere; +} + +.cc-github-resolved-task-id { + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.cc-github-resolved-date { + display: inline-flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: var(--space-xs); + color: var(--text); +} + +.cc-github-resolved-date-approx { + white-space: nowrap; +} + +@media (min-width: 769px) and (max-width: 1024px) { + .cc-github-resolved-table th:first-child, + .cc-github-resolved-table td:first-child { + inline-size: 32%; + } + + .cc-github-resolved-table th:nth-child(2), + .cc-github-resolved-table td:nth-child(2) { + inline-size: 44%; + } +} + +@media (max-width: 768px) { + .cc-github-resolved-table th, + .cc-github-resolved-table td { + padding: var(--space-sm); + } + + .cc-github-resolved-table th:first-child, + .cc-github-resolved-table td:first-child { + inline-size: 31%; + } + + .cc-github-resolved-table th:nth-child(2), + .cc-github-resolved-table td:nth-child(2) { + inline-size: 45%; + } + + .cc-github-resolved-table th:last-child, + .cc-github-resolved-table td:last-child { + inline-size: 24%; + } + + .cc-github-resolved-date { + justify-content: flex-start; + } +} + .cc-sort-caret { margin-inline-start: var(--space-xs); font-size: 0.7em; diff --git a/packages/dashboard/scripts/browser-layout-smoke.mjs b/packages/dashboard/scripts/browser-layout-smoke.mjs index 70d1e410ec..f836ce14d3 100644 --- a/packages/dashboard/scripts/browser-layout-smoke.mjs +++ b/packages/dashboard/scripts/browser-layout-smoke.mjs @@ -24,6 +24,9 @@ const gitManagerAfterMobileScreenshotPath = process.env.FUSION_GIT_MANAGER_AFTER const gitHubImportBeforeMobileScreenshotPath = process.env.FUSION_GITHUB_IMPORT_BEFORE_MOBILE_SCREENSHOT; const gitHubImportAfterMobileScreenshotPath = process.env.FUSION_GITHUB_IMPORT_AFTER_MOBILE_SCREENSHOT; const gitHubImportAfterShortScreenshotPath = process.env.FUSION_GITHUB_IMPORT_AFTER_SHORT_SCREENSHOT; +const resolvedGithubDesktopScreenshotPath = process.env.FUSION_RESOLVED_GITHUB_DESKTOP_SCREENSHOT; +const resolvedGithubMobileScreenshotPath = process.env.FUSION_RESOLVED_GITHUB_MOBILE_SCREENSHOT; +const smokeTheme = process.env.FUSION_BROWSER_SMOKE_THEME === "light" ? "light" : "dark"; function log(message) { console.log(`[dashboard-browser-smoke] ${message}`); @@ -205,6 +208,41 @@ export function createSmokeHtml() { Blink must measure the production responsive contract at each supported phone width because jsdom cannot detect wrapping, flex-track shrinkage, overflow, or touch-target geometry. */ + /* + FNXC:CommandCenterGithub 2026-08-03-04:08: + FN-8750 needs a real-browser, production-CSS fixture because jsdom cannot measure fixed-table tracks, + long-word wrapping, or page overflow. The fixture mirrors URL/no-URL, exact/approximate, and title-fallback rows + so the desktop and mobile proof captures show the same resilient resolved-issue contract operators use. + */ + const resolvedGithubTableFixture = ` +
+
+
+
+

Resolved issues

+
+
{t("commandCenter.github.resolvedIssue", "Issue")}
+ {issue.url ? ( ) : ( - {issueRef} + {issueRef} )} - {issue.taskTitle || issue.taskId}{" "} - ({issue.taskId}) + + + {issue.taskTitle || issue.taskId} + {hasTaskTitle ? {issue.taskId} : null} + - {resolvedAt}{" "} - {!issue.resolvedAtExact ? ( - - {t("commandCenter.github.resolvedApprox", "approx")} - - ) : null} + + + {resolvedAt} + {!issue.resolvedAtExact ? ( + + {t("commandCenter.github.resolvedApprox", "approx")} + + ) : null} +
+ + + + + + + + + + + + + +
IssueResolving taskResolved at
acme/a-deliberately-long-repository-reference#123Resolve a deliberately long imported GitHub issue title without forcing the Command Center table beyond its responsive containerFN-1006/10/2026, 12:34 PM
(unknown)FN-1016/09/2026, 8:00 AMapprox
+
+
+ + + + `; + const githubImportMobileActionFixture = `
@@ -293,7 +331,7 @@ export function createSmokeHtml() { Fusion dashboard browser smoke - +
${gitManagerFixtures} ${gitHubImportFixtures} @@ -375,6 +413,7 @@ export function createSmokeHtml() {
${githubImportMobileActionFixture} + ${resolvedGithubTableFixture}