Skip to content

Release: develop を main にマージ#492

Merged
otomatty merged 18 commits into
mainfrom
develop
Apr 6, 2026
Merged

Release: develop を main にマージ#492
otomatty merged 18 commits into
mainfrom
develop

Conversation

@otomatty
Copy link
Copy Markdown
Owner

@otomatty otomatty commented Apr 6, 2026

概要

develop の最新を main に取り込むリリース用 PR です。Tauri / Claude Code 連携、AI チャット・エディタ拡張、MCP、HTML サニタイズの強化、各種 UI 改善などを含みます。

Release PR merging the current develop into main.

変更点

  • デスクトップ / Claude Code: Tauri 2.0、サイドカー・デスクトップコマンド、エージェント・スラッシュコマンド、Claude Code マルチステップワークフローパネル、MCP サーバー連携
  • AI: Claude Code プロバイダとプロバイダ統合、サイドパネル・エージェントチャット、AI 設定モード、ワークスペース連携ノート(@file: / cwd)
  • エディタ / UI: 統合プロバイダ・実行可能コードブロック、WikiLink ホバーカードプレビュー、YouTube 埋め込み、モバイルでカード上スクロール不具合の修正、ページ一覧のコンテンツプレビュー同期
  • セキュリティ: カスタム HTML サニタイザを DOMPurify に置き換え
  • 依存関係: @anthropic-ai/sdk などの更新

変更の種類

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

テスト方法

  1. CI(lint / test 等)がグリーンであることを確認する
  2. マージ後、本番相当環境で主要フロー(ログイン、ノート編集、AI チャット、デスクトップ利用時は Tauri 周り)をスポット確認する

チェックリスト

  • テストがすべてパスする(CI を確認)
  • Lint エラーがない(CI を確認)
  • 必要に応じてドキュメントを更新した
  • コミットメッセージが Conventional Commits に従っている

マージ方法

プロジェクト方針に合わせ、Create a merge commit でのマージを推奨します。

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

UI 変更を含みます。必要に応じてリリースノート用に主要画面のキャプチャを添付してください。

関連 Issue


Open with Devin

Summary by CodeRabbit

  • New Features

    • デスクトップアプリ(Tauri)対応とClaude Codeサイドカー統合
    • 実行可能なコードブロック(実行・解釈機能)とワークフロー実行パネル
    • MCPサーバーの追加・管理、ノート単位ワークスペース連携、ファイルプレビュー
    • YouTube埋め込み・外部ファイル参照・スラッシュエージェントコマンド
  • 改善

    • AIモード選択/モデル選択UI・ヘッダ表示の更新(モードバッジ、MCPステータス表示)
    • コピー/ノート挿入操作、モバイル長押しなど操作性向上
    • ページのコンテンツプレビュー生成と永続化の改善
  • Bug Fixes

    • サーバー書き込みをトランザクション化し整合性を向上

github-actions Bot and others added 17 commits April 1, 2026 09:00
chore: sync main to develop
* feat(tauri): tauri 2.0 プロジェクト初期化 (#49)

- src-tauri/ ディレクトリを作成し Rust バックエンドを構成
- tauri.conf.json でアプリ設定(ポート30000, WebView ウィンドウ等)
- vite.config.ts に Tauri 互換設定を追加(strictPort, envPrefix, build target 等)
- tauri-plugin-shell, tauri-plugin-store を先行導入(Phase F 準備)
- アプリアイコンを既存 PWA アイコンから生成
- package.json に tauri:dev, tauri:build スクリプトを追加
- .gitignore に src-tauri/target/, src-tauri/gen/ を追加

Made-with: Cursor

* fix(tauri): app.title フィールドを削除し設定スキーマに準拠

tauri-build が認識しない unknown field `title` エラーを修正。
タイトルは windows[].title で設定済み。

Made-with: Cursor

* fix(tauri): tauri 環境で IndexedDB にフォールバックし起動可能にする

TauriStorageAdapter 未実装 (#50) の間、Tauri 環境でも暫定的に
IndexedDB を使用してアプリが起動・表示できるようにする。

Made-with: Cursor

* docs: add desktop app (Tauri) setup instructions to README

- Desktop セクション追加(前提条件、起動コマンド、Windows 注意事項)
- Tech Stack に Tauri 2.0 を追記、React バージョンを 19 に更新
- Project Structure に src-tauri/ と server/ を追記

Made-with: Cursor

* docs: remove completed tasks from roadmap in README

- Removed "Ghost Link System" and "Semantic Search(ベクトル検索)" from the roadmap section as they are no longer relevant.

* test: update createStorageAdapter test for Tauri IndexedDB fallback

Align with createStorageAdapter behavior when __TAURI_INTERNALS__ is set.

Made-with: Cursor

* chore: ignore Tauri JS packages in knip

@tauri-apps/api and plugins are wired from Rust; not yet imported in src.

Made-with: Cursor

* fix: address PR #464 review comments

- vite: merge process.env with loadEnv for Tauri; envPrefix TAURI_ENV_; build uses same vars
- tauri.conf: remove csp null (use framework default CSP)
- storage: TSDoc for createStorageAdapter; warn once for IndexedDB fallback
- test: afterEach restoreAllMocks; assert warn called once across two calls

Made-with: Cursor

* fix: address PR #464 review comments (README MD028, TAURI_ENV_DEBUG parsing)

Made-with: Cursor
… (#465)

* feat(tauri): add claude code sidecar bridge and desktop commands (#456)

- Add packages/claude-sidecar JSONL bridge for @anthropic-ai/claude-agent-sdk
- Rust: claude_query, claude_abort, claude_status, check_claude_installation
- Emit claude-stream-chunk, claude-stream-complete, claude-error
- Frontend bridge src/lib/claudeCode; externalBin + shell capability
- Build/ensure scripts and README; tests for protocol and bridge

Made-with: Cursor

* fix: address pr review for claude sidecar (#465)

- Rust: UTF-8 line buffer, error string emit, drain pending RPC, optional repo_root, block_in_place write, rpc_json error mapping
- capabilities: shell args false for sidecar
- sidecar: abort emits error not stream-complete; default maxTurns 25; Windows claude check via cmd
- tokio: enable rt for block_in_place

Made-with: Cursor

* fix: address pr #465 review round 2

- tokio: rt-multi-thread for block_in_place safety
- status: rename abortAll to clearAll (tracks only, no abort)
- index: reject duplicate query ids
- build script: shell: true on Windows for bun spawn
- types.ts: bilingual JSDoc per AGENTS.md

Made-with: Cursor
…it (#466)

* feat(ai): claude code provider, unified providers, and ai service split

- Add claude-code provider via Tauri bridge; platform detection (platform.ts)

- Split aiService into direct/server/models modules; registry + OpenAI/Anthropic/Google providers

- AI settings UI: ProviderSelector, useAISettingsForm helpers, i18n

- Wiki/mermaid: unsupported paths for claude code where applicable

- Tests: platform, providers, registry, claudeCodeProvider

Made-with: Cursor

* chore: remove unused aiProviders barrel for knip

src/lib/aiProviders/index.ts was never imported; knip reported it as an unused file.

Made-with: Cursor

* fix: address PR #466 review comments

- claudeCodeProvider: stream wait race, single bridge import incl. claudeAbort, abort captures reqId

- mergeAbortSignals + OpenAI/Anthropic/Google: caller abort and provider.abort both cancel SDK

- isAIConfigured: claude-code gated on Tauri + checkClaudeInstallation

- getAISettingsOrThrow (wiki/mermaid): early return for claude-code before api key check

- JSDoc: fill or remove blank blocks (ai types, aiService, mermaid, useAISettings, aiClient, FetchServerModelsError)

- AIServiceRequest.provider typed as AIProviderType

- tests: mergeAbortSignals.test.ts

Made-with: Cursor

* fix: address PR #466 second-round review comments

- i18n: fix useTranslation("aiSettings") → useTranslation() with aiSettings. prefix
  (ProviderSelector, CapabilityMatrix, ClaudeCodeSection)
- useAISettings: preserve apiKey when switching to claude-code (prevent key erasure)
- claudeCodeProvider: hoist resolveWait/wake to closure; abort() now releases wait
- registry: api_server mode skips requiresApiKey check for availability
- aiServiceDirectProviders: separate system messages into Anthropic system field
- aiServiceModels: runtime type guards (normalizeToAIModel, fetchUsage),
  return type UserTier, explicit getAIAPIBaseUrl return annotation
- aiServiceServer: flush TextDecoder and process trailing SSE buffer after read loop
- platform.test: replace any cast with typed TauriWindow interface

Made-with: Cursor

* fix: address PR #466 third-round review comments

- claudeCodeProvider: listen to signal abort event during await Promise
  to prevent indefinite hang when user cancels during stream wait
- aiService: always fire onComplete callback when for-await loop exits
  (even when fullContent is empty) to prevent UI stuck in streaming state
- registry: remove unused isProviderAvailable export (api_server
  inconsistency and no call sites)

Made-with: Cursor

* fix: route claude-code through callAIService in wiki/mermaid generators

When apiMode is "user_api_key" and provider is "claude-code", generators
previously fell into the direct-SDK switch where claude-code throws
"not supported". Now check settings.provider === "claude-code" before
the effectiveMode branch so callAIService correctly routes to the
Claude Code sidecar regardless of apiMode.

Made-with: Cursor

* fix: prevent data loss when text and done chunks arrive simultaneously

Move `done` check from while-loop condition to an else-if branch after
chunk consumption. This ensures all queued text chunks are yielded
before the loop exits, even when the completion event sets done=true
in the same event-loop turn as a text chunk event.

Made-with: Cursor
* feat(ai-chat): サイドパネル・エージェントチャット (#458)

ノートエディタ横のAIチャットパネルに以下の機能を追加:

- 「ノートに挿入」ボタン: アシスタントの回答をエディタのカーソル位置に挿入
- ツール実行状況の可視化: Claude Code のツール使用をリアルタイム表示
  (Read, Write, Bash, WebSearch 等のステータス表示)
- パネルリサイズ: ドラッグハンドルによる幅変更(280px〜600px)
- メッセージコピーボタン: アシスタントメッセージのコピー機能

Full-stack changes:
- sidecar protocol: tool-use-start/complete events
- Rust host: forward tool events as Tauri events
- bridge/provider: listen for and yield tool chunks
- ChatMessage type: toolExecutions tracking
- i18n: ja/en translation keys for new UI

Made-with: Cursor

* feat(ai-settings): 3モード切替UI・モデルセレクター改修・モードバッジ (#458)

AI利用モードを「デフォルト / APIキー / Claude Code」の3択で
明確に選択できるよう設定画面とチャットUIを改修:

- types/ai.ts: AIInteractionMode 型と getInteractionMode() 追加
- AIChatModelSelector: Claude Code 時はセレクター非表示、
  APIキーモード時はプロバイダーでフィルタ
- AIChatHeader: 現在のモードバッジを表示(クリックで設定へ遷移)
- AISettingsForm: プロバイダー+トグル → 3モードセグメントコントロールに再設計
- useAISettingsForm: handleModeChange で適切な設定リセット
- ProviderSelector: apiProvidersOnly prop 追加
- i18n: ja/en のモードラベル・設定画面テキスト追加

Made-with: Cursor

* feat(ai-settings): claude code 前提条件チェックリスト (#458)

Claude Code モード選択時に、利用に必要な前提条件を
チェックリストとして表示し、各項目の状態をリアルタイムに確認:

1. デスクトップアプリ (Tauri) で実行中か
2. Claude Code CLI がインストール済みか(バージョン表示付き)
3. サイドカープロセスと通信可能か

- ClaudeCodePrerequisites コンポーネントを新規作成
- 「再確認」ボタンで再チェック可能
- 全項目パス時にメッセージ表示
- i18n: ja/en の前提条件チェックテキスト追加

Made-with: Cursor

* fix(ai-chat): claude code 選択時のモデルセレクター無限ループを修正

loadModels の useCallback 依存配列に selectedModel が含まれており、
Claude Code モード時に setSelectedModel → 再レンダー → loadModels 再生成
→ useEffect 再実行の無限ループが発生していた。

selectedModel を依存配列から除外し、useAIChatStore.getState() で
レンダー外から最新値を取得するよう変更。

Made-with: Cursor

* refactor(ai-chat): サイドパネルのリサイズ機能を削除

panelWidth / setPanelWidth / MIN_PANEL_WIDTH / MAX_PANEL_WIDTH と
ドラッグハンドルを削除し、CSS 変数 --ai-chat-width による固定幅に戻す。

Made-with: Cursor

* feat(ai-chat): claude code モデル動的取得・選択機能

サイドカー経由で Claude Code の利用可能モデル一覧を動的に取得し、
チャットパネルのモデルセレクターで選択できるようにする。

フルスタック変更:
- sidecar protocol: list_models リクエスト / models-list レスポンス追加、
  query に model フィールド追加
- sidecar handler: SDK Query.initializationResult() でモデル一覧取得
- sidecar query: 選択されたモデルを SDK options.model に渡す
- Rust host: claude_list_models コマンド追加、claude_query に model 引数追加
- bridge: claudeListModels() 関数追加、claudeQuery に model オプション追加
- provider: request.model を claudeQuery に透過的に渡す
- AIChatModelSelector: Claude Code 時にサイドカーからモデル取得しドロップダウン表示

Made-with: Cursor

* fix: address PR #467 review comments

- 空 JSDoc ブロックを削除/修正 (AIChatContext, models.ts)
- DisplayModel.provider を AIProviderType に変更し不正な型キャストを除去
- navigator.clipboard.writeText のエラーハンドリングを追加 (try/catch + toast)
- ModeSelector を RadioGroup/RadioGroupItem ベースに置換 (a11y 改善)

Made-with: Cursor

* fix: テストを新しい bridge API に合わせて更新

- bridge.test.ts: claudeQuery の期待値に model: null を追加
- claudeCodeProvider.test.ts: モックに onClaudeToolUseStart / onClaudeToolUseComplete を追加

Made-with: Cursor

* fix: address PR #467 review (list_models RPC, mode snapshots, tool UI on error)

Made-with: Cursor

* fix: model selector resync, Claude mode always selectable, mode group a11y

Made-with: Cursor

* fix: claude code fallback models outside tauri (#467)

Made-with: Cursor

* fix: address PR 467 review (empty models, tool switch, mode badge)

Made-with: Cursor
… code blocks (#458 #459) (#474)

* feat(ai-chat): サイドパネル・エージェントチャット (#458)

ノートエディタ横のAIチャットパネルに以下の機能を追加:

- 「ノートに挿入」ボタン: アシスタントの回答をエディタのカーソル位置に挿入
- ツール実行状況の可視化: Claude Code のツール使用をリアルタイム表示
  (Read, Write, Bash, WebSearch 等のステータス表示)
- パネルリサイズ: ドラッグハンドルによる幅変更(280px〜600px)
- メッセージコピーボタン: アシスタントメッセージのコピー機能

Full-stack changes:
- sidecar protocol: tool-use-start/complete events
- Rust host: forward tool events as Tauri events
- bridge/provider: listen for and yield tool chunks
- ChatMessage type: toolExecutions tracking
- i18n: ja/en translation keys for new UI

Made-with: Cursor

* feat(ai-settings): 3モード切替UI・モデルセレクター改修・モードバッジ (#458)

AI利用モードを「デフォルト / APIキー / Claude Code」の3択で
明確に選択できるよう設定画面とチャットUIを改修:

- types/ai.ts: AIInteractionMode 型と getInteractionMode() 追加
- AIChatModelSelector: Claude Code 時はセレクター非表示、
  APIキーモード時はプロバイダーでフィルタ
- AIChatHeader: 現在のモードバッジを表示(クリックで設定へ遷移)
- AISettingsForm: プロバイダー+トグル → 3モードセグメントコントロールに再設計
- useAISettingsForm: handleModeChange で適切な設定リセット
- ProviderSelector: apiProvidersOnly prop 追加
- i18n: ja/en のモードラベル・設定画面テキスト追加

Made-with: Cursor

* feat(ai-settings): claude code 前提条件チェックリスト (#458)

Claude Code モード選択時に、利用に必要な前提条件を
チェックリストとして表示し、各項目の状態をリアルタイムに確認:

1. デスクトップアプリ (Tauri) で実行中か
2. Claude Code CLI がインストール済みか(バージョン表示付き)
3. サイドカープロセスと通信可能か

- ClaudeCodePrerequisites コンポーネントを新規作成
- 「再確認」ボタンで再チェック可能
- 全項目パス時にメッセージ表示
- i18n: ja/en の前提条件チェックテキスト追加

Made-with: Cursor

* fix(ai-chat): claude code 選択時のモデルセレクター無限ループを修正

loadModels の useCallback 依存配列に selectedModel が含まれており、
Claude Code モード時に setSelectedModel → 再レンダー → loadModels 再生成
→ useEffect 再実行の無限ループが発生していた。

selectedModel を依存配列から除外し、useAIChatStore.getState() で
レンダー外から最新値を取得するよう変更。

Made-with: Cursor

* refactor(ai-chat): サイドパネルのリサイズ機能を削除

panelWidth / setPanelWidth / MIN_PANEL_WIDTH / MAX_PANEL_WIDTH と
ドラッグハンドルを削除し、CSS 変数 --ai-chat-width による固定幅に戻す。

Made-with: Cursor

* feat(ai-chat): claude code モデル動的取得・選択機能

サイドカー経由で Claude Code の利用可能モデル一覧を動的に取得し、
チャットパネルのモデルセレクターで選択できるようにする。

フルスタック変更:
- sidecar protocol: list_models リクエスト / models-list レスポンス追加、
  query に model フィールド追加
- sidecar handler: SDK Query.initializationResult() でモデル一覧取得
- sidecar query: 選択されたモデルを SDK options.model に渡す
- Rust host: claude_list_models コマンド追加、claude_query に model 引数追加
- bridge: claudeListModels() 関数追加、claudeQuery に model オプション追加
- provider: request.model を claudeQuery に透過的に渡す
- AIChatModelSelector: Claude Code 時にサイドカーからモデル取得しドロップダウン表示

Made-with: Cursor

* fix: address PR #467 review comments

- 空 JSDoc ブロックを削除/修正 (AIChatContext, models.ts)
- DisplayModel.provider を AIProviderType に変更し不正な型キャストを除去
- navigator.clipboard.writeText のエラーハンドリングを追加 (try/catch + toast)
- ModeSelector を RadioGroup/RadioGroupItem ベースに置換 (a11y 改善)

Made-with: Cursor

* fix: テストを新しい bridge API に合わせて更新

- bridge.test.ts: claudeQuery の期待値に model: null を追加
- claudeCodeProvider.test.ts: モックに onClaudeToolUseStart / onClaudeToolUseComplete を追加

Made-with: Cursor

* fix: address PR #467 review (list_models RPC, mode snapshots, tool UI on error)

Made-with: Cursor

* fix: model selector resync, Claude mode always selectable, mode group a11y

Made-with: Cursor

* fix: claude code fallback models outside tauri (#467)

Made-with: Cursor

* fix: address PR 467 review (empty models, tool switch, mode badge)

Made-with: Cursor

* feat(editor): executable code blocks via Claude Code (#459)

- Add ExecutableCodeBlock Tiptap node, slash command, and split NodeView UI
- runQueryToCompletion with pre-id event buffer; sidecar allows empty allowedTools
- General setting to confirm before run; i18n en/ja
- Tests for runQueryToCompletion, executeExecutableCode, parseExecutionResult

Made-with: Cursor

* fix: address PR #474 review comments

Made-with: Cursor
* feat(editor): agent slash commands via claude code sidecar (#460)

Wire /analyze, /git-summary, /run, /research, /review, /test, /explain, /summarize
to the sidecar; add tauri path listing, loading overlay, and split menu modules.

Made-with: Cursor

* fix(ci): add agentSlashCommands index to knip entry points

Knip treated the barrel as an unused file; list it as an entry like claudeCode.

Made-with: Cursor

* fix: address PR #475 review comments

Made-with: Cursor

* fix: address PR #475 review comments

Made-with: Cursor

* fix: address PR #475 review comments

Made-with: Cursor
* feat: workspace-linked notes for @file: and Claude cwd (#461)

- Tauri: safe path resolution under root, read/list workspace files, dialog plugin
- Editor: FileReference @file: mark, preview dialog host, slash path completions
- AI: workspace root as Claude cwd for agent slash, executable code, chat
- ESLint: pass workspaceRoot string + useEditor dep instead of ref during render

Made-with: Cursor

* fix: address PR #476 review (workspace root ref, Rust path, imports, JSDoc)

- useEditor: stable deps + useWorkspaceRootRef + eslint block for extension config
- workspace_paths: lexical resolve_under_root with fewer canonicalize calls
- useTiptapEditorController: single React import line

Made-with: Cursor

* fix: address CodeRabbit PR review (paths, @file:, preview, docs)

- Rust: re-canonicalize before open; document openat limitation
- FileReference: spaces in path regex; stale preview guard
- FilePreviewDialogHost: CustomEvent + detail guard
- Note workspace: clear entries on tree open; normalize empty root; safe map keys
- JSDoc: Japanese for useEditorSetup, aiService cwd, PageContext noteId

Made-with: Cursor

* fix: note workspace registry + noteId IPC for Tauri reads (#476)

- Rust: persist note_id→root registry, read/list by note_id only, enforce size cap with single File::take
- TS: register/clear registry from NoteWorkspaceContext; readNoteWorkspaceFile/listNoteWorkspaceEntries use noteId
- Editor @file: and slash path completion thread noteId; agent cwd still uses absolute workspace path

Made-with: Cursor

* fix: atomic registry write, async tree guard, surface noteWorkspace I/O errors (#476)

- Rust: write registry via temp file + rename; mutex around register/clear RMW
- Toolbar: sequence ref so only the latest list result applies
- noteWorkspaceIo: propagate invoke errors; callers log or clear UI

Made-with: Cursor

* fix: note workspace provider key + serialized Rust registry sync (#476)

- Remount NoteWorkspaceProvider on note.id to avoid noteId/workspaceRoot mismatch
- Queue register/clear invocations so async completions cannot reorder

Made-with: Cursor

* fix: address PR #476 review comments

- NotePageEditorEditable: explicit React.JSX.Element return type
- NoteWorkspace: Rust sync only in effect (remove duplicate enqueue from setters)
- workspace_paths: parse_note_id_key returns trimmed key for registry I/O

Made-with: Cursor

* fix: address PR #476 review comments

- atomic_write_file: rely on fs::rename replace on Windows (drop pre-remove)
- list_note_workspace_entries: optional namePrefix filter before cap
- NotePageView: NoteWorkspaceProvider wraps read-only editor for @file: context

Made-with: Cursor

* fix: address PR #476 review comments

- list_directory_names: allow .zedi directory in listings (Issue #461)
- NoteWorkspace: enqueue Rust sync on mount + in setters; drop deferred workspaceRoot effect

Made-with: Cursor
* feat(ai-chat): add Claude Code multi-step workflow panel (#462)

- 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

* fix: address PR #477 review comments

- 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

* fix: address CodeRabbit PR #477 review (workflow UI, a11y, streamClaudeQuery)

- 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

* fix: address PR #477 Devin review (workflow height, resume by step id)

- 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

* fix: address PR #477 review (chat input mount, progress step alignment)

- 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
* feat: mcp server integration for claude code on desktop

- Sidecar: pass mcpServers, emit mcp-status; Tauri forwards claude-mcp-status

- UI: MCP settings, chat header indicator, editor MCP resource embed

- Persist config without secrets in localStorage; tests for helpers and store

Made-with: Cursor

* fix: add tauri-plugin-fs for mcp config import

- Declare @tauri-apps/plugin-fs so vite build and knip resolve the module

- Register tauri_plugin_fs and allow read_text_file for home Claude config paths

Made-with: Cursor

* fix: address PR #478 review comments

- McpResourceNodeView: register Tauri listeners before claudeQuery; buffer until requestId; unmount cleanup; require non-empty resource
- parseStdioArgsLine for quoted stdio args; isValidMcpServerConfig on import
- Regenerate path passes MCP servers to buildSystemPrompt; claudeCodeProvider normalizes MCP status
- JSDoc/comment fixes (mcpConfigStore, aiChatPrompt, McpResource, McpServerRow); extension header matches behavior
- Slash command prompts for server/resource; MCP status button aria-label; i18n for new strings

Made-with: Cursor
Regenerate bun.lock so CI bun install --frozen-lockfile succeeds.
Resolves Dependabot PR #454 (lockfile was out of sync).

Made-with: Cursor
chore(deps): bump @anthropic-ai/sdk from 0.80.0 to 0.82.0 in the npm_and_yarn group across 1 directory
* fix(security): replace custom HTML sanitizer with DOMPurify (#380)

カスタム sanitizeHtml を DOMPurify に置き換え、Mutation XSS・未処理要素
(svg/math/template 等)・危険スキームの網羅的対策を導入。
ホワイトリスト設定を sanitizeHtmlConfig.ts に分離し、テスト 23 件を追加。

Made-with: Cursor

* chore: remove redundant @types/dompurify

dompurify v3 bundles TypeScript types; knip flagged @types/dompurify as unused.

Made-with: Cursor
* feat(editor): 外部URLのビジュアル変換(Gyazo / YouTube)を追加

Gyazo パーマリンクを画像として、YouTube URLをiframe埋め込みとして
ペースト時・入力時に自動変換する機能を実装。

- Gyazo permalink → i.gyazo.com 画像URLに変換して表示
- YouTube URL → レスポンシブiframeで動画を埋め込み表示
- URL変換ユーティリティ (urlTransform.ts) を新規作成
- YouTubeEmbed ノード拡張とNodeViewを新規作成
- セキュリティ制約(localhost/プライベートIP拒否)を維持

Closes #363

https://claude.ai/code/session_01B65Y1xTeRncHhMokAMpfTo

* fix(editor): address PR review comments for external URL transformation

- Remove src attribute from youtubeEmbed schema; derive embed URL from
  videoId only to prevent URL injection and data inconsistency
- Add videoId validation (11-char alphanumeric pattern)
- Add case-insensitive flag to YouTube URL regex patterns
- Replace deprecated frameBorder/frameborder with CSS border:0
- Add bilingual JSDoc to StorageImageOptions and StorageImage exports
- Remove empty JSDoc blocks from InputRule handlers

https://claude.ai/code/session_01B65Y1xTeRncHhMokAMpfTo

* fix(editor): use boolean for allowfullscreen in renderHTML

Align allowfullscreen attribute type with NodeView (boolean instead of
string "true") for consistency.

https://claude.ai/code/session_01B65Y1xTeRncHhMokAMpfTo

---------

Co-authored-by: Claude <noreply@anthropic.com>
* fix: モバイルでカード上スクロールが効かない問題を修正

Radix UI ContextMenuTrigger がモバイルでタッチイベントを
preventDefault() するためスクロールが阻害されていた。
モバイルでは ContextMenu の代わりに独自の長押し検出フック
(useLongPress) + DropdownMenu を使用するように変更し、
スクロールと長押しメニューを両立させる。

- useLongPress フックを新規追加(タッチ移動量による閾値判定付き)
- PageCard: モバイルでは DropdownMenu、デスクトップでは ContextMenu を使い分け
- メニュー項目は共通関数で共有

https://claude.ai/code/session_01S9srRSXaC2drSWTCJ7ymfC

* feat: add GitHub MCP server configuration

プロジェクトルートに .mcp.json を追加し、Claude Code から
GitHub MCP ツール(PR作成・Issue管理等)を利用可能にする。
トークンは環境変数 GITHUB_PERSONAL_ACCESS_TOKEN で参照する。

https://claude.ai/code/session_01S9srRSXaC2drSWTCJ7ymfC

---------

Co-authored-by: Claude <noreply@anthropic.com>
* feat(editor): 外部URLのビジュアル変換(Gyazo / YouTube)を追加

Gyazo パーマリンクを画像として、YouTube URLをiframe埋め込みとして
ペースト時・入力時に自動変換する機能を実装。

- Gyazo permalink → i.gyazo.com 画像URLに変換して表示
- YouTube URL → レスポンシブiframeで動画を埋め込み表示
- URL変換ユーティリティ (urlTransform.ts) を新規作成
- YouTubeEmbed ノード拡張とNodeViewを新規作成
- セキュリティ制約(localhost/プライベートIP拒否)を維持

Closes #363

https://claude.ai/code/session_01B65Y1xTeRncHhMokAMpfTo

* fix(editor): address PR review comments for external URL transformation

- Remove src attribute from youtubeEmbed schema; derive embed URL from
  videoId only to prevent URL injection and data inconsistency
- Add videoId validation (11-char alphanumeric pattern)
- Add case-insensitive flag to YouTube URL regex patterns
- Replace deprecated frameBorder/frameborder with CSS border:0
- Add bilingual JSDoc to StorageImageOptions and StorageImage exports
- Remove empty JSDoc blocks from InputRule handlers

https://claude.ai/code/session_01B65Y1xTeRncHhMokAMpfTo

* feat(ui): add wikilink hover card preview for editor and ai chat

- shared WikiLinkPreviewContent and unit tests
- Tiptap WikiLinkHoverCardLayer / useWikiLinkHover (long-press, typing suppress)
- AIChatWikiLink with HoverCard and long-press
- expose handleLinkClick from useTiptapEditorController

Made-with: Cursor

* fix(editor): remove duplicate YouTubeEmbed import in editorConfig

Resolves vite build PARSE_ERROR: Identifier YouTubeEmbed has already been declared (merge artifact).

Made-with: Cursor

* fix: address PR #487 gemini review (hover card edge cases)

- AIChatWikiLink: reset preventClickRef after 100ms; clear timers on unmount
- useWikiLinkHover: add isVisible to effect deps so click closes stale card
- WikiLinkPreviewContent: show create CTA only when onClick; div when no action

Made-with: Cursor

* fix(editor): remove duplicate YouTubeEmbed in editorConfig (PR #487)

Made-with: Cursor

* fix: address PR #487 CodeRabbit review (wiki link preview)

Made-with: Cursor

* fix: address PR #487 review comments

- AIChatWikiLink: handleTouchCancel clears long-press timers; ghost link uses focusable button with navigation
- WikiLinkHoverCardLayer: derive exists/referenced from page store (not stale DOM attrs)
- WikiLinkPreviewContent: phrasing-only markup inside interactive buttons (span layout)

Made-with: Cursor

* fix: address PR #487 Devin review (hover re-enter, bilingual JSDoc)

- useWikiLinkHover: cancel close timer when pointer returns to same wiki-link from card
- AIChatWikiLink: add English line to handleTouchCancel JSDoc (AGENTS.md)

Made-with: Cursor

* fix: address PR #487 CodeRabbit review (useWikiLinkHover)

- Clear open/long-press timers on keydown; re-check typing window in delayed open
- touchcancel clears long-press timer (same as AIChat chat path)
- Capture-phase click guard after long-press open to block synthetic navigation
- isWithinTypingSuppressWindow helper; eslint max-lines exception for cohesive hook

Made-with: Cursor

* fix: add explicit return type UseWikiLinkHoverResult to useWikiLinkHover

Address PR #487 CodeRabbit review nitpick: define an explicit return
interface so internal changes do not break consumers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(agents): consolidate pr review workflow into spec-driven skill

- Expand handle-pr-review skill with TSDoc/test/type alignment steps

- Trim AGENTS.md and point to the skill

- Add Claude Code Bash allowlist for gh api and bun run in settings.local.json

- Remove obsolete .claude/commands/review-pr-comments.md

Made-with: Cursor

* fix(editor): align WikiLink hover refs with CodeRabbit review

- Use explicit undefined initial values for timer refs (React 19 typings).
- Reset currentElementRef on keydown and defer assignment until after typing
  suppression so the same link can reopen after suppress/interrupt.

Made-with: Cursor

---------

Co-authored-by: Claude <noreply@anthropic.com>
* fix(sync): include content_preview in PUT /content to sync page list summary across devices

CollaborationManager.buildPutContentBody() now generates a 120-char
content_preview from the plain text and includes it in the PUT payload.
The server PUT /pages/:id/content endpoint accepts content_preview and
persists it to the pages table. Hocuspocus saveDocumentToDb() also
extracts text from Y.Doc and updates pages.content_preview so
collaborative editing keeps the preview in sync.

Closes #489

https://claude.ai/code/session_01WPpwEVBuAB2MGshdr7Xi5S

* refactor: address review feedback on content_preview sync

- Extract duplicated pages-table update logic into applyPagesMetadataUpdate()
- Change buildContentPreview() to accept plain text instead of re-traversing Y.Doc
- Wrap Hocuspocus saveDocumentToDb() in a transaction for atomicity

https://claude.ai/code/session_01WPpwEVBuAB2MGshdr7Xi5S

* fix: add missing Database type import in pages route

https://claude.ai/code/session_01WPpwEVBuAB2MGshdr7Xi5S

---------

Co-authored-by: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

Tauri デスクトップアプリと Claude Code サイドカーの導入、TipTap エディタ拡張(実行可能コードブロック、MCP リソース、スラッシュエージェント等)、ワークフロー実行・管理、ワークスペース連携、多プロバイダ AI 統合、および関連 UI/テスト/ローカライズの大規模追加。

Changes

Cohort / File(s) Summary
Tauri & sidecar 基盤
src-tauri/Cargo.toml, src-tauri/build.rs, src-tauri/tauri.conf.json, src-tauri/src/lib.rs, src-tauri/src/main.rs, src-tauri/capabilities/default.json, src-tauri/icons/...
Tauri アプリ設定、プラグイン・権限・アイコン、外部バイナリ(claude-sidecar)を含むデスクトップランタイムを追加。
claude-sidecar パッケージ & プロトコル
packages/claude-sidecar/src/*, packages/claude-sidecar/package.json, packages/claude-sidecar/tsconfig.json, packages/claude-sidecar/vitest.config.ts, packages/claude-sidecar/src/index.ts, scripts/*.mjs
JSONL プロトコル、クエリ実行/中止/ステータス/モデル列挙、ビルド/ensure スクリプト、テスト設定を追加。
Tauri ⇄ sidecar ブリッジ(フロント)
src/lib/claudeCode/*, src/lib/claudeCode/bridge.ts, src/lib/claudeCode/types.ts, src/lib/claudeCode/streamClaudeQuery.ts, src/lib/claudeCode/runQueryToCompletion.ts
フロントでの Tauri ブリッジ、イベント購読、ストリーミング集約ロジックを導入。
Tauri ⇄ sidecar ブリッジ(Rust)
src-tauri/src/claude_sidecar.rs
Rust 側で sidecar プロセス管理、JSONL RPC ルーティング、Tauri コマンド公開を追加。
AI サービス層 & プロバイダ統合
src/lib/aiService.ts, src/lib/aiServiceServer.ts, src/lib/aiServiceDirectProviders.ts, src/lib/aiServiceModels.ts, src/lib/aiClient.ts
AI 呼び出しの統一レイヤーをリファクタ/拡張し、claude-code を経路に追加、モデル取得と使用量 API を分離。
プロバイダ実装
src/lib/aiProviders/*.ts, src/lib/aiProviders/registry.ts, src/lib/aiProviders/types.ts
OpenAI/Anthropic/Google/provide registry と claude-code provider の追加(統一インターフェース)。
エディタ拡張(TipTap)全般
src/components/editor/..., src/components/editor/extensions/*, src/components/editor/TiptapEditor/*
ExecutableCodeBlock, FileReference, McpResource, YouTubeEmbed, StorageImage 等の拡張、スラッシュメニュー再構成、ホバーカード、パス補完、insertAtCursorRef 等を追加・連携。
エージェントスラッシュコマンド
src/lib/agentSlashCommands/*
エージェントコマンドパーサ、プロンプトビルダ、実行フロー、挿入ユーティリティ、フックを追加(/analyze 等)。
実行可能コード実行基盤
src/lib/executableCode/*, src/components/editor/executableCodeBlock/*
実行プロンプトの構築、Claude 経由での実行・解釈、NodeView UI、ツールバー、確認ダイアログ、出力パースを追加。
ワークフロー機能
src/lib/workflow/*, src/hooks/useWorkflowDraft.ts, src/hooks/useWorkflowRunSession.ts, src/hooks/useWorkflowPanelLogic.ts, src/components/ai-chat/*Workflow*
ワークフロードラフト管理、実行セッション(中断/再開)、プロンプト生成、進捗フォーマット、ワークフローパネル UI を導入。
MCP サーバー統合 & 管理 UI
src/components/settings/McpServerSettings.tsx, src/components/settings/*McpServerForm*.tsx, src/lib/mcpServerImportHelpers.ts, src/components/settings/mcpServerFormInitialState.ts
MCP サーバー CRUD、インポート/検証、フォーム状態と UI を追加。
ワークスペース連携 / ファイルプレビュー
src/lib/noteWorkspace/*, src/contexts/NoteWorkspaceContext.tsx, src/components/note/*
note に紐づくワークスペース登録・永続化、Tauri 経由ファイル読み取り・一覧、FilePreview イベント/ダイアログ、管理ツールバーを実装。
AI チャット UI 変更
src/components/ai-chat/*, src/contexts/AIChatContext.tsx
モードバッジ、モデルセレクタ拡張、MCP ステータス表示、メッセージ挿入/コピーアクション、workflow タブを追加。
設定周り・i18n・ドキュメント
src/components/settings/*, src/i18n/locales/*, .claude/settings.local.json, .mcp.json, .env.example, AGENTS.md, .cursor/skills/handle-pr-review/SKILL.md, .claude/commands/review-pr-comments.md (削除)
AI 設定の 3 モード化、Claude Code 前提チェック、i18n 追加、PR レビュー運用のドキュメント更新。
ビルド/パッケージ等
package.json, knip.json, .gitignore, scripts/build-claude-sidecar.mjs, scripts/ensure-claude-sidecar-bin.mjs
npm スクリプト追加(sidecar/tauri)、依存追加、ワークスペース・サイドカーのビルド/配置スクリプト、ignore ルール更新。
サニタイズ & WebClipper
src/lib/webClipper/sanitizeHtml.ts, src/lib/webClipper/sanitizeHtmlConfig.ts, src/components/editor/utils/urlTransform.ts
DOMPurify ベースのサニタイズへ移行、Gyazo/YouTube URL 変換ユーティリティ追加。
テスト追加・修正
src/**/*.test.ts?(x)(多数)
プロバイダ、sidecar ブリッジ、エディタ機能、ワークフロー、parse/ユーティリティ等のユニット/統合テスト多数を追加。

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant UI as App (Renderer)
    participant Tauri as Tauri (Rust)
    participant Sidecar as Sidecar (JSONL proc)
    participant Claude as Claude SDK

    UI->>Tauri: invoke `claude_query` (prompt, options)
    Tauri->>Sidecar: write JSONL request to stdin
    Sidecar->>Claude: run query via Claude SDK / agent
    Claude-->>Sidecar: stream chunk events (tool start/stop, text deltas)
    Sidecar-->>Tauri: emit JSONL response lines (stream-chunk / tool-use / mcp-status)
    Tauri-->>UI: emit Tauri events (claude-stream-chunk / claude-tool-use-*)
    UI->>Tauri: (optionally) invoke `claude_abort`
    Tauri->>Sidecar: write abort JSONL
    Sidecar->>Claude: abort underlying request
    Sidecar-->>Tauri: emit stream-complete / error
    Tauri-->>UI: emit completion/error event
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 分

Possibly related PRs

Suggested labels

enhancement

"🐰 こりゃ新機能いっぱい、コード跳ねるよ!
サイドカー走らせ、ワークフロー回し、
エディタに魔法を、MCP もつなげて、
デスクトップで Claude が歌うよ ✨"

✨ 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 develop

@otomatty otomatty self-assigned this Apr 6, 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 desktop-native workflow feature using Tauri and Claude Code, including a sidecar process for secure communication, a new workflow UI panel, and support for MCP resources. I have reviewed the changes and found an issue in the text extraction logic for content previews in server/hocuspocus/src/index.ts, where improper newline insertion can lead to fragmented text. A suggestion has been provided to use spaces instead of newlines for block elements to ensure better formatting.

Comment thread server/hocuspocus/src/index.ts Outdated
Comment on lines +207 to +209
} else if (child instanceof Y.XmlElement) {
text += extractTextFromFragment(child) + "\n";
}
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

現在のテキスト抽出ロジックでは、インライン要素(<strong>など)の後にも改行(\n)が追加されてしまい、不自然なスペースがプレビューや検索用テキストに含まれる可能性があります。

例えば、<p>Hello <strong>world</strong>!</p>Hello world\n! のように変換されてしまいます。

ブロック要素とインライン要素を区別するのは難しいですが、ここでは改行の代わりにスペースを追加することで、後続の replace(/\s+/g, " ") 処理で適切に整形されるようにするのが安全です。

Suggested change
} else if (child instanceof Y.XmlElement) {
text += extractTextFromFragment(child) + "\n";
}
} else if (child instanceof Y.XmlElement) {
text += extractTextFromFragment(child) + " ";
}

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.

ご指摘のとおりでした。インライン要素のあとに一律 \n を付けないよう、bold / italic などインライン相当の nodeName を列挙し、その XmlElement の後はスペース、ブロック要素の後は改行とする実装に変更しました(server/hocuspocus/src/extractPlainTextFromYXml.ts)。単体テストも追加済みです。対応は develop 向け PR #493 経由でマージ済みです。

Implemented inline vs block suffixes in extractPlainTextFromYXml.ts (not a blanket space for every element), with tests. Landed via #493.

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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

coderabbitai[bot]

This comment was marked as resolved.

otomatty added a commit that referenced this pull request Apr 6, 2026
- Hocuspocus: extract inline mark elements (bold, etc.) without spurious newlines;
  add extractPlainTextFromYXml module + tests (Gemini review).
- aiClient: allow claude-code branch when API key is empty (CodeRabbit review).
- AIChatWikiLink: useRef initial values for React 19 types (CodeRabbit review).

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

otomatty commented Apr 6, 2026

PR #492 のインラインレビュー(Gemini / CodeRabbit)に対する修正を develop 向け PR #493 で出しています。マージ後に本リリース PR の差分を更新してください。

内容: Hocuspocus の Y.Xml プレーンテキスト抽出(インライン要素で不要改行を避ける)、testConnection の claude-code + 空キー、AIChatWikiLinkuseRef 初期値。

#493)

- Hocuspocus: extract inline mark elements (bold, etc.) without spurious newlines;
  add extractPlainTextFromYXml module + tests (Gemini review).
- aiClient: allow claude-code branch when API key is empty (CodeRabbit review).
- AIChatWikiLink: useRef initial values for React 19 types (CodeRabbit review).

Made-with: Cursor
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: 1

🧹 Nitpick comments (3)
server/hocuspocus/src/extractPlainTextFromYXml.test.ts (1)

54-62: 境界値テストの追加を検討してください(任意)

buildContentPreview のテストに、ちょうど 120 文字の境界値ケースを追加すると、切り詰めロジックの正確性をより確実に検証できます。

🧪 追加テストの提案
 describe("buildContentPreview", () => {
   it("collapses whitespace and truncates", () => {
     expect(buildContentPreview("  a \n b  ")).toBe("a b");
     const long = "x".repeat(200);
     const prev = buildContentPreview(long);
     expect(prev.endsWith("...")).toBe(true);
     expect(prev.length).toBeLessThanOrEqual(124);
   });
+
+  it("does not truncate text exactly at max length", () => {
+    const exact = "x".repeat(120);
+    expect(buildContentPreview(exact)).toBe(exact);
+    expect(buildContentPreview(exact).endsWith("...")).toBe(false);
+  });
+
+  it("returns empty string for empty input", () => {
+    expect(buildContentPreview("")).toBe("");
+    expect(buildContentPreview("   ")).toBe("");
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/hocuspocus/src/extractPlainTextFromYXml.test.ts` around lines 54 - 62,
Add a boundary test for buildContentPreview that supplies an input string
exactly at the truncation threshold (e.g., 120 characters) to verify the cut-off
logic; create a 120-char string (using repeat or concatenation), call
buildContentPreview with it, and assert that the result is not suffixed with
"..." and that its length meets the expected non-truncated length (or matches
the original preview length rules), so you validate the edge-case behavior of
buildContentPreview.
server/hocuspocus/src/extractPlainTextFromYXml.ts (1)

10-21: インライン要素のカスタマイズについて(任意の改善)

現在の INLINE_XML_ELEMENT_NAMES は一般的な TipTap マークをカバーしていますが、カスタムマークやエディタ拡張が追加された場合、不自然な改行が発生する可能性があります。将来的には、この Set を設定可能にするか、フォールバック検出ロジック(例:子要素が XmlText のみの場合はインラインとみなす)を検討してください。

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

In `@server/hocuspocus/src/extractPlainTextFromYXml.ts` around lines 10 - 21, The
hardcoded INLINE_XML_ELEMENT_NAMES Set can cause incorrect block/inline
detection for custom marks; update the code to allow configuration and add a
fallback that treats an XmlXMLElement as inline when its children are all
XmlText (or empty) — expose INLINE_XML_ELEMENT_NAMES as a configurable parameter
(or accept an optional set in extractPlainTextFromYXml and/or its caller), and
inside the detection logic in extractPlainTextFromYXml use: first check the
configured set, and if not present, treat elements as inline when every child
node is of type XmlText (or there are no child elements); ensure to reference
and update the INLINE_XML_ELEMENT_NAMES constant and the element-kind check in
extractPlainTextFromYXml accordingly.
server/hocuspocus/src/index.ts (1)

199-200: 空文字列と null の扱いの一貫性について(任意の改善)

clipAndCreate.ts では contentText || null パターンを使用していますが、ここでは空文字列をそのまま保存しています。動作上は問題ありませんが、他のコードとの一貫性を保つ場合は以下の変更を検討してください。

♻️ 一貫性のための修正案
-  const contentText = extractTextFromYXml(document.getXmlFragment("default"));
-  const contentPreview = buildContentPreview(contentText);
+  const rawText = extractTextFromYXml(document.getXmlFragment("default"));
+  const contentText = rawText.trim() || null;
+  const contentPreview = contentText ? buildContentPreview(rawText) : null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/hocuspocus/src/index.ts` around lines 199 - 200, The code currently
assigns contentText = extractTextFromYXml(document.getXmlFragment("default"))
and leaves empty string as-is whereas other code (clipAndCreate.ts) uses
contentText || null for consistency; update this location to normalize empty
content to null (e.g., set contentText = extractTextFromYXml(...) || null) or
adjust buildContentPreview to accept nulls, and ensure contentPreview is derived
from that normalized value so the module uses the same empty-string/null
convention as clipAndCreate.ts; reference extractTextFromYXml,
buildContentPreview, contentText, and contentPreview when making the change.
🤖 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/lib/aiClient.ts`:
- Around line 8-9: The exported type AIClient and the exported
ConnectionTestResult need bilingual JSDoc/TSDoc comments (Japanese and English)
per repo guidelines; update the JSDoc above the AIClient type declaration and
above the ConnectionTestResult declaration to include both a short Japanese
description and an English description, mention that AIClient is a union of SDK
clients (OpenAI, Anthropic, GoogleGenAI) and that ConnectionTestResult
represents the result shape for connection tests so reviewers can find them by
symbol names AIClient and ConnectionTestResult.

---

Nitpick comments:
In `@server/hocuspocus/src/extractPlainTextFromYXml.test.ts`:
- Around line 54-62: Add a boundary test for buildContentPreview that supplies
an input string exactly at the truncation threshold (e.g., 120 characters) to
verify the cut-off logic; create a 120-char string (using repeat or
concatenation), call buildContentPreview with it, and assert that the result is
not suffixed with "..." and that its length meets the expected non-truncated
length (or matches the original preview length rules), so you validate the
edge-case behavior of buildContentPreview.

In `@server/hocuspocus/src/extractPlainTextFromYXml.ts`:
- Around line 10-21: The hardcoded INLINE_XML_ELEMENT_NAMES Set can cause
incorrect block/inline detection for custom marks; update the code to allow
configuration and add a fallback that treats an XmlXMLElement as inline when its
children are all XmlText (or empty) — expose INLINE_XML_ELEMENT_NAMES as a
configurable parameter (or accept an optional set in extractPlainTextFromYXml
and/or its caller), and inside the detection logic in extractPlainTextFromYXml
use: first check the configured set, and if not present, treat elements as
inline when every child node is of type XmlText (or there are no child
elements); ensure to reference and update the INLINE_XML_ELEMENT_NAMES constant
and the element-kind check in extractPlainTextFromYXml accordingly.

In `@server/hocuspocus/src/index.ts`:
- Around line 199-200: The code currently assigns contentText =
extractTextFromYXml(document.getXmlFragment("default")) and leaves empty string
as-is whereas other code (clipAndCreate.ts) uses contentText || null for
consistency; update this location to normalize empty content to null (e.g., set
contentText = extractTextFromYXml(...) || null) or adjust buildContentPreview to
accept nulls, and ensure contentPreview is derived from that normalized value so
the module uses the same empty-string/null convention as clipAndCreate.ts;
reference extractTextFromYXml, buildContentPreview, contentText, and
contentPreview when making the change.
🪄 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: b01516f7-c303-49aa-9205-c243b5f1bb0e

📥 Commits

Reviewing files that changed from the base of the PR and between 324b91a and e0d93b1.

📒 Files selected for processing (6)
  • server/hocuspocus/src/extractPlainTextFromYXml.test.ts
  • server/hocuspocus/src/extractPlainTextFromYXml.ts
  • server/hocuspocus/src/index.ts
  • src/components/ai-chat/AIChatWikiLink.tsx
  • src/lib/aiClient.test.ts
  • src/lib/aiClient.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/ai-chat/AIChatWikiLink.tsx

Comment thread src/lib/aiClient.ts
Comment on lines +8 to 9
/** Union of SDK client types used for user-API-key mode. */
export type AIClient = OpenAI | Anthropic | GoogleGenAI;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

公開型のJSDocを日英併記にしてください。

AIClientConnectionTestResult の新規JSDocが英語のみで、リポジトリのコメント規約を満たしていません。

✍️ 提案差分
-/** Union of SDK client types used for user-API-key mode. */
+/**
+ * ユーザーAPIキー方式で使用するSDKクライアントのユニオン型。
+ * Union of SDK client types used for user-API-key mode.
+ */
 export type AIClient = OpenAI | Anthropic | GoogleGenAI;
@@
-/** Result of a user-API-key connection test from the settings UI. */
+/**
+ * 設定画面におけるユーザーAPIキー接続テストの結果。
+ * Result of a user-API-key connection test from the settings UI.
+ */
 export interface ConnectionTestResult {

As per coding guidelines Export functions, types, and interfaces must include TSDoc or JSDoc comments. Comments and documentation must include both Japanese and English explanations where applicable.

Also applies to: 43-44

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

In `@src/lib/aiClient.ts` around lines 8 - 9, The exported type AIClient and the
exported ConnectionTestResult need bilingual JSDoc/TSDoc comments (Japanese and
English) per repo guidelines; update the JSDoc above the AIClient type
declaration and above the ConnectionTestResult declaration to include both a
short Japanese description and an English description, mention that AIClient is
a union of SDK clients (OpenAI, Anthropic, GoogleGenAI) and that
ConnectionTestResult represents the result shape for connection tests so
reviewers can find them by symbol names AIClient and ConnectionTestResult.

@otomatty otomatty merged commit 427beb9 into main Apr 6, 2026
16 of 17 checks passed
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 4 new potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment on lines +213 to +220
<Button
variant="link"
size="sm"
className="ml-2 h-auto p-0 text-xs"
onClick={handleFetch}
>
{t("common.retry", "Retry")}
</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 McpResourceNodeView Retry button lacks disabled guard, allowing concurrent fetches that leak Tauri event listeners

The "Retry" button inside the error block (src/components/editor/McpResourceNodeView.tsx:213-220) does not set disabled={isLoading}, unlike the main Fetch button at line 177. If the user clicks Retry while a previous fetch is already in-flight (possible with quick double-clicks), a new set of onClaudeStreamComplete / onClaudeError listeners is registered and activeUnlistenRef.current is overwritten at line 142, orphaning the previous listeners. The old listeners are never unsubscribed, causing a resource leak. Each leaked listener also risks processing stale events for a different request id.

Suggested change
<Button
variant="link"
size="sm"
className="ml-2 h-auto p-0 text-xs"
onClick={handleFetch}
>
{t("common.retry", "Retry")}
</Button>
<Button
variant="link"
size="sm"
className="ml-2 h-auto p-0 text-xs"
onClick={handleFetch}
disabled={isLoading}
>
Open in Devin Review

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

Comment on lines +254 to +259
const text = extractAssistantText(msg);
const delta = text.length > 0 ? text.slice(aggregated.length) : "";
if (delta.length > 0) {
aggregated += delta;
emit({ type: "stream-chunk", id, content: delta });
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Sidecar assistant-message delta uses positional slice instead of prefix-aware diff, producing corrupt text when SDK message differs from streamed aggregation

In packages/claude-sidecar/src/handlers/query.ts:255, the delta for an assistant message is computed as text.slice(aggregated.length). This assumes the assistant message's full text always starts with exactly the same characters as aggregated (built from prior stream_event text deltas). If the SDK sends an assistant message whose text is shorter than aggregated (e.g. the model revised its output), the slice produces an empty string and the revision is silently dropped. If the text diverges from aggregated (same length, different prefix), the slice produces incorrect trailing characters that are emitted as a "delta" to the host, corrupting the displayed output.

Prompt for agents
In runQuery (packages/claude-sidecar/src/handlers/query.ts), the assistant-message handler at line 255 computes the delta as text.slice(aggregated.length). This is fragile because it assumes the full assistant text always starts with the exact same bytes as the aggregated stream content. A safer approach would be to check whether text starts with aggregated (e.g. text.startsWith(aggregated)) and only emit the suffix as a delta in that case. When the text does NOT start with aggregated, the handler should either replace the entire aggregated content and emit the full new text as a delta, or diff the two strings. The key functions involved are extractAssistantText and the msg.type === 'assistant' branch in runQuery.
Open in Devin Review

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

Comment on lines +43 to +45
let currentRequestId: string | null = null;
let aborted = false;
let resolveWait: (() => void) | null = null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 claudeCodeProvider closure shares mutable aborted/currentRequestId state across concurrent query() calls

In src/lib/aiProviders/claudeCodeProvider.ts:43-44, currentRequestId and aborted are declared as closure variables outside the query() generator. If two consumers call query() concurrently (e.g. chat + workflow panel), the second call resets aborted = false (line 63), overwrites currentRequestId (line 143), and steals all subsequent sidecar events. The first query silently stops receiving chunks and never completes. Calling abort() only cancels whichever query last wrote to currentRequestId.

Affected variables
  • currentRequestId (line 43): overwritten by each claudeQuery() call
  • aborted (line 44): reset at the start of each query()
  • resolveWait (line 45): overwritten by the wait loop
Prompt for agents
The createClaudeCodeProvider factory in src/lib/aiProviders/claudeCodeProvider.ts uses closure-scoped variables (currentRequestId, aborted, resolveWait) that are shared across all invocations of query(). Move these variables inside the query() async generator so each call has its own isolated state. The abort() method needs a way to signal the active query - consider storing per-request AbortControllers in a Map keyed by request id, or having query() accept an AbortSignal (which it already does via the signal parameter) instead of relying on the shared aborted flag. The currentRequestId used by event listeners should also be scoped per invocation (e.g. captured by the listener closures registered in that specific call).
Open in Devin Review

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

Comment on lines +80 to +137
const unlistenChunk = await onClaudeStreamChunk((payload) => {
if (currentRequestId && payload.id === currentRequestId) {
chunks.push({ type: "text", content: payload.content });
wake();
}
});

const unlistenComplete = await onClaudeStreamComplete((payload) => {
if (currentRequestId && payload.id === currentRequestId) {
chunks.push({ type: "done", content: "" });
done = true;
wake();
}
});

const unlistenError = await onClaudeError((payload) => {
if (currentRequestId && payload.id === currentRequestId) {
chunks.push({ type: "error", content: payload.error });
done = true;
wake();
}
});

const unlistenToolStart = await onClaudeToolUseStart((payload) => {
if (currentRequestId && payload.id === currentRequestId) {
chunks.push({
type: "tool_use_start",
content: payload.toolInput,
toolName: payload.toolName,
});
wake();
}
});

const unlistenToolComplete = await onClaudeToolUseComplete((payload) => {
if (currentRequestId && payload.id === currentRequestId) {
chunks.push({
type: "tool_use_complete",
content: "",
toolName: payload.toolName,
});
wake();
}
});

const unlistenMcpStatus = await onClaudeMcpStatus((payload) => {
if (currentRequestId && payload.id === currentRequestId && payload.servers) {
const store = useMcpConfigStore.getState();
store.updateStatuses(
payload.servers.map((s) => ({
name: s.name,
status: normalizeMcpConnectionStatus(s.status),
error: s.error,
tools: s.tools as McpServerTool[] | undefined,
})),
);
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Tauri event listener leak when early onClaude* registration throws in claudeCodeProvider

In src/lib/aiProviders/claudeCodeProvider.ts:80-118, all six Tauri event listeners (onClaudeStreamChunk, onClaudeStreamComplete, etc.) are registered sequentially before the try block at line 139. If, for example, onClaudeToolUseComplete (line 110) throws, the previously registered listeners (unlistenChunk, unlistenComplete, unlistenError, unlistenToolStart) are never cleaned up because the finally block at line 180 is inside a separate try that is never entered. The leaked listeners continue receiving events from all future sidecar queries.

Prompt for agents
In createClaudeCodeProvider (src/lib/aiProviders/claudeCodeProvider.ts), the six listener registrations at lines 80-118 happen before the try/finally block at lines 139-188. If any registration throws partway through, the already-registered listeners are leaked. Move all listener registrations inside the try block, and collect them into an array (const cleanups: Array<() => void> = []). In the finally block, iterate and call each cleanup. The streamClaudeQuery function in src/lib/claudeCode/streamClaudeQuery.ts already uses this pattern correctly (lines 110-183, cleanups array) and can serve as a reference.
Open in Devin Review

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

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.

1 participant