Skip to content

improvement(search): redesign Cmd-K palette with curated empty state and drill-down browse#5194

Open
waleedlatif1 wants to merge 1 commit into
stagingfrom
worktree-block-picker-redesign
Open

improvement(search): redesign Cmd-K palette with curated empty state and drill-down browse#5194
waleedlatif1 wants to merge 1 commit into
stagingfrom
worktree-block-picker-redesign

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • The Cmd-K command palette (also the canvas "add block" picker) dumped the entire catalog into the list on open — ~36 blocks, ~221 tools, 11 triggers, 1,000+ tool operations, and ~250 docs — ~1,500 unvirtualized DOM rows rendered before the user typed a character, and re-fuzzy-matched on every keystroke. It was overwhelming to browse and the single most expensive render in the palette.
  • Now the empty state is curated and navigable, and the full catalog surfaces only on search — extending the pattern the integrations group already used.

What changed

  • Catalog gated behind search. Blocks, tools, triggers, tool operations, and docs only render once the user types. Tool operations and docs are now search-only (never browsed by scroll).
  • Recents. New persisted, frecency-ranked store (frequency + recency, 7-day half-life) surfaces the blocks you actually use at the top of the empty state. Directly helps follow-along during recorded/guided builds.
  • Browse → drill-down. A Browse section lists categories (Core Blocks, Triggers, and one per integration type — AI, Communication, Sales, …) derived from category + integrationType. Selecting one scopes the list to just that category. Typing from the root still does a flat global search.
  • Scope pill. The active drill-down shows as a removable pill in the search bar (Linear/Raycast pattern). Backspace on an empty input or Escape pops the scope. Deliberately not an in-list back row — that would steal Enter from cmdk's first-item highlight while searching within a scope.
  • Per-group result caps keep the DOM bounded on broad queries (results are score-sorted, so only the low-relevance tail is trimmed).

Toolbar block picker (separate component) intentionally left as a follow-up to keep this PR focused.

Type of Change

  • Improvement

Testing

  • store.test.ts updated + new coverage for integrationType capture and category building (5/5 passing)
  • Full tsc clean (0 errors), biome clean
  • Manual visual pass pending

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…and drill-down browse

- Stop dumping the full ~1,500-item catalog (blocks, tools, triggers, 1,000+ tool operations, docs) into the palette on open; surface them only once the user types, mirroring the existing integrations group
- Add a curated empty state: frecency-ranked Recents (new persisted store) and a Browse section that drills into block/trigger/integration categories
- Scope drill-down shown as a removable pill in the search bar; Backspace on empty input or Escape pops the scope
- Cap each result group to keep the DOM bounded on broad queries
- Derive browse categories from block category + integrationType
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 24, 2026 7:30am

Request Review

@cursor

cursor Bot commented Jun 24, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Search-modal UX and client-side localStorage only; add-block behavior is unchanged aside from when catalog rows render and recents recording.

Overview
The Cmd-K palette no longer renders the full block/tool/trigger/operation/docs catalog on open. On the workflow page, blocks, tools, triggers, tool operations, and docs only appear after the user types; global search still works from the root, with 50 results per group to cap DOM size.

The empty state adds Recent (persisted frecency in localStorage via useSearchRecentsStore, recorded on block/tool/trigger/operation picks) and Browse categories built from core blocks, triggers, and tool integrationType. Choosing a category drills down into a scoped list; a scope pill in the search bar, Escape, and Backspace on an empty input exit the scope instead of closing the modal.

Store/types gain SearchCategory, integrationType on tools, and buildCategories; UI adds MemoizedCategoryItem, RecentsGroup, and BrowseGroup with optional group headings for scoped views.

Reviewed by Cursor Bugbot for commit 637a046. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR redesigns the Cmd-K command palette from a single flat list of ~1,500 unvirtualized catalog entries into a curated empty state with frecency-ranked recents, a browsable drill-down by category, and per-group result caps (50) that keep the DOM bounded during search.

  • Catalog gated behind search: blocks, tools, triggers, tool-ops, and docs are now rendered only when the user types; the empty state replaces them with RecentsGroup and BrowseGroup.
  • Frecency recents store: a new useSearchRecentsStore persists selections keyed by <kind>:<type>, scores them with frequency × exponential-time-decay (7-day half-life), and resolves them to renderable rows in a useMemo inside the modal.
  • Scope / drill-down: selecting a browse category sets a scope state that filters the list to that category; a removable pill in the search bar shows the active scope; Escape and Backspace on an empty input exit the scope.

Confidence Score: 4/5

Safe to merge with minor fixes; no user data is at risk and all behavioral regressions are ranking-only.

The pruning logic in the new recents store sorts by lastUsedAt instead of frecencyScore, so high-use blocks can be evicted in favour of recently-used-once items. Separately, Date.now() is captured inside a useMemo keyed on recentEntries, so frecency ordering can drift between palette opens when no selection is made. Both issues affect recents ranking UX rather than correctness of block insertion or data persistence.

recents.ts (pruning and timestamp logic) and search-modal.tsx (stale now in the recents useMemo) deserve a second look before merging.

Important Files Changed

Filename Overview
apps/sim/stores/modals/search/recents.ts New Zustand persist store for frecency-ranked recents; pruning logic sorts by lastUsedAt instead of frecencyScore, which can evict high-frequency items in favour of recently-used-once items.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx Major refactor gating the full catalog behind search and adding scope/drill-down state; Date.now() captured inside recents useMemo can produce stale frecency ordering between palette opens.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/command-items.tsx Adds MemoizedCategoryItem for browse rows; onSelect is missing from the memo equality comparator, creating a latent stale-closure risk.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/search-groups/search-groups.tsx Adds RecentsGroup and BrowseGroup components and CATEGORY_ICONS map; productivity and blocks share the same icon.
apps/sim/stores/modals/search/store.ts Adds buildCategories helper and threads integrationType onto tools; logic is clean and well-tested.
apps/sim/stores/modals/search/types.ts Adds SearchCategory, SearchCategoryKind, and integrationType field to SearchBlockItem; type definitions are clear and well-documented.
apps/sim/stores/modals/search/store.test.ts Adds coverage for integrationType propagation and buildCategories ordering; tests are focused and correct.

Comments Outside Diff (2)

  1. apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx, line 533-536 (link)

    P2 Stale Date.now() inside useMemo skews frecency ranking between palette opens

    now is captured once when the memo evaluates and then cached until any dependency changes. If the user opens the palette, makes zero selections (so recentEntries stays the same reference), and reopens it hours or days later, the memo is served from cache and all frecency scores are still computed against the original now. Items that should have decayed in ranking are kept artificially high.

    A simple fix is to derive now outside the memo (e.g., via a useRef updated on each open event), so the timestamp is always fresh when the palette is shown.

  2. apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/command-items.tsx, line 46-52 (link)

    P2 onSelect is excluded from the MemoizedCategoryItem equality check

    The custom comparator skips onSelect, so if the prop changes (e.g., a parent re-render creates a new inline () => onSelect(category) closure), the memoized component silently retains the previous handler. Currently safe because enterScope is a stable useCallback([], []) reference, but it is a latent trap for future refactors.

    Consider adding prev.onSelect === next.onSelect to the comparator, or hoisting the inline closures in BrowseGroup into useCallbacks so stability is explicit.

Reviews (1): Last reviewed commit: "improvement(search): redesign Cmd-K pale..." | Re-trigger Greptile

Comment on lines +51 to +56
const kept = keys
.sort((a, b) => entries[b].lastUsedAt - entries[a].lastUsedAt)
.slice(0, MAX_ENTRIES)
const pruned: Record<string, RecentEntry> = {}
for (const k of kept) pruned[k] = entries[k]
return { entries: pruned }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Pruning sorts by recency, not frecency

When the store grows beyond MAX_ENTRIES, entries are pruned by lastUsedAt (most-recent-first), not by frecencyScore. This means a block used 100 times last week can be evicted in favour of a block used once yesterday — the opposite of the frecency guarantee described in the PR and in the jsdoc comment above (frecencyScore). When the user re-opens the palette after the eviction, their high-frequency block simply disappears from Recents.

The sort should use frecencyScore so that eviction decisions match the display ordering.

Comment on lines +114 to +115
return (
<Command.Group heading='Recent' className={GROUP_HEADING_CLASSNAME}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 productivity maps to the same Blocks icon as the blocks category

Both 'blocks' and 'productivity' resolve to the Blocks icon, so users would see identical icons for two different browse categories. A more fitting choice for productivity might be Briefcase, LayoutDashboard, or CheckSquare from Lucide.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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