Skip to content

Windows日本語IME対応#2

Open
6m10cmUK wants to merge 4 commits into
masterfrom
feature/windows
Open

Windows日本語IME対応#2
6m10cmUK wants to merge 4 commits into
masterfrom
feature/windows

Conversation

@6m10cmUK

@6m10cmUK 6m10cmUK commented Mar 8, 2026

Copy link
Copy Markdown
Owner

現状

Windows版で日本語入力(IME)ができない。英語入力は動く。

原因の分析

packages/xterm/lib/src/ui/custom_text_edit.dart_onKeyEvent を修正済み(148-157行):

  • デスクトップで印字可能文字(>=0x20)をCtrl/Altなしで打った場合、KeyEventResult.ignoredを返す
  • これによりFlutterのTextInputチャネルにキーが流れ、IMEが処理するはず

確認すべきこと

  1. Windows実機で flutter run して日本語入力を試す
  2. うまくいかない場合のデバッグポイント:
    • _onKeyEventprint を入れて、印字可能文字で ignored が返ってるか確認
    • updateEditingValueprint を入れて、IMEから値が来てるか確認
    • _onInsertprint を入れて、テキストが届いてるか確認

ファイル構成

packages/xterm/lib/src/ui/custom_text_edit.dart  ← IME修正箇所
packages/xterm/lib/src/terminal_view.dart         ← _handleKeyEvent, _onInsert

キーイベントの流れ

キー押下
  → Focus.onKeyEvent (_onKeyEvent)
    → composing中? → skipRemainingHandlers(IMEに任せる)
    → デスクトップ & 印字可能 & Ctrl/Altなし? → ignored(TextInputへ流す)
    → それ以外 → widget.onKeyEvent (_handleKeyEvent) → terminal.keyInput
  
TextInput経由(IME確定後)
  → updateEditingValue → onInsert → terminal.textInput

Android側への影響

_isDesktopPlatform フラグで分岐してるのでAndroidには影響なし。

その他メモ

  • feature/autocomplete はmasterにmerge済み(コマンド履歴補完)
  • Android最新は android-v1.0.10
  • Windows最新は windows-v1.0.0(IME修正入りだが未確認)
  • Windows実機で flutter run してデバッグするのが一番早い

Summary by CodeRabbit

リリース ノート

  • 新機能

    • デスクトップ向けにドラッグで分割/配置できるドック式タブ UI とサイドバーを追加しました。
    • デスクトップ専用のナビゲーションとタブ追加の操作を導入しました。
  • 不具合修正

    • クリップボード貼り付けの取り扱いを改善(ペーストとキー入力を区別)。
    • テキスト消去と表示更新の境界処理を安全化。
  • リファクタリング

    • ターミナルのフォーカスとライフサイクル管理を見直し、安定性を向上しました。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@6m10cmUK

6m10cmUK commented Mar 9, 2026

Copy link
Copy Markdown
Owner Author

Windows実機テスト結果

全くキーボード操作を受け付けない。 日本語どころか英語も矢印キーも何も効かない。

以前の状態との差分

前回(windows-v1.0.0)は英語入力は動いていた。今回動かなくなった原因候補:

1. focusNode変更(疑い度: 高)

_TerminalTabにFocusNodeを追加し、TerminalViewに渡すようにした。

// _TerminalTab
final FocusNode focusNode = FocusNode();

// _buildTerminalPane
TerminalView(
  tab.terminal,
  focusNode: tab.focusNode,  // ← これが新しい
  ...
)

TerminalView内部でもFocusNodeを作ってるので、外部から渡すと二重管理になってフォーカスが当たらない可能性がある。

2. autocompleteのonOutputラッパー(疑い度: 低)

tab.terminal.onOutput = (data) {
  tab.session?.write(...);
  _onTerminalOutput(tab, data);  // ← 追加
};

デバッグ手順

git stash  # 一旦退避

# Step 1: focusNodeを外部から渡すのをやめてみる
# terminal_screen.dart の _buildTerminalPane で focusNode: tab.focusNode を削除
# → これで英語入力が復活するか確認

# Step 2: 復活したらfocusNodeが原因確定
# キーボードトグルボタンは別の方法で実装する

Use GlobalKey instead of FocusNode for terminal reference.
External FocusNode likely broke keyboard input on Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

デスクトップ向けにSplitViewを廃止して新たにDockView/DockViewControllerを導入、タブ描画とドラッグ/ドロップでの分割・移動を可能に。ターミナル画面はFocusNode→GlobalKeyへ切替え、セッション/サイドバー挙動とペースト処理等を調整した。

Changes

Cohort / File(s) Summary
ターミナル画面(UI/ライフサイクル)
lib/screens/terminal_screen.dart
タブ毎のFocusNodeGlobalKey terminalKeyへ置換。タブ開閉・破棄でのdispose削除。sessionNameを必須→nullableに変更。デスクトップでDockViewへ移行、サイドバー状態・初期タブ追加ロジック、開くセッションUI(desktop: SimpleDialog / mobile: BottomSheet)を変更。clipboard pasteはtab.terminal.paste(...)呼び出しへ。asyncでのsetStatemountedチェックを追加。
新規 Dock レイアウトウィジェット
lib/widgets/dock_view.dart
新規追加。DockNodeツリー(DockLeaf/DockBranch)、DockViewController(タブ追加/移動/分割/アクティベート/クローズ等)、DockViewレンダラを導入。ドラッグ・ドロップのドロップゾーン(center/left/right/top/bottom)、リサイズ可能なブランチ比率、フォーカス通知、タブ再配置・削除ロジックを実装。
SplitView 削除
lib/widgets/split_view.dart
既存のSplitView実装を丸ごと削除(ノードモデル・コントローラ・レンダリング・公開APIを含む)。
ホスト一覧のナビゲーション条件分岐
lib/screens/host_list_screen.dart
プラットフォーム判定(defaultTargetPlatform)によりWindows/macOS/Linuxでは直接TerminalScreen(host: host)へ遷移、それ以外はSessionListScreen(host: host)へ遷移するよう変更。
xterm ライブラリの細かい修正群
packages/xterm/lib/src/core/buffer/buffer.dart, packages/xterm/lib/src/terminal_view.dart, packages/xterm/lib/src/ui/custom_text_edit.dart
Bufferのerase範囲をクランプして境界安全化。terminal_viewで挿入文字列が複数文字または改行を含む場合はpaste経路へ振り分け。custom_text_editでプラットフォームの“reset echo”を抑制する内部状態、レンダー変換取得の改善、TextInputConfigurationにviewId追加、編集値更新時の保護ロジックなどを実装。

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as "User"
    participant DockView as "DockView (UI)"
    participant Controller as "DockViewController"
    participant Terminal as "TerminalView (tab)"
    participant SessionMgr as "SessionManager"

    User->>DockView: ドラッグ&ドロップでタブを移動/分割
    DockView->>Controller: splitLeaf/ moveTabToLeaf/ addTabToLeaf
    Controller->>Controller: 更新(root tree, focusedLeafId)
    Controller-->>DockView: onChanged / onFocusChanged
    DockView->>Terminal: paneBuilder(tabIndex,... ) をレンダリング
    User->>Terminal: クリックでアクティブ化
    Terminal->>Controller: アクティブ化イベント (via onFocusChanged)
    User->>Terminal: クリップボード貼付け
    Terminal->>Terminal: terminal.paste(text) を実行
    SessionMgr-->>Terminal: セッション作成/接続ライフサイクル管理
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 ぽんと跳ねた小さなウサギが言う
キーを抱え、タブを結び分けるよ
ドックは伸びて、サイドは開き
ペーストは滑らかに、フォーカスは確かなり 🐾
さあ、新しい窓で走ろう!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive PR title "Windows日本語IME対応" describes Windows Japanese IME support, which aligns with the stated objective but is only partially reflected in the actual changes—the changeset includes significant unrelated architectural refactoring (DockView, SplitView replacement, desktop sidebar, session management UI) that goes beyond IME-only scope. Clarify whether the title should reflect the primary IME fix or include the broader desktop/UI refactoring; if IME-only, consider retitling to explicitly reflect that alongside mentioning the architectural changes, or split into focused PRs.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

Important

Merge conflicts detected (Beta)

  • Resolve merge conflict in branch feature/windows
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/windows

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/screens/terminal_screen.dart`:
- Around line 854-858: The current implicit focus request using
FocusScope.of(context).requestFocus() depends on traversal behavior; replace it
with an explicit focus lookup by calling Focus.of(context, skipTraversal:
true)?.requestFocus() when using tab.terminalKey.currentContext to ensure the
terminal's focus node is targeted; alternatively (if you prefer exposing the
node) make TerminalViewState expose its _focusNode as a public field and type
terminalKey as GlobalKey<TerminalViewState> so you can call
terminalKey.currentState!.focusNode.requestFocus()—prefer the Focus.of approach
(use Focus.of(context, skipTraversal: true)?.requestFocus()) and update the code
references around tab.terminalKey.currentContext and
CustomTextEdit/TerminalViewState accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6a1f7b2d-ec08-4546-afe0-233058866d69

📥 Commits

Reviewing files that changed from the base of the PR and between 1d415a8 and 9b8c0fa.

📒 Files selected for processing (1)
  • lib/screens/terminal_screen.dart

Comment on lines +854 to +858
// Re-focus terminal to open keyboard
final context = tab.terminalKey.currentContext;
if (context != null) {
FocusScope.of(context).requestFocus();
}

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

🧩 Analysis chain

🌐 Web query:

Flutter FocusScope.of(context).requestFocus() without arguments behavior

💡 Result:

FocusScope.of(context).requestFocus() (no argument) asks the current FocusScopeNode itself to take primary focus. Internally it calls _doRequestFocus(findFirstFocus: true), meaning the scope will try to put focus on the “first focus” inside that scope (typically the previously-focused child if one exists, otherwise the first focusable descendant it can find). If nothing inside can take focus, the scope ends up focused. Focus change notifications are delivered in a microtask (can appear up to a frame later). [1], [2]

If your intent is to “remove focus / dismiss keyboard”, prefer FocusScope.of(context).unfocus() instead of relying on requestFocus() patterns. [3]

Sources:
[1] Flutter API: FocusNode.requestFocus (also used by FocusScopeNode) https://api.flutter.dev/flutter/widgets/FocusNode/requestFocus.html
[2] Flutter focus internals note on scopes requesting focus and finding first descendant https://flutter.megathink.com/interaction/focus
[3] Flutter docs: “Understanding Flutter’s keyboard focus system” (recommends unfocus) https://docs.flutter.dev/ui/interactivity/focus


🏁 Script executed:

#!/bin/bash
# Description: Check if TerminalViewState exposes a focusNode field and its visibility

# Search for focusNode field declaration in TerminalViewState
rg -n -A 2 -B 2 'class TerminalViewState' packages/xterm/lib/src/terminal_view.dart

# Check focusNode field visibility (late FocusNode _focusNode or late FocusNode focusNode)
rg -n 'FocusNode.*focusNode' packages/xterm/lib/src/terminal_view.dart

Repository: 6m10cmUK/Themisto

Length of output: 342


🏁 Script executed:

#!/bin/bash
# Check terminalKey declaration and its type
rg -n 'terminalKey.*=' lib/screens/terminal_screen.dart | head -5
# Also search for the full terminalKey field definition
rg -n -A 1 'final.*terminalKey' lib/screens/terminal_screen.dart

Repository: 6m10cmUK/Themisto

Length of output: 424


🏁 Script executed:

#!/bin/bash
# Examine TerminalView widget to understand focus structure
rg -n -A 10 'class TerminalView' packages/xterm/lib/src/terminal_view.dart | head -30
# Check if TerminalView wraps its focusNode in a Focus widget
rg -n 'Focus\(' packages/xterm/lib/src/terminal_view.dart

Repository: 6m10cmUK/Themisto

Length of output: 775


🏁 Script executed:

#!/bin/bash
# Find and examine TerminalViewState's build method
rg -n -A 50 'Widget build\(BuildContext context\)' packages/xterm/lib/src/terminal_view.dart | head -80

Repository: 6m10cmUK/Themisto

Length of output: 2025


🏁 Script executed:

#!/bin/bash
# Check how _focusNode is initialized and used
rg -n 'initState\|_focusNode' packages/xterm/lib/src/terminal_view.dart | head -20

Repository: 6m10cmUK/Themisto

Length of output: 43


フォーカスリクエストの実装を明確化してください。

FocusScope.of(context).requestFocus()(引数なし)は、スコープ内の最初にフォーカス可能な子ノードを探してフォーカスするため、実装は機能しています。CustomTextEditがTerminalViewStateの最初にフォーカス可能なウィジェットであり、内部の_focusNodeを持つため、結果としてターミナルは正しくフォーカスされキーボードが開きます。

ただし、この実装は暗黙的な動作に依存しており、脆弱です。より明示的で保守しやすいアプローチを検討してください:

  1. Focus.of()を使用(推奨):FocusScope.of(context).requestFocus()の代わりにFocus.of(context, skipTraversal: true)?.requestFocus()を使用する
  2. TerminalViewStateを変更:focusNodeをpublicフィールドとして公開し、GlobalKeyを型付けする(ただしxtermnパッケージの変更が必要)

現在のコードは動作していますが、次のいずれかの方法で改善することをお勧めします。

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

In `@lib/screens/terminal_screen.dart` around lines 854 - 858, The current
implicit focus request using FocusScope.of(context).requestFocus() depends on
traversal behavior; replace it with an explicit focus lookup by calling
Focus.of(context, skipTraversal: true)?.requestFocus() when using
tab.terminalKey.currentContext to ensure the terminal's focus node is targeted;
alternatively (if you prefer exposing the node) make TerminalViewState expose
its _focusNode as a public field and type terminalKey as
GlobalKey<TerminalViewState> so you can call
terminalKey.currentState!.focusNode.requestFocus()—prefer the Focus.of approach
(use Focus.of(context, skipTraversal: true)?.requestFocus()) and update the code
references around tab.terminalKey.currentContext and
CustomTextEdit/TerminalViewState accordingly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/screens/terminal_screen.dart (1)

963-977: ⚠️ Potential issue | 🟠 Major

TerminalViewへのfocusNode未渡しによるキーボード入力問題

TerminalViewfocusNode パラメータを渡していないため、TerminalViewState は内部で独自の _focusNode を作成します。これにより外部からのフォーカス制御が切断され、PR目的で報告されている「キーボード入力が受け付けられない」問題の原因となっている可能性があります。

PRコメントで指摘されている「外部FocusNodeのダブル管理」問題と関連しています。

🔧 検討オプション

オプション1: _TerminalTabFocusNode を復元し、TerminalView に渡す

class _TerminalTab {
  // ...
  final GlobalKey terminalKey = GlobalKey();
  final FocusNode focusNode = FocusNode();
  // ...
}
child: TerminalView(
  tab.terminal,
  key: tab.terminalKey,
  focusNode: tab.focusNode,  // 追加
  // ...
)

オプション2: GlobalKey<TerminalViewState> を使用して内部の _focusNode にアクセス(ただし _focusNode は private)

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

In `@lib/screens/terminal_screen.dart` around lines 963 - 977, TerminalView is not
receiving an external FocusNode so TerminalViewState creates its own private
_focusNode, breaking external focus control and causing keyboard input issues;
restore a FocusNode on the owning tab (add a FocusNode field to _TerminalTab)
and pass that FocusNode into the TerminalView constructor (use tab.focusNode
when building TerminalView alongside tab.terminal and tab.terminalKey), ensuring
you do not create a second FocusNode elsewhere to avoid double-management of
focus.
🧹 Nitpick comments (3)
lib/widgets/dock_view.dart (2)

52-64: 初期化後の focusedLeafId の整合性

コンストラクタで focusedLeafId = '0' を設定していますが、その後の操作(タブ移動、リーフ削除など)で整合性が崩れる可能性があります。focusedLeafId が常に有効なリーフを指すことを保証するバリデーションの追加を検討してください。

♻️ バリデーション追加の例
String? get safeFocusedLeafId {
  if (focusedLeafId == null) return null;
  if (_findLeaf(root, focusedLeafId!) != null) return focusedLeafId;
  // Fallback to first available leaf
  focusedLeafId = _firstLeaf(root)?.id;
  return focusedLeafId;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/widgets/dock_view.dart` around lines 52 - 64, The constructor sets
focusedLeafId = '0' but offers no validation after mutations; add a safe getter
and validation logic to ensure focusedLeafId always references an existing leaf
by checking the tree (use existing helpers like _findLeaf and _firstLeaf against
root) and update focusedLeafId to a valid fallback when missing (e.g., set to
the first available leaf's id or null). Implement this as either a
safeFocusedLeafId getter or a private validateFocusedLeaf method invoked after
operations that mutate the tree (e.g., in methods that move tabs or remove
leaves) and use that validated value wherever focusedLeafId is consumed.

350-360: 分割比率の永続化を検討

onPanUpdate でリアルタイムに branch.ratio を更新していますが、onChanged コールバック経由で親に通知されています。ウィンドウリサイズ後も比率が保持されることを確認してください。

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

In `@lib/widgets/dock_view.dart` around lines 350 - 360, The current onPanUpdate
updates branch.ratio live but doesn't guarantee persistence across window
resizes; modify the split-pane logic so branch.ratio is treated as the source of
truth and persisted whenever the user finishes dragging (use onPanEnd on the
GestureDetector to call widget.onChanged with the final ratio or otherwise save
it), ensure layout rebuilds compute pane sizes from branch.ratio and
availableSize (respecting _kMinPaneSize) rather than storing absolute pixel
sizes, and make sure any window resize handler reads branch.ratio to recompute
firstSize/secondSize so the ratio is preserved.
lib/screens/host_list_screen.dart (1)

39-52: デスクトッププラットフォーム判定の共通化を検討してください

ロジックは正しいですが、terminal_screen.dart_isDesktopcustom_text_edit.dart_isDesktopPlatform と同様のパターンが重複しています。共通ユーティリティへの抽出を検討してください。

♻️ 共通ユーティリティの提案
// lib/utils/platform_utils.dart
import 'package:flutter/foundation.dart';

bool get isDesktopPlatform =>
    defaultTargetPlatform == TargetPlatform.windows ||
    defaultTargetPlatform == TargetPlatform.macOS ||
    defaultTargetPlatform == TargetPlatform.linux;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/host_list_screen.dart` around lines 39 - 52, Extract the repeated
desktop-detection logic into a shared utility (e.g. a top-level getter
isDesktopPlatform in a new lib/utils/platform_utils.dart) and replace the inline
checks in HostListScreen's onTap (the local isDesktop), TerminalScreen's
_isDesktop, and CustomTextEdit's _isDesktopPlatform with that common
isDesktopPlatform getter; update imports where needed and use isDesktopPlatform
in the MaterialPageRoute builder to choose TerminalScreen(host: host) vs
SessionListScreen(host: host).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/screens/terminal_screen.dart`:
- Around line 89-99: The _currentTab getter can throw RangeError when _tabs is
empty (e.g. when sessionName is null on desktop); update any code that accesses
_currentTab or _tabs[_currentIndex] (notably the _currentTab getter itself and
callers like _copySelection, _pasteClipboard, _acceptSuggestion) to first check
_tabs.isEmpty (or validate _currentIndex) and handle the empty state gracefully
(early return, no-op, or fallback UI behavior) so no direct index access occurs
when there are no tabs.
- Around line 1158-1164: The code attempts to access a non-existent focusNode on
_TerminalTab (same bug as line 543). Fix by either calling the tab's existing
focus API (e.g. use _tabs[_currentIndex].requestFocus() or
_tabs[_currentIndex].focus() if such a method exists) or add a FocusNode field
to the _TerminalTab class (e.g. final FocusNode focusNode) and ensure it's
initialized and used by the tab widget, then keep the call
_tabs[_currentIndex].focusNode.requestFocus(); update whichever approach matches
the rest of the codebase.
- Around line 542-545: The code still references a removed focusNode on
_TerminalTab causing a compile error; replace the focusNode usage with the new
terminalKey-based API: locate where _tabs, _currentIndex and the _TerminalTab
class are used and change the focus call from
_tabs[_currentIndex].focusNode.requestFocus() to using the tab's terminalKey
(e.g. access the tab's terminalKey.currentState or currentContext and call the
appropriate requestFocus method) ensuring null checks (?.) are applied so focus
is only requested when terminalKey is available.

In `@packages/xterm/lib/src/ui/custom_text_edit.dart`:
- Around line 182-183: The code sets TextInputConfiguration(viewId:
View.of(context).viewId) which requires Flutter >= 3.19.0; ensure the package
constraint in pubspec.yaml enforces Flutter 3.19.0 or newer (e.g., update the
sdk constraint or bump the Flutter SDK version) so View.of(context).viewId is
available, or gate the use of viewId behind a runtime/compile-time check and
fall back to omitting viewId if older Flutter must be supported; locate the
occurrence in TextInputConfiguration and update pubspec.yaml SDK constraint or
add version-guarding around View.of(context).viewId accordingly.

---

Outside diff comments:
In `@lib/screens/terminal_screen.dart`:
- Around line 963-977: TerminalView is not receiving an external FocusNode so
TerminalViewState creates its own private _focusNode, breaking external focus
control and causing keyboard input issues; restore a FocusNode on the owning tab
(add a FocusNode field to _TerminalTab) and pass that FocusNode into the
TerminalView constructor (use tab.focusNode when building TerminalView alongside
tab.terminal and tab.terminalKey), ensuring you do not create a second FocusNode
elsewhere to avoid double-management of focus.

---

Nitpick comments:
In `@lib/screens/host_list_screen.dart`:
- Around line 39-52: Extract the repeated desktop-detection logic into a shared
utility (e.g. a top-level getter isDesktopPlatform in a new
lib/utils/platform_utils.dart) and replace the inline checks in HostListScreen's
onTap (the local isDesktop), TerminalScreen's _isDesktop, and CustomTextEdit's
_isDesktopPlatform with that common isDesktopPlatform getter; update imports
where needed and use isDesktopPlatform in the MaterialPageRoute builder to
choose TerminalScreen(host: host) vs SessionListScreen(host: host).

In `@lib/widgets/dock_view.dart`:
- Around line 52-64: The constructor sets focusedLeafId = '0' but offers no
validation after mutations; add a safe getter and validation logic to ensure
focusedLeafId always references an existing leaf by checking the tree (use
existing helpers like _findLeaf and _firstLeaf against root) and update
focusedLeafId to a valid fallback when missing (e.g., set to the first available
leaf's id or null). Implement this as either a safeFocusedLeafId getter or a
private validateFocusedLeaf method invoked after operations that mutate the tree
(e.g., in methods that move tabs or remove leaves) and use that validated value
wherever focusedLeafId is consumed.
- Around line 350-360: The current onPanUpdate updates branch.ratio live but
doesn't guarantee persistence across window resizes; modify the split-pane logic
so branch.ratio is treated as the source of truth and persisted whenever the
user finishes dragging (use onPanEnd on the GestureDetector to call
widget.onChanged with the final ratio or otherwise save it), ensure layout
rebuilds compute pane sizes from branch.ratio and availableSize (respecting
_kMinPaneSize) rather than storing absolute pixel sizes, and make sure any
window resize handler reads branch.ratio to recompute firstSize/secondSize so
the ratio is preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 89fe5a9d-3fd4-4a35-b96a-503ebbde464a

📥 Commits

Reviewing files that changed from the base of the PR and between 9b8c0fa and 2ef8220.

📒 Files selected for processing (7)
  • lib/screens/host_list_screen.dart
  • lib/screens/terminal_screen.dart
  • lib/widgets/dock_view.dart
  • lib/widgets/split_view.dart
  • packages/xterm/lib/src/core/buffer/buffer.dart
  • packages/xterm/lib/src/terminal_view.dart
  • packages/xterm/lib/src/ui/custom_text_edit.dart
💤 Files with no reviewable changes (1)
  • lib/widgets/split_view.dart

Comment on lines +89 to +99
if (widget.sessionName != null) {
_addTab(widget.sessionName!);
if (_isDesktop) {
_sidebarOpen = true;
_loadSidebarSessions();
}
} else {
// Desktop: open with sidebar, no initial tab
_sidebarOpen = true;
_loadSidebarSessions();
}

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

sessionName が null の場合のタブなし起動

デスクトップで sessionName が null の場合、サイドバーのみで起動しタブは追加されません。_tabs.isEmpty の状態で _currentTab getter にアクセスするとエラーになる可能性があります。

🔍 確認が必要な箇所

_currentTab getter (line 70) は _tabs[_currentIndex] を返しますが、_tabs が空の場合は RangeError が発生します。_tabs.isEmpty チェックが必要な箇所を確認してください:

  • _copySelection (line 725)
  • _pasteClipboard (line 741)
  • _acceptSuggestion (line 878)
  • etc.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/terminal_screen.dart` around lines 89 - 99, The _currentTab
getter can throw RangeError when _tabs is empty (e.g. when sessionName is null
on desktop); update any code that accesses _currentTab or _tabs[_currentIndex]
(notably the _currentTab getter itself and callers like _copySelection,
_pasteClipboard, _acceptSuggestion) to first check _tabs.isEmpty (or validate
_currentIndex) and handle the empty state gracefully (early return, no-op, or
fallback UI behavior) so no direct index access occurs when there are no tabs.

Comment on lines +542 to +545
if (_isDesktop && _currentIndex < _tabs.length) {
_tabs[_currentIndex].focusNode.requestFocus();
}
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

存在しない focusNode フィールドへの参照

_TerminalTab クラスから focusNode フィールドが削除されましたが、このコードは依然として tab.focusNode を参照しています。これはコンパイルエラーになります。

🐛 修正案

terminalKey を使用してフォーカスを要求する方法に変更:

      if (_isDesktop && _currentIndex < _tabs.length) {
-       _tabs[_currentIndex].focusNode.requestFocus();
+       final context = _tabs[_currentIndex].terminalKey.currentContext;
+       if (context != null) {
+         FocusScope.of(context).requestFocus();
+       }
      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (_isDesktop && _currentIndex < _tabs.length) {
_tabs[_currentIndex].focusNode.requestFocus();
}
return;
if (_isDesktop && _currentIndex < _tabs.length) {
final context = _tabs[_currentIndex].terminalKey.currentContext;
if (context != null) {
FocusScope.of(context).requestFocus();
}
}
return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/terminal_screen.dart` around lines 542 - 545, The code still
references a removed focusNode on _TerminalTab causing a compile error; replace
the focusNode usage with the new terminalKey-based API: locate where _tabs,
_currentIndex and the _TerminalTab class are used and change the focus call from
_tabs[_currentIndex].focusNode.requestFocus() to using the tab's terminalKey
(e.g. access the tab's terminalKey.currentState or currentContext and call the
appropriate requestFocus method) ensuring null checks (?.) are applied so focus
is only requested when terminalKey is available.

Comment on lines +1158 to 1164
final leaf = _dockController!.focusedLeaf();
if (leaf != null && leaf.tabIndices.isNotEmpty) {
setState(() => _currentIndex = leaf.activeTabIndex);
if (_currentIndex >= 0 && _currentIndex < _tabs.length) {
_tabs[_currentIndex].focusNode.requestFocus();
}
}

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 | 🔴 Critical

同様の focusNode 参照エラー

Line 543と同じ問題です。_TerminalTabfocusNode フィールドは存在しません。

🐛 修正案
          if (_currentIndex >= 0 && _currentIndex < _tabs.length) {
-           _tabs[_currentIndex].focusNode.requestFocus();
+           final context = _tabs[_currentIndex].terminalKey.currentContext;
+           if (context != null) {
+             FocusScope.of(context).requestFocus();
+           }
          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final leaf = _dockController!.focusedLeaf();
if (leaf != null && leaf.tabIndices.isNotEmpty) {
setState(() => _currentIndex = leaf.activeTabIndex);
if (_currentIndex >= 0 && _currentIndex < _tabs.length) {
_tabs[_currentIndex].focusNode.requestFocus();
}
}
final leaf = _dockController!.focusedLeaf();
if (leaf != null && leaf.tabIndices.isNotEmpty) {
setState(() => _currentIndex = leaf.activeTabIndex);
if (_currentIndex >= 0 && _currentIndex < _tabs.length) {
final context = _tabs[_currentIndex].terminalKey.currentContext;
if (context != null) {
FocusScope.of(context).requestFocus();
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/screens/terminal_screen.dart` around lines 1158 - 1164, The code attempts
to access a non-existent focusNode on _TerminalTab (same bug as line 543). Fix
by either calling the tab's existing focus API (e.g. use
_tabs[_currentIndex].requestFocus() or _tabs[_currentIndex].focus() if such a
method exists) or add a FocusNode field to the _TerminalTab class (e.g. final
FocusNode focusNode) and ensure it's initialized and used by the tab widget,
then keep the call _tabs[_currentIndex].focusNode.requestFocus(); update
whichever approach matches the rest of the codebase.

Comment on lines 182 to +183
final config = TextInputConfiguration(
viewId: View.of(context).viewId,

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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Flutter TextInputConfiguration viewId parameter minimum version requirement

💡 Result:

The viewId parameter in Flutter's TextInputConfiguration was added in framework pull request #145708, merged to master on 2024-03-28. The corresponding engine support was added in PR #51099, merged on 2024-03-29. This feature became available in the first stable Flutter release after these merges, which is Flutter 3.19.0 (stable release around April 2024), as earlier stable versions like 3.19 beta predate the merge, and 3.19 release notes confirm framework updates post-merge. The API docs confirm viewId as int? viewId with default null, usable from this version onward. No explicit minimum version is stated in docs, but usage requires Flutter >= 3.19.0.

Citations:


viewId パラメータの Flutter バージョン要件を確認してください

viewId は マルチビュー/マルチウィンドウのサポートに必要ですが、このパラメータは Flutter 3.19.0 以降 で利用可能です。プロジェクトの pubspec.yaml の Flutter バージョン制約が 3.19.0 以上に設定されていることを確認してください。

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

In `@packages/xterm/lib/src/ui/custom_text_edit.dart` around lines 182 - 183, The
code sets TextInputConfiguration(viewId: View.of(context).viewId) which requires
Flutter >= 3.19.0; ensure the package constraint in pubspec.yaml enforces
Flutter 3.19.0 or newer (e.g., update the sdk constraint or bump the Flutter SDK
version) so View.of(context).viewId is available, or gate the use of viewId
behind a runtime/compile-time check and fall back to omitting viewId if older
Flutter must be supported; locate the occurrence in TextInputConfiguration and
update pubspec.yaml SDK constraint or add version-guarding around
View.of(context).viewId accordingly.

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