FN-8702: fix Git Manager phone sheet spacing
Make Git Manager use the standalone phone layout only below the 768px boundary. - Remove the hidden resize-handle gutter from Git Manager phone sheets - Preserve embedded, tablet, and desktop Git Manager geometry - Add responsive layout smoke fixtures and regression coverage - Document the phone-sheet behavior and add a patch changeset Files changed: .changeset/fn-8702-git-manager-mobile-spacing.md | 7 + docs/dashboard-guide.md | 5 +- .../__tests__/browser-layout-smoke-fixture.test.ts | 23 ++++ .../dashboard/app/components/FloatingWindow.css | 12 ++ packages/dashboard/app/components/ScriptsModal.css | 10 +- .../components/__tests__/GitManagerModal.test.tsx | 31 +++-- packages/dashboard/scripts/browser-layout-smoke.mjs| 143 +++++++++++++++++++++ 7 files changed, 211 insertions(+), 20 deletions(-) Fusion-Task-Id: FN-8702 Fusion-Task-Lineage: 52473a8a-d3a7-48d2-b768-fc2f6fcd18b9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8702-git-manager-mobile-spacing.md
Normal file
7
.changeset/fn-8702-git-manager-mobile-spacing.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix Git Manager mobile sheets leaving excess space at the right edge.
|
||||
category: fix
|
||||
dev: Resets the resize-handle gutter only below the standalone 768px phone boundary.
|
||||
@@ -903,7 +903,8 @@ See `docs/solutions/developer-experience/login-shell-profile-latency.md` for the
|
||||
|
||||
## Git Manager
|
||||
|
||||
Git Manager centralizes repo operations in the dashboard. On desktop/tablet it is available as an embedded right-dock panel and can expand into a resizable modal; on mobile it opens from the compact More surfaces.
|
||||
<!-- FNXC:GitManagerMobileSpacing 2026-08-01-19:31: FN-8702 documents that the standalone phone sheet ends strictly below 768px so its hidden desktop resize chrome cannot leave a right-edge gutter, while embedded and tablet presentations keep host-owned geometry. -->
|
||||
Git Manager centralizes repo operations in the dashboard. On desktop/tablet (including exactly 768px) it is available as an embedded right-dock panel and can expand into a resizable modal; below 768px it opens as a full-width standalone phone sheet from the compact More surfaces.
|
||||
|
||||
Use Git Manager:
|
||||
|
||||
@@ -912,7 +913,7 @@ Use Git Manager:
|
||||
2. Select the dock expand action if you need more room.
|
||||
Expected outcome: the same Git Manager surface opens in a resizable modal without changing the selected dock tool.
|
||||
3. On mobile, open the compact Header overflow or bottom **More** sheet and select **Git Manager**.
|
||||
Expected outcome: Git Manager opens in the mobile modal layout with the section tabs restored as a horizontal scrolling strip.
|
||||
Expected outcome: Git Manager opens as a full-width phone sheet with no desktop resize-handle gutter; its section tabs remain a horizontal scrolling strip. Embedded dock panels never take over the viewport.
|
||||
4. Select **Status**, **Changes**, **Commits**, **Branches**, **Worktrees**, **Stashes**, **Recovery**, or **Remotes**.
|
||||
Expected outcome: the corresponding section panel replaces the previous section while preserving the same Git Manager session.
|
||||
|
||||
|
||||
@@ -2,6 +2,29 @@ import { describe, expect, it } from "vitest";
|
||||
import { createSmokeHtml } from "../../scripts/browser-layout-smoke.mjs";
|
||||
|
||||
describe("browser layout smoke fixture", () => {
|
||||
it("includes standalone and embedded Git Manager shell fixtures", () => {
|
||||
const html = createSmokeHtml();
|
||||
for (const hook of [
|
||||
"git-manager-standalone",
|
||||
"git-manager-standalone-body",
|
||||
"git-manager-standalone-modal",
|
||||
"git-manager-standalone-header",
|
||||
"git-manager-standalone-close",
|
||||
"git-manager-standalone-layout",
|
||||
"git-manager-standalone-content",
|
||||
"git-manager-embedded-host",
|
||||
"git-manager-embedded-modal",
|
||||
"git-manager-embedded-header",
|
||||
"git-manager-embedded-close",
|
||||
"git-manager-embedded-layout",
|
||||
"git-manager-embedded-content",
|
||||
]) {
|
||||
expect(html).toContain(`data-smoke="${hook}"`);
|
||||
}
|
||||
expect(html).toContain("floating-window--git-manager");
|
||||
expect(html).toContain("gm-modal--embedded");
|
||||
});
|
||||
|
||||
it("includes PR flow fixture sections and class hooks", () => {
|
||||
const html = createSmokeHtml();
|
||||
expect(html).toContain('data-smoke="pr-create-modal"');
|
||||
|
||||
@@ -490,6 +490,18 @@ all floating affordances so persisted desktop geometry cannot imply a draggable
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
/*
|
||||
FNXC:GitManagerMobileSpacing 2026-08-01-19:10:
|
||||
FN-8702 keeps the shared desktop resize-handle clearance for tablet and desktop windows.
|
||||
A standalone Git Manager phone sheet hides those handles, so only below the strict 768px
|
||||
boundary its body clears the obsolete inline-end gutter and aligns its header and close control.
|
||||
*/
|
||||
.floating-window--git-manager .floating-window__body {
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:FloatingWindow 2026-07-15-18:20:
|
||||
Long titles (e.g. the GitHub import detail's "#2159 — <issue title>") must truncate with an ellipsis rather than hard-cut mid-word against the close button.
|
||||
|
||||
@@ -2140,7 +2140,7 @@ The embedded host fills its right-dock container; the inner shell drops overlay-
|
||||
FNXC:GitManager 2026-06-22-17:30:
|
||||
The embedded Git Manager adapts to its CONTAINER width, not the viewport, so the SAME embedded render works in both the narrow right dock and the wide pop-out (expand) modal:
|
||||
- Wide container (expand modal, > 560px): inherits the default desktop layout — vertical section sidebar + content (two-pane, like before). Those default rules live OUTSIDE this container query and are untouched.
|
||||
- Narrow container (dock, <= 560px): MIRRORS the phone-width (@media max-width:768px) gm layout exactly. Section tabs become a horizontal strip with ICON + TEXT LABEL per tab (the mobile .gm-nav-item: column, icon over label, comfortable padding), and every section (status grid, create form, branches, stashes, changes/files split, and the REMOTES view) collapses to its mobile single-column form. The remotes view is CSS-only (no JS width gating in GitManagerModal.tsx), so mirroring the @media remotes rules here gives the dock the mobile remotes selector strip + stacked detail.
|
||||
- Narrow container (dock, <= 560px): MIRRORS the phone-width (@media max-width:767.98px) gm layout exactly. Section tabs become a horizontal strip with ICON + TEXT LABEL per tab (the mobile .gm-nav-item: column, icon over label, comfortable padding), and every section (status grid, create form, branches, stashes, changes/files split, and the REMOTES view) collapses to its mobile single-column form. The remotes view is CSS-only (no JS width gating in GitManagerModal.tsx), so mirroring the @media remotes rules here gives the dock the mobile remotes selector strip + stacked detail.
|
||||
The previous bespoke rules here hid the tab labels (icon-only) and used a cramped strip; the user wants labeled tabs, more spacing, and the mobile remotes layout, so we now mirror the @media gm INTERNAL rules verbatim under the .gm-modal--embedded prefix. Only the viewport-takeover (.modal.gm-modal 100vw/100dvh) rules are NOT mirrored — they are scoped to :not(.gm-modal--embedded) and must never apply to the embedded pane.
|
||||
*/
|
||||
@container gm-embedded (max-width: 560px) {
|
||||
@@ -4364,7 +4364,7 @@ Worktree commit-history jumps are read-only navigation affordances. Keep the act
|
||||
|
||||
/* ── Responsive ── */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 767.98px) {
|
||||
.gm-changes-split {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -4453,7 +4453,7 @@ Worktree commit-history jumps are read-only navigation affordances. Keep the act
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 767.98px) {
|
||||
/* Full-screen sheet on mobile — drop overlay padding so the modal
|
||||
actually fills the viewport instead of being pushed below it.
|
||||
FNXC:GitManager 2026-06-22-16:00: scope the viewport-takeover to the
|
||||
@@ -4547,7 +4547,7 @@ Worktree commit-history jumps are read-only navigation affordances. Keep the act
|
||||
|
||||
/*
|
||||
FNXC:GitManager 2026-06-22-19:35:
|
||||
Mobile (<=768px) nav strip: ICON-ONLY compact tabs in one horizontally-scrolling row so multiple sections are visible at once. width:auto overrides the base .gm-nav-item width:100% that otherwise made each tab fill the row (one tab, swipe-only). Labels are sr-only (button title is the tooltip).
|
||||
Phone (<768px) nav strip: ICON-ONLY compact tabs in one horizontally-scrolling row so multiple sections are visible at once. width:auto overrides the base .gm-nav-item width:100% that otherwise made each tab fill the row (one tab, swipe-only). Labels are sr-only (button title is the tooltip).
|
||||
*/
|
||||
.gm-nav-item {
|
||||
flex: 0 0 auto;
|
||||
@@ -5063,7 +5063,7 @@ Worktree commit-history jumps are read-only navigation affordances. Keep the act
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 767.98px) {
|
||||
/* Git manager: extend the canonical mobile rules above with safe-area and panel scroll behavior only. */
|
||||
.gm-content {
|
||||
padding-bottom: max(var(--space-md), env(safe-area-inset-bottom, 0px));
|
||||
|
||||
@@ -3712,6 +3712,11 @@ describe("GitManagerModal", () => {
|
||||
});
|
||||
|
||||
describe("CSS regression coverage", () => {
|
||||
/*
|
||||
FNXC:GitManagerMobileSpacing 2026-08-01-19:10:
|
||||
FN-8702 makes Git Manager's standalone sheet rules strictly phone-only. These source
|
||||
assertions protect the 767.98px selector boundary while Chromium verifies the resulting geometry.
|
||||
*/
|
||||
it("includes remotes layout selectors and mobile rules", () => {
|
||||
const css = loadAllAppCss();
|
||||
expect(css).toContain(".gm-remotes-layout");
|
||||
@@ -3719,7 +3724,7 @@ describe("GitManagerModal", () => {
|
||||
expect(css).toContain(".gm-remote-detail");
|
||||
expect(css).toContain(".gm-remote-sync-card");
|
||||
expect(css).toContain(".gm-remote-detail-card");
|
||||
expect(css).toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.gm-remotes-layout[\s\S]*?\.gm-remote-selector[\s\S]*?\}/);
|
||||
expect(css).toMatch(/@media[^{]*\(max-width: 767\.98px\)[^{]*\{[\s\S]*?\.gm-remotes-layout[\s\S]*?\.gm-remote-selector[\s\S]*?\}/);
|
||||
});
|
||||
|
||||
it("keeps remotes-specific status/surface styles tokenized", () => {
|
||||
@@ -3733,24 +3738,24 @@ describe("GitManagerModal", () => {
|
||||
it("includes mobile wrapping rules for changes file rows and section actions", () => {
|
||||
const css = loadAllAppCss();
|
||||
|
||||
expect(css).toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.gm-file-section-actions\s*\{[\s\S]*?flex-wrap:\s*wrap;[\s\S]*?flex:\s*1 1 100%;/);
|
||||
expect(css).toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.gm-file-item\s*\{[\s\S]*?min-width:\s*0;[\s\S]*?flex-wrap:\s*wrap;/);
|
||||
expect(css).toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.gm-file-section\s*\{[\s\S]*?max-width:\s*100%;/);
|
||||
expect(css).toMatch(/@media[^{]*\(max-width: 767\.98px\)[^{]*\{[\s\S]*?\.gm-file-section-actions\s*\{[\s\S]*?flex-wrap:\s*wrap;[\s\S]*?flex:\s*1 1 100%;/);
|
||||
expect(css).toMatch(/@media[^{]*\(max-width: 767\.98px\)[^{]*\{[\s\S]*?\.gm-file-item\s*\{[\s\S]*?min-width:\s*0;[\s\S]*?flex-wrap:\s*wrap;/);
|
||||
expect(css).toMatch(/@media[^{]*\(max-width: 767\.98px\)[^{]*\{[\s\S]*?\.gm-file-section\s*\{[\s\S]*?max-width:\s*100%;/);
|
||||
});
|
||||
|
||||
it("includes commit target and worktree actions in modal mobile and embedded narrow layouts", () => {
|
||||
const css = loadAllAppCss();
|
||||
const mobile768 = getMediaBlocks(css, /@media[^{]*\(max-width:\s*768px\)[^{]*\{/g).join("\n");
|
||||
const phone767 = getMediaBlocks(css, /@media[^{]*\(max-width:\s*767\.98px\)[^{]*\{/g).join("\n");
|
||||
const embeddedNarrow = getMediaBlocks(css, /@container\s+gm-embedded\s+\(max-width:\s*560px\)\s*\{/g).join("\n");
|
||||
|
||||
expect(css).toContain(".gm-commit-target");
|
||||
expect(css).toContain(".gm-worktree-actions");
|
||||
expect(mobile768).toContain(".gm-commit-target select");
|
||||
expect(mobile768).toContain(".gm-worktree-actions .btn");
|
||||
expect(phone767).toContain(".gm-commit-target select");
|
||||
expect(phone767).toContain(".gm-worktree-actions .btn");
|
||||
expect(embeddedNarrow).toContain(".gm-modal--embedded .gm-commit-target select");
|
||||
expect(embeddedNarrow).toContain(".gm-modal--embedded .gm-worktree-actions .btn");
|
||||
|
||||
const mobileTargetRules = getRuleBlocks(mobile768, ".gm-commit-target,\n .gm-commit-target select,\n .gm-commit-controls .gm-search-box");
|
||||
const mobileTargetRules = getRuleBlocks(phone767, ".gm-commit-target,\n .gm-commit-target select,\n .gm-commit-controls .gm-search-box");
|
||||
expect(mobileTargetRules).toHaveLength(1);
|
||||
expect(mobileTargetRules[0]).toContain("width: 100%;");
|
||||
const embeddedTargetRules = getRuleBlocks(embeddedNarrow, ".gm-modal--embedded .gm-commit-target,\n .gm-modal--embedded .gm-commit-target select,\n .gm-modal--embedded .gm-commit-controls .gm-search-box");
|
||||
@@ -3758,12 +3763,12 @@ describe("GitManagerModal", () => {
|
||||
expect(embeddedTargetRules[0]).toContain("width: 100%;");
|
||||
});
|
||||
|
||||
it("keeps the mobile Git Manager tab strip non-shrinking at 768px and 720px breakpoints", () => {
|
||||
it("keeps the mobile Git Manager tab strip non-shrinking below 768px and at 720px", () => {
|
||||
const css = loadAllAppCss();
|
||||
const mobile768 = getMediaBlocks(css, /@media[^{]*\(max-width:\s*768px\)[^{]*\{/g).join("\n");
|
||||
const phone767 = getMediaBlocks(css, /@media[^{]*\(max-width:\s*767\.98px\)[^{]*\{/g).join("\n");
|
||||
const mobile720 = getMediaBlocks(css, /@media[^{]*\(max-width:\s*720px\)[^{]*\{/g).join("\n");
|
||||
|
||||
const sidebarRules = getRuleBlocks(mobile768, ".gm-sidebar");
|
||||
const sidebarRules = getRuleBlocks(phone767, ".gm-sidebar");
|
||||
expect(sidebarRules).toHaveLength(1);
|
||||
expect(sidebarRules[0]).toContain("flex: 0 0 auto;");
|
||||
expect(sidebarRules[0]).toContain("min-height: calc(var(--space-2xl) + var(--space-md));");
|
||||
@@ -3774,13 +3779,13 @@ describe("GitManagerModal", () => {
|
||||
expect(sidebarRules[0]).toContain("-webkit-overflow-scrolling: touch;");
|
||||
expect(sidebarRules[0]).toContain("overscroll-behavior-x: contain;");
|
||||
|
||||
const navItemRules = getRuleBlocks(mobile768, ".gm-nav-item");
|
||||
const navItemRules = getRuleBlocks(phone767, ".gm-nav-item");
|
||||
expect(navItemRules).toHaveLength(1);
|
||||
// Mobile tabs are compact ICON-ONLY in one scrolling row: non-shrinking via flex:0 0 auto + intrinsic width:auto (overrides the base .gm-nav-item width:100% that otherwise made one tab fill the row).
|
||||
expect(navItemRules[0]).toContain("flex: 0 0 auto;");
|
||||
expect(navItemRules[0]).toContain("width: auto;");
|
||||
|
||||
const refreshRules = getRuleBlocks(mobile768, ".gm-nav-refresh");
|
||||
const refreshRules = getRuleBlocks(phone767, ".gm-nav-refresh");
|
||||
expect(refreshRules).toHaveLength(1);
|
||||
expect(refreshRules[0]).toContain("flex: 0 0 auto;");
|
||||
expect(refreshRules[0]).toContain("width: auto;");
|
||||
|
||||
@@ -19,6 +19,8 @@ const requireBrowser = process.argv.includes("--require-browser") || process.env
|
||||
const screenshotPath = process.env.FUSION_BROWSER_SMOKE_SCREENSHOT;
|
||||
const agentHeartbeatMobileScreenshotPath = process.env.FUSION_AGENT_HEARTBEAT_MOBILE_SCREENSHOT;
|
||||
const agentHeartbeatDesktopScreenshotPath = process.env.FUSION_AGENT_HEARTBEAT_DESKTOP_SCREENSHOT;
|
||||
const gitManagerBeforeMobileScreenshotPath = process.env.FUSION_GIT_MANAGER_BEFORE_MOBILE_SCREENSHOT;
|
||||
const gitManagerAfterMobileScreenshotPath = process.env.FUSION_GIT_MANAGER_AFTER_MOBILE_SCREENSHOT;
|
||||
|
||||
function log(message) {
|
||||
console.log(`[dashboard-browser-smoke] ${message}`);
|
||||
@@ -230,6 +232,29 @@ export function createSmokeHtml() {
|
||||
</section>
|
||||
`).join("");
|
||||
|
||||
/*
|
||||
FNXC:GitManagerMobileSpacing 2026-08-01-19:10:
|
||||
FN-8702 measures the emitted standalone FloatingWindow chain and the embedded container separately.
|
||||
The phone-only gutter reset must align every standalone shell edge below 768px without turning the
|
||||
embedded pane or the 768px-and-up movable window into a viewport sheet.
|
||||
*/
|
||||
const gitManagerFixtures = `
|
||||
<section class="floating-window floating-window--git-manager" data-smoke="git-manager-standalone" style="width: min(680px, calc(100vw - var(--space-2xl))); height: min(640px, calc(100dvh - var(--space-2xl)));">
|
||||
<div class="floating-window__body" data-smoke="git-manager-standalone-body">
|
||||
<section class="modal gm-modal" data-smoke="git-manager-standalone-modal">
|
||||
<header class="modal-header" data-smoke="git-manager-standalone-header"><h2>Git Manager</h2><div class="gm-header-actions"><button class="modal-close" data-smoke="git-manager-standalone-close" type="button" aria-label="Close Git Manager">×</button></div></header>
|
||||
<div class="gm-layout" data-smoke="git-manager-standalone-layout"><nav class="gm-sidebar"><button class="gm-nav-item active" type="button">Status</button><button class="gm-nav-item" type="button">Changes</button></nav><main class="gm-content" data-smoke="git-manager-standalone-content"><div class="gm-loading">Loading repository</div><div class="gm-error">Fixture error state stays contained</div><div class="gm-panel">${"Long populated Git Manager row ".repeat(30)}</div></main></div>
|
||||
</section>
|
||||
</div>
|
||||
<i class="floating-window__resize-handle floating-window__resize-handle--se" aria-hidden="true"></i>
|
||||
</section>
|
||||
<section class="git-manager-embedded" data-smoke="git-manager-embedded-host" style="width: min(320px, calc(100vw - var(--space-lg))); height: 420px;">
|
||||
<section class="modal gm-modal gm-modal--embedded" data-smoke="git-manager-embedded-modal">
|
||||
<header class="modal-header" data-smoke="git-manager-embedded-header"><h2>Git Manager</h2><div class="gm-header-actions"><button class="modal-close" data-smoke="git-manager-embedded-close" type="button" aria-label="Close embedded Git Manager">×</button></div></header>
|
||||
<div class="gm-layout" data-smoke="git-manager-embedded-layout"><nav class="gm-sidebar"><button class="gm-nav-item active" type="button">Status</button></nav><main class="gm-content" data-smoke="git-manager-embedded-content"><div class="gm-loading">Loading repository</div><div class="gm-error">Embedded fixture error state stays contained</div><div class="gm-panel">${"Embedded populated row ".repeat(30)}</div></main></div>
|
||||
</section>
|
||||
</section>`;
|
||||
|
||||
return `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -240,6 +265,7 @@ export function createSmokeHtml() {
|
||||
</head>
|
||||
<body data-theme="dark">
|
||||
<div id="root">
|
||||
${gitManagerFixtures}
|
||||
<div class="header-wrapper">
|
||||
<header class="header" data-smoke="header">
|
||||
<div class="header-left">
|
||||
@@ -1143,6 +1169,123 @@ async function runSmokeChecks(page, pageUrl) {
|
||||
JSON.stringify(modalLayout),
|
||||
);
|
||||
|
||||
/*
|
||||
FNXC:GitManagerMobileSpacing 2026-08-01-19:10:
|
||||
Real Chromium must prove the original 390px body-gutter failure is gone across the header,
|
||||
close control, layout, and populated/error content. The strict 767.98px boundary intentionally
|
||||
does not claim a false 768px containment failure: production flex shrinking contains that child,
|
||||
while its desktop/tablet gutter and movable geometry must remain present.
|
||||
*/
|
||||
for (const width of [390, 767, 768, 1024]) {
|
||||
await page.send("Emulation.setDeviceMetricsOverride", {
|
||||
width,
|
||||
height: 844,
|
||||
deviceScaleFactor: width < 768 ? 2 : 1,
|
||||
mobile: width < 768,
|
||||
});
|
||||
await evaluate(page, "document.fonts ? document.fonts.ready.then(() => true) : true");
|
||||
const gitManagerLayout = await evaluate(page, `(() => {
|
||||
const viewportWidth = window.innerWidth;
|
||||
const readRect = (selector) => {
|
||||
const rect = document.querySelector(selector).getBoundingClientRect();
|
||||
return { left: rect.left, right: rect.right, width: rect.width, top: rect.top, bottom: rect.bottom };
|
||||
};
|
||||
const standalone = document.querySelector('[data-smoke="git-manager-standalone"]');
|
||||
const standaloneBody = document.querySelector('[data-smoke="git-manager-standalone-body"]');
|
||||
const embeddedHost = document.querySelector('[data-smoke="git-manager-embedded-host"]');
|
||||
return {
|
||||
viewportWidth,
|
||||
documentOverflow: document.documentElement.scrollWidth - viewportWidth,
|
||||
standalone: {
|
||||
host: readRect('[data-smoke="git-manager-standalone"]'),
|
||||
body: readRect('[data-smoke="git-manager-standalone-body"]'),
|
||||
modal: readRect('[data-smoke="git-manager-standalone-modal"]'),
|
||||
header: readRect('[data-smoke="git-manager-standalone-header"]'),
|
||||
close: readRect('[data-smoke="git-manager-standalone-close"]'),
|
||||
layout: readRect('[data-smoke="git-manager-standalone-layout"]'),
|
||||
content: readRect('[data-smoke="git-manager-standalone-content"]'),
|
||||
bodyMarginInlineEnd: getComputedStyle(standaloneBody).marginInlineEnd,
|
||||
overflow: standalone.scrollWidth - standalone.clientWidth,
|
||||
resizeHandleDisplay: getComputedStyle(standalone.querySelector('.floating-window__resize-handle')).display,
|
||||
},
|
||||
embedded: {
|
||||
host: readRect('[data-smoke="git-manager-embedded-host"]'),
|
||||
modal: readRect('[data-smoke="git-manager-embedded-modal"]'),
|
||||
header: readRect('[data-smoke="git-manager-embedded-header"]'),
|
||||
close: readRect('[data-smoke="git-manager-embedded-close"]'),
|
||||
layout: readRect('[data-smoke="git-manager-embedded-layout"]'),
|
||||
content: readRect('[data-smoke="git-manager-embedded-content"]'),
|
||||
overflow: embeddedHost.scrollWidth - embeddedHost.clientWidth,
|
||||
},
|
||||
};
|
||||
})()`);
|
||||
if (width === 390 && gitManagerBeforeMobileScreenshotPath) {
|
||||
/*
|
||||
FNXC:GitManagerMobileSpacing 2026-08-01-19:31:
|
||||
FN-8702 preserves an executable reproduction by temporarily restoring the inherited desktop
|
||||
resize-handle gutter. Chromium must observe that resulting right-edge asymmetry before the
|
||||
production phone-sheet assertion proves the reset removes it.
|
||||
*/
|
||||
const preFixLayout = await evaluate(page, `(() => {
|
||||
const body = document.querySelector('[data-smoke="git-manager-standalone-body"]');
|
||||
body.style.marginInlineEnd = 'var(--space-lg)';
|
||||
const rect = body.getBoundingClientRect();
|
||||
return { right: rect.right, marginInlineEnd: getComputedStyle(body).marginInlineEnd, viewportWidth: window.innerWidth };
|
||||
})()`);
|
||||
assertSmokeResult(
|
||||
"Git Manager 390px desktop-gutter reproduction",
|
||||
parseFloat(preFixLayout.marginInlineEnd) > 0 && preFixLayout.right < preFixLayout.viewportWidth - 1,
|
||||
JSON.stringify(preFixLayout),
|
||||
);
|
||||
const screenshot = await page.send("Page.captureScreenshot", { format: "png" });
|
||||
await writeFile(gitManagerBeforeMobileScreenshotPath, Buffer.from(screenshot.data, "base64"));
|
||||
await evaluate(page, "document.querySelector('[data-smoke=\"git-manager-standalone-body\"]').style.removeProperty('margin-inline-end')");
|
||||
log(`saved Git Manager before mobile screenshot to ${gitManagerBeforeMobileScreenshotPath}`);
|
||||
}
|
||||
if (width === 390 && gitManagerAfterMobileScreenshotPath) {
|
||||
const screenshot = await page.send("Page.captureScreenshot", { format: "png" });
|
||||
await writeFile(gitManagerAfterMobileScreenshotPath, Buffer.from(screenshot.data, "base64"));
|
||||
log(`saved Git Manager after mobile screenshot to ${gitManagerAfterMobileScreenshotPath}`);
|
||||
}
|
||||
|
||||
const standaloneRects = Object.values(gitManagerLayout.standalone)
|
||||
.filter((value) => value && typeof value === "object" && "left" in value);
|
||||
const embeddedRects = Object.values(gitManagerLayout.embedded)
|
||||
.filter((value) => value && typeof value === "object" && "left" in value);
|
||||
const embeddedContained = embeddedRects.every((rect) => rect.left >= gitManagerLayout.embedded.host.left - 1
|
||||
&& rect.right <= gitManagerLayout.embedded.host.right + 1);
|
||||
const standaloneContained = standaloneRects.every((rect) => rect.left >= -1 && rect.right <= width + 1);
|
||||
const common = gitManagerLayout.documentOverflow <= 1
|
||||
&& gitManagerLayout.standalone.overflow <= 1
|
||||
&& gitManagerLayout.embedded.overflow <= 1
|
||||
&& standaloneContained
|
||||
&& embeddedContained
|
||||
&& gitManagerLayout.standalone.close.right <= gitManagerLayout.standalone.header.right + 1
|
||||
&& gitManagerLayout.embedded.close.right <= gitManagerLayout.embedded.header.right + 1;
|
||||
const passed = width < 768
|
||||
? common
|
||||
&& Math.abs(gitManagerLayout.standalone.host.left) <= 1
|
||||
&& Math.abs(gitManagerLayout.standalone.host.right - width) <= 1
|
||||
&& Math.abs(gitManagerLayout.standalone.body.right - width) <= 1
|
||||
&& Math.abs(gitManagerLayout.standalone.modal.right - width) <= 1
|
||||
&& gitManagerLayout.standalone.bodyMarginInlineEnd === "0px"
|
||||
&& gitManagerLayout.embedded.modal.width < width
|
||||
: common
|
||||
&& gitManagerLayout.standalone.host.width < width - 1
|
||||
&& parseFloat(gitManagerLayout.standalone.bodyMarginInlineEnd) > 0
|
||||
&& gitManagerLayout.standalone.resizeHandleDisplay !== "none"
|
||||
&& gitManagerLayout.embedded.modal.width < width;
|
||||
assertSmokeResult(`Git Manager standalone and embedded geometry at ${width}px`, passed, JSON.stringify(gitManagerLayout));
|
||||
}
|
||||
|
||||
await page.send("Emulation.setDeviceMetricsOverride", {
|
||||
width: 390,
|
||||
height: 844,
|
||||
deviceScaleFactor: 2,
|
||||
mobile: true,
|
||||
});
|
||||
await evaluate(page, "document.fonts ? document.fonts.ready.then(() => true) : true");
|
||||
|
||||
const prCreateModalLayout = await evaluate(page, `(() => {
|
||||
document.querySelector('[data-smoke="show-pr-create"]').click();
|
||||
const modal = document.querySelector('[data-smoke="pr-create-modal"] .modal.modal-lg');
|
||||
|
||||
Reference in New Issue
Block a user