Files
notebook-ai-extension/docs/superpowers/specs/2026-07-23-cell-toolbar-actions-design.md

4.9 KiB

AI 操作按钮迁入原生 Cell Toolbar — 设计

  • 日期: 2026-07-23
  • 状态: 已批准(用户于 2026-07-23 确认)

背景与问题

当前 ✨ 优化 / 🐛 排错 / 🪄 编辑 三个按钮渲染在每个 cell 底部ICellFooter 里,链路为:installOpenCodeEverywhere 替换每个 notebook 的 contentFactoryOpenCodeCellContentFactory.createCellFooter()OpenCodeCellFooter widget。

问题:按钮位置不符合预期 —— 应出现在每个 cell 右上角、JupyterLab 原生 cell toolbar 的上移/下移按钮(move-cell-up / move-cell-down)旁边。

已确认的决策

问题 决策
可见性语义 跟随原生 toolbar:只在 active cell 右上角显示(与上移/下移按钮行为严格一致)
底部 footer 删除,三个按钮全部上移
点击交互 本次不变:🪄 编辑 仍用 window.prompt;结果仍走 Notification;inline prompt box / diff 面板留待后续
作用范围 CodeCell;Markdown 等 cell 不显示 AI 按钮(现状 footer 挂在所有 cell 上,属顺带修正)

机制(JupyterLab 4.6 真实扩展点,已核实源码)

  • 原生 cell toolbar 由内置 @jupyterlab/cell-toolbar-extension 提供,toolbar factory 名为 'Cell',settings 驱动;默认按钮(含 move-cell-up / move-cell-down,rank 均为默认 50)由其 schema 的 jupyter.lab.toolbars 贡献。
  • 第三方扩展通过 IToolbarWidgetRegistry.addFactory<Cell>('Cell', '<itemName>', factory) 注册 widget factory;factory 收到具体的 Cell 实例,toolbar 由 tracker 按 cell model 缓存。
  • 自己的 schema/plugin.json 顶层加 "jupyter.lab.toolbars": { "Cell": [...] } 即可把该项贡献进原生 toolbar,无需 DOM 注入或 CSS 定位。
  • 原生 toolbar 只渲染在 active cell 内(activeCellChanged 时移动),空间不足时自动隐藏 —— 我们的按钮随它一起获得这些行为。

设计

组件改造

  • src/components/opencode_cell_footer.ts → 改造为 src/components/opencode_cell_actions.ts:
    • OpenCodeCellActions extends Widget,构造函数直接接收 CodeCell(factory 传入),不再在 onAfterAttach 里靠 this.parent instanceof CodeCell 解析宿主。
    • model.contentChanged / stateChanged 订阅保持,onBeforeDetach 摘除。
    • 渲染逻辑不变:✨ 优化 / 🐛 排错(无 error output 时禁用) / 🪄 编辑;loading 时全部禁用。
    • 错误提示统一走 Notification,widget 内不再渲染 error span(toolbar 空间小)。
    • 模块级 setOpenCodeRuntime() 运行时注入机制不变。
  • 删除 src/components/opencode_cell_factory.tssrc/components/opencode_installer.ts

注册与配置

  • src/index.ts:
    • optional 增加 IToolbarWidgetRegistry(来自 @jupyterlab/apputils,已是依赖)。
    • activate 中:toolbarRegistry.addFactory('Cell', 'opencode-cell-actions', (cell) => cell instanceof CodeCell ? new OpenCodeCellActions(cell) : new Widget());非 CodeCell 返回空 widget。
    • 移除 installOpenCodeEverywhere 调用及 import。
  • schema/plugin.json 顶层增加:
    "jupyter.lab.toolbars": {
      "Cell": [{ "name": "opencode-cell-actions", "rank": 100 }]
    }
    
    默认按钮 rank 为 50,我们排 100 → 出现在 delete-cell 右侧,紧邻上移/下移按钮组。
  • style/base.css:新增 .opencode-cell-actions 紧凑按钮样式(适配原生 toolbar 高度)。

上下文提取(不变)

widget attach 后仍用现有 extractCellContextFromCell(沿 cell.parent 链 duck-typing 找 NotebookPanel)。cell 实例改由构造参数传入,比现在更可靠。

测试与文档

  • src/__tests__/opencode_cell_footer.spec.tsopencode_cell_actions.spec.ts:构造时直接传 cell,删除 Object.defineProperty(footer, 'parent', ...) 相关 hack,断言不变(三按钮渲染、禁用态)。
  • design.md 第 2 节交互描述更新:悬浮 toolbar → 原生 cell toolbar(仅 active cell 显示)。

兼容性

  • 纯前端 UI 搬迁;REST 契约、Python server 端零改动。
  • cell-toolbar-extension 被禁用时,我们的按钮与原生按钮一起消失(行为一致,可接受)。
  • 移除 contentFactory 覆写后,不再受"已打开的 cell 需 reload 才生效"的限制。
  • IToolbarWidgetRegistry 声明为 optional,registry 缺失时插件仍可激活(按钮不显示)。

验证

  • jlpm testjlpm lint:checkjlpm build 全部通过。
  • 手动:jupyter lab 打开 notebook,active cell 右上角出现三个按钮,位于 delete 按钮右侧;无错误的 cell 上 🐛 排错 禁用;切换 active cell 按钮随之移动。