「入力したのに空になる」— WinForms を Web に移すときに踏んだ Tab/blur 依存の罠
WinForms → Blazor の移行中、郵便番号の2分割入力が「入力してください」エラーになる現象を踏みました。原因は、入力値の確定が Tab / blur に依存していたこと。古い GUI パラダイムを Web に持ち込むときに必ず出る罠です。
これは「レガシー移行で学んだこと」シリーズの第4回です。前回は Claude CodeとCodexを分業させた話 を参照ください。
ここまでは運用・ワークフローの話でしたが、今回から具体的な事故事例に入ります。まずは WinFormsをWeb(Blazor Server)に移す時にほぼ確実に踏む落とし穴 について。
最初に踏んだバグ
ある入力画面で、郵便番号を2分割入力するフォームがありました。左3桁、右4桁。隣に「設定」ボタンがあって、押すと住所が自動反映される仕様です。
動作確認でこんなケースを試しました。
- 左に
196、右に0013と入力 - そのまま「設定」ボタンをクリック
期待: 住所が自動反映される。
実際: 「郵便番号を入力してください」 というバリデーションエラーが出る。
え? 入力してるのに? と最初は画面表示のバグを疑いました。でも、よく見ると 「設定」ボタンを押した瞬間に、右側の入力欄の値が消える んです。
再現と切り分け
色々なパターンで試しました。
- 入力 → Tabキーで次のフィールドへ移動 → 設定ボタン押下 → エラー
- 入力 → 別のフィールドをクリック(blurさせる) → 設定ボタン押下 → エラー
- 入力 → そのまま設定ボタンを直接押下 → エラー
どのパターンでも、「郵便番号を入力してください」 というバリデーションエラーが等しく出ました。つまり、フォーカスを外しても親モデルに値が届いていない 状態でした。
ここで印象的だったのが、Codex(レビュー担当)の初期仮説が外れた ことです。Codexは「Blazorの @bind はchangeイベント発火時(= blur時)に親モデルに反映されるはずなので、Tab / 別フィールドクリックの2パターンは通るはず」と推論していました。理屈としてはもっともらしいんですが、実際に手で動かしたら全滅でした。
AIの推論は既知のパラダイム(WinForms的な「blurで確定」や、標準的な @bind の挙動)を前提にしがちです。実際の挙動は、画面側のコンポーネント実装や状態管理の組み方によってはそれとも違う、というのを身に染みて学びました。仮説は必ず手で動かしてから採用する の原則を、改めて叩き込まれました。
なぜ起きるか(推測込み)
WinForms世代のUIでは、「入力欄にフォーカスがあるうちは確定前、フォーカスが外れたら確定」が当たり前の前提でした。ボタン押下時には当然フォーカスが移動するので、暗黙に値が反映されます。
Blazor Server側では、@bind のイベントタイミング、ボタンハンドラがどのモデル値を読むか、フォームコンポーネントの内部状態管理など、複数の要素が絡んで「DOMに見えている値」と「ハンドラが読み取る値」が一致しないケースが起きます。今回の画面もそのパターンで、細かい根本原因は画面ごとに違う可能性がありますが、共通していたのは「クリック時点で入力値が親ハンドラに届いていない」 ことでした。
対策: Click-Time Syncをルール化
個別に直すだけでは、画面を増やすたびに同じバグが再生産されます。そこで、「入力 + ボタン」というForm+Buttonパターンを見つけたら必ずclick-time syncを入れる というルールに変えました。
具体的には、
- 入力値の親モデル反映を
oninputベースに寄せる、もしくは - ボタン側で「クリック時点のDOM値を明示的に読み直してからAPIを呼ぶ」構造にする
さらに、この手の画面を触るたびにReviewer Sub Agent(前回書いたOrchestratorの話参照)に以下を必ず確認させるようにしました。
- 値の更新がTab / blur依存になっていないか
- クリック時点でstaleな(古い)モデル値を読んでいないか
- クリック後に意図しない上書きや消失が起きないか
- 影響範囲に、他のForm+Button箇所(別の検索、住所コピー、コード入力からの自動反映など)が含まれていないか
事故った後の横断チェック
この手のバグは、1画面で踏んだ時点で「他の画面でも絶対に起きている」と思ったほうがいいです。実際、横断チェックをかけたら以下でも同じ問題が出ていました。
- User住所をRegisterer住所にコピーするボタン(クリック時点で住所入力欄の値が未確定)
- 別マスタのコード入力からの自動反映(コードを入れてすぐボタンを押すと反映されない)
- 検索ダイアログの検索条件入力(入力してすぐ「検索」を押すと条件が1個抜ける)
「Form+Buttonがある画面は全部見直し対象」 として横断修正をかけました。このとき、Claude Codeに一気に全箇所修正させて、Codexに差分レビューさせる形で回しました(前回書いた分業フローがここで効きました)。
わかったこと
- WinForms世代のUXの「当たり前」は、Web側の「当たり前」とズレている。見た目を同じに作れるからこそ、挙動差分が見えにくい
- このズレは 画面バグ というより パラダイムのズレ で、個別修正では収束しない。ルール化しないと画面ごとに再生産される
- レビュー時に「クリック時点で値が親に届いているか」を固定観点で見るようにしてから、事故の再発が止まった
- AIの推論は既知のパラダイムに引きずられる。「Tab / blurなら通るはず」のような、もっともらしい仮説は必ず手で動かしてから採用する。AIに全部預けていると、この種の「理屈は合ってるけど実機で外れる」仮説で時間を溶かします
- ちなみにこの話、WinForms → Blazor固有ではなく、Qt → Electron、Swing → Web、あらゆる旧GUIパラダイム → Web移行で出る話だと思います。当時のUIモデルが「フォーカスイベント駆動」で動いていた時代の遺物です
次にやること
UI側の話は一段落ですが、SQL / API側でも元画面との振る舞い差分が出ます。次回は、見た目はまったく同じなのに、削除対象テーブルの範囲やOracle関数の呼び出しがズレていて業務同等性が崩れた話を書きます。
渡邊 賢
等差級数的Commit 運営 / ICD VIETNAM.LLC General Manager
AI駆動開発と段階的なレガシーモダン化をテーマに、日々の試行錯誤をこのブログに記録しています。
プロフィール詳細 arrow_forward似たような課題に困っている方、一緒に考えませんか。
AI駆動開発・Vibe Coding・レガシーマイグレーションに関するご相談を受け付けています。