Skip to content

Commit 39a8156

Browse files
JonasBaclaude
andcommitted
ref(cmdk): migrate command palette to JSX collection model (steps 2–5)
Wire CMDKQueryContext and CMDKProvider so async CMDKGroup nodes can read the current search query. Mount CMDKProvider inside CommandPaletteStateProvider in the existing CommandPaletteProvider so the collection store is live for the full app lifetime. Add GlobalCommandPaletteActions component that registers all global actions into the CMDK collection via JSX (CMDKGroup / CMDKAction) rather than the old useCommandPaletteActionsRegister reducer hook. Both systems run in parallel during this transition step. Swap the CommandPalette UI to read from CMDKCollection.useStore() instead of the old useCommandPaletteActions() reducer pipeline. Remove collectResourceActions, asyncQueries, and asyncChildrenMap — async fetching is now handled inside CMDKGroup before nodes appear in the tree. Rewrite scoreTree and flattenActions to work with CollectionTreeNode<CMDKActionData> using direct field access. Replace the linked-list navigation stack (which stored full action objects) with CMDKNavStack which stores only the group key and label. Push action now dispatches {key, label} instead of a full CommandPaletteActionWithKey. Update modal.tsx, stories, analytics hook, and tests to use the new types. Fix pre-existing duplicate Item declaration in commandPalette.tsx that caused a Babel parse error in the test suite. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7b1717b commit 39a8156

File tree

12 files changed

+669
-357
lines changed

12 files changed

+669
-357
lines changed

static/app/components/commandPalette/CMDK_PLAN.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ registration, unregistration, and data freshness.
6262
Async groups (`CMDKGroup` with `resource`) need the current search query to call
6363
`resource(query)`. The query lives in `CommandPaletteStateContext` (`state.query`).
6464

65-
- [ ] Add `CMDKQueryContext = createContext<string>('')` to `ui/cmdk.tsx`
66-
- [ ] Update `CMDKCollection.Provider` to also provide `CMDKQueryContext`:
65+
- [x] Add `CMDKQueryContext = createContext<string>('')` to `ui/cmdk.tsx`
66+
- [x] Update `CMDKCollection.Provider` to also provide `CMDKQueryContext`:
6767
```tsx
6868
function CMDKCollectionProvider({children}) {
6969
const {query} = useCommandPaletteState();
@@ -75,47 +75,47 @@ Async groups (`CMDKGroup` with `resource`) need the current search query to call
7575
}
7676
```
7777
Export this as `CMDKProvider` — callers use this instead of `CMDKCollection.Provider` directly.
78-
- [ ] In `CMDKGroup`, read `const query = useContext(CMDKQueryContext)`
79-
- [ ] When `resource` prop is present, call `useQuery({ ...resource(query), enabled: !!resource })`
78+
- [x] In `CMDKGroup`, read `const query = useContext(CMDKQueryContext)`
79+
- [x] When `resource` prop is present, call `useQuery({ ...resource(query), enabled: !!resource })`
8080
inside `CMDKGroup`
81-
- [ ] Resolve children based on whether `children` is a render prop:
81+
- [x] Resolve children based on whether `children` is a render prop:
8282
```ts
8383
const resolvedChildren =
8484
typeof children === 'function' ? (data ? children(data) : null) : children;
8585
```
86-
- [ ] Wrap resolved children in `<CMDKCollection.GroupContext.Provider value={key}>` as before
86+
- [x] Wrap resolved children in `<CMDKCollection.GroupContext.Provider value={key}>` as before
8787

8888
### Step 3 — Wire CMDKProvider into the provider tree
8989

9090
`CMDKProvider` (from Step 2) must sit inside `CommandPaletteStateProvider` because it
9191
reads from `useCommandPaletteState()`.
9292

93-
- [ ] Find where `CommandPaletteProvider` and `CommandPaletteStateProvider` are mounted —
93+
- [x] Find where `CommandPaletteProvider` and `CommandPaletteStateProvider` are mounted —
9494
search for `CommandPaletteProvider` in the codebase to locate the mount point
95-
- [ ] Place `<CMDKProvider>` as a child of `CommandPaletteStateProvider`, wrapping
95+
- [x] Place `<CMDKProvider>` as a child of `CommandPaletteStateProvider`, wrapping
9696
whatever subtree currently lives inside it
97-
- [ ] Verify no runtime errors — the collection store is live but empty
97+
- [x] Verify no runtime errors — the collection store is live but empty
9898

9999
### Step 4 — Convert global actions to a JSX component
100100

101101
`useGlobalCommandPaletteActions` calls `useCommandPaletteActionsRegister([...actions])`
102102
with a large static action tree. Replace it with a component that renders the equivalent
103103
JSX tree. The old hook stays alive during this step so both systems run in parallel.
104104

105-
- [ ] Create `GlobalCommandPaletteActions` component (can live in `useGlobalCommandPaletteActions.tsx`
105+
- [x] Create `GlobalCommandPaletteActions` component (can live in `useGlobalCommandPaletteActions.tsx`
106106
or a new file `globalActions.tsx`)
107-
- [ ] Translate each section — read `useGlobalCommandPaletteActions.tsx` carefully before translating:
108-
- [ ] **Navigation** — one `<CMDKGroup display={{label: t('Go to...')}}>` containing a
107+
- [x] Translate each section — read `useGlobalCommandPaletteActions.tsx` carefully before translating:
108+
- [x] **Navigation** — one `<CMDKGroup display={{label: t('Go to...')}}>` containing a
109109
`<CMDKAction>` per destination (Issues, Explore, Dashboards, Insights, Settings)
110-
- [ ] **Create** — one `<CMDKGroup>` with `<CMDKAction onAction={...}>` for Dashboard,
110+
- [x] **Create** — one `<CMDKGroup>` with `<CMDKAction onAction={...}>` for Dashboard,
111111
Alert, Project, Invite Members
112-
- [ ] **DSN Lookup**`<CMDKGroup resource={dsnQueryFn}>` with render-prop children:
112+
- [x] **DSN Lookup**`<CMDKGroup resource={dsnQueryFn}>` with render-prop children:
113113
`{data => data.map(item => <CMDKAction key={...} display={item.display} to={item.to} />)}`
114-
- [ ] **Help** — static `<CMDKAction>` nodes for Docs/Discord/GitHub/Changelog plus a
114+
- [x] **Help** — static `<CMDKAction>` nodes for Docs/Discord/GitHub/Changelog plus a
115115
`<CMDKGroup resource={helpSearchQueryFn}>` with render-prop children for search results
116-
- [ ] **Interface**`<CMDKAction onAction={...}>` for navigation toggle and theme switching
117-
- [ ] Mount `<GlobalCommandPaletteActions />` inside `<CMDKProvider>` in the provider tree
118-
- [ ] Verify `CMDKCollection.useStore().tree()` returns the expected structure by adding a
116+
- [x] **Interface**`<CMDKAction onAction={...}>` for navigation toggle and theme switching
117+
- [x] Mount `<GlobalCommandPaletteActions />` inside `<CMDKProvider>` in the provider tree
118+
- [x] Verify `CMDKCollection.useStore().tree()` returns the expected structure by adding a
119119
temporary log or test — do not remove old system yet
120120

121121
### Step 5 — Update the palette UI to read from the collection store

static/app/components/commandPalette/__stories__/components.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import {useCallback} from 'react';
33
import {addSuccessMessage} from 'sentry/actionCreators/indicator';
44
import {CommandPaletteProvider} from 'sentry/components/commandPalette/context';
55
import {useCommandPaletteActionsRegister} from 'sentry/components/commandPalette/context';
6-
import type {
7-
CommandPaletteAction,
8-
CommandPaletteActionWithKey,
9-
} from 'sentry/components/commandPalette/types';
6+
import type {CommandPaletteAction} from 'sentry/components/commandPalette/types';
7+
import type {CMDKActionData} from 'sentry/components/commandPalette/ui/cmdk';
8+
import type {CollectionTreeNode} from 'sentry/components/commandPalette/ui/collection';
109
import {CommandPalette} from 'sentry/components/commandPalette/ui/commandPalette';
1110
import {normalizeUrl} from 'sentry/utils/url/normalizeUrl';
1211
import {useNavigate} from 'sentry/utils/useNavigate';
@@ -20,13 +19,11 @@ export function CommandPaletteDemo() {
2019
const navigate = useNavigate();
2120

2221
const handleAction = useCallback(
23-
(action: CommandPaletteActionWithKey) => {
22+
(action: CollectionTreeNode<CMDKActionData>) => {
2423
if ('to' in action) {
25-
navigate(normalizeUrl(action.to));
24+
navigate(normalizeUrl(String(action.to)));
2625
} else if ('onAction' in action) {
2726
action.onAction();
28-
} else {
29-
// @TODO: implement async actions
3027
}
3128
},
3229
[navigate]

static/app/components/commandPalette/context.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {uuid4} from '@sentry/core';
1111
import {slugify} from 'sentry/utils/slugify';
1212
import {unreachable} from 'sentry/utils/unreachable';
1313

14+
import {CMDKProvider} from './ui/cmdk';
1415
import {CommandPaletteStateProvider} from './ui/commandPaletteStateContext';
1516
import type {CommandPaletteAction, CommandPaletteActionWithKey} from './types';
1617

@@ -151,7 +152,9 @@ export function CommandPaletteProvider({children}: CommandPaletteProviderProps)
151152
return (
152153
<CommandPaletteRegistrationContext.Provider value={registerContext}>
153154
<CommandPaletteActionsContext.Provider value={actions}>
154-
<CommandPaletteStateProvider>{children}</CommandPaletteStateProvider>
155+
<CommandPaletteStateProvider>
156+
<CMDKProvider>{children}</CMDKProvider>
157+
</CommandPaletteStateProvider>
155158
</CommandPaletteActionsContext.Provider>
156159
</CommandPaletteRegistrationContext.Provider>
157160
);

0 commit comments

Comments
 (0)