You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adding a channel-scoped feature to the desktop client today means editing a spread of centrally-owned files in lockstep: the ChannelScreen dispatch if/else, the two near-identical *ChannelContent tab shells, the settings section wiring (a union type + a values array + a render switch with a never exhaustiveness gate + a nav-groups map), the scattered binding parsers plus the glyph mapper, and a hand-rolled read/write hook per feature. A correct new feature must touch all of them consistently — the client-side equivalent of the relay's per-kind "fork tax" (companion proposal: #3167).
This isn't hypothetical. #3275 ("host MCP Apps as channel tabs") adds a genuinely new tab type today by extending those shared shells directly. Sequence/board/docs-style surfaces do the same. Every such addition edits the same central files and grows the switch/if-else chains.
Proposed solution
A ChannelFeaturePlugin registry for the desktop client — modeled on the existing shared/features/ flag manifest ("typed definition list + resolver hook + gate"), but for channel features. A plugin declares, in one object: how it classifies a channel (parseBinding off the kind-39000 about), and the surfaces it contributes — tabs, settingsPanel, sidebar group/create-actions, headerAction, and glyph.
One generic ChannelFeatureShell renders a plugin's tabs (lazy) — collapsing the two hand-rolled *ChannelContent shells into one. The ChannelScreen dispatch if/else becomes a classify + shell render.
The settings section list becomes a single descriptor array (render per entry), retiring the parallel union / values-array / switch / nav-map.
Sidebar grouping, create-actions, and glyphs are driven by the plugins.
A shared useReplaceableEvent / usePublishReplaceable hook pair removes the duplicated "fetch → subscribe → newest-wins" read logic and the "sign → publish" write logic that each feature currently re-implements.
Built-in surfaces (chat/forum, the settings sections) register through the same path — dogfooded, so it's a behavior-preserving refactor, not a new privileged plugin lane. Because every icon/affordance (tab, glyph, sidebar group, create button) is a declared field, a feature like #3275's MCP-App tabs plugs in without editing any shared file.
Alternatives considered
Keep hand-wiring each feature into the shared shells (status quo) — the fork-tax remains, the central switch/if-else chains keep growing, and feat(desktop): host MCP Apps as channel tabs #3275-style additions keep editing central files.
A heavier dynamic plugin/loader system — unnecessary. This is compile-time registration, exactly like the existing shared/features/ manifest; no runtime plugin machinery.
Additional context
We have a complete working implementation, verified behavior-preserving against the existing desktop Playwright specs (channel tabs, sidebar hierarchy, settings), with downstream-specific features split into an opt-in pack that a vanilla build omits. It's the client-side companion to the relay-side registry proposed in #3167.
It also directly complements #3275 (host MCP Apps as channel tabs): a channel-feature registry would give those App tabs — and Sequence/board/docs — a first-class extension point rather than each tab type extending the shared shells. Happy to align on the shape (registry surface, how MCP-App tabs would register) before writing the PR, and to split it into review-sized commits.
Searched open issues and PRs for duplicates (channel feature registry / tab registry / feature plugin): none found.
Motivation
Adding a channel-scoped feature to the desktop client today means editing a spread of centrally-owned files in lockstep: the
ChannelScreendispatch if/else, the two near-identical*ChannelContenttab shells, the settings section wiring (a union type + a values array + a renderswitchwith aneverexhaustiveness gate + a nav-groups map), the scattered binding parsers plus the glyph mapper, and a hand-rolled read/write hook per feature. A correct new feature must touch all of them consistently — the client-side equivalent of the relay's per-kind "fork tax" (companion proposal: #3167).This isn't hypothetical. #3275 ("host MCP Apps as channel tabs") adds a genuinely new tab type today by extending those shared shells directly. Sequence/board/docs-style surfaces do the same. Every such addition edits the same central files and grows the
switch/if-else chains.Proposed solution
A
ChannelFeaturePluginregistry for the desktop client — modeled on the existingshared/features/flag manifest ("typed definition list + resolver hook + gate"), but for channel features. A plugin declares, in one object: how it classifies a channel (parseBindingoff the kind-39000about), and the surfaces it contributes —tabs,settingsPanel,sidebargroup/create-actions,headerAction, andglyph.ChannelFeatureShellrenders a plugin'stabs(lazy) — collapsing the two hand-rolled*ChannelContentshells into one. TheChannelScreendispatch if/else becomes a classify + shell render.renderper entry), retiring the parallel union / values-array /switch/ nav-map.useReplaceableEvent/usePublishReplaceablehook pair removes the duplicated "fetch → subscribe → newest-wins" read logic and the "sign → publish" write logic that each feature currently re-implements.Built-in surfaces (chat/forum, the settings sections) register through the same path — dogfooded, so it's a behavior-preserving refactor, not a new privileged plugin lane. Because every icon/affordance (tab, glyph, sidebar group, create button) is a declared field, a feature like #3275's MCP-App tabs plugs in without editing any shared file.
Alternatives considered
switch/if-else chains keep growing, and feat(desktop): host MCP Apps as channel tabs #3275-style additions keep editing central files.shared/features/manifest; no runtime plugin machinery.Additional context
We have a complete working implementation, verified behavior-preserving against the existing desktop Playwright specs (channel tabs, sidebar hierarchy, settings), with downstream-specific features split into an opt-in pack that a vanilla build omits. It's the client-side companion to the relay-side registry proposed in #3167.
It also directly complements #3275 (host MCP Apps as channel tabs): a channel-feature registry would give those App tabs — and Sequence/board/docs — a first-class extension point rather than each tab type extending the shared shells. Happy to align on the shape (registry surface, how MCP-App tabs would register) before writing the PR, and to split it into review-sized commits.
Searched open issues and PRs for duplicates (channel feature registry / tab registry / feature plugin): none found.