story-211: skip selection screen when CLI path argument provided

When a project path is passed on the command line, skip the project
selection screen in the frontend and go straight to the main UI.

Squash merge of feature/story-211

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-26 16:18:47 +00:00
parent 8715e648ba
commit 01f3a3697a
3 changed files with 48 additions and 0 deletions

View File

@@ -57,6 +57,7 @@ describe("App", () => {
beforeEach(() => { beforeEach(() => {
vi.resetModules(); vi.resetModules();
vi.clearAllMocks(); vi.clearAllMocks();
mockedApi.getCurrentProject.mockResolvedValue(null);
mockedApi.getKnownProjects.mockResolvedValue([]); mockedApi.getKnownProjects.mockResolvedValue([]);
mockedApi.getHomeDirectory.mockResolvedValue("/home/user"); mockedApi.getHomeDirectory.mockResolvedValue("/home/user");
mockedApi.listDirectoryAbsolute.mockResolvedValue([]); mockedApi.listDirectoryAbsolute.mockResolvedValue([]);
@@ -71,6 +72,26 @@ describe("App", () => {
return render(<App />); return render(<App />);
} }
it("calls getCurrentProject() on mount", async () => {
await renderApp();
await waitFor(() => {
expect(mockedApi.getCurrentProject).toHaveBeenCalledTimes(1);
});
});
it("skips selection screen and shows workspace when server already has a project open", async () => {
mockedApi.getCurrentProject.mockResolvedValue("/home/user/myproject");
await renderApp();
await waitFor(() => {
expect(
screen.queryByPlaceholderText(/\/path\/to\/project/i),
).not.toBeInTheDocument();
});
});
it("renders the selection screen when no project is open", async () => { it("renders the selection screen when no project is open", async () => {
await renderApp(); await renderApp();

View File

@@ -7,12 +7,27 @@ import "./App.css";
function App() { function App() {
const [projectPath, setProjectPath] = React.useState<string | null>(null); const [projectPath, setProjectPath] = React.useState<string | null>(null);
const [isCheckingProject, setIsCheckingProject] = React.useState(true);
const [errorMsg, setErrorMsg] = React.useState<string | null>(null); const [errorMsg, setErrorMsg] = React.useState<string | null>(null);
const [pathInput, setPathInput] = React.useState(""); const [pathInput, setPathInput] = React.useState("");
const [isOpening, setIsOpening] = React.useState(false); const [isOpening, setIsOpening] = React.useState(false);
const [knownProjects, setKnownProjects] = React.useState<string[]>([]); const [knownProjects, setKnownProjects] = React.useState<string[]>([]);
const [homeDir, setHomeDir] = React.useState<string | null>(null); const [homeDir, setHomeDir] = React.useState<string | null>(null);
React.useEffect(() => {
api
.getCurrentProject()
.then((path) => {
if (path) {
setProjectPath(path);
}
})
.catch((error) => console.error(error))
.finally(() => {
setIsCheckingProject(false);
});
}, []);
React.useEffect(() => { React.useEffect(() => {
api api
.getKnownProjects() .getKnownProjects()
@@ -138,6 +153,10 @@ function App() {
} }
} }
if (isCheckingProject) {
return null;
}
return ( return (
<main <main
className="container" className="container"

View File

@@ -1,6 +1,14 @@
import { expect, test } from "@playwright/test"; import { expect, test } from "@playwright/test";
test.describe("App boot smoke test", () => { test.describe("App boot smoke test", () => {
test.beforeEach(async ({ request }) => {
// Close any project the server may have auto-opened (e.g. via CLI path
// argument) so we always start from the selection screen.
await request.delete("/api/project").catch(() => {
// Ignore errors when no project is open or backend is unavailable.
});
});
test("renders the project selection screen", async ({ page }) => { test("renders the project selection screen", async ({ page }) => {
await page.goto("/"); await page.goto("/");