コメント
まだコメントはありません。
まだコメントはありません。
このblogでは、deploy scriptの堅牢化、security header、lint cleanup、管理consoleの分割、component treeのatoms / molecules / organisms再編を、小さな変更の連続として進めました。
大規模refactorを「一気に書き換える作業」にすると、構造変更とbehavior変更が混ざり、失敗時に原因を特定できません。
この記事は変更一覧ではなく、外から見えるbehaviorを保ちながら進めるための6つのguardrailです。
重要なのはtestの本数ではありません。
どの契約を守るtestなのか、どの段階でどのgateを実行するのか、失敗時にどこまで戻すのかを明確にします。

*図: 外部behaviorをtestで固定し、1 root causeずつ小さく変更して同じ結果を再確認する。*
管理consoleを分割するとき、logicのrewriteとcomponent境界の変更を同じcommitへ入れませんでした。
状態とhandlerはuse-admin-console.ts、一覧・tag・analytics・設定・運用の表示はpanel、editorはeditor-panel.tsxへ移します。
admin-console.tsxは現在、controllerを取得しpanelを組み立てる薄いorchestratorです。
進め方も固定します。
移動中に見つけた改善案は別sliceへ送ります。
「ついでに綺麗にする」を混ぜないと、diffは移植として読め、regressionの原因範囲も狭くなります。
一番危険なのは、commandが0で終わったように見えても期待した状態へ到達していない処理です。
このblogのdeploy helperはbuild後のWorkerをno-routes configでuploadし、version IDを確定してからwrangler versions deployで100%へ切り替えます。
その後にwrangler deployments list --jsonを読み、新versionが100% trafficでactiveか検証します。
確認できなければ成功扱いにせず例外で停止します。
wrangler deployのhuman-readable outputだけへ依存するのも危険です。
version IDを取り出せない場合は、upload前後のwrangler versions list --jsonを比較し、追加されたversionが一意な場合だけ採用します。
曖昧ならdeployを続けません。
同じ原則はTurnstileにも使います。
site key未設定を静かな空欄にせず、serverとclientの両方へboundedなconfiguration errorを記録し、bot protectionが無いのに送信できたような状態を避けます。
static analyzerやAI reviewは、調査候補を増やす道具であって判決ではありません。
例えばSQLiteのMAX(likes - 1, 0)をaggregateのMAX()と混同すると、危険なSQLに見えるかもしれません。
しかしSQLite公式docsでは、max(X,Y,...)は複数argumentを受けるscalar functionです。
この場合は0を下限にする式として成立します。
報告を受けたら、まず公式spec、現在のsource、最小のreproductionを確認します。
誤検知を直すためにSQLやcontrol flowを変えると、その「修正」が新しいbugになります。
逆に本物の問題なら、再現testを先に置くことでreviewerと将来の変更者へ根拠を残せます。
ReactのsetState-in-effectをsetTimeoutで包めばwarningを隠せる場合がありますが、それでは同期effectが追加renderを起こす構造は説明できません。
このblogではhydration後だけtrueになる値をuseSyncExternalStoreでserver snapshotとclient snapshotへ分けました。
ただしuseSyncExternalStoreは万能な置換ではありません。
props / stateから導出できる値はrender中に計算し、React内部だけのstateにはuseStateやuseReducerを使います。
外部storeやbrowser APIなら、immutableなsnapshotと実際のsubscribe / unsubscribe契約を用意します。
同じdocument中に変化しないhydration flagだけはstableなno-op subscriptionを使えます。
例外的なlint disableが必要なら、広いfile単位ではなく対象1行へ限定し、理由を隣へ残します。
lint zeroは数字を作るgameではなく、data sourceと更新経路を説明可能にする作業です。
D1 migration historyはforward-onlyです。
適用済みmigration fileやd1_migrationsを手で書き換えて履歴だけを戻すと、実schemaとWranglerの認識が分離します。
localの.down.sql fixtureをremoteへ直接実行する運用もしません。
migration後のwriteを残す必要がある部分障害では、問題を修正する新しいforward migrationを作ります。
既存historyは変更せず、次の番号でschema / dataを前進修正します。
database全体を適用前へ戻す重大障害ではwriteを停止し、事前に保存したbookmarkを二者確認してD1 Time Travelを使います。
restore後は事故migrationが再びpendingになるため、automationを自動再開しません。
next canaryとeslint-config-nextも同じversionへexact pinしています。
upgradeは単独sliceにし、失敗時に戻せるversion差分と検証結果を残します。
「戻り道」は危険なcommandを準備することではなく、どのdataを保持し、どのrecovery pathを選ぶかを先に決めることです。
すべての重いgateを手作業で毎回繰り返すと、feedbackが遅くなり、失敗したroot causeも曖昧になります。
まず各sliceでfocused testをRED→GREENにし、関連diffだけをstageします。
現在のpre-commitは、format、typecheck、unit test、staged diffのGitleaks、Clawpatch statusを自動実行します。
一方pre-pushはverify:pushで、format / typecheck / unit testに加えて管理機能coverage、広告audit、実server E2E、全dependency audit、Clawpatch CIを通し、最後にGitleaks historyも確認します。
lintは独立commandとして0 error / 0 warningを維持し、release前に確認します。
この層分けなら、日々のdebugではfocused feedbackを先に得つつ、push前にはrepository全体の契約を一度まとめて検証できます。
構造testがsource pathやpatternの変更で落ちた場合も、production変更だけでなくtest contractの追従まで同じroot causeとして扱います。
6つのguardrailは次の通りです。
大きなrefactorを安全にするのは、巨大な最後のtestではありません。
1 root cause、1 focused test、1 review可能なcommitを積み重ね、release境界でfull gateを通すことです。