Skip to content

Commit 8ded031

Browse files
committed
fix(hub-ui): keep grouped actions out of dock memory
1 parent 0abf43f commit 8ded031

4 files changed

Lines changed: 50 additions & 9 deletions

File tree

packages/hub-ui/src/client/state/context.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,31 @@ describe('createDocksContext', () => {
233233
expect(context.docks.selected?.id).toBe('nuxt:modules')
234234
})
235235

236+
it('does not remember a grouped action as the group\'s last-opened member', async () => {
237+
expect.assertions(1)
238+
239+
const { rpc, sharedStates, trust } = createStubRpc()
240+
const session = ref<DockSessionStorage>({
241+
open: false,
242+
selectedDockId: null,
243+
selectedDockRoute: null,
244+
groupLastChildIds: {},
245+
})
246+
const context = await createDocksContext('embedded', rpc, undefined, session)
247+
248+
trust()
249+
sharedStates.get('devframe:docks')!.push([
250+
{ id: 'tools', type: 'group', title: 'Tools', icon: 'ph:wrench-duotone' },
251+
{ id: 'tools:run', type: 'action', action: { importFrom: '/action.js' }, title: 'Run', icon: 'ph:play-duotone', groupId: 'tools' },
252+
] satisfies DevframeDockEntry[])
253+
sharedStates.get('devframe:dock-renderers')!.push({})
254+
await flushRestore()
255+
256+
await context.docks.switchEntry('tools:run')
257+
258+
expect(session.value.groupLastChildIds).toEqual({})
259+
})
260+
236261
it('keeps a dock closed when the user closes it before initialization finishes', async () => {
237262
expect.assertions(2)
238263

packages/hub-ui/src/client/state/context.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,14 @@ export async function createDocksContext(
243243
await executeSetupScript(entry, scriptContext)
244244
}
245245

246-
// Remember this selection for later redirects: a member tab (carries its
247-
// anchor's `frameId`) as the frame's live tab, a grouped member as its
248-
// group's last-opened child. Only iframes own an address-bar route, so clear
249-
// a stale route for anything else. Guarded: a store predating these fields
250-
// has no map yet.
246+
// Remember selection redirects: a member tab as its frame's live tab, and a
247+
// grouped non-action member as its group's last-opened child. One-shot actions
248+
// leave the preferred panel unchanged. Only iframes own an address-bar route,
249+
// so clear a stale route for anything else.
251250
const rememberEntrySelection = (entry: DevframeDockEntry) => {
252251
if (entry.type === 'iframe' && entry.frameId && !entry.subTabs)
253252
frameNavCurrentMember.set(entry.frameId, entry.id)
254-
if (entry.groupId)
253+
if (entry.type !== 'action' && entry.groupId)
255254
(sessionStore.value.groupLastChildIds ??= {})[entry.groupId] = entry.id
256255
if (entry.type !== 'iframe')
257256
sessionStore.value.selectedDockRoute = null

packages/hub-ui/src/client/state/dock-settings.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ function group(id: string, extra: Partial<DevframeViewGroup> = {}): DevframeDock
1111
return { id, type: 'group', title: id.toUpperCase(), icon: 'ph:folder-duotone', ...extra } as DevframeDockEntry
1212
}
1313

14+
function action(id: string, extra: Partial<DevframeDockEntry> = {}): DevframeDockEntry {
15+
return { id, type: 'action', action: { importFrom: '/action.js' }, title: id.toUpperCase(), icon: 'ph:play-duotone', ...extra } as DevframeDockEntry
16+
}
17+
1418
function ids(groups: DevframeDockEntriesGrouped): string[] {
1519
return groups.flatMap(([, items]) => items.map(item => item.id))
1620
}
@@ -129,10 +133,21 @@ describe('resolveGroupPreferredChild', () => {
129133
expect(resolveGroupPreferredChild([g, defaultMember, gated], g, 'g:gated', whenContext)).toBe(defaultMember)
130134
})
131135

136+
it('falls back to defaultChildId when the remembered member is an action', () => {
137+
const rememberedAction = action('g:run', { groupId: 'g' })
138+
expect(resolveGroupPreferredChild([...entries, rememberedAction], g, 'g:run')).toBe(defaultMember)
139+
})
140+
132141
it('resolves nothing for a popover-only group without memory', () => {
133142
const bare = group('bare') as DevframeViewGroup
134143
expect(resolveGroupPreferredChild([bare, iframe('bare:x', { groupId: 'bare' })], bare, undefined)).toBeUndefined()
135144
})
145+
146+
it('ignores a remembered action for a popover-only group', () => {
147+
const bare = group('bare') as DevframeViewGroup
148+
const rememberedAction = action('bare:run', { groupId: 'bare' })
149+
expect(resolveGroupPreferredChild([bare, rememberedAction], bare, 'bare:run')).toBeUndefined()
150+
})
136151
})
137152

138153
describe('resolveRecentDockEntry', () => {

packages/hub-ui/src/client/state/dock-settings.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,10 @@ export function resolveGroupDefaultChild(
222222
/**
223223
* Resolve the member a group activation opens, layering the per-tab "last
224224
* opened member" memory (`DockSessionStorage.groupLastChildIds`) over the
225-
* author's `defaultChildId`. The remembered member wins while it still
225+
* author's `defaultChildId`. A remembered non-action member wins while it still
226226
* resolves (it exists in the group and its `when` clause holds), so reopening
227-
* a group lands back on the member the developer last used; otherwise the
227+
* a group lands back on the panel the developer last used. One-shot actions are
228+
* skipped, including values persisted by an older client. Otherwise the
228229
* `defaultChildId` target is tried under the same rules (both via
229230
* {@link resolveGroupDefaultChild}, so the render-only `visibility` clause is
230231
* ignored for either candidate). Returns `undefined` when neither resolves:
@@ -237,7 +238,8 @@ export function resolveGroupPreferredChild(
237238
lastChildId: string | undefined,
238239
whenContext?: WhenContext,
239240
): DevframeDockEntry | undefined {
240-
return resolveGroupDefaultChild(entries, group.id, lastChildId, whenContext)
241+
const remembered = resolveGroupDefaultChild(entries, group.id, lastChildId, whenContext)
242+
return (remembered?.type === 'action' ? undefined : remembered)
241243
?? resolveGroupDefaultChild(entries, group.id, group.defaultChildId, whenContext)
242244
}
243245

0 commit comments

Comments
 (0)