Skip to content

feat(ui): fold storage buckets and tables into the Cmd+K palette - #664

Merged
padak merged 2 commits into
mainfrom
claude/cmd-k-bucket-search-4d59d6
Aug 23, 2026
Merged

feat(ui): fold storage buckets and tables into the Cmd+K palette#664
padak merged 2 commits into
mainfrom
claude/cmd-k-bucket-search-4d59d6

Conversation

@padak

@padak padak commented Aug 23, 2026

Copy link
Copy Markdown
Member

What

The Ctrl/Cmd+K command palette (introduced in #658) now finds storage buckets and tables, not just pages/projects/actions — plus an always-available escape row that hands the query to the Search page.

Why

Typing a bucket name like oltp into the palette previously matched nothing — the palette could reach the Storage page but not a storage object. This closes that gap while keeping the palette's core contract: nothing waits on the network while you type.

Note: this deliberately does NOT build on the /search endpoint — server-side textual search requires the global-search project feature, which many projects (including the demo org) don't have. Cheap listings + client-side fuzzy matching work everywhere.

How it works / how it scales

Three data layers, all fetched on palette open (never per keystroke), matched locally:

Layer What Cost
1 Active project's buckets + tables usually 0 requests — react-query keys are shared with the Storage page's own queries
2 Buckets of all registered projects one request at staleTime: 60s; the server fans out in parallel (_run_parallel) and degrades per project via errors[], which the palette ignores silently
3 Foreign-project tables deliberately not loaded — reachable via their bucket; keeps the haystack bounded as project count grows

With e.g. 30 projects the haystack is a few thousand rows; the fuzzy matcher is a plain char scan (~ms per keystroke) and results stay capped at 50.

UX

  • Enter on a bucket → Storage page, tables tab, bucket filter applied. A foreign bucket switches the active project first (and resets the branch, same as the palette's project command).
  • Enter on a table → same, plus the table detail drawer opens.
  • Search "…" across projects — always the last row for a non-empty query; jumps to the Search page with the query prefilled and auto-run.
  • Ranking: data rows carry a score bias (+25, foreign buckets +35) so short queries keep pages/actions on top; a specific query wins on raw match quality. The idle (empty-query) list is unchanged.

Implementation

  • CommandPalette.tsx: three useQuery layers gated on open, new command kinds bucket/table/search, score bias, escape row appended after sort/slice (keyboard-reachable, outside the 50 cap), header comment rewritten (the old "deliberately NOT a search over Keboola data" contract is superseded).
  • state.tsx: two hand-off slots following the pendingLocalAiMessage precedent — pendingStorageTarget (bucket filter + optional table drawer) and pendingSearchQuery.
  • Storage.tsx: consumes pendingStorageTarget in two steps (global slot cleared immediately to avoid remount double-fire; the table row is looked up once tablesQ resolves — a table deleted in the meantime degrades silently to the filtered list).
  • Search.tsx: mutation now takes the query as an argument (avoids the stale-closure trap the LocalAi consumer documents); consumes pendingSearchQuery with auto-run.

No backend, CLI, or changelog changes (UI-only; changelog lands in the release PR per #648).

Testing

  • tsc --noEmit + vite build clean (the frontend CI gate from ci: gate web/frontend PRs on tsc --noEmit + vite build #661).
  • Verified live against kbagent serve --ui with 4 registered projects: bucket jump, table drawer, foreign-project bucket switch (erpanalytics · in.c-erp and back), and the Search hand-off with auto-run.

Update after rebase onto #665

#665 (shareable deep links) landed mid-flight and made the original pendingStorageTarget hand-off slot obsolete: the Storage page now owns a URL-mirrored ?sel= selection. This PR was reworked to build on it instead:

  • The palette navigates by writing sel via the Storage page's own (now exported) grammar helper — so a palette jump produces the same shareable URL as clicking the row (#/p/erp/storage?sel=bucket%2Fin.c-erp, ...?sel=tables%2F<tableId>).
  • The grammar gained a bucket/<bucketId> form; the bucket filter chip is now in the URL and survives a cold reload (it previously was deliberately not encoded — a palette pick is a destination, not a transient narrowing).
  • Fixed a latent mount-only assumption in feat(ui): shareable deep links, rendered detail views, component-detail 502 fix #665's restore effect: a sel written while the page is already mounted (palette retarget, incompatible bucket filter) now applies instead of being consumed by a one-shot ref.
  • pendingSearchQuery (the Search-page escape row) stays — a pending action (auto-run) is not a selection, so it does not belong in the URL.
  • New vitest suite storageSel.test.ts (11 tests) for the grammar, joining feat(ui): shareable deep links, rendered detail views, component-detail 502 fix #665's router suite in the CI npm test step.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@padak
padak force-pushed the claude/cmd-k-bucket-search-4d59d6 branch from 31fdd39 to 6409703 Compare August 23, 2026 17:47
padak added 2 commits August 23, 2026 22:07
The command palette now finds storage objects, not just pages: the
active project's buckets and tables plus every registered project's
buckets are fetched when the palette opens (react-query cache shared
with the Storage page; the cross-project bucket list held at 60s
staleTime) and fuzzy-matched locally, so typing still never waits on a
network round-trip. Cross-project tables are deliberately not loaded --
a foreign table is reachable through its bucket, which keeps the
haystack bounded as the number of registered projects grows.

Enter on a bucket lands on the Storage page with the bucket filter
applied (switching the active project first for a foreign bucket);
Enter on a table additionally opens its detail drawer. Both travel via
a new pendingStorageTarget hand-off slot in UIState, following the
pendingLocalAiMessage precedent. A non-empty query also always offers a
final "Search '...' across projects" escape row that jumps to the
Search page and auto-runs the query via a pendingSearchQuery slot --
the palette answers what its local haystack can, and hands everything
else to the page built for server-side search.

Ranking keeps pages and actions on top for short queries via a score
bias on data rows (+25, foreign-project buckets +35); a specific query
still wins on raw match quality. The idle (no-query) list is unchanged.

Verified live against kbagent serve --ui with 4 registered projects:
bucket jump, table drawer, foreign-project switch, and the Search
hand-off.
The deep-link restore effect from #665 was mount-oriented: a one-shot
ref meant a `?sel=` written by the command palette while Storage was
already open (or against a stale, still-filtered tables list) was
consumed without opening anything, stranding the drawer shut.

Split it into two effects: one adopts an externally-written sel (tab,
bucket filter -- dropping a filter that cannot contain the target
table), one opens the table once a list containing it has loaded, with
no give-up flag (a closed drawer rewrites sel, so it cannot re-open).

Verified live: palette table jump with a matching filter (kept), with a
conflicting filter (dropped), and cold reload of both URL forms.
@padak
padak force-pushed the claude/cmd-k-bucket-search-4d59d6 branch from 6409703 to 41ab45f Compare August 23, 2026 20:19
@padak
padak merged commit 011a243 into main Aug 23, 2026
5 checks passed
@padak
padak deleted the claude/cmd-k-bucket-search-4d59d6 branch August 23, 2026 20:44
@padak padak mentioned this pull request Aug 23, 2026
10 tasks
padak added a commit that referenced this pull request Aug 23, 2026
* chore(release): 0.90.0

Bumps pyproject.toml to 0.90.0 and adds the changelog entry covering every
PR merged since v0.89.0 (#658, #662, #661, #663, #665, #666, #664, #668,
#667, #623), resolves the vNEXT placeholders those PRs left behind, and
adds the curated What's new reel for the release.

* docs(web-server): keep the What's-new anchor stable across releases

The '### What's-new popup *(since vNEXT)*' heading put the version gate in
the heading itself, so resolving the placeholder to 0.90.0 changed the
generated slug to 'whats-new-popup-since-0900' and broke the in-page link
at line 138 -- and would have broken it again on every future release.

Moved the '(since 0.90.0)' tag to the first body line: the anchor is now
the stable 'whats-new-popup', the gate stays visible, and
check_version_gates.py still sees it (it scans the whole file, not just
headings).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant