diff --git a/packages/dashboard/app/components/GoalsView.css b/packages/dashboard/app/components/GoalsView.css index 7d6052279b..cbaa40efb1 100644 --- a/packages/dashboard/app/components/GoalsView.css +++ b/packages/dashboard/app/components/GoalsView.css @@ -1,9 +1,16 @@ +/* +FNXC:GoalsViewStyling 2026-06-20-01:33: +FN-6789 mounts Goals as a flex child of .project-content; grow, zero min-width, and use 100% width so the view fills the viewport instead of collapsing to intrinsic content width, mirroring the FN-6446 SecretsView fix. +*/ .goals-view { display: flex; + flex: 1 1 auto; flex-direction: column; gap: var(--space-lg); height: 100%; min-height: 0; + min-width: 0; + width: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: var(--space-lg); diff --git a/packages/dashboard/app/components/ResearchView.css b/packages/dashboard/app/components/ResearchView.css index ef719ebde6..e005513ffd 100644 --- a/packages/dashboard/app/components/ResearchView.css +++ b/packages/dashboard/app/components/ResearchView.css @@ -1,9 +1,16 @@ +/* +FNXC:ResearchViewStyling 2026-06-20-01:33: +FN-6789 mounts Research as a flex child of .project-content; grow, zero min-width, and use 100% width so the view fills the viewport instead of collapsing to intrinsic content width, mirroring the FN-6446 SecretsView fix. +*/ .research-view { display: flex; + flex: 1 1 auto; flex-direction: column; gap: var(--space-md); height: 100%; min-height: 0; + min-width: 0; + width: 100%; overflow: hidden; padding: var(--space-lg); padding-bottom: var(--space-lg); diff --git a/packages/dashboard/app/components/StashRecoveryView.css b/packages/dashboard/app/components/StashRecoveryView.css index bc1a789201..88d26ad2b2 100644 --- a/packages/dashboard/app/components/StashRecoveryView.css +++ b/packages/dashboard/app/components/StashRecoveryView.css @@ -1,8 +1,15 @@ +/* +FNXC:StashRecoveryStyling 2026-06-20-01:33: +FN-6789 mounts Stash Recovery as a flex child of .project-content; grow, zero min-width, and use 100% width so the view fills the viewport instead of collapsing to intrinsic content width, mirroring the FN-6446 SecretsView fix. +*/ .stash-recovery-view { padding: var(--space-lg); display: flex; + flex: 1 1 auto; flex-direction: column; gap: var(--space-md); + min-width: 0; + width: 100%; } .stash-recovery-header { diff --git a/packages/dashboard/app/components/__tests__/GoalsView.test.tsx b/packages/dashboard/app/components/__tests__/GoalsView.test.tsx index 5239e2c5bf..de8d591c8c 100644 --- a/packages/dashboard/app/components/__tests__/GoalsView.test.tsx +++ b/packages/dashboard/app/components/__tests__/GoalsView.test.tsx @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import type { Goal } from "@fusion/core"; import { draftGoalDescription } from "../../api"; +import { loadAllAppCss, loadAllAppCssBaseOnly } from "../../test/cssFixture"; import { GoalsView } from "../GoalsView"; vi.mock("../../api", async () => ({ @@ -18,6 +19,20 @@ vi.mock("lucide-react", () => ({ const mockDraftGoalDescription = vi.mocked(draftGoalDescription); +function extractRuleBlock(css: string, selector: string): string { + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = css.match(new RegExp(`${escapedSelector}\\s*\\{([\\s\\S]*?)\\}`)); + return match?.[1] ?? ""; +} + +function expectRootGrowContract(css: string, selector: string) { + const rootBlock = extractRuleBlock(css, selector); + + expect(rootBlock).toMatch(/flex\s*:\s*1\s+1\s+auto/); + expect(rootBlock).toMatch(/min-width\s*:\s*0/); + expect(rootBlock).toMatch(/width\s*:\s*100%/); +} + function makeGoal(overrides: Partial & Pick): Goal { return { id: overrides.id, @@ -30,6 +45,11 @@ function makeGoal(overrides: Partial & Pick): Goal { } describe("GoalsView", () => { + it("grows the root container to fill the project-content flex row", () => { + expectRootGrowContract(loadAllAppCss(), ".goals-view"); + expectRootGrowContract(loadAllAppCssBaseOnly(), ".goals-view"); + }); + beforeEach(() => { vi.unstubAllGlobals(); mockDraftGoalDescription.mockReset(); diff --git a/packages/dashboard/app/components/__tests__/ResearchView.test.tsx b/packages/dashboard/app/components/__tests__/ResearchView.test.tsx index f78194c2d0..7441b92026 100644 --- a/packages/dashboard/app/components/__tests__/ResearchView.test.tsx +++ b/packages/dashboard/app/components/__tests__/ResearchView.test.tsx @@ -54,6 +54,20 @@ function mockMatchMediaDesktop() { }); } +function extractRuleBlock(css: string, selector: string): string { + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = css.match(new RegExp(`${escapedSelector}\\s*\\{([\\s\\S]*?)\\}`)); + return match?.[1] ?? ""; +} + +function expectRootGrowContract(css: string, selector: string) { + const rootBlock = extractRuleBlock(css, selector); + + expect(rootBlock).toMatch(/flex\s*:\s*1\s+1\s+auto/); + expect(rootBlock).toMatch(/min-width\s*:\s*0/); + expect(rootBlock).toMatch(/width\s*:\s*100%/); +} + describe("Research navigation", () => { it("shows research in header overflow and activates view change", async () => { mockMatchMediaDesktop(); @@ -81,6 +95,11 @@ describe("Research navigation", () => { }); describe("ResearchView", () => { + it("grows the root container to fill the project-content flex row", () => { + expectRootGrowContract(loadAllAppCss(), ".research-view"); + expectRootGrowContract(loadAllAppCssBaseOnly(), ".research-view"); + }); + const baseHookValue = { runs: [], selectedRun: null, diff --git a/packages/dashboard/app/components/__tests__/StashRecoveryView.test.tsx b/packages/dashboard/app/components/__tests__/StashRecoveryView.test.tsx index c96d8022f3..c7f51aa241 100644 --- a/packages/dashboard/app/components/__tests__/StashRecoveryView.test.tsx +++ b/packages/dashboard/app/components/__tests__/StashRecoveryView.test.tsx @@ -1,5 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { loadAllAppCss, loadAllAppCssBaseOnly } from "../../test/cssFixture"; import { StashRecoveryView } from "../StashRecoveryView"; const apiMock = vi.fn(); @@ -8,7 +9,26 @@ const confirmMock = vi.fn(); vi.mock("../../api", () => ({ api: (...args: unknown[]) => apiMock(...args) })); vi.mock("../../hooks/useConfirm", () => ({ useConfirm: () => ({ confirm: confirmMock }) })); +function extractRuleBlock(css: string, selector: string): string { + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = css.match(new RegExp(`${escapedSelector}\\s*\\{([\\s\\S]*?)\\}`)); + return match?.[1] ?? ""; +} + +function expectRootGrowContract(css: string, selector: string) { + const rootBlock = extractRuleBlock(css, selector); + + expect(rootBlock).toMatch(/flex\s*:\s*1\s+1\s+auto/); + expect(rootBlock).toMatch(/min-width\s*:\s*0/); + expect(rootBlock).toMatch(/width\s*:\s*100%/); +} + describe("StashRecoveryView", () => { + it("grows the root container to fill the project-content flex row", () => { + expectRootGrowContract(loadAllAppCss(), ".stash-recovery-view"); + expectRootGrowContract(loadAllAppCssBaseOnly(), ".stash-recovery-view"); + }); + beforeEach(() => { vi.clearAllMocks(); });