mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
fix(webapp): the palette always offers a way back (BEA-52) (#102)
⌘K on a path that doesn't resolve degraded to History ×2 and Sign out — the tree-derived entries are gone on a dead route and the switcher lists only OTHER projects, so on a single-project hub the palette, which is the natural escape hatch there, was the one surface with no way out. Four static entries now lead the candidate list whenever hub && project: Go to project root, Dashboard, Installation, Settings — the same four destinations (and icons) the sidebar has. They're independent of the tree and of whether the path resolves, and each navigates to a real VIEW_ROUTES URL, so a reload renders the same view. Panels only close on a location change, so selecting the page you're already on closes them explicitly. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fe872dd1fa
commit
c5f8114e07
@@ -153,6 +153,30 @@ test("palette (⌘K) fuzzy-jumps to a file", async ({ page }) => {
|
||||
await expect(page.locator("#content h1")).toHaveText("Topic");
|
||||
});
|
||||
|
||||
// BEA-52: on a path that doesn't resolve the tree entries are gone and the
|
||||
// switcher lists only other projects, so the palette used to offer no way
|
||||
// back. cmdk owns the list's id (it overwrites ours), hence [cmdk-list].
|
||||
test("palette on a dead route still offers the way back", async ({ page }) => {
|
||||
await login(page);
|
||||
const pid = await wikiId(page);
|
||||
await page.goto(`/${pid}/does-not-exist.md`);
|
||||
await expect(page.locator(".notfound")).toBeVisible();
|
||||
await page.keyboard.press("ControlOrMeta+k");
|
||||
await expect(page.locator("#palette")).toBeVisible();
|
||||
for (const label of ["Go to project root", "Dashboard", "Installation", "Settings"]) {
|
||||
await expect(page.locator("#palette [cmdk-list]")).toContainText(label);
|
||||
}
|
||||
// exactly one whole-project history entry, no duplicate
|
||||
expect(
|
||||
await page.locator("#palette [cmdk-item]", { hasText: "History: whole project" }).count(),
|
||||
).toBe(1);
|
||||
await page.fill("#palette input", "Dashboard");
|
||||
await page.keyboard.press("Enter");
|
||||
await page.waitForURL(`/${pid}/dashboard`);
|
||||
await page.reload(); // the entries are real URLs, not panel state
|
||||
await expect(page.locator(".in-treemap")).toBeVisible();
|
||||
});
|
||||
|
||||
// BEA-54: cmdk overwrites the `id` we pass its primitives, so every palette
|
||||
// rule anchored on one was dead — the input lost its only author `color` and
|
||||
// fell back to the UA's black. Anchors here are ours (#palette) or cmdk's own
|
||||
|
||||
@@ -283,6 +283,24 @@ export default function Browser(props: {
|
||||
const items: PaletteItem[] = [];
|
||||
const add = (icon: string, label: string, kind: string, run: () => void) =>
|
||||
items.push({ icon, label, kind, run });
|
||||
// The project's own destinations, first and unconditional: on a path that
|
||||
// doesn't resolve the tree-derived entries are gone and the switcher lists
|
||||
// only OTHER projects, so without these the palette — the natural escape
|
||||
// hatch on a dead route — is the one surface with no way back. An empty
|
||||
// query scores everything 0 and the sort is stable, so first here is first
|
||||
// on screen. Panels aren't routes and only close on a location CHANGE, so
|
||||
// picking the page you're already on needs the explicit close.
|
||||
if (hub && project) {
|
||||
const pid = project.id;
|
||||
const go = (to: string) => () => {
|
||||
props.onClosePanel?.();
|
||||
navigate(to);
|
||||
};
|
||||
add("folder", "Go to project root", "action", go("/" + pid));
|
||||
add("dashboard", "Dashboard", "action", go(urlForView("dashboard", pid)));
|
||||
add("terminal", "Installation", "action", go(urlForView("install", pid)));
|
||||
add("gear", "Settings", "action", go(urlForView("settings", pid)));
|
||||
}
|
||||
if (hub && project && path) {
|
||||
if (isFile) add("share", "Share: " + path, "action", shareNow);
|
||||
add("hist", "History: " + path, "action", historyNow);
|
||||
@@ -302,7 +320,7 @@ export default function Browser(props: {
|
||||
for (const d of dirIndex.keys()) add("folder", d, "folder", () => openPath(d));
|
||||
for (const f of flatFiles) add("doc", f.path, "file", () => openPath(f.path));
|
||||
return items;
|
||||
}, [hub, project, path, isFile, config.auth?.enabled, dirIndex, flatFiles, props.projects, shareNow, historyNow, openHistory, openPath]);
|
||||
}, [hub, project, path, isFile, config.auth?.enabled, dirIndex, flatFiles, props.projects, props.onClosePanel, shareNow, historyNow, openHistory, openPath]);
|
||||
|
||||
/* ---- "⋯ More" menu (secondary actions on narrow screens) ---- */
|
||||
useEffect(() => {
|
||||
|
||||
+25
-25
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>BearDrive</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='%23f5a623'><rect x='4' y='4' width='5.6' height='24'/><rect x='11.2' y='4' width='14.4' height='11.2'/><rect x='11.2' y='16.8' width='16.8' height='11.2'/></svg>">
|
||||
<script type="module" crossorigin src="/assets/index-CMWTx-Od.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DDAA5G2N.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B-zSoeQX.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user