* feat(command-center): add workspace file search Use the existing workspace-aware fuzzy search so file lookup follows the file explorer's ignore and hidden-directory rules. Cmd/Ctrl+P opens a removable Files scope while the normal command center also includes file matches. * fix(command-center): stabilize file search results * fix(shortcuts): preserve project picker command * fix(command-center): keep file results while searching * feat(command-center): integrate the files scope into the search row The scope was a 24px pill with 12px text sitting inside the 16px input line, so it read as a badge dropped into the query rather than the scope of the search. Make it a leading cell of the search row instead: folder icon, input-sized label, and a hairline divider before the caret. It stretches to the row's cross size and bleeds through the header padding with negative margins, so its height is driven entirely by the input and the panel's own radius clips its corner. Toggling the scope cannot resize the header, which the new spec pins. * fix(command-center): restore the files scope as a chip The previous commit turned the scope into a flush leading cell of the header. Put the chip back: rounded, bordered, folder icon and 14px label, no divider. It stretches to the row instead of carrying a height, so it is exactly as tall as the input's line box and cannot resize the header. * refactor(shortcuts): regroup and reorder the shortcut help sections Panels was a junk drawer: the command center, file search, the shortcuts sheet itself, new terminal, the sidebars, settings, focus mode and theme cycling. The two shortcuts people reach for most, Cmd+K and Cmd+P, sat inside it below three other sections. Navigation held both workspace and tab movement. Sections are now General, Projects & Workspaces, Tabs & Panes, Layout, and Agent Input. General leads with the command center and file search. Tab movement joins Tabs & Panes, new terminal joins the tabs it creates, and Layout keeps only the sidebar and focus toggles. Row order is now data rather than a side effect of binding-definition order, so the row someone opens the sheet to find sits at the top of its section. A test fails when a new shortcut has no place in that order instead of letting it sink to the bottom unnoticed. * Keep file search failures in view
140 lines
5.2 KiB
TypeScript
140 lines
5.2 KiB
TypeScript
import { expect, test } from "../support/fixtures";
|
|
import { openCommandCenter } from "../support/helpers/command-center";
|
|
import {
|
|
delayDirectorySuggestionResponses,
|
|
expectOneLineTruncatedFileResult,
|
|
expectStableCommandCenterLayout,
|
|
failDirectorySuggestionRequests,
|
|
startCommandCenterLayoutObservation,
|
|
} from "../support/helpers/command-center-file-search";
|
|
import { expectFileTabOpen } from "../support/helpers/file-explorer";
|
|
import { gotoWorkspace } from "../support/helpers/launcher";
|
|
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
|
|
|
|
const FILE_PATH =
|
|
"packages/app/src/command-center/features/workspace-file-search/layout-stability/needle-layout-stability-command-center.tsx";
|
|
const FILE_NAME = "needle-layout-stability-command-center.tsx";
|
|
const FILE_DIRECTORY =
|
|
"packages/app/src/command-center/features/workspace-file-search/layout-stability";
|
|
|
|
function paseoSizedFiles(): Array<{ path: string; content: string }> {
|
|
const files = Array.from({ length: 120 }, (_, index) => ({
|
|
path: `packages/app/src/features/feature-${String(index).padStart(3, "0")}/index.ts`,
|
|
content: `export const feature${index} = ${index};\n`,
|
|
}));
|
|
files.push({ path: FILE_PATH, content: "export const layoutNeedle = true;\n" });
|
|
return files;
|
|
}
|
|
|
|
async function seedMatchingWorkspaces(count: number): Promise<SeededWorkspace[]> {
|
|
return Promise.all(
|
|
Array.from({ length: count }, (_, index) =>
|
|
seedWorkspace({
|
|
repoPrefix: `command-center-file-search-error-${String(index).padStart(2, "0")}-`,
|
|
title: `Home failure ${String(index).padStart(2, "0")}`,
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
|
|
test.use({
|
|
userAgent:
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/145.0 Safari/537.36",
|
|
});
|
|
|
|
test("workspace file search stays geometrically stable through delayed loading and results", async ({
|
|
page,
|
|
}) => {
|
|
test.setTimeout(120_000);
|
|
const seeded = await seedWorkspace({
|
|
repoPrefix: "command-center-file-search-",
|
|
title: "Paseo-shaped file search",
|
|
repo: { files: paseoSizedFiles() },
|
|
});
|
|
|
|
try {
|
|
await delayDirectorySuggestionResponses(page, 800);
|
|
await gotoWorkspace(page, seeded.workspaceId);
|
|
await page.keyboard.press("Meta+P");
|
|
|
|
const panel = page.getByTestId("command-center-panel");
|
|
await expect(panel).toBeVisible({ timeout: 30_000 });
|
|
await expect(panel.getByRole("button", { name: "Files" })).toBeVisible();
|
|
await startCommandCenterLayoutObservation(page);
|
|
|
|
await panel.getByTestId("command-center-input").fill(FILE_NAME);
|
|
const row = panel.getByRole("button", {
|
|
name: `${FILE_NAME} ${FILE_DIRECTORY}`,
|
|
});
|
|
await expect(row).toBeVisible({ timeout: 30_000 });
|
|
await expectOneLineTruncatedFileResult(row, {
|
|
filename: FILE_NAME,
|
|
path: FILE_DIRECTORY,
|
|
});
|
|
|
|
await panel.getByTestId("command-center-input").fill("needle-layout");
|
|
await page.waitForTimeout(150);
|
|
expect(await row.isVisible()).toBe(true);
|
|
await expect(panel.getByTestId("command-center-file-search-loading")).toBeVisible();
|
|
await expect(panel.getByText("Searching files...", { exact: true })).toHaveCount(0);
|
|
await expect(panel.getByTestId("command-center-file-search-loading")).toBeHidden();
|
|
await expectStableCommandCenterLayout(page);
|
|
|
|
await row.click();
|
|
await expectFileTabOpen(page, FILE_PATH);
|
|
} finally {
|
|
await seeded.cleanup();
|
|
}
|
|
});
|
|
|
|
test("dropping the files scope leaves the search row the same height", async ({ page }) => {
|
|
const seeded = await seedWorkspace({
|
|
repoPrefix: "command-center-file-search-scope-",
|
|
title: "Scope chip height",
|
|
});
|
|
|
|
try {
|
|
await gotoWorkspace(page, seeded.workspaceId);
|
|
await page.keyboard.press("Meta+P");
|
|
|
|
const panel = page.getByTestId("command-center-panel");
|
|
await expect(panel).toBeVisible({ timeout: 30_000 });
|
|
const header = panel.getByTestId("command-center-header");
|
|
const scoped = await header.boundingBox();
|
|
|
|
await page.getByTestId("command-center-files-scope").click();
|
|
await expect(page.getByTestId("command-center-files-scope")).toHaveCount(0);
|
|
const unscoped = await header.boundingBox();
|
|
|
|
expect(scoped?.height).toBe(unscoped?.height);
|
|
} finally {
|
|
await seeded.cleanup();
|
|
}
|
|
});
|
|
|
|
test("unscoped command search keeps commands visible and reports file-search failures", async ({
|
|
page,
|
|
}) => {
|
|
test.setTimeout(120_000);
|
|
const seeded = await seedMatchingWorkspaces(9);
|
|
|
|
try {
|
|
await failDirectorySuggestionRequests(page);
|
|
await gotoWorkspace(page, seeded[0].workspaceId);
|
|
const panel = await openCommandCenter(page);
|
|
await panel.getByTestId("command-center-input").fill("home");
|
|
|
|
await expect(panel.getByRole("button", { name: "Home", exact: true })).toBeVisible();
|
|
await expect(panel.getByRole("button", { name: /Home failure 08/ })).toBeVisible();
|
|
await expect(panel.getByTestId("command-center-file-search-error")).toHaveText(
|
|
"Error: Test file search transport failure.",
|
|
);
|
|
await expect(panel.getByTestId("command-center-file-search-error")).toBeInViewport({
|
|
ratio: 1,
|
|
});
|
|
await expect(panel.getByText("No matches", { exact: true })).toHaveCount(0);
|
|
} finally {
|
|
await Promise.allSettled(seeded.map((workspace) => workspace.cleanup()));
|
|
}
|
|
});
|