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) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-08-02 21:50:26 -07:00
parent a3bb584bff
commit 34ed2d1ffb
7 changed files with 333 additions and 23 deletions

View File

@@ -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);
});
});

View File

@@ -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 ? (
<div className="cc-area-section" data-testid="cc-github-resolved">
<h3 className="cc-area-section-title">{t("commandCenter.github.resolvedTitle", "Resolved issues")}</h3>
<div className="cc-table-wrap">
<table className="cc-table">
<div className="cc-table-wrap cc-github-resolved-table-wrap" data-testid="cc-github-resolved-table-wrap">
<table className="cc-table cc-github-resolved-table" data-testid="cc-github-resolved-table">
<thead>
<tr>
<th scope="col">{t("commandCenter.github.resolvedIssue", "Issue")}</th>
@@ -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 (
<tr key={issue.taskId}>
<td>
<td className="cc-github-resolved-issue-cell">
{issue.url ? (
<a
className="cc-github-resolved-issue-link"
href={issue.url}
target="_blank"
rel="noopener noreferrer"
@@ -298,20 +303,24 @@ export function GithubArea({ range, projectId }: { range: DateRange; projectId?:
{issueRef}
</a>
) : (
<span>{issueRef}</span>
<span className="cc-github-resolved-issue-ref">{issueRef}</span>
)}
</td>
<td>
<span>{issue.taskTitle || issue.taskId}</span>{" "}
<span className="cc-stat-sub">({issue.taskId})</span>
<td className="cc-github-resolved-task-cell">
<span className="cc-github-resolved-task">
<span className="cc-github-resolved-task-title">{issue.taskTitle || issue.taskId}</span>
{hasTaskTitle ? <span className="cc-stat-sub cc-github-resolved-task-id">{issue.taskId}</span> : null}
</span>
</td>
<td>
<span>{resolvedAt}</span>{" "}
{!issue.resolvedAtExact ? (
<span className="cc-stat-sub">
{t("commandCenter.github.resolvedApprox", "approx")}
</span>
) : null}
<td className="cc-github-resolved-date-cell">
<span className="cc-github-resolved-date">
<span>{resolvedAt}</span>
{!issue.resolvedAtExact ? (
<span className="cc-stat-sub cc-github-resolved-date-approx">
{t("commandCenter.github.resolvedApprox", "approx")}
</span>
) : null}
</span>
</td>
</tr>
);

View File

@@ -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(<GithubArea range={range7d} />);
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 () => {

View File

@@ -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;

View File

@@ -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 = `
<section class="command-center" data-smoke="github-resolved-table" aria-label="Resolved GitHub issues">
<div class="cc-tabpanel" role="tabpanel">
<section class="cc-area">
<div class="cc-area-section">
<h3 class="cc-area-section-title">Resolved issues</h3>
<div class="cc-table-wrap cc-github-resolved-table-wrap">
<table class="cc-table cc-github-resolved-table">
<thead><tr><th scope="col">Issue</th><th scope="col">Resolving task</th><th scope="col">Resolved at</th></tr></thead>
<tbody>
<tr>
<td class="cc-github-resolved-issue-cell"><a class="cc-github-resolved-issue-link" href="https://github.com/acme/a-deliberately-long-repository-reference/issues/123" target="_blank" rel="noopener noreferrer">acme/a-deliberately-long-repository-reference#123</a></td>
<td class="cc-github-resolved-task-cell"><span class="cc-github-resolved-task"><span class="cc-github-resolved-task-title">Resolve a deliberately long imported GitHub issue title without forcing the Command Center table beyond its responsive container</span><span class="cc-stat-sub cc-github-resolved-task-id">FN-100</span></span></td>
<td class="cc-github-resolved-date-cell"><span class="cc-github-resolved-date"><span>6/10/2026, 12:34 PM</span></span></td>
</tr>
<tr>
<td class="cc-github-resolved-issue-cell"><span class="cc-github-resolved-issue-ref">(unknown)</span></td>
<td class="cc-github-resolved-task-cell"><span class="cc-github-resolved-task"><span class="cc-github-resolved-task-title">FN-101</span></span></td>
<td class="cc-github-resolved-date-cell"><span class="cc-github-resolved-date"><span>6/09/2026, 8:00 AM</span><span class="cc-stat-sub cc-github-resolved-date-approx">approx</span></span></td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
</div>
</section>
`;
const githubImportMobileActionFixture = `
<section data-smoke="github-import-mobile-actions" aria-label="GitHub issue detail actions">
<div class="github-import-detail-actions" data-testid="github-import-detail-actions">
@@ -293,7 +331,7 @@ export function createSmokeHtml() {
<title>Fusion dashboard browser smoke</title>
<link rel="stylesheet" href="/app.css" />
</head>
<body data-theme="dark">
<body data-theme="${smokeTheme}">
<div id="root">
${gitManagerFixtures}
${gitHubImportFixtures}
@@ -375,6 +413,7 @@ export function createSmokeHtml() {
</section>
${githubImportMobileActionFixture}
${resolvedGithubTableFixture}
<footer class="executor-status-bar">
<div class="executor-status-bar__segment">
@@ -951,6 +990,31 @@ async function evaluate(page, expression) {
return result.result.value;
}
/*
FNXC:CommandCenterGithub 2026-08-03-04:32:
FN-8750 proof captures must preserve the production fixture layout so overflow checks at later viewports
measure the table rather than a temporary overlay. Render a disposable clone in an isolated host,
which keeps unrelated dashboard UI out of the artifact without mutating the measured fixture.
*/
async function captureFixtureScreenshot(page, selector, outputPath) {
await evaluate(page, `(async () => {
const fixture = document.querySelector(${JSON.stringify(selector)});
const host = document.createElement("div");
host.dataset.smokeCaptureHost = "true";
host.style.cssText = "position: fixed; inset: 0; z-index: 9999; display: flex; overflow: auto; background: var(--bg);";
const clone = fixture.cloneNode(true);
host.append(clone);
document.body.append(host);
await new Promise(requestAnimationFrame);
})()`);
try {
const screenshot = await page.send("Page.captureScreenshot", { format: "png" });
await writeFile(outputPath, Buffer.from(screenshot.data, "base64"));
} finally {
await evaluate(page, "document.querySelector('[data-smoke-capture-host]').remove()");
}
}
function assertSmokeResult(name, passed, details) {
if (!passed) {
fail(`${name} failed: ${details}`);
@@ -1067,6 +1131,43 @@ async function runSmokeChecks(page, pageUrl) {
return { viewportWidth, controls, workerToggleCount: worker.querySelectorAll('[data-smoke="agent-heartbeat-toggle"]').length, fixtureOverflow: fixture.scrollWidth - fixture.clientWidth, documentOverflow: document.documentElement.scrollWidth - viewportWidth };
})()`);
const collectResolvedGithubTableLayout = () => evaluate(page, `(() => {
const fixture = document.querySelector('[data-smoke="github-resolved-table"]');
const table = fixture.querySelector('.cc-github-resolved-table');
const title = fixture.querySelector('.cc-github-resolved-task-title');
const link = fixture.querySelector('.cc-github-resolved-issue-link');
const fixtureRect = fixture.getBoundingClientRect();
const titleRect = title.getBoundingClientRect();
const linkStyle = getComputedStyle(link);
return {
documentOverflow: document.documentElement.scrollWidth - window.innerWidth,
fixtureOverflow: fixture.scrollWidth - fixture.clientWidth,
tableOverflow: table.scrollWidth - table.clientWidth,
titleHeight: titleRect.height,
titleWidth: titleRect.width,
fixtureWidth: fixtureRect.width,
linkColor: linkStyle.color,
linkTextDecoration: linkStyle.textDecorationLine,
};
})()`);
const mobileResolvedGithubTableLayout = await collectResolvedGithubTableLayout();
assertSmokeResult(
"resolved GitHub table wraps long content without mobile page overflow",
mobileResolvedGithubTableLayout.documentOverflow <= 1
&& mobileResolvedGithubTableLayout.fixtureOverflow <= 1
&& mobileResolvedGithubTableLayout.tableOverflow <= 1
&& mobileResolvedGithubTableLayout.titleHeight > 0
&& mobileResolvedGithubTableLayout.titleWidth <= mobileResolvedGithubTableLayout.fixtureWidth
&& mobileResolvedGithubTableLayout.linkColor !== ""
&& mobileResolvedGithubTableLayout.linkTextDecoration.includes("underline"),
JSON.stringify(mobileResolvedGithubTableLayout),
);
if (resolvedGithubMobileScreenshotPath) {
await captureFixtureScreenshot(page, '[data-smoke="github-resolved-table"]', resolvedGithubMobileScreenshotPath);
log(`saved resolved GitHub mobile screenshot to ${resolvedGithubMobileScreenshotPath}`);
}
const mobileAgentHeartbeatLayout = await collectAgentHeartbeatControlLayout();
assertSmokeResult(
"agent heartbeat controls stay visible on mobile and omit ephemeral shells",
@@ -1687,6 +1788,23 @@ async function runSmokeChecks(page, pageUrl) {
mobile: false,
});
await evaluate(page, "document.fonts ? document.fonts.ready.then(() => true) : true");
const desktopResolvedGithubTableLayout = await collectResolvedGithubTableLayout();
assertSmokeResult(
"resolved GitHub table keeps readable desktop columns without overflow",
desktopResolvedGithubTableLayout.documentOverflow <= 1
&& desktopResolvedGithubTableLayout.fixtureOverflow <= 1
&& desktopResolvedGithubTableLayout.tableOverflow <= 1
&& desktopResolvedGithubTableLayout.titleHeight > 0
&& desktopResolvedGithubTableLayout.titleWidth <= desktopResolvedGithubTableLayout.fixtureWidth
&& desktopResolvedGithubTableLayout.linkColor !== ""
&& desktopResolvedGithubTableLayout.linkTextDecoration.includes("underline"),
JSON.stringify(desktopResolvedGithubTableLayout),
);
if (resolvedGithubDesktopScreenshotPath) {
await captureFixtureScreenshot(page, '[data-smoke="github-resolved-table"]', resolvedGithubDesktopScreenshotPath);
log(`saved resolved GitHub desktop screenshot to ${resolvedGithubDesktopScreenshotPath}`);
}
const desktopAgentHeartbeatLayout = await collectAgentHeartbeatControlLayout();
assertSmokeResult(
"agent heartbeat controls stay visible on desktop and omit ephemeral shells",

Binary file not shown.

Binary file not shown.