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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user