Files
obsidian-vault/AI Coding/inbox/20260824-composed-slot-before-dom-seam.md
yuxuanhui dcd6d44960 feat: add Feishu user authorization flow documentation and update frontend guidelines
- Added new documentation for the Feishu user authorization flow, detailing the backend protocol, authorization initiation, callback handling, and token management.
- Updated frontend index to link to the new authorization flow documentation for better accessibility and guidance on UI design consistency.
2026-08-31 09:18:03 +08:00

7.7 KiB
Raw Permalink Blame History

id, title, created, updated, status, scope, category, confidence, last_verified, promotion_target, projects, tags
id title created updated status scope category confidence last_verified promotion_target projects tags
20260824-composed-slot-before-dom-seam 先查宿主组合包 SlotMap,再退回 DOM seam 2026-08-24 2026-08-24 candidate global api-discovery high 2026-08-24 pattern
dsh-lark-cli
host-extension
slotmap
declaration-merging
native-tab
api-discovery
dsh

先查宿主组合包 SlotMap,再退回 DOM seam

Trigger

需要判断一个模块化 Web 宿主的插件能否原生增加页面、Tab、导航项或面板,但当前插件仓库和它直接依赖的 runtime 类型里没有找到对应扩展 API。

Context

调研 DeepSeek Harness(DSH)插件能否增加与 Chat、Trajectory 并列的 Session 顶部 Tab 时,先检索当前插件仓库和直接安装的 @deepseek-ai/dsh-client-runtime,只看到了 root、shell.overlay 以及仓库已有的 DOM Portal 路径,一度推断“没有原生 Tab API”。

继续检查实际 DSH Web Host 组合的 UI packages 后,@deepseek-ai/dsh-client-ui-conversation 的 TypeScript declaration merging 明确补充了 conversation.view:这是 kind: 'list'、scope: 'session' 的视图环,一个注册项就是一个顶部 Tab。第一方 ui-trajectory 插件正是通过同一 slot 注册 Trajectory。

这次有意义的复利点不是记住一个 slot 名,而是:扩展契约可能由“视觉表面的所有者包”声明,不在核心 runtime,也不一定出现在业务插件的直接依赖树中。否定一个扩展能力前,必须检查宿主最终组合和第一方同类贡献者。

Evidence

  • 2026-08-24,本机 dsh --version 返回 0.1.0-rc.7;Web UI packages 为 0.1.0-rc.8。
  • 官方 ui-conversation SlotMap 在 packages/client/ui-conversation/src/client/contract/slots.ts 声明 conversation.view,注释说明它是 “one list entry per view tab”。
  • 官方会话组装在 packages/client/ui-conversation/src/client/apply.ts 遍历 slots.entries('conversation.view'),从 entry 的 id 和 label 生成 Tab;Chat 以 id: 'chat'、order: 0 注册。
  • 第一方 packages/client/ui-trajectory/src/client/index.ts 使用 ctx.slots.inject('conversation.view', ...) 注册 id: 'trajectory'、order: 10 的 Trajectory Tab。
  • 当前 dsh-lark-cli 只注册 sidebar.footer.action 和 shell.overlay,再通过 React Portal 使用 Conversation DOM seam;它证明了另一种布局扩展方式,但不能证明原生 Tab slot 不存在。
  • 本次同时核对了已安装包的 .d.ts、编译后 client.js、官方中文 README、Context7 官方库 /deepseek-ai/deepseek-harness 和 GitHub master 原始源码。
  • 项目内完整调研记录:dsh-lark-cli/.scratch/research/dsh-plugin-custom-tab.md。

Root cause

已验证:DSH 的 SlotMap 使用 TypeScript declaration merging,由各 UI owner package 分散声明。dsh-client-runtime 只声明内建 root slot;conversation.view 由 dsh-client-ui-conversation 声明,因此只查 runtime 会得到不完整的扩展面。

已验证:宿主最终装配包含的包可以多于业务插件的直接依赖。当前项目没有直接安装 dsh-client-ui-conversation,但全局 DSH Web bundle 已组合该包及 ui-trajectory,所以项目局部 node_modules 不是宿主能力全集。

已验证:slot 类型声明只能证明 seat 存在;宿主如何把 seat 投影为 UI,需要继续核对 owner 实现。ui-conversation 的 header/render 代码证明 id、order、label 会形成 Tab,active id 会选择唯一视图。

推断:在其他可组合插件宿主中,同类误判也容易发生:核心 SDK 暴露注册机制,具体贡献点由 feature package 增补;从业务仓库向内搜索会漏掉最终装配才拥有的契约。

Preferred action

  1. 先确定目标 UI 的所有者:Tab 属于 Conversation、Settings、Sidebar 还是根 Layout;不要默认所有扩展点都由 runtime 声明。
  2. 枚举实际宿主装配的 feature/client packages。优先读取 bundle manifest、profile dump、全局安装目录或已发布 package metadata,而不是只看业务插件的直接依赖。
  3. 搜索所有 SlotMap declaration merging、slot declaration 和 slots.register 调用,区分:
    • single:替换整个表面;
    • list:可追加贡献;
    • keyed / chain:按 key 或选择器扩展;
    • slot 的 root / session scope。
  4. 用三类一手证据闭环:
    • owner package 的契约声明;
    • owner 如何把注册项投影为实际 UI;
    • 第一方同类插件的最小注册示例。
  5. 分别核对“当前部署版本”和“上游当前版本”。前者回答现在能否工作,后者回答接口是否仍存在;不要用其中一个替代另一个。
  6. 只有确认没有满足需求的 additive slot 后,才评估 DOM seam、Portal、CSS 重排或宿主 fork。若已有原生 slot,用它承担生命周期、排序、卸载和 active state。
  7. 实现时把 owner package 加入插件的 client inject,并按宿主版本固定 peer/dev dependency;RC API 必须做真实 Web profile smoke test。

Boundaries

  • 该方法适用于由多个包组合、扩展契约分散声明的插件宿主。单体应用或拥有集中式完整 schema 的 SDK 不需要扫描全部 feature packages。
  • 发现内部 slot 不等于它是稳定公共 API。至少需要 package 导出、类型声明、owner 文档或第一方插件用例之一;仅从 minified DOM、私有类名或未导出符号推断时仍按内部实现处理。
  • conversation.view 适合“切换到插件拥有的整页视图”。若需求是文件树、编辑器与官方 Chat 同时可见,原生 Tab 会隐藏非 active view,仍应评估 20260822-extension-slot-portal-three-column-grid。
  • conversation.view、dsh.client.inject 和具体 order 值是 DSH 当前契约,不应推广成其他宿主的通用命名。
  • 当前只完成源码、类型、包版本和上游 master 核验,没有为 dsh-lark-cli 实际注册新 Tab,也没有运行浏览器集成测试。实现层面的兼容性结论仍需 smoke test。

Failed approaches

  • 只检索业务仓库和直接依赖的 runtime:会把“当前项目没有导入 owner package”误判成“宿主没有扩展点”。
  • 从 root 或 conversation.session 是 single slot 推导不能新增 Tab:这混淆了“替换整棵表面”和其内部声明的 additive child slot。
  • 看到当前项目已经使用 DOM Portal,就把它当作该视觉需求的唯一方案:既有实现只能证明一条可行路径,不能穷举宿主扩展面。
  • 只搜 tab、route、router:slot 系统可能把 Tab 表达成抽象的 view contribution,应同时沿 UI owner、slot ledger 和第一方插件反向定位。

Examples

DSH 当前原生 Session Tab 的最小形态:

ctx.slots.inject('conversation.view', () => ctx.slots.register({
  name: 'conversation.view',
  id: 'plugin-view',
  order: -10,
  label: () => 'Plugin View',
}, PluginView))

验证顺序应是:SlotMap declaration → host tab projection → first-party trajectory registration → target version smoke test。

Promotion record

  • Not promoted. 当前已有一次高置信度纠错和一套可复用调查流程;待在另一个分包式宿主扩展调研中独立复用后,再考虑提升为通用 host-extension discovery pattern。