fix(native): give the "Files" guide row its glyph

The guide renders `entry.icon`, and the `files` kind takeover registered
without one — so after the editor type stopped contributing its own
duplicate row (b56ceec), the single "Files" row was the only entry in the
new-tab list with a blank icon slot. The takeover now carries the editor
descriptor's icon (the takeover IS that descriptor's page).

Extracted `guideIconOf` so both registration sites build the icon field
the same way; the lifecycle test asserts the takeover's guide entry has
an icon.
This commit is contained in:
Menghuan1918
2026-09-09 15:18:04 +08:00
parent b56ceec41d
commit 91de0e2932
2 changed files with 26 additions and 7 deletions
+20 -5
View File
@@ -63,6 +63,17 @@ function titleOf(descriptor: TabDescriptor): string {
return typeof descriptor.title === 'function' ? descriptor.title() : descriptor.title
}
/**
* The guide row's glyph for a descriptor icon (nothing when it has none).
* The native guide renders `entry.icon`, so a takeover registered without one
* is the only row in the list with a blank leading slot.
* @param icon - the descriptor's icon value.
* @returns the guide-entry icon fields, or an empty object.
*/
function guideIconOf(icon: TabDescriptor['icon']): { icon?: (props: { size?: number }) => unknown } {
return typeof icon === 'function' ? { icon: (props: { size?: number }) => icon(props.size ?? 16) } : {}
}
/**
* The chip title of a file address: its own file name. A resource tab is
* identified by its address, so the strip must name the FILE — the
@@ -171,9 +182,7 @@ export function registerNativeSurface(deps: NativeSurfaceDeps): () => void {
order: descriptor.order ?? 100,
title: () => titleOf(descriptor),
description: () => t('nativeGuideDesc'),
...(typeof icon === 'function'
? { icon: (props: { size?: number }) => icon(props.size ?? 16) }
: {}),
...guideIconOf(icon),
}],
}),
})
@@ -189,7 +198,7 @@ export function registerNativeSurface(deps: NativeSurfaceDeps): () => void {
}
/** One `files`-kind takeover: the plugin's explorer under the built-in kind. */
const registerFilesKind = (): (() => void) => {
const registerFilesKind = (editor: TabDescriptor | undefined): (() => void) => {
const id = 'dsh-better-sidebar:files'
const disposeType = tabs.register({
id,
@@ -200,6 +209,10 @@ export function registerNativeSurface(deps: NativeSurfaceDeps): () => void {
order: 10,
title: () => t('files'),
description: () => t('nativeGuideDesc'),
// The takeover IS the editor descriptor's page, so it carries the
// editor's glyph: without it the "Files" row is the only guide
// entry with an empty icon slot.
...guideIconOf(editor?.icon),
}],
})
const slots = registerSlots(id, { ctx, store, service, records, descriptorId: EDITOR_KIND }, {})
@@ -229,7 +242,9 @@ export function registerNativeSurface(deps: NativeSurfaceDeps): () => void {
// editor disabled the plugin has no explorer to put there.
const wantsFiles = service.isTabEnabled(EDITOR_KIND)
const hasFiles = live.has(FILES_KIND)
if (wantsFiles && !hasFiles) live.set(FILES_KIND, { dispose: registerFilesKind() })
if (wantsFiles && !hasFiles) {
live.set(FILES_KIND, { dispose: registerFilesKind(service.getTab(EDITOR_KIND)) })
}
if (!wantsFiles && hasFiles) {
live.get(FILES_KIND)?.dispose()
live.delete(FILES_KIND)
+6 -2
View File
@@ -88,7 +88,7 @@ describe('service routing into the native surface', () => {
service.setSurface(surface)
service.registerTab({ id: 'terminal', title: 'Terminal', component: () => null, createTab: state => ({ tab: { id: `terminal:${state.nextTerminal}`, type: 'terminal', title: 'Terminal', meta: { n: state.nextTerminal } } }) })
service.registerTab({ id: 'git', title: 'Changes', component: () => null })
service.registerTab({ id: 'editor', title: 'Files', component: () => null })
service.registerTab({ id: 'editor', title: 'Files', component: () => null, icon: () => null })
return { surface, calls, service }
}
@@ -163,7 +163,7 @@ describe('registerNativeSurface lifecycle (service-driven registration)', () =>
store.setSession('s1')
const service = createBetterSidebarService(store)
service.registerTab({ id: 'terminal', title: 'Terminal', component: () => null })
service.registerTab({ id: 'editor', title: 'Files', component: () => null })
service.registerTab({ id: 'editor', title: 'Files', component: () => null, icon: () => null })
const records = createNativeTabRecords()
const registered: Array<{ id: string; kind: string; title: (address: string) => string; guide: unknown }> = []
@@ -223,6 +223,10 @@ describe('registerNativeSurface lifecycle (service-driven registration)', () =>
expect(editorType?.guide).toBeUndefined()
const filesType = registered.find(entry => entry.kind === 'files')
expect(filesType?.guide).toBeDefined()
// The takeover carries the editor's glyph, so the "Files" guide row is
// not the only one with a blank icon slot.
const filesGuide = filesType?.guide as Array<{ icon?: unknown }> | undefined
expect(filesGuide?.[0]?.icon).toBeDefined()
dispose()
})