FN-9110: contain GitHub pull request imports on mobile

Keep GitHub pull-request rows and previews readable within narrow import layouts.

- Let mobile pull-request rows grow with wrapped titles, badges, and branch metadata.
- Break long branch names within list rows and detail previews.
- Cover modal and embedded presentations with layout and interaction regressions.
- Add a patch changeset for the mobile layout fix.

Files changed:
 .changeset/fn-9110-github-import-pulls-mobile.md   |  7 +++
 .../github-import-pulls-mobile-layout.test.ts      | 54 ++++++++++++++++++++++
 .../dashboard/app/components/GitHubImportModal.css | 27 ++++++++++-
 .../__tests__/GitHubImportModal.test.tsx           | 32 +++++++++++++
 4 files changed, 119 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-9110

Fusion-Task-Lineage: 572b5fb3-076e-448a-9f6f-185b836b4161

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-08-15 17:53:29 -07:00
parent 66bbeaa28d
commit e9089ee8a1
4 changed files with 119 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Keep GitHub pull-request imports readable on mobile screens.
category: fix
dev: Pull rows and preview branch names now wrap safely in mobile import layouts.

View File

@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import { readFileSync } from "fs";
import { resolve } from "path";
const githubImportCss = readFileSync(
resolve(__dirname, "../components/GitHubImportModal.css"),
"utf8",
);
function extractBlock(source: string, marker: string): string {
const start = source.indexOf(marker);
expect(start, `missing ${marker}`).toBeGreaterThanOrEqual(0);
const openingBrace = source.indexOf("{", start);
let depth = 0;
for (let index = openingBrace; index < source.length; index += 1) {
if (source[index] === "{") depth += 1;
if (source[index] === "}") depth -= 1;
if (depth === 0) return source.slice(openingBrace + 1, index);
}
throw new Error(`unterminated ${marker}`);
}
function extractRule(source: string, selector: string): string {
return extractBlock(source, `${selector} {`);
}
describe("GitHub import pull-request mobile layout (FN-9110)", () => {
it("lets Pull Requests rows grow and wraps unbroken branch names at the phone breakpoint", () => {
const phoneRules = extractBlock(githubImportCss, "@media (max-width: 640px)");
const rowRule = extractRule(phoneRules, ".issue-item");
const branchRule = extractRule(phoneRules, ".pull-branch-info");
expect(rowRule).toContain("flex-wrap: wrap;");
expect(rowRule).not.toMatch(/min-height\s*:/);
expect(branchRule).toContain("min-width: 0;");
expect(branchRule).toContain("overflow-wrap: anywhere;");
});
it("contains long PR preview branch text and keeps the detail body scrollable on narrow sheets", () => {
const detailActionSection = githubImportCss.slice(
githubImportCss.indexOf("The selected-issue mobile action row"),
);
const narrowRules = extractBlock(detailActionSection, "@media (max-width: 768px)");
const previewBranchRule = extractRule(narrowRules, ".preview-branch");
const detailPanelRule = extractRule(githubImportCss, ".github-import-detail-panel");
expect(previewBranchRule).toContain("min-width: 0;");
expect(previewBranchRule).toContain("overflow-wrap: anywhere;");
expect(detailPanelRule).toContain("min-height: 0;");
expect(detailPanelRule).toContain("overflow: hidden;");
});
});

View File

@@ -1638,9 +1638,24 @@ counterpart added later cannot silently reintroduce the band.
}
/*
FNXC:GitHubImport 2026-08-16-00:33:
Pull-request rows can contain a wrapped title, a branch line, and an Imported badge. They must use
their intrinsic height on phones: the former fixed minimum height constrained wrapped content and
made consecutive rows paint over one another.
*/
.issue-item {
flex-wrap: wrap;
min-height: 36px;
}
/*
FNXC:GitHubImport 2026-08-16-00:33:
Pull head/base names can be unbroken generated identifiers. Let the branch line shrink and break
them inside the shared list row so both dialog and embedded phone presentations stay contained.
*/
.pull-branch-info {
min-width: 0;
overflow-wrap: anywhere;
}
.imported-badge {
@@ -1678,6 +1693,16 @@ shrinkable action tracks preserve every full label by wrapping it inside its own
shared touch-target token preserves every required target without clipping or scrolling.
*/
@media (max-width: 768px) {
/*
FNXC:GitHubImport 2026-08-16-00:33:
The selected pull-request sheet shares this preview branch block with desktop. On narrow and
landscape sheets, break unbroken branch names so its scrollable detail content cannot overflow.
*/
.preview-branch {
min-width: 0;
overflow-wrap: anywhere;
}
.github-import-detail-actions {
gap: var(--space-xs);
}

View File

@@ -2701,6 +2701,38 @@ describe("GitHubImportModal", () => {
it.each(["modal", "embedded"] as const)("retains long pull rows, imported state, and the detail sheet in the %s presentation", async (presentation) => {
const longBranch = "branch-".repeat(24);
const longPull = {
number: 31,
title: "A deliberately long pull-request title that remains available to the mobile list row",
body: "PR body",
html_url: "https://github.com/owner/repo/pull/31",
headBranch: longBranch,
baseBranch: longBranch,
};
const importedTask: Task = {
...mockPRTask,
description: "Review and address any issues in this pull request.\n\nPR: https://github.com/owner/repo/pull/31",
};
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(singleRemote);
vi.mocked(apiFetchGitHubPulls).mockResolvedValueOnce([longPull, mockPulls[0]]);
render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[importedTask]} presentation={presentation} />);
fireEvent.click(screen.getByRole("tab", { name: /Pull Requests/i }));
const longRow = await screen.findByRole("button", { name: /Select pull request #31/i });
expect(longRow).toHaveTextContent(longPull.title);
expect(longRow).toHaveTextContent(`${longBranch} → ${longBranch}`);
expect(longRow).toBeDisabled();
expect(within(longRow).getByText("Imported")).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: /Select pull request #1/i }));
const detail = await screen.findByTestId("floating-window-github-import-detail");
expect(detail.querySelector(".github-import-detail-panel")).toBeTruthy();
expect(within(detail).getByTestId("github-import-detail-actions")).toBeTruthy();
});
it("calls apiImportGitHubPull when Import is clicked on PRs tab", async () => {
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(singleRemote);
vi.mocked(apiFetchGitHubPulls).mockResolvedValueOnce(mockPulls);