feat(ui): fold storage buckets and tables into the Cmd+K palette - #664
Merged
Conversation
padak
force-pushed
the
claude/cmd-k-bucket-search-4d59d6
branch
from
August 23, 2026 17:47
31fdd39 to
6409703
Compare
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
force-pushed
the
claude/cmd-k-bucket-search-4d59d6
branch
from
August 23, 2026 20:19
6409703 to
41ab45f
Compare
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
oltpinto 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
/searchendpoint — server-side textual search requires theglobal-searchproject 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:
staleTime: 60s; the server fans out in parallel (_run_parallel) and degrades per project viaerrors[], which the palette ignores silentlyWith 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
tablestab, bucket filter applied. A foreign bucket switches the active project first (and resets the branch, same as the palette's project command).Search "…" across projects— always the last row for a non-empty query; jumps to the Search page with the query prefilled and auto-run.Implementation
CommandPalette.tsx: threeuseQuerylayers gated onopen, new command kindsbucket/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 thependingLocalAiMessageprecedent —pendingStorageTarget(bucket filter + optional table drawer) andpendingSearchQuery.Storage.tsx: consumespendingStorageTargetin two steps (global slot cleared immediately to avoid remount double-fire; the table row is looked up oncetablesQresolves — 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); consumespendingSearchQuerywith auto-run.No backend, CLI, or changelog changes (UI-only; changelog lands in the release PR per #648).
Testing
tsc --noEmit+vite buildclean (the frontend CI gate from ci: gate web/frontend PRs on tsc --noEmit + vite build #661).kbagent serve --uiwith 4 registered projects: bucket jump, table drawer, foreign-project bucket switch (erp→analytics · in.c-erpand 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
pendingStorageTargethand-off slot obsolete: the Storage page now owns a URL-mirrored?sel=selection. This PR was reworked to build on it instead:selvia 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>).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).selwritten 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.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 CInpm teststep.