


TypeScriptのフロントエンドのモノレポで、パッケージの境目を守って作業する
モジュールに分かれたTypeScriptのフロントエンドで、AIに作業させる時に使う。パッケージの境目とフレームワークに頼らないコア、状態を持ち主経由で変える事、ファイルとテストの置き場所、コミット前のチェック、リリース手順、UIの層の分け方を伝える。
このAIルールを使うには
## パッケージの境目は、公開されたエクスポートからだけ越える
- マルチパッケージのワークスペースでは、各パッケージに単一の責務を持たせ、パッケージ境界を越える際は公開エクスポートのみを経由すること。アプリケーションコードから他パッケージの内部実装を直接importしてはならない。内部importは呼び出し元を非公開構造に密結合させ、変更時に壊れる原因になる。境界の遵守はレビューではなく自動化されたlintとアーキテクチャチェックで強制し、適切な置き場所が存在しない場合は意図的に新設し、明文化された規約を更新する。
- 共有コアはフレームワーク非依存かつブラウザDOMに依存しない状態を保つこと。UIフレームワークへのバインディングやDOM/CSSへの投影は専用レイヤーに置く。WASMモジュールや大規模SDKなど重いランタイム依存は、呼び出し箇所ごとに場当たり的にimportするのではなく、単一の中央モジュールから読み込み、注入されたインスタンスとして呼び出し元に渡す。
- 他パッケージのAPIを転送するだけの再エクスポート用シムファイルを作らないこと。呼び出し箇所では所有パッケージを直接importする。互換性維持のためのbarrelファイルは、既存の公開APIを保つ目的に限り所有元を再エクスポートしてよい。
- 小さな単位でのimportによって依存関係の意図が明確になる場合は、何でも詰め込んだ単一のbarrelファイルより、狭く的を絞ったサブパスエクスポートを優先する。
## 共有の状態は、持ち主のactionを通してだけ変える
- 大規模なサブシステムは、肥大化する一つのファイルではなく、共有コンテキストとドメイン別のアクションモジュール(viewport、selection、history、structureなど)の組み合わせで構成すること。新しい振る舞いを追加する前に、既存のフォルダ構成と近隣のパターンを確認し、最も適合するドメインモジュールに変更を配置する。たまたま開いているファイルに無関係なロジックを積み上げてはならない。
- すべての状態変更は、所有者であるsetterまたはactionを経由させ、イベント発火・undoのバッチ処理・派生値の更新といった副作用が一貫して実行されるようにする。共有状態のフィールドに直接代入してはならない。直接代入するとこれらの副作用が黙って実行されなくなる。また、コンポーネント内にモジュールレベルのミュータブルな状態を持たせず、共有状態はストアに保持する。
- 変更は型付きイベントシステムを通じて公開し、購読側はポーリングではなくイベントに反応させる。ドメインモジュール間の状態共有は、アプリケーションコードやフレームワークコードのimportではなく、コンテキストオブジェクトを介して行う。
- アプリケーション側のラッパーは、コアの周囲にリアクティブな状態・永続化・プラットフォームI/Oを追加してよいが、コア自体はフレームワーク非依存であり続けること。共有コード内でブラウザAPIを使う場合は、使用前に明示的なランタイムチェックでガードする。
## TypeScriptで any とnon-nullアサーションを使わない
- `any`は禁止し、正確な型・ジェネリクス・宣言のマージを使うこと。non-nullアサーションも禁止し、代わりにガード・オプショナルチェイニング・null合体演算子を使う。どちらも、型チェッカーが実際のエラーを隠さずに検出できる状態を保つためのルールである。
- 緩いプリミティブ型よりも正確なユニオン型を優先する。例えば `number | string | null` の代わりに小さな文字列リテラルのユニオン型を使う。名前付きの共有型がすでに存在する場合は、型定義をインラインで書かず、importして再利用する。
- 繰り返し使われる定数や複数機能にまたがる定数は、コードベース全体にリテラルを重複させるのではなく、共有の定数モジュールに名前を付けて定義する。
- 識別子やセキュリティに関わる用途には、暗号学的でない乱数源を絶対に使わないこと。常に暗号学的乱数生成器を使う。ネストしたデータを変更する際は、浅いスプレッドではなく構造化クローンでネストしたオブジェクトをディープコピーする。
## フォルダをドメインごとに分け、テストを種類ごとの場所に置く
- 深い形式的な構造ではなく、ドメインの名前空間で整理する。アプリケーションサービス、ルートやレイアウトのビュー、UIはそれぞれ独立したトップレベル領域に置く。
- 複数ファイルにまたがるドメインは、同じ接頭辞を共有する並列ファイルではなくサブフォルダにまとめる。あるドメインが2つ目のファイルを持つ時点で、ファイル名に接頭辞を付けるのではなく即座にフォルダを作成する。`selection-hit-test` より `selection/hit-test` を優先する。
- `index`、`types`、`context` のような慣例的なエントリーポイントのファイル名は小文字を保ち、コンポーネントファイルはそのエコシステムの命名規則(大文字小文字の使い方)に従う。コードやテストを場当たり的に配置しないこと。適切な置き場所が存在しない場合は、意図的に新設して文書化する。
- テストの配置は厳格に、種類ごとの決まった場所に基づいて行う。各テスト種別(E2E、統合、ユニット、共有ヘルパー、パッケージ単位)は、それぞれ確立されたディレクトリに置く。ユーザーに見える振る舞いはE2Eテストで検証し、内部状態やデータの検証はユニットテストで行う。一時的なテストファイルやプロファイリング用のテストファイルは絶対にコミットしない。
## 同じ処理を自作せず、既存のライブラリを使う
- 既存の依存関係がすでにできることを自前で実装しないこと。まず既存の依存関係を確認し、適合するものがなければ、実装をインラインで書くのではなく質の良いライブラリを採用する。例えば、unified diffを生成する場合は自前のループではなくdiffライブラリを、色の計算にはカラーライブラリを使う。
- カスタムUI・コントロール・コンポーザブルを自作する前に、ローカルでの使われ方から推測するのではなく、依存関係のアップストリームのドキュメントを読むこと。機械可読なドキュメントの入口が提供されている場合はそれを優先する。アップストリームのドキュメントとローカルのパターンが矛盾する場合は、現行のアップストリームAPIに従い、ローカルのラッパーを意図的に更新する。
- 保守すべきプログラムやヘルパーは、ルート直下の場当たり的なスクリプトとしてではなく、ドメインごとに整理された専用のtools領域に置き、それぞれに焦点を絞ったテストを付ける。scripts領域は、tools領域からimportするだけの小さな互換性エントリーポイント用シムのために予約し、実装ロジックをシム側に持たせない。
- ツール類のテスト、lint、フォーマットのチェックを標準チェックに含め、tools領域がプロダクトコードと同じ基準で扱われるようにする。
## コミット前にチェックを全部通し、Conventional Commitsで書く
- コミット前と、レビューのために変更をオープンする前の両方で、品質ゲート全体を実行すること。型を考慮したlintとtypecheckをエラーゼロで通し、import順序を含むフォーマッターを適用し、コピペ・重複検出をクローンゼロで通し、関連するユニットテストとE2Eテストのスイートを実行する。
- 提出前にセルフレビューを行う。重複が増えている場合は共有ヘルパーを抽出するか既存の型を再利用する。名前付き型と重複するインライン型定義や、コピペされたロジックは取り除く。ファイルは数百行未満に保ち、肥大化したらドメインごとに分割する。
- コミットメッセージはConventional Commits形式(`feat`、`fix`、`refactor`、`perf`、`docs`、`test`、`build`、`ci`、`chore`)で書く。1行目は短く命令形で、小文字のtypeと任意のscopeを付ける。本文には理由を書き、本文の各行は大文字で始まる単語から始める。
- 件名や本文では、製品名や略語の大文字小文字を平文化して小文字に崩さず、そのまま保持する。scopeは実態に即した最も狭い範囲を使い、変更が無関係な複数領域にまたがる場合はscopeを省略する。
## 決まった順でリリースし、変更履歴を最新に保つ
- リリース手順は固定された順序に従うこと。バージョン番号を持つすべてのファイルでバージョンを上げ、changelogの「Unreleased」セクションのエントリを日付入りの新しいバージョン見出しの下に移動し、リリース用のメッセージでコミットしてから、タグを作成しプッシュする。タグ付け前に、署名やリリース用のシークレットが存在することを確認する。
- ビルド・リリースのワークフローを、パッケージングと公開に関する唯一の正本として扱い、重複したパッケージ一覧があればそれと同期させる。本番デプロイは、リリースタグまたは明示的な手動ディスパッチのみをトリガーとし、通常のデフォルトブランチへのpushでは絶対にトリガーしないことで、リリースが意図的なものであり続けるようにする。
- ドキュメントは常に最新の状態に保つこと。振る舞いが変わるたびにchangelogの「Unreleased」セクションとユーザー向けドキュメントを更新し、アーキテクチャや規約が変わるたびにコントリビューター/エージェント向けリファレンスを更新する。
- 公開ドキュメントには、推測段階の内容や内部向けの実装計画を含めないこと。一時的な計画はgit管理対象外のスクラッチ領域に置き、恒久的な方針だけを正本のドキュメントに反映する。プレースホルダーの翻訳版コピーは作成せず、実際の翻訳ができるまではローカライズ済みのナビゲーションを正本ページへリンクさせておく。
## 共有のUIプリミティブから、アプリ側のコードをimportしない
- 共有・再利用可能なUIプリミティブ層は、機能層やシェル層と分離すること。共有プリミティブと低レベルなスタイル付きコントロールは、アプリケーションサービス・ストア・機能パネルをimportしてはならない。機能固有のコントロールは、その機能のコンポーネントが所有する。
- スタイリングは、条件分岐によるユーティリティクラスの分散や、スロットごとの場当たり的なclass propではなく、1か所で定義され各インスタンスにマージされる静的で型付けされたテーマバリアントを通じて解決する。視覚的な状態はセマンティックなdata属性でバインドし、型付きバリアントの解決はスクリプト側で行う。
- テストのロケーターには、ユーザーに見えるrole・label・テキストを優先して使う。識別のためにはスコープを絞ったセマンティックな属性を公開し、専用のtest idは稀な統合境界のためだけに予約する。共有コンポーネント内でグローバルに一意なidを無理に作り出さない。
- コンポーネントが明示的にレンダーレスなプリミティブでない限り、命令的なsetterやactionをslot経由で渡さないこと。モデルバインディング・イベントの発行・通常のpropsを優先する。生のインラインSVGやUnicode記号ではなくアイコンライブラリを使い、ショートカットのテキストをラベルに直接埋め込むのではなく、コマンドのメタデータからキーバインドをレンダリングすることでラベルの意味を保つ。
