FN-6789: make dashboard utility views fill flex rows

Goals, Research, and Stash Recovery now keep full-width layouts inside the project-content flex row.

- Add flex growth, zero min-width, and full-width root sizing to Goals, Research, and Stash Recovery views.
- Document the view-level flex sizing requirement with FNXC comments.
- Add CSS contract tests for both bundled and base CSS fixtures.

Files changed:
 packages/dashboard/app/components/GoalsView.css      |  7 +++++++
 packages/dashboard/app/components/ResearchView.css   |  7 +++++++
 .../dashboard/app/components/StashRecoveryView.css   |  7 +++++++
 .../app/components/__tests__/GoalsView.test.tsx      | 20 ++++++++++++++++++++
 .../app/components/__tests__/ResearchView.test.tsx   | 19 +++++++++++++++++++
 .../components/__tests__/StashRecoveryView.test.tsx  | 20 ++++++++++++++++++++
 6 files changed, 80 insertions(+)

Fusion-Task-Id: FN-6789

Fusion-Task-Lineage: f650d95b-583b-48c8-a2e4-fe63317b4576
This commit is contained in:
gsxdsm
2026-06-20 01:52:26 -07:00
parent 58a6dccfb2
commit 1ab2d4161a
6 changed files with 80 additions and 0 deletions

View File

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

View File

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

View File

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

View File

@@ -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<Goal> & Pick<Goal, "id" | "title">): Goal {
return {
id: overrides.id,
@@ -30,6 +45,11 @@ function makeGoal(overrides: Partial<Goal> & Pick<Goal, "id" | "title">): 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();

View File

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

View File

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