Skip to content

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

Open
waleedlatif1 wants to merge 8 commits 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 8 commits 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 4:43pm

Request Review

@cursor

cursor Bot commented Jun 24, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large UX change to the primary add-block/search path on the workflow canvas; behavior shifts (empty vs search-only catalog, drill-down, result caps) could surprise users or hide matches on very broad queries, though add-block dispatch and analytics are unchanged.

Overview
Redesigns the workspace Cmd-K search modal so opening it no longer renders the full blocks/tools/triggers/docs catalog (~1,500 rows). On the workflow page, the empty state shows Recent (up to five frecency-ranked picks from a persisted store, recorded when you add blocks/tools/triggers/operations) and Browse categories from the search store, with new MemoizedCategoryItem, RecentsGroup, and BrowseGroup UI.

Browse drill-down adds scope state: picking a category filters to that list (blocks, triggers, or tools by integration type), shows a removable scope pill in the search bar, and uses Escape / Backspace on an empty field to pop scope before closing. Catalog groups (blocks, tools, triggers, tool ops, docs) only appear when the user is typing at root scope; scoped views hide the global catalog. filterAndCap limits each result group to 50 ranked rows so broad searches stay bounded.

Input reset is centralized in clearInput so cmdk’s uncontrolled input stays in sync when opening the modal or changing scope.

Reviewed by Cursor Bugbot for commit 2848a0b. 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 to gate the full block/tool catalog behind search, replacing the previous 1,500-row unvirtualized render with a curated empty state featuring frecency-ranked recents and a drill-down browse panel. A new persisted useSearchRecentsStore records block selections and surfaces the top five by frecency score.

  • Catalog gated behind search: cappedCatalog ensures blocks, tools, triggers, tool ops, and docs only render when the user has typed; an active scope further switches to a single scoped list, keeping the DOM bounded (≤ 50 results per group).
  • Frecency store: A new recents.ts store persists up to 50 entries, ranked by count × 2^(−age/7d), with correct pruning (verified by a regression test added after a previous review).
  • Browse drill-down: BrowseGroup renders an exhaustive Record<IntegrationType, LucideIcon> icon map so a missing integration type is a compile error rather than a silent fallback.

Confidence Score: 5/5

Safe to merge after confirming the docx.cjs bundle change is intentional or reverting it.

The search-modal logic is well-structured: catalog gating, frecency scoring, scope drill-down, and Escape/Backspace navigation all behave correctly. The frecency pruning regression was addressed in a prior commit and is now covered by a dedicated test. The only outstanding question is the unrelated docx.cjs bundle appearing in the diff.

apps/sim/lib/execution/sandbox/bundles/docx.cjs — verify this bundle change was intentional and not an accidental staging artifact.

Important Files Changed

Filename Overview
apps/sim/stores/modals/search/store.ts Adds buildCategories() and integrationType capture on tools; clean store initialization with correct dependency handling.
apps/sim/stores/modals/search/recents.ts New persisted frecency store; pruning correctly uses frecencyScore (fixed from an earlier review), verified by regression tests.
apps/sim/stores/modals/search/types.ts Adds SearchCategory and SearchCategoryKind types; well-documented with JSDoc.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx Core modal rewrite with scope/recents logic; prevOpen pattern for reset-on-open is valid, ref-based handlers avoid stale closures, Escape key correctly exits scope before close.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/search-groups/search-groups.tsx New BrowseGroup and RecentsGroup components; INTEGRATION_CATEGORY_ICONS is an exhaustive Record giving compile-time safety for new categories.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/components/command-items/command-items.tsx Adds MemoizedCategoryItem with ChevronRight affordance and HighlightedText support; comparator omits onSelect but enterScope is a stable useCallback so it's safe in practice.
apps/sim/stores/modals/search/store.test.ts New test covers integrationType capture, category building with correct AI-first ordering, and count aggregation — all passing.
apps/sim/stores/modals/search/recents.test.ts Comprehensive frecency tests including the pruning-by-frecency regression; all five cases cover frequency, recency, decay, half-life, and eviction ordering.
apps/sim/lib/execution/sandbox/bundles/docx.cjs Unrelated CJS bundle file changed alongside a UI/search modal PR; not mentioned in the PR description — likely an accidental inclusion.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant SearchModal
    participant RecentsStore
    participant SearchStore

    User->>SearchModal: Cmd-K (open)
    SearchModal->>SearchModal: reset search + scope (prevOpen pattern)
    SearchModal->>RecentsStore: read entries (frecency-sorted)
    SearchModal->>SearchStore: read categories (blocks, triggers, integrations)
    SearchModal-->>User: Show Recents + Browse groups (empty state)

    User->>SearchModal: Click Browse category (e.g. AI)
    SearchModal->>SearchModal: enterScope(category) → setScope + clearInput
    SearchModal-->>User: Show scope pill + scoped tool list

    User->>SearchModal: Backspace / Escape on empty input
    SearchModal->>SearchModal: exitScope() → setScope(null) + clearInput
    SearchModal-->>User: Return to empty state (Recents + Browse)

    User->>SearchModal: Type query
    SearchModal->>SearchModal: cappedCatalog → filterAndSort (≤50 per group)
    SearchModal-->>User: Show scored results (blocks, tools, triggers, ops, docs)

    User->>SearchModal: Select a block
    SearchModal->>RecentsStore: record(block-type)
    SearchModal->>SearchModal: dispatch add-block-from-toolbar + close
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant SearchModal
    participant RecentsStore
    participant SearchStore

    User->>SearchModal: Cmd-K (open)
    SearchModal->>SearchModal: reset search + scope (prevOpen pattern)
    SearchModal->>RecentsStore: read entries (frecency-sorted)
    SearchModal->>SearchStore: read categories (blocks, triggers, integrations)
    SearchModal-->>User: Show Recents + Browse groups (empty state)

    User->>SearchModal: Click Browse category (e.g. AI)
    SearchModal->>SearchModal: enterScope(category) → setScope + clearInput
    SearchModal-->>User: Show scope pill + scoped tool list

    User->>SearchModal: Backspace / Escape on empty input
    SearchModal->>SearchModal: exitScope() → setScope(null) + clearInput
    SearchModal-->>User: Return to empty state (Recents + Browse)

    User->>SearchModal: Type query
    SearchModal->>SearchModal: cappedCatalog → filterAndSort (≤50 per group)
    SearchModal-->>User: Show scored results (blocks, tools, triggers, ops, docs)

    User->>SearchModal: Select a block
    SearchModal->>RecentsStore: record(block-type)
    SearchModal->>SearchModal: dispatch add-block-from-toolbar + close
Loading

Reviews (2): Last reviewed commit: "fix(search): type category icon map by t..." | Re-trigger Greptile

Comment thread apps/sim/stores/modals/search/recents.ts Outdated
- Fix stale input text when entering/exiting a browse scope: the cmdk
  Command.Input is uncontrolled, so resetting requires the native value
  setter, not just setSearch(''). Extract the existing open-reset logic into
  a shared clearInput() and reuse it for open, enter-scope, and exit-scope
- Short-circuit the recents memo when there are no recorded entries
- Use a distinct icon for the Productivity category (was reusing Core Blocks')
- Add unit coverage for the recents store and frecency scoring
…etically

- Lead with workflow primitives (Core Blocks, Triggers), then pin AI first
  (Sim is an AI workspace) and rank remaining integration categories by
  breadth (most integrations first) so the richest categories surface first
- Personal frequency is already covered by Recents, so the static order
  optimizes for discovery and self-maintains as integrations are added
- Reuses the canonical integration taxonomy (INTEGRATION_TYPE_LABELS) shared
  with the /integrations page rather than inventing a parallel one
…tive

Review-loop findings:
- Recents were pruned by raw recency but ranked by frecency, so a frequently
  used older block could be evicted while still ranking high. Prune by the same
  frecencyScore used for display; add a regression test
- Make the integration category icon map exhaustive over IntegrationType so a
  newly added category is a compile error instead of a silent generic-icon
  fallback; drive core-block/trigger icons off the category kind
- Factor the shared gate-and-cap rule for the catalog search groups into a
  single cappedCatalog() helper instead of repeating it across five memos
…the palette

Workflows/files/chats/tables/KBs were rendered in full at the empty state, so a
workspace with thousands of them dumped thousands of rows into the DOM. Factor
the catalog cap into a shared filterAndCap() and apply it to all variable-size
groups, bounding palette DOM to MAX_RESULTS_PER_GROUP per group regardless of
workspace size.
…es them

The directory imports resolve to the search-groups and command-items barrels;
BrowseGroup, RecentsGroup, RecentRenderItem, and MemoizedCategoryItem were
added to the source files but not re-exported, breaking the Turbopack build
(tsc resolved them via the source file and missed it).
…cideIcon

The map mixes lucide and emcn icons (Database comes from @/components/emcn/icons,
which is not a LucideIcon). Type it as ComponentType<{ className?: string }> —
the exact type MemoizedCategoryItem's icon prop consumes — which both icon
sources satisfy, keeping the exhaustive IntegrationType key check.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2848a0b. Configure here.

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