Skip to content

Commit 03c59e5

Browse files
authored
feat(hub): add builtinDocks option to gate built-in dock entries (#71)
1 parent 1fa119c commit 03c59e5

9 files changed

Lines changed: 131 additions & 13 deletions

File tree

packages/hub/src/node/__tests__/context.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ describe('createHubContext shared state', () => {
3030
'~settings',
3131
])
3232
})
33+
34+
it('suppresses built-ins gated off via builtinDocks', async () => {
35+
const context = await createHubContext({
36+
cwd: process.cwd(),
37+
mode: 'build',
38+
host: createHost(),
39+
builtinDocks: { terminals: false, messages: false },
40+
})
41+
42+
const docks = await context.rpc.sharedState.get<DevframeDockEntry[]>('devframe:docks')
43+
expect(docks.value().map(dock => dock.id)).toEqual(['~settings'])
44+
})
3345
})
3446

3547
describe('startHttpAndWs remote endpoint metadata', () => {

packages/hub/src/node/__tests__/host-docks.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ function createContext(): DevframeHubContext {
1616
resolveOrigin: () => 'http://localhost:5173',
1717
getStorageDir: () => storageDir,
1818
},
19+
// Minimal stubs so the built-in `~terminals`/`~messages` getters
20+
// (`when`/`badge`) can be evaluated without a full context.
21+
terminals: { sessions: new Map() },
22+
messages: { entries: new Map() },
1923
} as unknown as DevframeHubContext
2024
}
2125

@@ -189,3 +193,48 @@ describe('devframeDockHost grouping', () => {
189193
})).toThrow('Dock with id "ghost" is not registered and cannot be updated')
190194
})
191195
})
196+
197+
describe('devframeDockHost built-in gating', () => {
198+
it('includes all three built-ins by default', () => {
199+
const host = new DevframeDocksHost(createContext())
200+
const ids = host.values().map(entry => entry.id)
201+
expect(ids).toEqual(['~terminals', '~messages', '~settings'])
202+
})
203+
204+
it('treats an empty builtinDocks map as all-enabled', () => {
205+
const host = new DevframeDocksHost(createContext(), {})
206+
const ids = host.values().map(entry => entry.id)
207+
expect(ids).toEqual(['~terminals', '~messages', '~settings'])
208+
})
209+
210+
it('omits the built-ins gated with `false`, keeping the rest', () => {
211+
const host = new DevframeDocksHost(createContext(), { terminals: false, messages: false })
212+
const ids = host.values().map(entry => entry.id)
213+
expect(ids).toEqual(['~settings'])
214+
})
215+
216+
it('keeps an explicitly-enabled built-in and drops an omitted-as-false sibling', () => {
217+
const host = new DevframeDocksHost(createContext(), { terminals: true, settings: false })
218+
const ids = host.values().map(entry => entry.id)
219+
expect(ids).toEqual(['~terminals', '~messages'])
220+
})
221+
222+
it('keeps user views ahead of gated built-ins', () => {
223+
const host = new DevframeDocksHost(createContext(), { messages: false, settings: false })
224+
host.register({
225+
type: 'iframe',
226+
id: 'app:overview',
227+
title: 'Overview',
228+
icon: 'ph:gauge-duotone',
229+
url: '/__app/',
230+
})
231+
232+
const ids = host.values().map(entry => entry.id)
233+
expect(ids).toEqual(['app:overview', '~terminals'])
234+
})
235+
236+
it('drops every built-in when includeBuiltin is false, regardless of gating', () => {
237+
const host = new DevframeDocksHost(createContext(), { terminals: true, messages: true, settings: true })
238+
expect(host.values({ includeBuiltin: false })).toEqual([])
239+
})
240+
})

packages/hub/src/node/context.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { CreateHostContextOptions } from 'devframe/node'
22
import type { DevframeHost, DevframeNodeContext } from 'devframe/types'
33
import type { DevframeCommandsHost } from '../types/commands'
4-
import type { DevframeDocksHost } from '../types/docks'
4+
import type { BuiltinDocksOptions, DevframeDocksHost } from '../types/docks'
55
import type { JsonRenderer, JsonRenderSpec } from '../types/json-render'
66
import type { DevframeMessagesHost } from '../types/messages'
77
import type { DevframeTerminalsHost } from '../types/terminals'
@@ -57,7 +57,19 @@ export interface DevframeHubContext extends DevframeNodeContext {
5757
createJsonRenderer: (spec: JsonRenderSpec) => JsonRenderer
5858
}
5959

60-
export interface CreateHubContextOptions extends CreateHostContextOptions {}
60+
export interface CreateHubContextOptions extends CreateHostContextOptions {
61+
/**
62+
* Gate the hub's synthesized built-in dock entries (`~terminals`,
63+
* `~messages`, `~settings`). Each entry defaults to present; set one to
64+
* `false` to suppress it — e.g. when mounting `@devframes/plugin-terminals`
65+
* or `@devframes/plugin-messages`, which replace the built-in tabs.
66+
*
67+
* Omitting this option keeps all three built-ins.
68+
*
69+
* @default { terminals: true, messages: true, settings: true }
70+
*/
71+
builtinDocks?: BuiltinDocksOptions
72+
}
6173

6274
/**
6375
* Create a hub-level node context: wraps devframe's `createHostContext`,
@@ -75,7 +87,7 @@ export async function createHubContext(options: CreateHubContextOptions): Promis
7587
})
7688
const context = baseContext as DevframeHubContext
7789

78-
const docks = new DocksHostImpl(context)
90+
const docks = new DocksHostImpl(context, options.builtinDocks)
7991
const terminals = new TerminalsHostImpl(context)
8092
const messages = new MessagesHostImpl(context)
8193
const commands = new CommandsHostImpl(context)

packages/hub/src/node/host-docks.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { DevframeNodeContext } from 'devframe/types'
22
import type { SharedState } from 'devframe/utils/shared-state'
33
import type {
4+
BuiltinDocksOptions,
45
DevframeDockEntry,
56
DevframeDocksHost as DevframeDocksHostType,
67
DevframeDockUserEntry,
@@ -92,6 +93,12 @@ export class DevframeDocksHost implements DevframeDocksHostType {
9293

9394
constructor(
9495
public readonly context: DevframeHubContext,
96+
/**
97+
* Per-entry toggles for the synthesized built-in dock entries. An omitted
98+
* (or `undefined`) flag keeps that built-in; only an explicit `false`
99+
* suppresses it.
100+
*/
101+
private readonly builtinDocks: BuiltinDocksOptions = {},
95102
) {
96103

97104
}
@@ -111,8 +118,11 @@ export class DevframeDocksHost implements DevframeDocksHostType {
111118
includeBuiltin?: boolean
112119
} = {}): DevframeDockEntry[] {
113120
const context = this.context
114-
const builtinDocksEntries: DevframeViewBuiltin[] = [
115-
{
121+
// An omitted flag keeps the built-in; only an explicit `false` drops it.
122+
const { terminals = true, messages = true, settings = true } = this.builtinDocks
123+
const builtinDocksEntries: DevframeViewBuiltin[] = []
124+
if (terminals) {
125+
builtinDocksEntries.push({
116126
type: '~builtin',
117127
id: '~terminals',
118128
title: 'Terminals',
@@ -121,8 +131,10 @@ export class DevframeDocksHost implements DevframeDocksHostType {
121131
get when() {
122132
return context.terminals.sessions.size === 0 ? 'false' : undefined
123133
},
124-
},
125-
{
134+
})
135+
}
136+
if (messages) {
137+
builtinDocksEntries.push({
126138
type: '~builtin',
127139
id: '~messages',
128140
title: 'Messages & Notifications',
@@ -132,15 +144,17 @@ export class DevframeDocksHost implements DevframeDocksHostType {
132144
const size = context.messages.entries.size
133145
return size > 0 ? String(size) : undefined
134146
},
135-
},
136-
{
147+
})
148+
}
149+
if (settings) {
150+
builtinDocksEntries.push({
137151
type: '~builtin',
138152
id: '~settings',
139153
title: 'Settings',
140154
category: '~builtin',
141155
icon: 'ph:gear-duotone',
142-
},
143-
]
156+
})
157+
}
144158

145159
return [
146160
...Array.from(this.views.values(), view => this.projectView(view)),

packages/hub/src/types/docks.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,32 @@ export interface DevframeDocksHost {
1414
values: (options?: { includeBuiltin?: boolean }) => DevframeDockEntry[]
1515
}
1616

17+
/**
18+
* Per-entry toggles for the hub's synthesized built-in dock entries.
19+
*
20+
* Each flag defaults to `true` (the entry is present). Set one to `false` to
21+
* suppress that built-in everywhere it would otherwise appear — useful when a
22+
* host mounts a plugin that supersedes the built-in tab (e.g.
23+
* `@devframes/plugin-terminals` replacing the `~terminals` entry).
24+
*/
25+
export interface BuiltinDocksOptions {
26+
/**
27+
* Include the built-in `~terminals` dock entry.
28+
* @default true
29+
*/
30+
terminals?: boolean
31+
/**
32+
* Include the built-in `~messages` dock entry.
33+
* @default true
34+
*/
35+
messages?: boolean
36+
/**
37+
* Include the built-in `~settings` dock entry.
38+
* @default true
39+
*/
40+
settings?: boolean
41+
}
42+
1743
// Known categories the hub orders by default. Kits may pass their own
1844
// category ids; `(string & {})` keeps autocomplete on the known set while
1945
// allowing arbitrary string values.

tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export declare const defineHubRpcFunction: <NAME extends string, TYPE extends _$
1616
// #endregion
1717

1818
// #region Other
19+
export { BuiltinDocksOptions }
1920
export { ClientScriptEntry }
2021
export { ConnectionMeta }
2122
export { CreateHubContextOptions }

tests/__snapshots__/tsnapi/@devframes/hub/node.snapshot.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ export declare class DevframeCommandsHost implements DevframeCommandsHost$1 {
2323
}
2424
export declare class DevframeDocksHost implements DevframeDocksHost$1 {
2525
readonly context: DevframeHubContext;
26+
private readonly builtinDocks;
2627
readonly views: DevframeDocksHost$1['views'];
2728
readonly events: DevframeDocksHost$1['events'];
2829
userSettings: SharedState<DevframeDocksUserSettings>;
2930
private readonly remoteDocks;
30-
constructor(_: DevframeHubContext);
31+
constructor(_: DevframeHubContext,
32+
_?: BuiltinDocksOptions);
3133
init(): Promise<void>;
3234
values({
3335
includeBuiltin

tests/__snapshots__/tsnapi/@devframes/hub/node.snapshot.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ export class DevframeCommandsHost {
1616
}
1717
export class DevframeDocksHost {
1818
context
19+
builtinDocks
1920
views
2021
events
2122
userSettings
2223
remoteDocks
23-
constructor(_) {}
24+
constructor(_, _) {}
2425
async init() {}
2526
values(_) {}
2627
projectView(_) {}

tests/__snapshots__/tsnapi/@devframes/hub/types.snapshot.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Generated by tsnapi — public API snapshot of `@devframes/hub/types`
33
*/
44
// #region Other
5+
export { BuiltinDocksOptions }
56
export { ClientScriptEntry }
67
export { ConnectionMeta }
78
export { CreateHubContextOptions }

0 commit comments

Comments
 (0)