feat(ai-chat): add Claude Code multi-step workflow panel (#462)#477
Conversation
- 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
📝 WalkthroughWalkthroughAIチャットに「ワークフロー」タブを追加し、ワークフローの作成/編集/インポート/エクスポート/保存、テンプレート、実行(実行/一時停止/再開/停止)、およびClaude Codeストリーミング統合を実装。ワークフローパネルは遅延ロードされ、初回表示後はマウントを維持するラッチを導入。 Changes
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: 最終状態反映
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 等)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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.
| }; | ||
|
|
||
| workflowAbortRef.current = new AbortController(); | ||
| baseSnapshotRef.current = pageContext?.pageFullContent ?? ""; |
There was a problem hiding this comment.
runExecution が resume モードで呼び出された際にも baseSnapshotRef.current が現在のノート内容(既にワークフローブロックが含まれている状態)で更新されてしまうため、再開するたびにノート内にワークフローのステータスブロックが重複して挿入される問題があります。fresh モードの時のみベーススナップショットを更新するように修正すべきです。
| baseSnapshotRef.current = pageContext?.pageFullContent ?? ""; | |
| if (mode === "fresh") { | |
| baseSnapshotRef.current = pageContext?.pageFullContent ?? ""; | |
| } |
There was a problem hiding this comment.
対応しました。 は mode === "fresh" のときだけ現在のノート内容で更新し、再開時は既存スナップショットのままにしています(899e91e)。
There was a problem hiding this comment.
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.
| case "error": | ||
| setActiveRunSteps(null); | ||
| toast({ | ||
| title: t("aiChat.workflow.error", { message: result.error }), | ||
| variant: "destructive", | ||
| }); | ||
| return; |
There was a problem hiding this comment.
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;There was a problem hiding this comment.
対応しました。 時は setPausedState(null) と setProgress で phase: "aborted" にし、実行中表示が残らないようにしました(899e91e)。
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
There was a problem hiding this comment.
対応しました。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] ?? ""); |
There was a problem hiding this comment.
対応しました。prior は stepOutputs.slice(0, i) に置き換えました(899e91e)。
| }); | ||
| const base = baseContentBeforeWorkflow.trimEnd(); | ||
| const full = base.length > 0 ? `${base}\n\n${block}` : block; | ||
| onNoteMarkdown(full); |
There was a problem hiding this comment.
ご指摘のとおり、大きいノートではフル再構築がコストになり得ます。現状は仕様として全ノートを渡していますが、ワークフローブロックの差分更新は別途パフォーマンス改善として検討します。
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
| resumePartial: i === startStepIndex ? resumePartialForCurrentStep : undefined, | |
| resumeFromPartial: i === startStepIndex ? resumePartialForCurrentStep : undefined, |
There was a problem hiding this comment.
Fixed: call site now uses resumeFromPartial to match buildWorkflowStepPrompt (899e91e).
| 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Addressed in the same commit: error outcome clears pausedState and sets progress.phase to aborted (899e91e).
| case "stopped": | ||
| setActiveRunSteps(null); | ||
| setProgress((p) => (p ? { ...p, phase: "aborted" } : null)); | ||
| toast({ title: t("aiChat.workflow.stopped") }); | ||
| return; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Addressed: stopped outcome now also calls setPausedState(null) so resume is not offered after stop (899e91e).
| case "error": | ||
| setActiveRunSteps(null); | ||
| toast({ | ||
| title: t("aiChat.workflow.error", { message: result.error }), | ||
| variant: "destructive", | ||
| }); | ||
| return; |
There was a problem hiding this comment.
🔴 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.
| 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; |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
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
|
レビューコメントへの対応をコミットしました(899e91e)。最新の変更に対する再レビューをお願いします。 @coderabbitai review |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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;anytype 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
📒 Files selected for processing (29)
src/components/ai-chat/AIChatPanelContent.tsxsrc/components/ai-chat/AIChatViewTabs.test.tsxsrc/components/ai-chat/AIChatViewTabs.tsxsrc/components/ai-chat/AIChatWorkflowPanel.tsxsrc/components/ai-chat/WorkflowPanelForm.tsxsrc/components/ai-chat/WorkflowPanelMetaSection.tsxsrc/components/ai-chat/WorkflowPanelStepsAndProgress.tsxsrc/components/ai-chat/workflowPanelTypes.tssrc/hooks/useWorkflowDraft.tssrc/hooks/useWorkflowPanelLogic.tssrc/hooks/useWorkflowRunSession.tssrc/hooks/workflowRunOutcomeHandlers.tssrc/i18n/locales/en/aiChat.jsonsrc/i18n/locales/ja/aiChat.jsonsrc/lib/claudeCode/runQueryToCompletion.test.tssrc/lib/claudeCode/runQueryToCompletion.tssrc/lib/claudeCode/streamClaudeQuery.tssrc/lib/workflow/buildWorkflowStepPrompt.test.tssrc/lib/workflow/buildWorkflowStepPrompt.tssrc/lib/workflow/formatWorkflowNoteMarkdown.test.tssrc/lib/workflow/formatWorkflowNoteMarkdown.tssrc/lib/workflow/newWorkflowId.tssrc/lib/workflow/parseWorkflowDefinitionImport.test.tssrc/lib/workflow/parseWorkflowDefinitionImport.tssrc/lib/workflow/runWorkflowExecution.test.tssrc/lib/workflow/runWorkflowExecution.tssrc/lib/workflow/templates.tssrc/lib/workflow/types.tssrc/stores/workflowDefinitionsStore.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
|
CodeRabbit 指摘への対応をコミットしました(83bb2e1)。
@coderabbitai review |
|
最新の変更に対して再レビューを実施します。 🧠 Learnings used✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
🧹 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 によると
useWorkflowPanelLogicはReturnType<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;anytype 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
📒 Files selected for processing (14)
src/components/ai-chat/AIChatPanelContent.tsxsrc/components/ai-chat/WorkflowPanelForm.tsxsrc/components/ai-chat/WorkflowPanelMetaSection.tsxsrc/components/ai-chat/WorkflowPanelStepsAndProgress.tsxsrc/hooks/useWorkflowDraft.tssrc/hooks/workflowRunOutcomeHandlers.tssrc/i18n/locales/en/aiChat.jsonsrc/i18n/locales/ja/aiChat.jsonsrc/lib/claudeCode/streamClaudeQuery.tssrc/lib/workflow/formatWorkflowNoteMarkdown.test.tssrc/lib/workflow/formatWorkflowNoteMarkdown.tssrc/lib/workflow/parseWorkflowDefinitionImport.test.tssrc/lib/workflow/parseWorkflowDefinitionImport.tssrc/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
- 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
|
Devin レビュー2件を反映しました(f823f68)。再レビューをお願いします。 @coderabbitai review |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
- 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
|
CodeRabbit / Devin の未返信コメントに対応しました(07128a4)。再レビューをお願いします。 @coderabbitai review |
|
🧠 Learnings used✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
🧹 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
📒 Files selected for processing (2)
src/components/ai-chat/AIChatPanelContent.tsxsrc/hooks/workflowRunOutcomeHandlers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/hooks/workflowRunOutcomeHandlers.ts
概要
AI チャットに ワークフロー タブを追加し、Claude Code 経由で 複数ステップのワークフロー を定義・実行できるようにしました。ステップごとのプロンプト生成、ノートへの Markdown 追記、ストリーミング、一時停止・再開・停止、テンプレートと JSON インポート、定義のローカル永続化(Zustand persist)を含みます。
変更点
src/components/ai-chat/AIChatWorkflowPanel、フォーム・メタ・ステップ/進捗 UIsrc/hooks/useWorkflowDraft/useWorkflowRunSession/useWorkflowPanelLogic、workflowRunOutcomeHandlersでドラフト・実行セッション・結果ハンドリングを集約src/lib/claudeCode/streamClaudeQueryを追加し、runQueryToCompletionはストリーミング経路に委譲src/lib/workflow/runWorkflowExecution(ループ分割)、buildWorkflowStepPrompt、formatWorkflowNoteMarkdown、JSON 定義パース、単体テストsrc/stores/workflowDefinitionsStore(保存ワークフロー)src/i18n/locales/en|ja/src/components/ai-chat/src/hooks/src/lib/claudeCode/src/lib/workflow/src/stores/src/i18n/変更の種類
テスト方法
bun run dev等)。bun run test:run(またはsrc/lib/workflow/**/*.test.tsと関連テスト)。DB マイグレーション・新規環境変数・
package.json変更はありません。チェックリスト
eslint .は 0 errors、warnings のみ既存含む)スクリーンショット(UI 変更がある場合)
レビュアー向け: ワークフロータブの Before/After をこの PR に添付してください。
関連 Issue
Closes #462
Made with Cursor
Summary by CodeRabbit
New Features
Refactor
Tests
Localization