fix: constrain dialog content to the viewport

- Add dynamic viewport bounds and scrollable body regions to dialogs and modals\n- Make tool-launch popups use the shared scrollable body pattern\n- Keep mobile sheets, action sheets, and notification popups scroll-contained\n- Restore the ProjectsPage test setup required for the frontend suite\n\nOpenSpec: fix-dialog-scroll-containment\nQuality gates: npm run typecheck, npm run lint, npm test (88 passed), npm run build
This commit is contained in:
Developer
2026-07-15 10:20:20 +00:00
parent 5e06a2a226
commit 415aecc0dd
9 changed files with 160 additions and 62 deletions
@@ -578,19 +578,23 @@ export const InstanceList = ({
{showCreate && (
<div className="dialog-overlay" role="dialog" aria-modal="true">
<div className="dialog">
<h2>Launch Tool</h2>
<CreateSessionForm
projects={[]}
repositories={[]}
toolTypes={toolTypes}
fixedProjectId={projectId}
fixedRepoId={repoId}
projectName={projectName}
repoName={repoName}
onSuccess={handleCreateSuccess}
onCancel={() => setShowCreate(false)}
submitLabel="Launch"
/>
<div className="dialog-header">
<h2>Launch Tool</h2>
</div>
<div className="dialog-body">
<CreateSessionForm
projects={[]}
repositories={[]}
toolTypes={toolTypes}
fixedProjectId={projectId}
fixedRepoId={repoId}
projectName={projectName}
repoName={repoName}
onSuccess={handleCreateSuccess}
onCancel={() => setShowCreate(false)}
submitLabel="Launch"
/>
</div>
</div>
</div>
)}
@@ -62,6 +62,7 @@ export function StartToolFAB() {
</button>
</div>
<div className="modal-body">
{workspacesLoading ? (
<p className="muted">Loading workspaces...</p>
) : workspaces.length === 0 ? (
@@ -110,6 +111,7 @@ export function StartToolFAB() {
/>
</>
)}
</div>
</div>
</div>
)}
@@ -0,0 +1,47 @@
import "@testing-library/jest-dom/vitest";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { WorkspaceToolsPanel } from "./workspace-tools-panel";
import type { Workspace } from "../../../types/workspace";
vi.mock("../../../hooks/use-workspace-instances", () => ({
useWorkspaceInstances: () => ({
instances: [],
loading: false,
refresh: vi.fn(),
}),
}));
vi.mock("../tool/tool-starter", () => ({
ToolStarter: () => <div data-testid="tool-starter" />,
}));
const workspace: Workspace = {
id: "workspace-1",
name: "Main",
repo_id: "repo-1",
repo_name: "repository",
repo_ssh_key_id: null,
project_id: "project-1",
project_name: "Project",
user_id: "user-1",
branch: "main",
path: "/workspace",
status: "ready",
last_sync_at: null,
created_at: "2026-01-01T00:00:00Z",
updated_at: "2026-01-01T00:00:00Z",
instance_count: 0,
};
describe("WorkspaceToolsPanel", () => {
it("places the tool launcher inside the shared scrollable dialog body", () => {
const { container } = render(<WorkspaceToolsPanel workspace={workspace} />);
fireEvent.click(screen.getByRole("button", { name: "Start Tool" }));
const dialogBody = container.querySelector(".dialog-body");
expect(dialogBody).toContainElement(screen.getByTestId("tool-starter"));
});
});
@@ -64,15 +64,19 @@ export function WorkspaceToolsPanel({ workspace }: WorkspaceToolsPanelProps) {
{showModal && (
<div className="dialog-overlay" onClick={() => setShowModal(false)}>
<div className="dialog" onClick={(e) => e.stopPropagation()}>
<h3>Start Tool</h3>
<ToolStarter
workspace={workspace}
onStarted={() => {
setShowModal(false);
void refresh();
}}
onCancel={() => setShowModal(false)}
/>
<div className="dialog-header">
<h3>Start Tool</h3>
</div>
<div className="dialog-body">
<ToolStarter
workspace={workspace}
onStarted={() => {
setShowModal(false);
void refresh();
}}
onCancel={() => setShowModal(false)}
/>
</div>
</div>
</div>
)}