Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
1fae208
Upgraded `@cloudflare/kumo` to version `1.16.0`
NuroDev Apr 2, 2026
ad66137
Merge branch 'main' into NuroDev/local-explorer-sidebar
NuroDev Apr 2, 2026
47061ed
Merge branch 'main' into NuroDev/local-explorer-sidebar
NuroDev Apr 7, 2026
b47364f
Added initial re-worked sidebar
NuroDev Apr 7, 2026
931bcb2
Minor code formatting
NuroDev Apr 7, 2026
ef83ff7
Minor UI gap fix
NuroDev Apr 7, 2026
e579611
Minor sidebar item margin fix
NuroDev Apr 7, 2026
cb70bd5
Persist sidebar item collapse state to local storage
NuroDev Apr 7, 2026
0cbef86
Re-added worker selector
NuroDev Apr 7, 2026
7df3db6
Added collapsed sidebar support to worker selector
NuroDev Apr 7, 2026
7dce51e
Added basic theme switching support
NuroDev Apr 7, 2026
e4b2ef3
Updated worker selector badge label from "current" to "Host"
NuroDev Apr 7, 2026
1169f50
Persist sidebar collapsed state to local storage
NuroDev Apr 7, 2026
74aad1c
Added collapsed sidebare item groups
NuroDev Apr 7, 2026
b26f419
Added spacing to sidebar items
NuroDev Apr 7, 2026
1bbb75e
Offset empty sidebar group text
NuroDev Apr 7, 2026
355a43f
Fixed worker selector padding
NuroDev Apr 7, 2026
feb8307
Upgrade `@cloudflare/kumo` to version `1.17.0`
NuroDev Apr 7, 2026
a5de881
Added changeset
NuroDev Apr 7, 2026
cf97e57
Merge branch 'main' into NuroDev/local-explorer-sidebar
NuroDev Apr 7, 2026
21ca107
Merge branch 'main' into NuroDev/local-explorer-sidebar
NuroDev Apr 8, 2026
2e8b971
Minor sidebare error / empty label fixes
NuroDev Apr 8, 2026
3b30648
Add strictly typed `worker` search param to all routes
NuroDev Apr 8, 2026
1cc6b6b
Merge branch 'main' into NuroDev/local-explorer-sidebar
NuroDev Apr 9, 2026
9a5ae4a
Minor DO class name search input border color fix
NuroDev Apr 9, 2026
f7ad847
Fixed sidebar height
NuroDev Apr 9, 2026
0fa6026
Fixed sidebar toggle button hover background color
NuroDev Apr 9, 2026
05b957a
Minor sidebar item group active state / spacing fixes
NuroDev Apr 9, 2026
f9e3060
Removed sidebar resource disabled errors
NuroDev Apr 9, 2026
c1bf1ff
Minor type improvements
NuroDev Apr 9, 2026
39a467a
Merge branch 'main' into NuroDev/local-explorer-sidebar
NuroDev Apr 9, 2026
6c28b62
Minor sidebar item group margin fix
NuroDev Apr 9, 2026
2f0a5da
Temp: Patch sidebar collapse transition easing
NuroDev Apr 9, 2026
bda8932
Fixed DO E2E tests' `navigateToDOObjectByName` helper
NuroDev Apr 9, 2026
c9540e6
Added TODO
NuroDev Apr 9, 2026
2419395
Streamlined R2 breadcrumb E2E checks
NuroDev Apr 10, 2026
362dcbe
Minor code cleanup
NuroDev Apr 10, 2026
33bed5f
Merge branch 'main' into NuroDev/local-explorer-sidebar
NuroDev Apr 10, 2026
68e7feb
Updated `SidebarGroupPopup` component to use Tanstack's `Link` component
NuroDev Apr 10, 2026
cd3904a
Minor code formatting
NuroDev Apr 10, 2026
01b4b8d
Restore e2e test timeout of 10 seconds
NuroDev Apr 10, 2026
95aa5ed
Merge branch 'main' into NuroDev/local-explorer-sidebar
NuroDev Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/update-local-explorer-sidebar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@cloudflare/local-explorer-ui": minor
---

Update local explorer sidebar with collapsible groups, theme persistence, and Kumo v1.17

Adds localStorage persistence for sidebar group expansion states and theme mode (light/dark/system). The sidebar now uses Kumo v1.17 primitives with collapsible groups and a theme toggle in the footer.

Users can now cycle between light, dark, and system theme modes, and their preference will be persisted across sessions.

Sidebar groups (D1, Durable Objects, KV, R2, Workflows) also remember their collapsed/expanded state.
2 changes: 1 addition & 1 deletion packages/local-explorer-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"dependencies": {
"@base-ui/react": "^1.1.0",
"@cloudflare/kumo": "^1.5.0",
"@cloudflare/kumo": "^1.17.0",
"@cloudflare/workers-editor-shared": "^0.1.1",
"@codemirror/autocomplete": "^6.20.0",
"@codemirror/commands": "^6.10.2",
Expand Down
19 changes: 12 additions & 7 deletions packages/local-explorer-ui/src/__e2e__/r2/r2-bucket.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
navigateToR2Object,
page,
seedR2,
waitForBreadcrumbText,
waitForDialog,
waitForTableRows,
waitForText,
Expand Down Expand Up @@ -88,8 +89,8 @@ describe("R2 Bucket", () => {
await navigateToR2Bucket("my-bucket");
await waitForTableRows(1);

await waitForText("R2");
await waitForText("my-bucket");
await waitForBreadcrumbText("R2");
await waitForBreadcrumbText("my-bucket");
});

test("navigates into a directory and updates breadcrumbs", async () => {
Expand Down Expand Up @@ -273,10 +274,10 @@ describe("R2 Bucket", () => {
await navigateToR2Object("my-bucket", "documents/report.txt");

// Breadcrumbs should show: R2 > my-bucket > documents > report.txt
await waitForText("R2");
await waitForText("my-bucket");
await waitForText("documents");
await waitForText("report.txt");
await waitForBreadcrumbText("R2");
await waitForBreadcrumbText("my-bucket");
await waitForBreadcrumbText("documents");
await waitForBreadcrumbText("report.txt");
});
});

Expand Down Expand Up @@ -358,7 +359,11 @@ describe("R2 Bucket", () => {

await waitForDialog();
await waitForText("Delete object?");
await waitForText("readme.txt");

// "readme.txt" appears in multiple places (sidebar, breadcrumbs, heading),
// so scope the assertion to the dialog.
const dialog = page.getByRole("dialog");
await dialog.getByText("readme.txt").waitFor({ state: "visible" });
});

test("deletes object from detail page and navigates back", async () => {
Expand Down
61 changes: 46 additions & 15 deletions packages/local-explorer-ui/src/__e2e__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,27 +144,28 @@ export async function navigateToDOObject(
*/
export async function navigateToDOObjectByName(
className: string,
table?: string
table?: string,
objectName: string = "test-object"
): Promise<string> {
await navigateToDOClass(className);
await waitForText(className);

const openStudioLink = page.locator('a:has-text("Open Studio")').first();
await openStudioLink.waitFor({ state: "visible", timeout: 10_000 });

const href = await openStudioLink.getAttribute("href");
if (!href) {
throw new Error("Could not find href on Open Studio link");
}
await fillByPlaceholder("Enter instance name or hex ID...", objectName);
await page.getByRole("button", { name: "Open Studio" }).click();
await waitForPageLoad();

// Extract the object ID from the href (format: /cdn-cgi/explorer/do/{className}/{objectId})
const match = href.match(/\/cdn-cgi\/explorer\/do\/[^/]+\/([a-f0-9]+)/);
// Extract the object ID from the current URL after navigation.
const objectPath = new URL(page.url()).pathname;
const match = objectPath.match(/\/cdn-cgi\/explorer\/do\/[^/]+\/([^/?#]+)/);
if (!match || !match[1]) {
throw new Error(`Could not extract object ID from href: ${href}`);
throw new Error(`Could not extract object ID from URL path: ${objectPath}`);
}

const objectId: string = match[1];

await navigateToDOObject(className, objectId, table);
if (table) {
await navigateToDOObject(className, objectId, table);
}

return objectId;
}
Expand All @@ -183,6 +184,34 @@ export async function waitForText(
});
}

/**
* Wait for text to appear inside the breadcrumb navigation bar.
*
* Use this instead of `waitForText` when the same text also appears in the
* sidebar (e.g. bucket names, object keys) to avoid Playwright resolving
* to the wrong element.
*/
export async function waitForBreadcrumbText(
text: string,
options?: {
timeout?: number;
}
): Promise<void> {
// The Kumo breadcrumb `<nav>` contains both a mobile and desktop layout.
// The desktop layout uses `hidden sm:contents`, so target it directly to
// avoid matching the hidden mobile `<span class="truncate">` first.
const desktopBreadcrumb = page.locator(
'nav[aria-label="breadcrumb"] > .hidden.sm\\:contents'
);
await desktopBreadcrumb
.getByText(text)
.first()
.waitFor({
state: "visible",
timeout: options?.timeout ?? WAIT_OPTIONS.timeout,
});
}

/**
* Wait for an element to be visible.
*/
Expand Down Expand Up @@ -318,9 +347,11 @@ export async function runAllQueries(): Promise<void> {
* Open the table selector dropdown in the breadcrumb bar.
*/
export async function openTableSelector(): Promise<void> {
// The `TableSelect` uses a `Select.Trigger` which contains either "Select table" or the table name
// Find the `Select` trigger by looking for the text + caret icon combo
const tableSelector = page.locator('text="Select table"').first();
// The `TableSelect` trigger text is dynamic ("Select table" or current table name).
// Target the table selector trigger by its unique utility class on the breadcrumb row.
const tableSelector = page
.locator('button[class*="-mx-1.5"]:visible')
.first();
await tableSelector.click();

await page.waitForSelector('[role="listbox"]', {
Expand Down
Loading
Loading