diff --git a/docs/superpowers/plans/2026-07-23-cell-toolbar-actions.md b/docs/superpowers/plans/2026-07-23-cell-toolbar-actions.md index 482adfd..fb35a6b 100644 --- a/docs/superpowers/plans/2026-07-23-cell-toolbar-actions.md +++ b/docs/superpowers/plans/2026-07-23-cell-toolbar-actions.md @@ -24,10 +24,12 @@ ### Task 1: OpenCodeCellActions 组件(TDD) **Files:** + - Test: `src/__tests__/opencode_cell_actions.spec.ts`(新建) - Create: `src/components/opencode_cell_actions.ts`(新建) **Interfaces:** + - Consumes: `callOpenCodeEdit(request, serverSettings)`(来自 `../api/opencode_client`,不变);`extractCellContext(cell, notebookPanel)`(来自 `../context/cell_context`,不变);`../types` 全部类型(不变)。 - Produces: `class OpenCodeCellActions extends Widget`,构造函数 `constructor(cell: CodeCell)`;`setOpenCodeRuntime(args: { settings: OpenCodeSettings; serverSettings: ServerConnection.ISettings }): void`;CSS 类 `opencode-cell-actions` / `opencode-btn opencode-btn-{optimize|fix|edit}`。Task 2 的 index.ts 依赖这两个导出。 @@ -115,15 +117,25 @@ function makeFakeCell( content: notebook }; for (let i = 0; i < cellIndex; i++) { - notebook.widgets.push({ model: { sharedModel: { getSource: () => '' } } } as any); + notebook.widgets.push({ + model: { sharedModel: { getSource: () => '' } } + } as any); } notebook.widgets.push(cell); for (let i = cellIndex + 1; i < totalCells; i++) { - notebook.widgets.push({ model: { sharedModel: { getSource: () => '' } } } as any); + notebook.widgets.push({ + model: { sharedModel: { getSource: () => '' } } + } as any); } - Object.defineProperty(cell, 'parent', { value: notebook, configurable: true }); - Object.defineProperty(notebook, 'parent', { value: panel, configurable: true }); + Object.defineProperty(cell, 'parent', { + value: notebook, + configurable: true + }); + Object.defineProperty(notebook, 'parent', { + value: panel, + configurable: true + }); return cell as CodeCell; } @@ -403,10 +415,12 @@ git commit -m "feat: add OpenCodeCellActions widget for the native Cell toolbar" ### Task 2: 注册进原生 Cell toolbar,拆除 footer 链路 **Files:** + - Modify: `src/index.ts`(整体重写,下方给完整内容) - Delete: `src/components/opencode_cell_footer.ts`、`src/components/opencode_cell_factory.ts`、`src/components/opencode_installer.ts`、`src/__tests__/opencode_cell_footer.spec.ts` **Interfaces:** + - Consumes: Task 1 的 `OpenCodeCellActions` / `setOpenCodeRuntime`;`IToolbarWidgetRegistry.addFactory(widgetFactory: string, toolbarItemName: string, factory: (main: T) => Widget)`(apputils 4.5.10)。 - Produces: 注册名 `opencode-cell-actions`(Task 3 的 schema 必须逐字引用);插件不再依赖 `INotebookTracker`。 @@ -476,7 +490,9 @@ const plugin: JupyterFrontEndPlugin = { void callOpenCodeProviders(app.serviceManager.serverSettings) .then(data => { - const lines: string[] = ['[opencode_bridge] Available OpenCode providers:']; + const lines: string[] = [ + '[opencode_bridge] Available OpenCode providers:' + ]; for (const p of data.providers) { const models = p.models.map(m => m.id).join(', '); lines.push(` - ${p.id}: ${models || '(no models)'}`); @@ -540,10 +556,12 @@ git commit -m "refactor: register AI actions in native Cell toolbar; remove cell ### Task 3: schema 贡献 + 样式 **Files:** + - Modify: `schema/plugin.json`(顶层加 `jupyter.lab.toolbars`) - Modify: `style/base.css` **Interfaces:** + - Consumes: Task 2 注册的 item 名 `opencode-cell-actions`;Task 1 的 CSS 类 `opencode-cell-actions` / `opencode-btn`。 - Produces: 无新接口。 @@ -616,9 +634,11 @@ git commit -m "feat: contribute opencode-cell-actions to Cell toolbar settings + ### Task 4: design.md 更新 + 全量验证 **Files:** + - Modify: `design.md`(第 2 节"交互与UI流程设计"第 1 条) **Interfaces:** + - Consumes: 无。 - Produces: 无。 diff --git a/docs/superpowers/specs/2026-07-23-cell-toolbar-actions-design.md b/docs/superpowers/specs/2026-07-23-cell-toolbar-actions-design.md index 14e3c9c..0b0ffa0 100644 --- a/docs/superpowers/specs/2026-07-23-cell-toolbar-actions-design.md +++ b/docs/superpowers/specs/2026-07-23-cell-toolbar-actions-design.md @@ -11,12 +11,12 @@ ## 已确认的决策 -| 问题 | 决策 | -|---|---| -| 可见性语义 | 跟随原生 toolbar:只在 **active cell** 右上角显示(与上移/下移按钮行为严格一致) | -| 底部 footer | **删除**,三个按钮全部上移 | -| 点击交互 | 本次不变:`🪄 编辑` 仍用 `window.prompt`;结果仍走 `Notification`;inline prompt box / diff 面板留待后续 | -| 作用范围 | 仅 `CodeCell`;Markdown 等 cell 不显示 AI 按钮(现状 footer 挂在所有 cell 上,属顺带修正) | +| 问题 | 决策 | +| ----------- | ----------------------------------------------------------------------------------------------------- | +| 可见性语义 | 跟随原生 toolbar:只在 **active cell** 右上角显示(与上移/下移按钮行为严格一致) | +| 底部 footer | **删除**,三个按钮全部上移 | +| 点击交互 | 本次不变:`🪄 编辑` 仍用 `window.prompt`;结果仍走 `Notification`;inline prompt box / diff 面板留待后续 | +| 作用范围 | 仅 `CodeCell`;Markdown 等 cell 不显示 AI 按钮(现状 footer 挂在所有 cell 上,属顺带修正) | ## 机制(JupyterLab 4.6 真实扩展点,已核实源码) diff --git a/src/__tests__/opencode_cell_actions.spec.ts b/src/__tests__/opencode_cell_actions.spec.ts index 0067753..22fb015 100644 --- a/src/__tests__/opencode_cell_actions.spec.ts +++ b/src/__tests__/opencode_cell_actions.spec.ts @@ -79,15 +79,25 @@ function makeFakeCell( content: notebook }; for (let i = 0; i < cellIndex; i++) { - notebook.widgets.push({ model: { sharedModel: { getSource: () => '' } } } as any); + notebook.widgets.push({ + model: { sharedModel: { getSource: () => '' } } + } as any); } notebook.widgets.push(cell); for (let i = cellIndex + 1; i < totalCells; i++) { - notebook.widgets.push({ model: { sharedModel: { getSource: () => '' } } } as any); + notebook.widgets.push({ + model: { sharedModel: { getSource: () => '' } } + } as any); } - Object.defineProperty(cell, 'parent', { value: notebook, configurable: true }); - Object.defineProperty(notebook, 'parent', { value: panel, configurable: true }); + Object.defineProperty(cell, 'parent', { + value: notebook, + configurable: true + }); + Object.defineProperty(notebook, 'parent', { + value: panel, + configurable: true + }); return cell as CodeCell; } diff --git a/src/components/opencode_cell_actions.ts b/src/components/opencode_cell_actions.ts index 02b2f5e..1d0f8ab 100644 --- a/src/components/opencode_cell_actions.ts +++ b/src/components/opencode_cell_actions.ts @@ -68,7 +68,7 @@ export class OpenCodeCellActions extends Widget { const node = this.node; node.textContent = ''; - const hasError = this._context?.error != null; + const hasError = !!this._context?.error; const loading = this._status === 'loading'; const baseDisabled = !this._context || loading; diff --git a/src/index.ts b/src/index.ts index c90eabe..cfae346 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,16 +61,16 @@ const plugin: JupyterFrontEndPlugin = { void callOpenCodeProviders(app.serviceManager.serverSettings) .then(data => { - const lines: string[] = ['[opencode_bridge] Available OpenCode providers:']; + const lines: string[] = [ + '[opencode_bridge] Available OpenCode providers:' + ]; for (const p of data.providers) { const models = p.models.map(m => m.id).join(', '); lines.push(` - ${p.id}: ${models || '(no models)'}`); } - // eslint-disable-next-line no-console console.log(lines.join('\n')); }) .catch(reason => { - // eslint-disable-next-line no-console console.warn( '[opencode_bridge] Could not fetch providers (is the opencode-bridge server extension enabled and opencode serve running?):', reason