Skip to content

feat(ai-chat): add Claude Code multi-step workflow panel (#462)#477

Merged
otomatty merged 5 commits into
developfrom
feature/462-claude-workflow-panel
Apr 5, 2026
Merged

feat(ai-chat): add Claude Code multi-step workflow panel (#462)#477
otomatty merged 5 commits into
developfrom
feature/462-claude-workflow-panel

Conversation

@otomatty
Copy link
Copy Markdown
Owner

@otomatty otomatty commented Apr 5, 2026

概要

AI チャットに ワークフロー タブを追加し、Claude Code 経由で 複数ステップのワークフロー を定義・実行できるようにしました。ステップごとのプロンプト生成、ノートへの Markdown 追記、ストリーミング、一時停止・再開・停止、テンプレートと JSON インポート、定義のローカル永続化(Zustand persist)を含みます。

変更点

src/components/ai-chat/

  • 「ワークフロー」ビュータブ、AIChatWorkflowPanel、フォーム・メタ・ステップ/進捗 UI
  • ワークフロータブではチャット入力を隠し、パネル操作に集中できるように調整

src/hooks/

  • useWorkflowDraft / useWorkflowRunSession / useWorkflowPanelLogicworkflowRunOutcomeHandlers でドラフト・実行セッション・結果ハンドリングを集約

src/lib/claudeCode/

  • streamClaudeQuery を追加し、runQueryToCompletion はストリーミング経路に委譲

src/lib/workflow/

  • 型・テンプレート、runWorkflowExecution(ループ分割)、buildWorkflowStepPromptformatWorkflowNoteMarkdown、JSON 定義パース、単体テスト

src/stores/

  • workflowDefinitionsStore(保存ワークフロー)

src/i18n/locales/en|ja/

  • ワークフロー/タブラベル等の文言追加
領域 主な変更
src/components/ai-chat/ ワークフロータブ・パネル UI
src/hooks/ ドラフト・実行・パネルロジック
src/lib/claudeCode/ ストリーミングクエリ
src/lib/workflow/ 実行エンジン・整形・import・テスト
src/stores/ 定義ストア
src/i18n/ 英日文言

変更の種類

  • 🐛 バグ修正 (Bug fix)
  • ✨ 新機能 (New feature)
  • 💥 破壊的変更 (Breaking change)
  • 📝 ドキュメント (Documentation)
  • 🎨 スタイル/リファクタリング (Style/Refactor)
  • 🧪 テスト (Tests)
  • 🔧 ビルド/CI (Build/CI)

テスト方法

  1. アプリを起動し、AI チャットパネルを開く(bun run dev 等)。
  2. ビュータブで ワークフロー を選択し、テンプレートから作成するか JSON をインポートする。
  3. 実行し、ノートへの追記・ストリーミング・ステップ進行を確認する(必要なら一時停止・再開・停止)。
  4. 単体テスト: bun run test:run(または src/lib/workflow/**/*.test.ts と関連テスト)。

DB マイグレーション・新規環境変数・package.json 変更はありません。

チェックリスト

  • テストがすべてパスする(ローカルでメイン Vitest スイート 1087 件パス)
  • Lint エラーがない(eslint . は 0 errors、warnings のみ既存含む)
  • 必要に応じてドキュメントを更新した(該当なし)
  • コミットメッセージが Conventional Commits に従っている

スクリーンショット(UI 変更がある場合)

レビュアー向け: ワークフロータブの Before/After をこの PR に添付してください。

関連 Issue

Closes #462

Made with Cursor


Open with Devin

Summary by CodeRabbit

  • New Features

    • AIチャットに「Workflow」タブを追加。ワークフローの編集・テンプレート生成・保存/読み込み/削除・JSONエクスポート/インポート、実行/一時停止/再開/停止、進捗表示、ワークフローのノート出力をサポート。ワークフロー選択時は専用パネルを遅延マウントして維持、入力フッターはワークフロー表示時に非表示。
  • Refactor

    • Claudeストリーミング処理を外部化して単純化。
  • Tests

    • ワークフロー周りの単体テストを多数追加。
  • Localization

    • 英語・日本語のワークフロー向け文言を追加。

- Workflow tab, templates, save/import JSON, run/pause/resume/stop
- streamClaudeQuery + runWorkflowExecution; note updates via editor handler
- parseWorkflowDefinitionImport for safe JSON import

Made-with: Cursor
Copilot AI review requested due to automatic review settings April 5, 2026 00:35
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

AIチャットに「ワークフロー」タブを追加し、ワークフローの作成/編集/インポート/エクスポート/保存、テンプレート、実行(実行/一時停止/再開/停止)、およびClaude Codeストリーミング統合を実装。ワークフローパネルは遅延ロードされ、初回表示後はマウントを維持するラッチを導入。

Changes

Cohort / File(s) Summary
AI Chat Panel Integration
src/components/ai-chat/AIChatPanelContent.tsx, src/components/ai-chat/AIChatViewTabs.tsx, src/components/ai-chat/AIChatViewTabs.test.tsx
"workflow" タブ追加。ワークフローパネルを遅延ロードし一度表示でマウント保持(ラッチ)。workflow選択時の表示/隠蔽ロジックとフッターの条件非表示。テスト更新を含む。
Workflow UI Components
src/components/ai-chat/AIChatWorkflowPanel.tsx, src/components/ai-chat/WorkflowPanelForm.tsx, src/components/ai-chat/WorkflowPanelMetaSection.tsx, src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx, src/components/ai-chat/workflowPanelTypes.ts
ワークフロー編集用コンポーネント群を追加(メタ情報、ステップ編集、進捗表示、実行コントロール)。共通 props 型をエクスポート。
Workflow Hooks / Logic
src/hooks/useWorkflowDraft.ts, src/hooks/useWorkflowPanelLogic.ts, src/hooks/useWorkflowRunSession.ts, src/hooks/workflowRunOutcomeHandlers.ts
ドラフト管理(add/remove/update、保存/削除/インポート/エクスポート)、実行セッション(run/pause/resume/stop)、実行結果適用ユーティリティを実装。
Execution Engine & Claude Streaming
src/lib/workflow/runWorkflowExecution.ts, src/lib/claudeCode/streamClaudeQuery.ts, src/lib/claudeCode/runQueryToCompletion.ts, src/lib/claudeCode/runQueryToCompletion.test.ts
マルチステップ実行エンジンを追加。Claudeストリーミングを streamClaudeQuery に集約し、旧ロジックを委譲へ変更。テストのモックを更新。
Workflow Library: prompts, formatting, ids, parsing, templates
src/lib/workflow/buildWorkflowStepPrompt.ts, src/lib/workflow/formatWorkflowNoteMarkdown.ts, src/lib/workflow/newWorkflowId.ts, src/lib/workflow/parseWorkflowDefinitionImport.ts, src/lib/workflow/templates.ts, src/lib/workflow/types.ts, src/lib/workflow/*.test.ts
ステップ向けプロンプト生成、ノートMarkdown整形、ID生成、インポート検証、テンプレート定義、型定義、および関連ユニットテストを追加。
Persistence Store
src/stores/workflowDefinitionsStore.ts
Zustandベースの永続ストアを追加(カスタムワークフローの upsert/remove、persist 設定)。
i18n
src/i18n/locales/en/aiChat.json, src/i18n/locales/ja/aiChat.json
ワークフロー用ラベル・メッセージ・テンプレート名等の英日ローカライズを追加。
Tests
src/components/ai-chat/AIChatViewTabs.test.tsx, src/lib/claudeCode/runQueryToCompletion.test.ts, src/lib/workflow/*.test.ts
ワークフロー関連ユニットテスト(プロンプト生成、インポートパース、実行シナリオ、UIタブ)を追加/更新。

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as WorkflowPanel UI
    participant Hooks as useWorkflowPanelLogic
    participant Exec as runWorkflowExecution
    participant Stream as streamClaudeQuery
    participant Claude as Claude Sidecar
    participant Editor as Note Editor

    User->>UI: Run ボタンを押す
    UI->>Hooks: runExecution("fresh")
    Hooks->>Exec: runWorkflowExecution(options)
    loop 各ステップ
        Exec->>Stream: streamClaudeQuery(stepPrompt,...)
        Stream->>Claude: claudeQuery() + リスナ登録
        Claude-->>Stream: イベント(chunks/tool start/complete/error)
        Stream-->>Exec: onChunk/onToolUse/onComplete callbacks
        Exec->>Exec: stepOutputs 更新, onProgress 呼出
        Exec->>Editor: onNoteMarkdown(updated markdown)
        Editor-->>UI: ノートに進捗が反映
    end
    Exec-->>Hooks: outcome (completed|paused|stopped|error)
    Hooks-->>UI: 最終状態反映
Loading
sequenceDiagram
    participant UI as WorkflowPanel
    participant Hooks as useWorkflowRunSession
    participant Exec as runWorkflowExecution

    UI->>Hooks: handlePause()
    Hooks->>Exec: abort current step controller
    Exec-->>Hooks: step stream が Aborted を返す
    Hooks->>Hooks: pausedState を保存
    Hooks-->>UI: progress.phase = "paused"
    Note over UI,Hooks: ユーザーが Resume をクリック
    UI->>Hooks: runExecution("resume")
    Hooks->>Exec: resumePartial + pausedState
    Exec-->>Hooks: 再開して処理継続
    Hooks-->>UI: progress 更新(running → completed 等)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

enhancement

🐰 新しい道を踏みしめて走るよ
ステップ並べてノートに書くよ
一時停止してもまた跳ねるよ
Claude と一緒に結末まで
ぴょんと完成、ワークフロー万歳! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 変更はワークフロー機能の実装に限定されています。ただし streamClaudeQuery への大規模なリファクタリング(runQueryToCompletion の簡略化)が含まれており、これは #462 の直接要件外です。 リファクタリング変更が既存機能に悪影響がないことを確認し、必要に応じてコミットを分離することを検討してください。
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRタイトルは「feat(ai-chat): add Claude Code multi-step workflow panel (#462)」であり、変更セットの主要な機能追加を正確に反映しています。AI チャットパネルに新しいワークフロータブと実行エンジンを追加する内容を簡潔かつ明確に説明しており、スキャンに適した形式です。
Linked Issues check ✅ Passed PR は #462 の全要件を実装しています:ワークフロー定義 UI、4つのテンプレート、Claude Code 統合実行エンジン、リアルタイム進捗表示、一時停止・再開・中断機能、カスタムワークフロー保存、JSON インポート・エクスポート機能が含まれています。
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/462-claude-workflow-panel

Comment @coderabbitai help to get the list of available commands and usage tips.

@otomatty otomatty self-assigned this Apr 5, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a multi-step workflow feature for Claude Code, allowing users to define, save, and execute sequences of AI tasks that update notes in real-time. The implementation includes new UI components for the AI chat panel, hooks for managing workflow drafts and execution sessions, and a library for orchestrating streaming queries. Review feedback identifies a bug where note content is duplicated when resuming a paused workflow and suggests improvements for UI state synchronization during errors, pre-query abort checks, and performance optimizations for high-frequency note updates.

Comment thread src/hooks/useWorkflowRunSession.ts Outdated
};

workflowAbortRef.current = new AbortController();
baseSnapshotRef.current = pageContext?.pageFullContent ?? "";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

runExecutionresume モードで呼び出された際にも baseSnapshotRef.current が現在のノート内容(既にワークフローブロックが含まれている状態)で更新されてしまうため、再開するたびにノート内にワークフローのステータスブロックが重複して挿入される問題があります。fresh モードの時のみベーススナップショットを更新するように修正すべきです。

Suggested change
baseSnapshotRef.current = pageContext?.pageFullContent ?? "";
if (mode === "fresh") {
baseSnapshotRef.current = pageContext?.pageFullContent ?? "";
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

対応しました。 は mode === "fresh" のときだけ現在のノート内容で更新し、再開時は既存スナップショットのままにしています(899e91e)。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarification (previous reply): baseSnapshotRef is updated only when mode is fresh; on resume we keep the prior snapshot so the workflow block is not duplicated.

Comment on lines +59 to +65
case "error":
setActiveRunSteps(null);
toast({
title: t("aiChat.workflow.error", { message: result.error }),
variant: "destructive",
});
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

error アウトカムが発生した際に setProgress が呼ばれていないため、UI 上の実行状態が「実行中」のまま残ってしまう可能性があります。エラー発生時も適切にフェーズを更新(例:aborted へ変更)して、実行が終了したことを UI に伝えるべきです。

    case "error":
      setActiveRunSteps(null);
      setProgress((p) => (p ? { ...p, phase: "aborted" } : null));
      toast({
        title: t("aiChat.workflow.error", { message: result.error }),
        variant: "destructive",
      });
      return;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

対応しました。 時は setPausedState(null)setProgressphase: "aborted" にし、実行中表示が残らないようにしました(899e91e)。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarification: the error outcome handler now calls setPausedState(null), setActiveRunSteps(null), and setProgress with phase aborted before showing the toast (899e91e).

});

try {
requestId = await claudeQuery(prompt, options);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

claudeQuery を呼び出す前に signal が既に aborted 状態でないか確認していません。ユーザーが実行直後に停止ボタンを押した場合など、不要なクエリ発行を避けるためにチェックを追加するのが望ましいです。

Suggested change
requestId = await claudeQuery(prompt, options);
if (signal?.aborted) return { ok: false, error: "Aborted" };
requestId = await claudeQuery(prompt, options);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

対応しました。claudeQuery 呼び出し直前に signal?.aborted をチェックし、不要なクエリ発行を避けます(899e91e)。

pushProgress("running", i, statusesBefore, initialStreaming, undefined);
emitNote(i, statusesBefore, i, initialStreaming);

const prior = stepOutputs.slice(0, i).map((_, idx) => stepOutputs[idx] ?? "");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

この map 処理は冗長です。stepOutputs.slice(0, i) は既に先行ステップの出力配列を返しており、normalizeStepOutputs によって各要素が文字列であることが保証されているため、そのまま利用できます。

Suggested change
const prior = stepOutputs.slice(0, i).map((_, idx) => stepOutputs[idx] ?? "");
const prior = stepOutputs.slice(0, i);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

対応しました。priorstepOutputs.slice(0, i) に置き換えました(899e91e)。

});
const base = baseContentBeforeWorkflow.trimEnd();
const full = base.length > 0 ? `${base}\n\n${block}` : block;
onNoteMarkdown(full);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

onChunk ごとにノート全体の Markdown(ベース内容 + ワークフローブロック)を再構築して onNoteMarkdown を呼び出しています。ノートのサイズが大きい場合、ストリーミング中の頻繁な更新がパフォーマンスのボトルネックとなり、エディタの動作が重くなる可能性があります。将来的に、ワークフローブロックのみを置換または追記するような部分更新の仕組みを検討することをお勧めします。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご指摘のとおり、大きいノートではフル再構築がコストになり得ます。現状は仕様として全ノートを渡していますが、ワークフローブロックの差分更新は別途パフォーマンス改善として検討します。

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

AI チャットに「ワークフロー」タブを追加し、Claude Code を使った複数ステップの実行(ストリーミング・一時停止/再開/停止・ノートへの Markdown 反映・テンプレート/JSON import/export・ローカル永続化)を提供する PR です。

Changes:

  • Workflow 定義(テンプレート/保存/JSON import/export)と実行セッション(pause/resume/stop + progress)を追加
  • Claude Code sidecar クエリにストリーミング API を追加し、既存の runClaudeQueryToCompletion を委譲
  • AI Chat UI に Workflow タブ + パネル UI を追加し、Workflow 表示中はチャット入力を非表示に変更

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/stores/workflowDefinitionsStore.ts ワークフロー定義の Zustand 永続化ストアを追加
src/lib/workflow/types.ts ワークフロー定義/実行進捗の型を追加
src/lib/workflow/templates.ts 組み込みテンプレートとインスタンス化関数を追加
src/lib/workflow/runWorkflowExecution.ts ステップ実行ループ + ストリーミング + ノート更新の実行エンジンを追加
src/lib/workflow/runWorkflowExecution.test.ts 実行エンジンの基本挙動(no steps / complete / pause)をテスト
src/lib/workflow/parseWorkflowDefinitionImport.ts JSON import のバリデーション/パースを追加
src/lib/workflow/parseWorkflowDefinitionImport.test.ts import パースのテストを追加
src/lib/workflow/newWorkflowId.ts workflow/step 用 ID 生成ユーティリティを追加
src/lib/workflow/formatWorkflowNoteMarkdown.ts ノートへ埋め込むワークフロー進捗 Markdown 生成を追加
src/lib/workflow/formatWorkflowNoteMarkdown.test.ts Markdown 整形のテストを追加
src/lib/workflow/buildWorkflowStepPrompt.ts ステッププロンプト生成 + maxTurns デフォルトを追加
src/lib/workflow/buildWorkflowStepPrompt.test.ts プロンプト生成/デフォルト maxTurns のテストを追加
src/lib/claudeCode/streamClaudeQuery.ts Claude Code クエリのストリーミング API を追加
src/lib/claudeCode/runQueryToCompletion.ts 既存 API を streamClaudeQuery に委譲するよう変更
src/lib/claudeCode/runQueryToCompletion.test.ts tool-use イベントの mock 追加(streamClaudeQuery の listener 増加に追随)
src/i18n/locales/ja/aiChat.json Workflow タブ/パネル文言(日本語)を追加
src/i18n/locales/en/aiChat.json Workflow タブ/パネル文言(英語)を追加
src/hooks/workflowRunOutcomeHandlers.ts 実行結果を UI state/toast へ反映するハンドラを追加
src/hooks/useWorkflowRunSession.ts workflow の run/pause/resume/stop と editor 反映を管理する hook を追加
src/hooks/useWorkflowPanelLogic.ts ドラフト編集 + 実行セッションを合成する hook を追加
src/hooks/useWorkflowDraft.ts テンプレート/保存/JSON import/export を含むドラフト編集 hook を追加
src/components/ai-chat/workflowPanelTypes.ts パネル subcomponent 用 props 型を追加
src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx ステップ編集 + 実行進捗 UI を追加
src/components/ai-chat/WorkflowPanelMetaSection.tsx 名前/テンプレート/保存/JSON import-export UI を追加
src/components/ai-chat/WorkflowPanelForm.tsx Workflow パネル全体のフォーム/操作 UI を追加
src/components/ai-chat/AIChatWorkflowPanel.tsx Workflow パネルを AI chat に組み込み
src/components/ai-chat/AIChatViewTabs.tsx Workflow タブを追加
src/components/ai-chat/AIChatViewTabs.test.tsx Workflow タブ追加に伴うテスト更新
src/components/ai-chat/AIChatPanelContent.tsx Workflow タブ表示と、Workflow 表示中のチャット入力非表示を追加

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

totalSteps: steps.length,
pageExcerpt,
priorOutputs: prior,
resumePartial: i === startStepIndex ? resumePartialForCurrentStep : undefined,
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildWorkflowStepPrompt is called with an option key resumePartial, but the helper’s parameter is named resumeFromPartial. This is a TypeScript type error and will fail the build; rename the option key (or the helper param) so they match.

Suggested change
resumePartial: i === startStepIndex ? resumePartialForCurrentStep : undefined,
resumeFromPartial: i === startStepIndex ? resumePartialForCurrentStep : undefined,

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: call site now uses resumeFromPartial to match buildWorkflowStepPrompt (899e91e).

Comment on lines +54 to +65
case "stopped":
setActiveRunSteps(null);
setProgress((p) => (p ? { ...p, phase: "aborted" } : null));
toast({ title: t("aiChat.workflow.stopped") });
return;
case "error":
setActiveRunSteps(null);
toast({
title: t("aiChat.workflow.error", { message: result.error }),
variant: "destructive",
});
return;
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For outcome: "error", the handler never updates/clears progress or pausedState. Since running is derived from progress.phase === "running", an error can leave the UI stuck in a running state with disabled controls. Update progress.phase to a non-running terminal phase (e.g. aborted) and clear pausedState as appropriate when errors occur.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the same commit: error outcome clears pausedState and sets progress.phase to aborted (899e91e).

Comment on lines +54 to +58
case "stopped":
setActiveRunSteps(null);
setProgress((p) => (p ? { ...p, phase: "aborted" } : null));
toast({ title: t("aiChat.workflow.stopped") });
return;
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For outcome: "stopped", the handler doesn’t clear pausedState. If a user stops during a resumed run (where pausedState is still set), the Resume button can remain enabled even though the run was stopped. Consider clearing pausedState (and potentially activeRunSteps) on stop to prevent resuming a stopped run.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed: stopped outcome now also calls setPausedState(null) so resume is not offered after stop (899e91e).

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +59 to +65
case "error":
setActiveRunSteps(null);
toast({
title: t("aiChat.workflow.error", { message: result.error }),
variant: "destructive",
});
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Missing setProgress in error outcome leaves UI permanently stuck in 'running' state

When a workflow step fails with a non-abort error, applyWorkflowRunOutcome for the "error" case does not call setProgress, unlike the "completed", "paused", and "stopped" cases which all update the progress phase. The last progress update comes from runWorkflowExecution.ts:240 which sets phase: "running". Since running is derived as progress?.phase === "running" at useWorkflowRunSession.ts:168, it stays true after an error. This disables the Run button (disabled={running || !isEditor}), keeps the Pause and Stop buttons enabled (no-ops since controllers are exhausted), and locks all form inputs (disabled={running}) — making the workflow panel completely unusable until the user navigates away and back.

Suggested change
case "error":
setActiveRunSteps(null);
toast({
title: t("aiChat.workflow.error", { message: result.error }),
variant: "destructive",
});
return;
case "error":
setActiveRunSteps(null);
setProgress((p) => (p ? { ...p, phase: "aborted" } : null));
toast({
title: t("aiChat.workflow.error", { message: result.error }),
variant: "destructive",
});
return;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same fix as above: error path now clears pausedState and sets progress to aborted (899e91e).

- Only refresh workflow base snapshot on fresh runs (avoid duplicate blocks on resume).
- On error/stop: clear paused state, set progress to aborted, sync UI with Copilot/Gemini/Devin.
- Skip claudeQuery when already aborted.
- Fix buildWorkflowStepPrompt option name (resumeFromPartial) and simplify prior outputs slice.

Made-with: Cursor
@otomatty
Copy link
Copy Markdown
Owner Author

otomatty commented Apr 5, 2026

レビューコメントへの対応をコミットしました(899e91e)。最新の変更に対する再レビューをお願いします。

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 5, 2026

@otomatty レビューコメントへの対応、ありがとうございます!コミット 899e91e を含む最新の変更に対して再レビューを実施します。


🧠 Learnings used
Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-14T12:40:46.017Z
Learning: PR review focus areas: check for security and performance impacts, breaking changes in public APIs or types, proper error handling and logging, and ensure comments in Japanese/English match project tone.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-29T06:01:47.717Z
Learning: Review PRs for security and performance impact, API/type breaking changes, appropriate error handling and logging, and adherence to project communication tone in Japanese and English.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: .cursor/rules/pr-and-branch-naming.mdc:0-0
Timestamp: 2026-03-08T07:34:11.938Z
Learning: PR titles must accurately represent the changes made. Use Conventional Commits format (e.g., `feat(admin): AIモデル管理UI拡張 (`#218`)`). For single-topic PRs, use the representative commit message directly. Match the language (Japanese or English) to the majority language of commits.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-29T06:01:47.717Z
Learning: PR titles must follow Conventional Commits format and accurately describe the changes (e.g., `feat(admin): AIモデル管理UI拡張 (`#218`)`). Avoid unrelated or generic descriptions.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-14T12:40:46.017Z
Learning: PR titles must follow Conventional Commits format (e.g., `feat(admin): AIモデル管理UI拡張 (`#218`)`) and accurately represent the changes. When using Cursor Cloud Agent, include instructions in the prompt to ensure PR titles follow Conventional Commits format.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-29T06:01:24.318Z
Learning: 日本語・英語のコメント・ドキュメントがプロジェクトのトーンに合っているか確認する。

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T12:40:46.017Z
Learning: Ensure Japanese and English comments and documentation align with the project's tone.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (6)
src/lib/workflow/formatWorkflowNoteMarkdown.test.ts (1)

4-32: done + 空出力ケースの回帰テストを追加したいです

formatWorkflowNoteMarkdown 側の空出力フォールバックを守るため、stepStatuses: ["done"] かつ stepOutputs: [""](empty) を期待するテストを 1 件追加するのを推奨します。

✅ 追加テスト例
 describe("formatWorkflowNoteMarkdown", () => {
@@
   it("includes streaming text for the running step", () => {
@@
   });
+
+  it("renders (empty) when a done step has empty output", () => {
+    const md = formatWorkflowNoteMarkdown({
+      title: "E",
+      stepTitles: ["Only"],
+      stepStatuses: ["done"],
+      stepOutputs: [""],
+      streamingStepIndex: null,
+      streamingText: "",
+    });
+    expect(md).toContain("### ☑ 1. Only");
+    expect(md).toContain("(empty)");
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/workflow/formatWorkflowNoteMarkdown.test.ts` around lines 4 - 32, Add
a regression test to ensure formatWorkflowNoteMarkdown falls back to "(empty)"
when a step is marked "done" but its output is an empty string: create a new it
block calling formatWorkflowNoteMarkdown with stepStatuses: ["done"],
stepOutputs: [""], and assert the returned markdown contains the "### ☑ 1.
<title>" marker and the literal "(empty)". Reference the existing test file's
describe block for formatWorkflowNoteMarkdown and mirror the structure of the
other cases (title, stepTitles, streamingStepIndex null) when adding this
assertion.
src/components/ai-chat/AIChatWorkflowPanel.tsx (1)

13-15: exported component の戻り値型を明示した方がよいです。

新規の exported component なので、ここも戻り値型を明示しておくと repo の TS ルールに揃えられます。

As per coding guidelines "**/*.{ts,tsx}: TypeScript strict mode enforced; any type is forbidden. All functions, variables, and types must have explicit type annotations."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai-chat/AIChatWorkflowPanel.tsx` around lines 13 - 15, The
exported React component AIChatWorkflowPanel lacks an explicit return type;
update its signature to include a React return annotation (e.g., : JSX.Element
or : React.ReactElement) so it complies with the project's strict TS rules—keep
the body using useWorkflowPanelLogic() and returning <WorkflowPanelForm
{...logic} /> unchanged and ensure React is imported if needed for the chosen
return type.
src/lib/workflow/runWorkflowExecution.ts (2)

100-113: 内部ヘルパー関数に TSDoc を追加することを検討してください

normalizeStepOutputs 関数にはインラインコメント(lines 100-103)がありますが、正式な TSDoc 形式ではありません。また buildStatuses(lines 268-280)にはドキュメントがありません。コーディングガイドラインでは「Export functions, types, and interfaces must include TSDoc」とありますが、これらは内部関数なので必須ではありませんが、可読性向上のため検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/workflow/runWorkflowExecution.ts` around lines 100 - 113, Add TSDoc
comments for the internal helper normalizeStepOutputs and the buildStatuses
variable to improve readability: above function normalizeStepOutputs document
the purpose, params (initialOutputs: string[], stepsLength: number), and return
type (string[]) and describe that it pads with empty strings or trims to match
stepsLength; for buildStatuses add a brief TSDoc describing what the structure
represents (e.g., mapping of step indices to their current status or an array of
statuses used during workflow execution) and the expected value types; mark both
with `@internal` if you want to indicate they are not part of the public API.

201-201: mergeAbortSignals() ユーティリティを使用してください

AbortSignal.any() はTauri 2.xが使用する最新のWebView(WebView2/WebKit)でサポートされています。ただし、コードベースの他の部分と同様に、既存の mergeAbortSignals() ユーティリティ(src/lib/mergeAbortSignals.ts)を使用することで、一貫性と保守性が向上します。このユーティリティは undefined の外部シグナルを適切に処理します。

// 現在:
const merged = AbortSignal.any([workflowSignal, stepController.signal]);

// 推奨:
const merged = mergeAbortSignals(workflowSignal, stepController);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/workflow/runWorkflowExecution.ts` at line 201, Replace the direct use
of AbortSignal.any with the existing mergeAbortSignals utility: locate the line
creating the merged signal (currently using AbortSignal.any with workflowSignal
and stepController.signal) and call mergeAbortSignals instead, passing the
external workflowSignal and the stepController (or its signal as
mergeAbortSignals expects) so undefined signals are handled consistently; update
the reference where merged is used to rely on the new merged signal returned by
mergeAbortSignals.
src/hooks/useWorkflowRunSession.ts (1)

111-116: stepOutputs 配列生成ロジックの重複

初期 stepOutputs 配列の生成ロジックが setProgress 呼び出し(lines 114-116)と runWorkflowExecution 呼び出し(lines 131-133)で重複しています。将来的なメンテナンス性を考慮すると、変数に抽出して共有することを検討してください。

♻️ 提案する修正
+      const initialStepOutputs = initialOutputs.length
+        ? initialOutputs
+        : Array.from({ length: validSteps.length }, () => "");
+
       setProgress({
         phase: "running",
         currentStepIndex: startIndex,
         stepStatuses: validSteps.map((_, i) =>
           i < startIndex ? "done" : i === startIndex ? "running" : "pending",
         ),
-        stepOutputs: initialOutputs.length
-          ? initialOutputs
-          : Array.from({ length: validSteps.length }, () => ""),
+        stepOutputs: initialStepOutputs,
         currentStepStreaming: resumePartial ?? "",
       });

       const result = await runWorkflowExecution({
         // ...
         startStepIndex: startIndex,
-        stepOutputs: initialOutputs.length
-          ? initialOutputs
-          : Array.from({ length: validSteps.length }, () => ""),
+        stepOutputs: initialStepOutputs,
         // ...
       });

Also applies to: 131-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useWorkflowRunSession.ts` around lines 111 - 116, The stepOutputs
initialisation logic is duplicated between the setProgress call and
runWorkflowExecution call; extract the expression Array.from({ length:
validSteps.length }, () => "") or the resolved initialOutputs fallback into a
single variable (e.g., computedStepOutputs) within useWorkflowRunSession, then
pass that variable into both setProgress (where stepOutputs is assigned) and
runWorkflowExecution so both use the same source of truth (update references to
stepOutputs, initialOutputs, validSteps, setProgress, and runWorkflowExecution
accordingly).
src/hooks/useWorkflowDraft.ts (1)

19-28: emptyDraft 関数に TSDoc を追加することを検討してください

この関数はモジュール内部で使用されていますが、ワークフロー定義の初期構造を定義する重要な役割を持っています。簡潔なコメントがあると、将来のメンテナンス時に役立ちます。

📝 提案する修正
+/** Creates an empty workflow draft with fresh IDs. / 新規 ID を持つ空のワークフロードラフトを作成。 */
 function emptyDraft(): WorkflowDefinition {
   const now = Date.now();
   return {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useWorkflowDraft.ts` around lines 19 - 28, Add a concise TSDoc
block above the emptyDraft function describing its purpose and return value:
note that emptyDraft constructs a new WorkflowDefinition skeleton with a
generated id (uses newWorkflowId()), an empty name, a single empty step (step id
generated via newWorkflowId(), empty title and instruction), and populated
createdAt/updatedAt timestamps; include the return type WorkflowDefinition in
the doc and a short summary so future maintainers understand the function's
role.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ai-chat/AIChatPanelContent.tsx`:
- Around line 134-137: AIChatWorkflowPanel がタブ切替でアンマウントされるため
useWorkflowPanelLogic → useWorkflowRunSession 内の state(progress, activeRunSteps,
pausedState)や useWorkflowDraft の draft がリセットされています。簡潔に直すには AIChatPanelContent
の条件付きレンダリングをやめて AIChatWorkflowPanel を常にマウントしたまま表示/非表示を CSS(display:none
等)で切り替えるか、もしくは useWorkflowRunSession 内の progress / pausedState(および
useWorkflowDraft の draft)を外部ストアへ移行してアンマウントに影響されないようにしてください;対象の識別子は
AIChatWorkflowPanel, useWorkflowPanelLogic, useWorkflowRunSession,
useWorkflowDraft, emptyDraft, progress, activeRunSteps, pausedState,
useWorkflowDefinitionsStore です。

In `@src/components/ai-chat/WorkflowPanelForm.tsx`:
- Around line 53-55: The Run/Resume enablement and the desktop-only warning are
inconsistent: update the button enablement logic in WorkflowPanelForm so both
Run and Resume use the same combined check (isTauriDesktop() && isEditor)
instead of Run only checking !isEditor and Resume only checking pausedState;
locate the Run and Resume button render branches and replace their individual
conditions with a single shared boolean (e.g., const canRunOrResume =
isTauriDesktop() && isEditor) and use that for disabling/enabling and showing
the desktop-only warning, and ensure pausedState is still used only to toggle
between showing Run vs Resume while the new canRunOrResume gate controls
availability.

In `@src/components/ai-chat/WorkflowPanelMetaSection.tsx`:
- Around line 64-119: The Label elements in WorkflowPanelMetaSection are not
associated with their controls (Input/Select) and the delete Button only uses a
title, which breaks screen-reader accessibility; update the name/labeling by
adding unique ids to the Input and each Select (e.g., id for the workflow name
input, template select, saved-workflow select) and set corresponding htmlFor on
the matching Label components, and add aria-labelledby or aria-label on
composite/select components (SelectTrigger/Select) as needed; also add an
explicit aria-label for the delete Button (used by deleteSaved) so screen
readers can announce its purpose, ensuring ids are deterministic and derived
from the control names (not runtime indexes) and do not change while running
(respecting the running state).

In `@src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx`:
- Around line 47-77: The step rows rendered in draft.steps lack accessible
labels; update the mapped JSX (the draft.steps.map block) to provide unique,
descriptive labels or aria-labels for the title Input, instruction textarea, and
the remove Button so screen readers can identify which step they belong to:
generate unique IDs using step.id (e.g., step-title-{step.id},
step-instruction-{step.id}) and either render visually-hidden <label> elements
tied to those IDs or add aria-label/aria-labelledby attributes (for example
aria-label={`Step ${index+1} title`} and aria-label={`Step ${index+1}
instruction`} and aria-label={`Remove step ${index+1}`} on the Button that calls
removeStep), and keep existing disabled/running logic and updateStep/removeStep
handlers unchanged.

In `@src/hooks/useWorkflowDraft.ts`:
- Around line 89-97: The exportJson function currently calls
URL.revokeObjectURL(url) immediately after a.click(), which can cause downloads
to fail for large files in some browsers; change it to delay revocation by
wrapping URL.revokeObjectURL(url) in a setTimeout(..., 0) so the browser
finishes the download before the object URL is revoked (update the exportJson
callback that references draft, a.click(), and URL.revokeObjectURL accordingly).

In `@src/hooks/useWorkflowPanelLogic.ts`:
- Around line 13-20: The exported hook useWorkflowPanelLogic currently returns
an inferred type; add an explicit return type annotation (e.g.,
WorkflowPanelFormProps or a composed interface that merges the shapes returned
by useWorkflowDraft and useWorkflowRunSession) on the function signature to
satisfy TS strict mode. Locate useWorkflowPanelLogic and declare its return
type, ensuring the type includes all properties from useWorkflowDraft and
useWorkflowRunSession (or import/extend WorkflowPanelFormProps) so the public
contract is explicit and stable.

In `@src/hooks/workflowRunOutcomeHandlers.ts`:
- Around line 54-67: The handler for "stopped"/"error" only updates
progress.phase but not the step-level state, so UI in
WorkflowPanelStepsAndProgress (which renders based on stepStatuses and
currentStepStreaming, not progress.phase) does not reflect terminal states;
update the handler (the case branches using setPausedState, setActiveRunSteps,
setProgress) to also set stepStatuses and currentStepStreaming to terminal
values (e.g., mark all steps as completed/aborted/errored and set
currentStepStreaming to false/null) so the panel sees the final state, or
alternatively update WorkflowPanelStepsAndProgress.tsx to read progress.phase
when deciding rendering; pick one approach and apply consistent updates to
setActiveRunSteps and currentStepStreaming to match the aborted/error terminal
phase.

In `@src/lib/claudeCode/streamClaudeQuery.ts`:
- Around line 24-27: PreRequestEvent currently only models chunk/complete/error
so tool start/complete events can be lost before requestId is assigned; update
the PreRequestEvent union to include types for tool-start and tool-complete
(e.g., { type: "tool-start"; id?: string; toolName: string; meta?: ... } and {
type: "tool-complete"; id?: string; toolName: string; result?: ... }), then
update applyPreRequestBuffer() to handle these new event kinds by routing them
into the same handlers used by onClaudeToolUseStart and onClaudeToolUseComplete
(or by calling those functions directly) so tool notifications emitted before
claudeQuery() returns a requestId are replayed correctly; adjust any callers and
tests that build/consume PreRequestEvent accordingly.
- Around line 102-152: The subscription handlers
(onClaudeStreamChunk/onClaudeStreamComplete/onClaudeError/onClaudeToolUseStart/onClaudeToolUseComplete)
are awaited outside any try, so if one registration throws the
previously-registered unlisten callbacks (unlistenChunk/unlistenComplete/...)
never get cleaned up; initialize each unlisten variable to a no-op cleanup
(e.g., () => {}) before registering, move the awaited onClaude* registrations
into the same try block so any thrown error will still run the finally cleanup,
and ensure the finally calls all unlistenX() regardless of which registration
failed; update the functions referenced: unlistenChunk, unlistenComplete,
unlistenError, unlistenToolStart, unlistenToolComplete and the onClaude*
registration calls accordingly.

In `@src/lib/workflow/formatWorkflowNoteMarkdown.ts`:
- Around line 44-46: The conditional that adds output for a "done" step
currently uses a truthy check on stepOutputs[i] which filters out empty strings
so the trim() || "(empty)" fallback never runs; inside
formatWorkflowNoteMarkdown change the guard for the "done" branch so it checks
existence rather than truthiness (e.g., use stepOutputs[i] !== undefined or i <
stepOutputs.length) before pushing to lines, leaving the
lines.push(stepOutputs[i].trim() || "(empty)") logic intact so empty outputs
render as "(empty)".

In `@src/lib/workflow/parseWorkflowDefinitionImport.ts`:
- Around line 25-27: The import currently accepts duplicate step ids (variable
s.id), causing React key collisions and unstable updates; fix by making ids
unique during import: while creating id for each step (where id is set from s.id
or newWorkflowId()), track seen ids in a Set and if an id is already taken,
generate a new one with newWorkflowId(), record an old->new mapping, and then
apply that mapping to update all internal references (any step reference fields,
transitions, or next pointers) so every step and its inbound/outbound links use
the deduped id; ensure this logic is applied in the step-processing block that
assigns id and in the subsequent code that builds relationships (the region
around the id assignment and the code later that uses those ids).

In `@src/lib/workflow/runWorkflowExecution.test.ts`:
- Around line 58-90: The test currently simulates an aborted step only by
returning streamClaudeQuery { ok: false, error: "Aborted" } but never actually
aborts the step controller; update the test so the created step AbortController
is actually aborted to firmly exercise the pause path: in the createStepAbort
callback that returns the `step` AbortController, call step.abort() (or call it
immediately after createStepAbort is invoked) so runWorkflowExecution receives a
real step.signal that has been aborted; keep the mocked streamClaudeQuery
response and existing assertions (expect createCount to be 1, outcome "paused",
pausedAtStepIndex 0, stepOutputs [""]) unchanged.

---

Nitpick comments:
In `@src/components/ai-chat/AIChatWorkflowPanel.tsx`:
- Around line 13-15: The exported React component AIChatWorkflowPanel lacks an
explicit return type; update its signature to include a React return annotation
(e.g., : JSX.Element or : React.ReactElement) so it complies with the project's
strict TS rules—keep the body using useWorkflowPanelLogic() and returning
<WorkflowPanelForm {...logic} /> unchanged and ensure React is imported if
needed for the chosen return type.

In `@src/hooks/useWorkflowDraft.ts`:
- Around line 19-28: Add a concise TSDoc block above the emptyDraft function
describing its purpose and return value: note that emptyDraft constructs a new
WorkflowDefinition skeleton with a generated id (uses newWorkflowId()), an empty
name, a single empty step (step id generated via newWorkflowId(), empty title
and instruction), and populated createdAt/updatedAt timestamps; include the
return type WorkflowDefinition in the doc and a short summary so future
maintainers understand the function's role.

In `@src/hooks/useWorkflowRunSession.ts`:
- Around line 111-116: The stepOutputs initialisation logic is duplicated
between the setProgress call and runWorkflowExecution call; extract the
expression Array.from({ length: validSteps.length }, () => "") or the resolved
initialOutputs fallback into a single variable (e.g., computedStepOutputs)
within useWorkflowRunSession, then pass that variable into both setProgress
(where stepOutputs is assigned) and runWorkflowExecution so both use the same
source of truth (update references to stepOutputs, initialOutputs, validSteps,
setProgress, and runWorkflowExecution accordingly).

In `@src/lib/workflow/formatWorkflowNoteMarkdown.test.ts`:
- Around line 4-32: Add a regression test to ensure formatWorkflowNoteMarkdown
falls back to "(empty)" when a step is marked "done" but its output is an empty
string: create a new it block calling formatWorkflowNoteMarkdown with
stepStatuses: ["done"], stepOutputs: [""], and assert the returned markdown
contains the "### ☑ 1. <title>" marker and the literal "(empty)". Reference the
existing test file's describe block for formatWorkflowNoteMarkdown and mirror
the structure of the other cases (title, stepTitles, streamingStepIndex null)
when adding this assertion.

In `@src/lib/workflow/runWorkflowExecution.ts`:
- Around line 100-113: Add TSDoc comments for the internal helper
normalizeStepOutputs and the buildStatuses variable to improve readability:
above function normalizeStepOutputs document the purpose, params
(initialOutputs: string[], stepsLength: number), and return type (string[]) and
describe that it pads with empty strings or trims to match stepsLength; for
buildStatuses add a brief TSDoc describing what the structure represents (e.g.,
mapping of step indices to their current status or an array of statuses used
during workflow execution) and the expected value types; mark both with
`@internal` if you want to indicate they are not part of the public API.
- Line 201: Replace the direct use of AbortSignal.any with the existing
mergeAbortSignals utility: locate the line creating the merged signal (currently
using AbortSignal.any with workflowSignal and stepController.signal) and call
mergeAbortSignals instead, passing the external workflowSignal and the
stepController (or its signal as mergeAbortSignals expects) so undefined signals
are handled consistently; update the reference where merged is used to rely on
the new merged signal returned by mergeAbortSignals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 98aef381-c041-410c-aa8b-7583b06c1ef3

📥 Commits

Reviewing files that changed from the base of the PR and between a5dd4fc and 899e91e.

📒 Files selected for processing (29)
  • src/components/ai-chat/AIChatPanelContent.tsx
  • src/components/ai-chat/AIChatViewTabs.test.tsx
  • src/components/ai-chat/AIChatViewTabs.tsx
  • src/components/ai-chat/AIChatWorkflowPanel.tsx
  • src/components/ai-chat/WorkflowPanelForm.tsx
  • src/components/ai-chat/WorkflowPanelMetaSection.tsx
  • src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx
  • src/components/ai-chat/workflowPanelTypes.ts
  • src/hooks/useWorkflowDraft.ts
  • src/hooks/useWorkflowPanelLogic.ts
  • src/hooks/useWorkflowRunSession.ts
  • src/hooks/workflowRunOutcomeHandlers.ts
  • src/i18n/locales/en/aiChat.json
  • src/i18n/locales/ja/aiChat.json
  • src/lib/claudeCode/runQueryToCompletion.test.ts
  • src/lib/claudeCode/runQueryToCompletion.ts
  • src/lib/claudeCode/streamClaudeQuery.ts
  • src/lib/workflow/buildWorkflowStepPrompt.test.ts
  • src/lib/workflow/buildWorkflowStepPrompt.ts
  • src/lib/workflow/formatWorkflowNoteMarkdown.test.ts
  • src/lib/workflow/formatWorkflowNoteMarkdown.ts
  • src/lib/workflow/newWorkflowId.ts
  • src/lib/workflow/parseWorkflowDefinitionImport.test.ts
  • src/lib/workflow/parseWorkflowDefinitionImport.ts
  • src/lib/workflow/runWorkflowExecution.test.ts
  • src/lib/workflow/runWorkflowExecution.ts
  • src/lib/workflow/templates.ts
  • src/lib/workflow/types.ts
  • src/stores/workflowDefinitionsStore.ts

Comment thread src/components/ai-chat/AIChatPanelContent.tsx Outdated
Comment thread src/components/ai-chat/WorkflowPanelForm.tsx
Comment thread src/components/ai-chat/WorkflowPanelMetaSection.tsx
Comment thread src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx
Comment thread src/hooks/useWorkflowDraft.ts
Comment thread src/lib/claudeCode/streamClaudeQuery.ts Outdated
Comment thread src/lib/claudeCode/streamClaudeQuery.ts Outdated
Comment thread src/lib/workflow/formatWorkflowNoteMarkdown.ts Outdated
Comment thread src/lib/workflow/parseWorkflowDefinitionImport.ts
Comment thread src/lib/workflow/runWorkflowExecution.test.ts
…deQuery)

- Gate Run/Resume on desktop + editor; sync stop/error UI state and streaming guard.
- Keep workflow panel mounted after first visit so tab switches do not reset runs.
- Meta/steps a11y (labels, aria-labels); empty done outputs in note markdown; dedupe import IDs.
- streamClaudeQuery: buffer pre-request tool events; cleanup listeners if subscribe fails.
- Defer revokeObjectURL after export; tighten pause test to use aborted step signal.

Made-with: Cursor
@otomatty
Copy link
Copy Markdown
Owner Author

otomatty commented Apr 5, 2026

CodeRabbit 指摘への対応をコミットしました(83bb2e1)。

  • ワークフロータブを一度開いたあとはパネルをマウントしたままにし、タブ切替で実行状態がリセットされないようにしました(AIChatPanelContent)。
  • Run/Resume は isTauriDesktop() && isEditor と整合させました。
  • 停止/エラー時は stepStatuses / currentStepStreaming も終端向けに揃え、進捗表示と整合させました。
  • メタ/ステップのラベル・aria、空の done 出力、JSON インポートの重複 ID、streamClaudeQuery の購読失敗時クリーンアップと pre-request tool イベント、エクスポート後の revokeObjectURL、pause テストを反映しました。

useWorkflowPanelLogic はドラフトと実行フックを薄く合成する意図のままにしています。

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 5, 2026

@otomatty ご対応ありがとうございます!コミット 83bb2e1 の内容を確認しました。以下の修正がすべて反映されていますね:

  • パネルマウント維持 (AIChatPanelContent): タブ切替時に実行状態がリセットされない対応
  • Run/Resume ガード: isTauriDesktop() && isEditor との整合
  • 終端状態の統一: 停止・エラー時の stepStatuses / currentStepStreaming の整合
  • 細部の品質向上: ラベル・aria 属性、空の done 出力、JSON インポートの重複 ID 対応、streamClaudeQuery クリーンアップ、revokeObjectURL、pause テスト追加
  • useWorkflowPanelLogic の設計意図(薄い合成フック)も確認しました

最新の変更に対して再レビューを実施します。


🧠 Learnings used
Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-14T12:40:46.017Z
Learning: PR titles must follow Conventional Commits format (e.g., `feat(admin): AIモデル管理UI拡張 (`#218`)`) and accurately represent the changes. When using Cursor Cloud Agent, include instructions in the prompt to ensure PR titles follow Conventional Commits format.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-14T12:40:46.017Z
Learning: PR review focus areas: check for security and performance impacts, breaking changes in public APIs or types, proper error handling and logging, and ensure comments in Japanese/English match project tone.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-29T06:01:47.717Z
Learning: PR titles must follow Conventional Commits format and accurately describe the changes (e.g., `feat(admin): AIモデル管理UI拡張 (`#218`)`). Avoid unrelated or generic descriptions.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx (2)

73-80: <textarea>@zedi/ui のコンポーネントに統一することを検討してください。

Input@zedi/ui からインポートしていますが、textarea はインラインスタイルで実装されています。UI ライブラリに Textarea コンポーネントがあれば、そちらを使うとスタイルの一貫性と保守性が向上します。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx` around lines 73 -
80, Replace the native <textarea> with the Textarea component from `@zedi/ui`:
import Textarea (or the library's textarea export) and use it in place of the
inline element inside WorkflowPanelStepsAndProgress, passing the same props
(className or variant, value={step.instruction},
placeholder={t("aiChat.workflow.stepInstructionPlaceholder")},
aria-label={t("aiChat.workflow.stepInstructionAria", { n: index + 1 })},
disabled={running}, and onChange to call updateStep(index, { instruction:
e.target.value }) or adapt to the Textarea onChange signature), so styling and
behavior remain consistent while removing the raw <textarea>.

93-94: 進捗ステータスの絵文字にスクリーンリーダー向けの補足を検討してください。

絵文字(☑, 🔄, ⚠️, ⬜)は視覚的には分かりやすいですが、スクリーンリーダーによっては意図通りに読み上げられない場合があります。aria-label または sr-only テキストでステータスを補足すると、アクセシビリティが向上します。

♻️ 改善例
 {(activeRunSteps ?? draft.steps).map((s, i) => {
   const st = progress.stepStatuses[i] ?? "pending";
   const mark =
     st === "done" ? "☑" : st === "running" ? "🔄" : st === "error" ? "⚠️" : "⬜";
+  const statusLabel =
+    st === "done"
+      ? t("aiChat.workflow.statusDone")
+      : st === "running"
+        ? t("aiChat.workflow.statusRunning")
+        : st === "error"
+          ? t("aiChat.workflow.statusError")
+          : t("aiChat.workflow.statusPending");
   return (
-    <li key={s.id} className="flex flex-col gap-0.5">
+    <li key={s.id} className="flex flex-col gap-0.5" aria-label={statusLabel}>
       <span>
         {mark} {s.title || t("aiChat.workflow.unnamedStep", { n: i + 1 })}
       </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx` around lines 93 -
94, The emoji-only status marker (variable mark in
WorkflowPanelStepsAndProgress.tsx) lacks accessible text for screen readers;
update the markup that renders mark so the emoji is wrapped with an accessible
label (e.g., use role="img" plus aria-label describing each status like "done",
"running", "error", "pending" or add a visually hidden span/sr-only text next to
the emoji). Locate where mark is output and replace the plain emoji render with
a span or element that includes the emoji visually and provides a descriptive
aria-label or sr-only text so assistive technologies convey the status.
src/hooks/useWorkflowDraft.ts (2)

99-126: FileReader.onerror のハンドリングを検討してください。

reader.onload のみを設定していますが、ファイル読み込みエラー時に reader.onerror が呼ばれる可能性があります。現状では読み込みエラーが無視され、ユーザーにフィードバックが表示されません。

♻️ 修正案
       reader.onload = () => {
         try {
           const parsed = JSON.parse(String(reader.result)) as unknown;
           const { name, steps } = parseWorkflowDefinitionImport(parsed);
           const now = Date.now();
           setDraft({
             id: newWorkflowId(),
             name,
             steps,
             createdAt: now,
             updatedAt: now,
           });
           setSelectedSavedId("");
           toast({ title: t("aiChat.workflow.imported") });
         } catch {
           toast({ title: t("aiChat.workflow.importFailed"), variant: "destructive" });
         }
       };
+      reader.onerror = () => {
+        toast({ title: t("aiChat.workflow.importFailed"), variant: "destructive" });
+      };
       reader.readAsText(file);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useWorkflowDraft.ts` around lines 99 - 126, The onImportFile
callback installs reader.onload but ignores read errors; add a reader.onerror
handler in the onImportFile function (the useCallback that defines onImportFile)
to handle FileReader failures by showing the same failure toast (toast({ title:
t("aiChat.workflow.importFailed"), variant: "destructive" })), clear/reset any
relevant state (e.g., e.target.value and avoid leaving partial draft), and
optionally log/inspect reader.error for diagnostics; ensure reader.onerror is
set before calling reader.readAsText(file).

34-163: useWorkflowDraft の戻り値型について

コーディングガイドラインでは明示的な型注釈が推奨されていますが、Retrieved learnings によると useWorkflowPanelLogicReturnType<typeof useWorkflowPanelLogic> でパブリック契約を定義しています。この useWorkflowDraft も同様のパターンを適用する場合、戻り値型を省略することは許容されます。

ただし、戻り値オブジェクトに t (TFunction) が含まれていますが、これは内部実装の詳細であり、消費側で使用する必要があるか確認してください。不要であれば戻り値から除外することで API サーフェスを小さくできます。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useWorkflowDraft.ts` around lines 34 - 163, The return value of
useWorkflowDraft currently exposes the translation function t (from
useTranslation) which is an internal implementation detail; inspect all call
sites of useWorkflowDraft to confirm whether they actually consume t, and if
not, remove t from the returned object (and from the return type if you add an
explicit one) to shrink the public API surface; keep use of t inside functions
like loadTemplate, saveCustom, onImportFile, deleteSaved, and exportJson, but
stop returning t from useWorkflowDraft (function name: useWorkflowDraft; symbol:
t) or, if you prefer the existing pattern, explicitly type the hook's return as
ReturnType<typeof useWorkflowPanelLogic> after adjusting that reference to match
the reduced public shape.
src/components/ai-chat/AIChatPanelContent.tsx (1)

17-19: 追加した TS シンボルの型は明示しておきたいです。

今回の追加分は lazy(...)useState(...) の推論に乗っているので、このリポジトリの strict 前提に合わせるならここも型を明示した方が揃います。

型を明示する最小差分
-const AIChatWorkflowPanel = lazy(() =>
+const AIChatWorkflowPanel = lazy<
+  (typeof import("./AIChatWorkflowPanel"))["AIChatWorkflowPanel"]
+>(() =>
   import("./AIChatWorkflowPanel").then((m) => ({ default: m.AIChatWorkflowPanel })),
 );
@@
-  const [keepWorkflowMounted, setKeepWorkflowMounted] = useState(
+  const [keepWorkflowMounted, setKeepWorkflowMounted] = useState<boolean>(
     () => activeViewTab === "workflow",
   );

As per coding guidelines, **/*.{ts,tsx}: TypeScript strict mode enforced; any type is forbidden. All functions, variables, and types must have explicit type annotations.

Also applies to: 95-97

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai-chat/AIChatPanelContent.tsx` around lines 17 - 19, The lazy
import and inferred state types need explicit TypeScript annotations to satisfy
strict mode: annotate the AIChatWorkflowPanel lazy import with an explicit
React.LazyExoticComponent type (e.g.,
React.LazyExoticComponent<React.ComponentType<...>> or React.FC with the correct
props) where AIChatWorkflowPanel is declared, and add explicit generic type
parameters for the useState calls referenced around lines 95–97 (e.g.,
useState<YourStateType>(...)) so no types are left to inference; update the
symbol AIChatWorkflowPanel and the related useState variables to include those
explicit types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/ai-chat/AIChatPanelContent.tsx`:
- Around line 17-19: The lazy import and inferred state types need explicit
TypeScript annotations to satisfy strict mode: annotate the AIChatWorkflowPanel
lazy import with an explicit React.LazyExoticComponent type (e.g.,
React.LazyExoticComponent<React.ComponentType<...>> or React.FC with the correct
props) where AIChatWorkflowPanel is declared, and add explicit generic type
parameters for the useState calls referenced around lines 95–97 (e.g.,
useState<YourStateType>(...)) so no types are left to inference; update the
symbol AIChatWorkflowPanel and the related useState variables to include those
explicit types.

In `@src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx`:
- Around line 73-80: Replace the native <textarea> with the Textarea component
from `@zedi/ui`: import Textarea (or the library's textarea export) and use it in
place of the inline element inside WorkflowPanelStepsAndProgress, passing the
same props (className or variant, value={step.instruction},
placeholder={t("aiChat.workflow.stepInstructionPlaceholder")},
aria-label={t("aiChat.workflow.stepInstructionAria", { n: index + 1 })},
disabled={running}, and onChange to call updateStep(index, { instruction:
e.target.value }) or adapt to the Textarea onChange signature), so styling and
behavior remain consistent while removing the raw <textarea>.
- Around line 93-94: The emoji-only status marker (variable mark in
WorkflowPanelStepsAndProgress.tsx) lacks accessible text for screen readers;
update the markup that renders mark so the emoji is wrapped with an accessible
label (e.g., use role="img" plus aria-label describing each status like "done",
"running", "error", "pending" or add a visually hidden span/sr-only text next to
the emoji). Locate where mark is output and replace the plain emoji render with
a span or element that includes the emoji visually and provides a descriptive
aria-label or sr-only text so assistive technologies convey the status.

In `@src/hooks/useWorkflowDraft.ts`:
- Around line 99-126: The onImportFile callback installs reader.onload but
ignores read errors; add a reader.onerror handler in the onImportFile function
(the useCallback that defines onImportFile) to handle FileReader failures by
showing the same failure toast (toast({ title:
t("aiChat.workflow.importFailed"), variant: "destructive" })), clear/reset any
relevant state (e.g., e.target.value and avoid leaving partial draft), and
optionally log/inspect reader.error for diagnostics; ensure reader.onerror is
set before calling reader.readAsText(file).
- Around line 34-163: The return value of useWorkflowDraft currently exposes the
translation function t (from useTranslation) which is an internal implementation
detail; inspect all call sites of useWorkflowDraft to confirm whether they
actually consume t, and if not, remove t from the returned object (and from the
return type if you add an explicit one) to shrink the public API surface; keep
use of t inside functions like loadTemplate, saveCustom, onImportFile,
deleteSaved, and exportJson, but stop returning t from useWorkflowDraft
(function name: useWorkflowDraft; symbol: t) or, if you prefer the existing
pattern, explicitly type the hook's return as ReturnType<typeof
useWorkflowPanelLogic> after adjusting that reference to match the reduced
public shape.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02dd792e-4d50-43af-a6f7-6c327c6d30cc

📥 Commits

Reviewing files that changed from the base of the PR and between 899e91e and 83bb2e1.

📒 Files selected for processing (14)
  • src/components/ai-chat/AIChatPanelContent.tsx
  • src/components/ai-chat/WorkflowPanelForm.tsx
  • src/components/ai-chat/WorkflowPanelMetaSection.tsx
  • src/components/ai-chat/WorkflowPanelStepsAndProgress.tsx
  • src/hooks/useWorkflowDraft.ts
  • src/hooks/workflowRunOutcomeHandlers.ts
  • src/i18n/locales/en/aiChat.json
  • src/i18n/locales/ja/aiChat.json
  • src/lib/claudeCode/streamClaudeQuery.ts
  • src/lib/workflow/formatWorkflowNoteMarkdown.test.ts
  • src/lib/workflow/formatWorkflowNoteMarkdown.ts
  • src/lib/workflow/parseWorkflowDefinitionImport.test.ts
  • src/lib/workflow/parseWorkflowDefinitionImport.ts
  • src/lib/workflow/runWorkflowExecution.test.ts
✅ Files skipped from review due to trivial changes (3)
  • src/lib/workflow/parseWorkflowDefinitionImport.test.ts
  • src/lib/workflow/formatWorkflowNoteMarkdown.test.ts
  • src/i18n/locales/ja/aiChat.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/ai-chat/WorkflowPanelMetaSection.tsx
  • src/lib/workflow/runWorkflowExecution.test.ts
  • src/lib/workflow/parseWorkflowDefinitionImport.ts
  • src/lib/claudeCode/streamClaudeQuery.ts

devin-ai-integration[bot]

This comment was marked as resolved.

- Use h-full on workflow panel wrapper so ScrollArea gets a bounded height.
- Pause outcome carries pausedStepId + stepOutputsById; resume resolves index by id.
- Toast when paused step is missing after draft edits; extract resolveResumeFromPaused.

Made-with: Cursor
@otomatty
Copy link
Copy Markdown
Owner Author

otomatty commented Apr 5, 2026

Devin レビュー2件を反映しました(f823f68)。再レビューをお願いします。

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 5, 2026

@otomatty Devin レビューへのご対応ありがとうございます!コミット f823f68 を含む最新の変更に対して再レビューを実施します。


🧠 Learnings used
Learnt from: otomatty
Repo: otomatty/zedi PR: 477
File: src/hooks/useWorkflowPanelLogic.ts:13-20
Timestamp: 2026-04-05T01:08:41.203Z
Learning: In `otomatty/zedi`, `useWorkflowPanelLogic` in `src/hooks/useWorkflowPanelLogic.ts` is intentionally kept as a thin composition hook (spreading `useWorkflowDraft` + `useWorkflowRunSession`) for `AIChatWorkflowPanel`. The author has decided NOT to add an explicit return type annotation on the function itself; the public contract is instead captured via `WorkflowPanelFormProps = ReturnType<typeof useWorkflowPanelLogic>` in `src/components/ai-chat/workflowPanelTypes.ts`. Do not flag the missing explicit return type on this hook in future reviews.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-14T12:40:46.017Z
Learning: PR titles must follow Conventional Commits format (e.g., `feat(admin): AIモデル管理UI拡張 (`#218`)`) and accurately represent the changes. When using Cursor Cloud Agent, include instructions in the prompt to ensure PR titles follow Conventional Commits format.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

- Keep AIChatInput mounted when workflow tab is active (hidden) so drafts are not cleared.
- On completed/stopped/error, keep activeRunSteps as validSteps so progress matches stepStatuses.

Made-with: Cursor
@otomatty
Copy link
Copy Markdown
Owner Author

otomatty commented Apr 5, 2026

CodeRabbit / Devin の未返信コメントに対応しました(07128a4)。再レビューをお願いします。

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 5, 2026

@otomatty ご対応ありがとうございます!コミット 07128a4 を含む最新の変更に対して再レビューを実施します。


🧠 Learnings used
Learnt from: otomatty
Repo: otomatty/zedi PR: 477
File: src/hooks/useWorkflowPanelLogic.ts:13-20
Timestamp: 2026-04-05T01:08:41.203Z
Learning: In `otomatty/zedi`, `useWorkflowPanelLogic` in `src/hooks/useWorkflowPanelLogic.ts` is intentionally kept as a thin composition hook (spreading `useWorkflowDraft` + `useWorkflowRunSession`) for `AIChatWorkflowPanel`. The author has decided NOT to add an explicit return type annotation on the function itself; the public contract is instead captured via `WorkflowPanelFormProps = ReturnType<typeof useWorkflowPanelLogic>` in `src/components/ai-chat/workflowPanelTypes.ts`. Do not flag the missing explicit return type on this hook in future reviews.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-14T12:40:46.017Z
Learning: PR titles must follow Conventional Commits format (e.g., `feat(admin): AIモデル管理UI拡張 (`#218`)`) and accurately represent the changes. When using Cursor Cloud Agent, include instructions in the prompt to ensure PR titles follow Conventional Commits format.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-29T06:01:47.717Z
Learning: Review PRs for security and performance impact, API/type breaking changes, appropriate error handling and logging, and adherence to project communication tone in Japanese and English.

Learnt from: CR
Repo: otomatty/zedi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-14T12:40:46.017Z
Learning: PR review focus areas: check for security and performance impacts, breaking changes in public APIs or types, proper error handling and logging, and ensure comments in Japanese/English match project tone.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/components/ai-chat/AIChatPanelContent.tsx (1)

162-171: 下書き保持の対応が適切です。

hidden による非表示で AIChatInput がマウント維持され、未送信テキストが保持されます。

検討: フォーカス管理の edge case

useAIChatInput.ts 内の prefillNonce / focusEditorNonce 監視 effect は、nonce 変更時に requestAnimationFrame で hidden 要素へ focus() を呼びます。ワークフロータブ操作中に nonce が変わると、予期せずフォーカスが奪われる可能性があります。

現状、nonce はチャット操作時のみ更新されるため実害は低いですが、将来的には以下のようなガードを検討できます:

🛡️ 防御的なガードの例 (useAIChatInput.ts)
useEffect(() => {
  if (prefillNonce === undefined) return;
  const editor = editorRef.current;
  if (!editor || editor.offsetParent === null) return; // hidden時はスキップ
  // ... existing logic
}, [prefillNonce, prefillText, syncRefsFromDOM, checkEmpty, editorRef]);

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ai-chat/AIChatPanelContent.tsx` around lines 162 - 171, The
focus effect in useAIChatInput (the useEffect watching
prefillNonce/prefillText/focusEditorNonce and using requestAnimationFrame to
call editor.focus()) can steal focus when the AIChatInput is hidden via CSS;
update that effect to early-return when the editor is not visible (e.g., check
editorRef.current and visibility like editor.offsetParent === null or
!editor.isConnected/other DOM visibility guard) so the requestAnimationFrame
focus call is skipped for hidden inputs, keeping the existing focus behavior
when visible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/ai-chat/AIChatPanelContent.tsx`:
- Around line 162-171: The focus effect in useAIChatInput (the useEffect
watching prefillNonce/prefillText/focusEditorNonce and using
requestAnimationFrame to call editor.focus()) can steal focus when the
AIChatInput is hidden via CSS; update that effect to early-return when the
editor is not visible (e.g., check editorRef.current and visibility like
editor.offsetParent === null or !editor.isConnected/other DOM visibility guard)
so the requestAnimationFrame focus call is skipped for hidden inputs, keeping
the existing focus behavior when visible.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dbb3d851-f2fb-4fa5-9f80-657e28814ee1

📥 Commits

Reviewing files that changed from the base of the PR and between f823f68 and 07128a4.

📒 Files selected for processing (2)
  • src/components/ai-chat/AIChatPanelContent.tsx
  • src/hooks/workflowRunOutcomeHandlers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/workflowRunOutcomeHandlers.ts

@otomatty otomatty merged commit 8913d97 into develop Apr 5, 2026
10 checks passed
@otomatty otomatty deleted the feature/462-claude-workflow-panel branch April 5, 2026 02:31
@coderabbitai coderabbitai Bot mentioned this pull request Apr 6, 2026
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants