Back to home@HuanLinOTO

dsh-plugin-sidebar-brand-text

替换侧边栏左上角的品牌名与构建徽标文案(WebUI 插件配置页卡片实时配置) | Replace the sidebar's top-left brand name and build-revision badge text via a live settings card in the Plugin Config page.

Stars
0
Language
TypeScript
Created
Aug 20, 2026
Updated
Aug 20, 2026
GitHub repo

Introduction

dsh-plugin-sidebar-brand-text card

dsh-plugin-sidebar-brand-text

替换侧边栏左上角的品牌名、构建徽标文案,以及浏览器标签页标题(document.title)。默认情况下,DSH 侧边栏在左上角显示 DSH Local Build 文字和 7 位 commit hash 徽标(构建期 DSH_CLIENT_COMMIT_HASH),浏览器标签页标题格式为 <会话标题> — DSH Local Build;本插件通过注册 sidebar.brand.name slot 占位者替换侧边栏文案,并订阅 sessions.list 覆写 document.title 让浏览器标签页也使用配置的品牌名,同时在 WebUI 设置 → 插件配置页提供一张可展开的配置卡。

替换前:                              替换后(name="My Build", revision="v1.0"):
┌─────────────────────────┐          ┌─────────────────────────┐
│ 🐟 DSH Local Build ▎141eb6f │       │ 🐟 My Build ▎v1.0         │
└─────────────────────────┘          └─────────────────────────┘
浏览器标签:会话 — DSH Local Build    浏览器标签:会话 — My Build

mark(鱼形 logo)不动——本插件只替换 name 行的文字 + 徽标 + 浏览器标签页标题。若需同时替换 logo,另行安装 @deepseek-ai/dsh-client-ui-brand-official 或其他占用 sidebar.brand.mark 的插件。

工作原理

遵循 plugin-settings-exposure.md 文档的「自托管 HTTP 路由 + settings.plugin.item 卡片」模式(与 ego-browser / dsh-plugin-interpreters 同路):

做法
存储层ctx.settings.register(namespace, Schema, { base }) 注册 sidebar-brand-text namespace(享受 schema 校验、文件持久化)
暴露层ctx.webServer.register({ kind: 'prefix', path: '/sbbt/api' }) 自托管 HTTP 路由,handler 里 in-process 调 settings.update(ns, patch)——绕过 apiproxy 的 WEB_SETTINGS_NAMESPACES allowlist
UI 层Client 注册 settings.plugin.item keyed slot(key: 'sidebar-brand-text')——出现在设置 → 插件配置页,与内置三张卡同列
读写层Client 用 fetch('/sbbt/api/get') / fetch('/sbbt/api/set') 读写,不 fetch 自开路由也不 connection.rpc.call

为什么不用 TypertRemoteService:当前 dsh 快照的 SRC discovery 不 claim 插件拥有的服务端点(ego-browser gateway.js 注释明确记录了此问题)。自托管 HTTP 路由是已验证的合规路径。

实时刷新

BrandText 组件和 BrandTextCard 共享同一个 BrandTextSettingsController(内含 createSnapshotStore)。卡片的 Save 写入后,store 更新,BrandText 通过 useSyncExternalStore 自动重新渲染——无需 DOM 事件、无需 RPC 重取。

浏览器标签页标题(document.title)

DSH 的 document.titlepackages/client/ui-renderer/src/client/DocumentTitle.tsxuseEffect 写入,格式 <会话标题> — <productTitle>,其中 productTitle构建期 process.env.DSH_CLIENT_TITLE 字面量(fallback 'DSH Local Build'),不是 slot、不是运行时 config——ui-brand-official/README.md 明确记载:"The browser title is independent — DSH_CLIENT_TITLE selects title text at build time rather than through a UI slot."

本插件的 titleWriter 订阅同一个 sessions.list feed 和共享的 BrandTextSettingsController store,在 DocumentTitleuseEffect 写完之后用 queueMicrotask 延迟覆写 document.title<会话标题> — <配置的品牌名>,并用 MutationObserver 兜底防止 React 18 concurrent 重渲染覆盖。卸载插件后所有订阅和 observer 被清理,DocumentTitle 的 cleanup 恢复构建期标题。

配置

WebUI 插件配置页(推荐)

安装插件后,在 DSH WebUI 的设置 → 插件配置页里会出现「侧边栏品牌文案」卡片,展开后含两个输入框:

字段说明
品牌名称侧边栏 logo 右侧显示的文案。替换默认的「DSH Local Build」。
版本徽标品牌名右侧的小徽标文案。留空则不显示徽标。

修改后点「保存」即持久化到 $DSH_HOME/settings.yaml,侧边栏实时生效,无需重启。

cordis.patch.yml(first-boot seed)

cordis.patch.yml 里的 config 块是 first-boot seed,仅在 settings.yaml 中无对应值时使用:

- insert:
    - id: sidebar-brand-text
      name: '@huanlin/dsh-plugin-sidebar-brand-text'
      config:
        name: 'DSH Local Build'
        revision: ''

用户在设置界面修改后,值持久化到 settings.yaml,覆盖 seed。

开发

前置:@deepseek-ai/* devDeps 从 npm registry 安装(0.1.0-rc.8),类型完整可独立 typecheck。

pnpm install            # 安装 registry 依赖
pnpm run typecheck      # tsc --noEmit
pnpm test               # vitest:注册形态 + 控制器 + 组件渲染
pnpm run build          # tsdown + tsc → lib/index.js、lib/invariant.js、lib/client.js、lib/types/

预构建 lib/ 策略:lib/ 入库(不在 .gitignore 中),无 prepare 脚本。github: 安装开箱即用。

运行(挂载到 profile)

开发热更新(本地 clone,改源码重建 lib/ 即生效):

dsh plugin --profile web add link:D:\Projects\deepseek-harness\dsh-plugin-sidebar-brand-text

分发安装(二选一):

dsh plugin --profile web add "github:huanlinoto/dsh-plugin-sidebar-brand-text"   # 源码分发
dsh plugin --profile web add "@huanlin/dsh-plugin-sidebar-brand-text"            # npm registry 分发

然后由人类重启 dsh web 进程并硬刷新浏览器(Ctrl+Shift+R)。

检查

  • pnpm run typecheck && pnpm test && pnpm run build 全绿;
  • git -C <dsh checkout> status 干净(零源码 patch);
  • 合规自检(plugin-settings-exposure.md §7):
    • 零源码 patch:未改 apiproxy 的 WEB_SETTINGS_NAMESPACES
    • GW1:服务名 = settings namespace = RPC path 段(sidebar-brand-text
    • GW3:set() 先用 Config schema 校验 patch 再写 settings 服务
    • GW5:多纤维去重——catch "already registered"
    • UI1:用 settings.plugin.item 槽位(不是 settings.section
    • UI2:import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
    • UI4:dsh.client.inject@deepseek-ai/dsh-client-ui-settings-plugins + @deepseek-ai/dsh-client-locale
  • 浏览器验证:
    • 左上角品牌行显示配置的 name 文案;
    • revision 非空时右侧显示徽标,为空时无徽标;
    • 浏览器标签页标题为 <会话标题> — <配置的品牌名>,切换会话实时更新;
    • 无会话时浏览器标签页仅显示配置的品牌名;
    • 卡片修改保存后侧边栏和浏览器标签页实时生效,无需重启;
    • mark(鱼形 logo)保持原样;
    • 卸载插件后恢复 DSH Local Build + commit hash 默认 fallback(侧边栏 + 浏览器标签页)。

边界行为

  • mark slot 不受影响:本插件只注册 sidebar.brand.name,不碰 sidebar.brand.mark
  • 折叠态:侧边栏折叠到 56px 轨道时只显示 mark,不渲染 name slot,因此本插件在折叠态不可见。展开侧边栏后可见配置的文案。但浏览器标签页标题不受折叠态影响,始终生效。
  • revision 为空白字符时仍渲染徽标(非空字符串判定);需要隐藏徽标请设为空字符串。
  • 配置变更实时生效:卡片 Save 后 BrandTextSettingsController.store 更新,BrandText 组件和 titleWriter 通过 useSyncExternalStore / store.subscribe 自动同步——侧边栏文案和浏览器标签页标题同时更新。
  • HTTP 路由不可达时 BrandText 和 titleWriter 回退到默认配置(DSH Local Build),卡片显示「配置通道不可用」提示。
  • titleWriter 与 DSH 内置 DocumentTitle 组件存在竞争:DocumentTitleuseEffect 会在 sessions 变化后覆写 document.title 回构建期值,本插件用 queueMicrotask 延迟到 useEffect 之后执行 + MutationObserver 兜底解决。极少数情况下(React 18 concurrent 模式的批次间隙)可能有 1 帧闪烁。