generate-program
自然言語の説明からFoldkitプログラムの完全なコードを生成します。ユーザーが新しいFoldkitプログラムを作成したい、プロジェクトのスカフォルディングが必要、「〜を作ってほしい」「〜というプログラムが欲しい」といった要望を述べた時に使用します。
description の原文を見る
Generate a complete, idiomatic Foldkit program from a natural language description. Use when the user wants to create a new Foldkit program, scaffold a project, or says things like "build me a..." or "I want a program that..."
SKILL.md 本文
以下の説明に基づいて、完全な Foldkit プログラムを生成します:
$ARGUMENTS
フェーズ 1: 説明を分析する
コードを書く前に、説明を分析して以下を特定します:
- ドメイン エンティティ — Model フィールドになる名詞 (例: "todos"、"user"、"score")
- ユーザー インタラクション — Message になる動詞 (例: "add"、"delete"、"filter"、"submit")
- 非同期操作 — Command になる外部データ (例: "fetch weather"、"save to localStorage")
- リアルタイム ニーズ — Subscription になるストリーミング データ (例: "live updates"、"countdown"、"WebSocket")
- ページ/ナビゲーション — ルートになる URL 構造 (例: "home page"、"detail page")
- UI コンポーネント ニーズ — Foldkit UI コンポーネントにマップされるインタラクティブ ウィジェット (例: "dropdown" → Menu、"modal" → Dialog、"tabs" → Tabs、"autocomplete" → Combobox、"date picker" → DatePicker、"file upload" → FileDrop、"並べ替え可能なリスト" → DragAndDrop、"toast/notification" → Toast、"hover tooltip" → Tooltip)
- フォーム検証 ニーズ — 必須フィールド、形式チェック、非同期一意性チェック →
foldkit/fieldValidationモジュール (フェーズ 4 を参照) - 日付処理 — 誕生日、期限、スケジューリング →
Calendarモジュール +Ui.DatePickerまたはUi.Calendar - ファイル処理 — アップロード、添付ファイル、画像 →
Fileモジュール +Ui.FileDrop
この分析をユーザーに提示してから進めます。
説明が詳細で明確な場合は、分析を要約して進める前に確認してください。ただし、ギャップがある場合 (不明確な状態遷移、曖昧な UI 要件、未指定のエラー処理、見落とされたエッジ ケース、曖昧なドメイン境界、不明確なカウンター/リセット セマンティクス)、進める前に対象を絞った確認質問をしてください。「他に何かありますか?」のようなオープンエンドの質問はしないでください。見つかったギャップについて具体的な質問をしてください。
UX/動作ギャップ:
- 「todo リストはページの再読み込み時に localStorage に保存されるべきですか、それとも各セッションで最初からですか?」
- 「API 呼び出しが失敗した場合、アプリはインライン エラーを表示するべきですか、それともダイアログを表示するべきですか?」
- 「ユーザーが項目を編集できると述べられていますが、これはインライン編集ですか、それとも別の編集ページですか?」
ドメイン ロジック ギャップ — 見落としやすく、修正代が高い:
- 「ユーザーが間隔をスキップした場合、ストリークの目的で『完了』とカウントされますか?」
- 「『完了』を追跡するカウンターは、成功したアクションのみで増加しますか、それともスキップされたアクションでも増加しますか?」
- 「ユーザーがフロー途中でリセットをトリガーした場合、カウンターはリセットと一緒にリセットされるか、それともリセット全体で保存されたままですか?」
- 「『N 個のイベント後に X をトリガー』と述べられていますが、これは合計 N 個のイベントですか、それとも最後の X 以降の N 個のイベントですか?」
- 「サイクル内の N 番目のアクションで、どのアクションをトリガーするか — サイクルの最初ですか最後ですか?」
ドメイン ロジック質問は、オフバイワン バグをコードに出る前に表面化することが多いです。説明にカウンター、サイクル、ストリーク、または「after N」フレーズが含まれる場合、0、1、および N で具体的にエッジ ケースについて質問してください。
目標は、曖昧性を早期に解決して、生成されたコードがユーザーが実際に望んでいるものと一致し、あなたが仮定したものではないようにすることです。
フェーズ 2: リファレンス例を学習する
アーキテクチャとコンベンション ガイドを読んで、ルールを内部化します:
アーキテクチャ ガイド— TEA 構造、ファイル組織、型パターンコンベンション ガイド— 命名規則、Effect-TS パターン、アンチパターン検証チェックリスト— フェーズ 5 だけではなく、生成バーも対象。品質バー セクションを今すぐスキャンして、レビューで失敗するのではなく、既に品質バーを満たすコードを生成するようにしてください。
context7 MCP ツールにアクセスできる場合は、それを使用して Effect-TS ドキュメントを検索してください (Effect-TS API について不確実な場合)。Effect は大きなライブラリです。推測するのではなく、関数シグネチャを確認してください。
品質の例
2 つのコードベースは、生成されたアプリの 品質バー です。単なる「パターンのコピー」ではなく、「マッチさせるべき工芸のレベル」です:
${CLAUDE_SKILL_DIR}/../../packages/typing-game/client/src/— プロダクション マルチページ アプリ: Submodels、OutMessage、update/view の分解、カリー化されたハンドラー抽出、subscription パターン、ドメイン モジュール。${CLAUDE_SKILL_DIR}/../../packages/website/src/— プロダクション Foldkit ウェブサイト: ページ組織、共有ビュー プリミティブ、ルート駆動レンダリング、慣用的なドメイン分離。
生成する前に、各ファイルから少なくとも 1 つをスポット チェックしてください。update.ts の形状 / ハンドラーの抽出方法 / ドメイン ファイルの構造化方法。出力でその工芸レベルと一致させてください。生成されたコードは、手書きの例示コードと区別がつかないはずです。
次に、生成されるアプリの複雑さと一致する層別の例示ファイルを読んでください。常に層別の例示ファイルを少なくとも 1 つ読んでください — メモリだけで生成しないでください。
複雑さの層
層 1 — シングル ページ、非同期なし、最小限の状態:
${CLAUDE_SKILL_DIR}/../../examples/counter/src/main.ts を読んでください
層 2 — タイマー、subscription、シンプルなステートフル アプリ:
${CLAUDE_SKILL_DIR}/../../examples/stopwatch/src/main.ts (subscription 経由のタイマー、Duration フィールド パターン) と ${CLAUDE_SKILL_DIR}/../../examples/todo/src/main.ts (flags 経由の localStorage を使用した CRUD) を読んでください
層 3 — 非同期操作、ローディング/エラー状態、API 呼び出し、フォーム検証:
${CLAUDE_SKILL_DIR}/../../examples/weather/src/main.ts (HttpClient を使用した HTTP) と ${CLAUDE_SKILL_DIR}/../../examples/form/src/main.ts (foldkit/fieldValidation を使用 — フェーズ 4 のフォーム検証セクションを参照) を読んでください
層 4 — URL ルーティング、複数ページ、クエリ パラメータ:
${CLAUDE_SKILL_DIR}/../../examples/routing/src/main.ts と ${CLAUDE_SKILL_DIR}/../../examples/query-sync/src/main.ts を読んでください
層 5 — 複雑な状態、ネストされたドメイン モデル、CRUD、ドラッグ アンド ドロップ:
${CLAUDE_SKILL_DIR}/../../examples/shopping-cart/src/main.ts (ネストされたドメイン スキーマ、カート状態) と ${CLAUDE_SKILL_DIR}/../../examples/kanban/src/main.ts (Ui.DragAndDrop を使用した CRUD、localStorage から復元するフラグ、subscription) を読んでください
層 6 — Submodels、OutMessage、マルチステップ フォーム、認証フロー、マルチモジュール アプリ:
${CLAUDE_SKILL_DIR}/../../examples/auth/src/main.ts (Submodels を使用したログイン/サインアップ、OutMessage、保護されたルート) と ${CLAUDE_SKILL_DIR}/../../examples/job-application/src/main.ts (ネストされた Submodels が step/ に深くあるマルチステップ フォーム、Ui.DatePicker、Ui.FileDrop、Ui.Menu、日付処理用の Calendar モジュール) を読んでください
層 7 — リアルタイム、WebSocket、マネージド リソース、プロダクション グレード:
${CLAUDE_SKILL_DIR}/../../packages/typing-game/client/src/main.ts を読んでから、完全な Submodel/OutMessage パターンについて page/home/ と page/room/ ディレクトリを探索してください。
対象の層の例を読んでください。また、より低い層の例もすべて読んでください。層 4 アプリは、層 1-3 のパターンも反映する必要があります。
フェーズ 2.5: Foldkit UI コンポーネント機会を特定する
Foldkit にはキーボード ナビゲーション、ARIA 属性、フォーカス管理を自動的に処理するアクセス可能な UI コンポーネントが付属しています。生成する前に、アプリの一部が組み込みコンポーネントにマップされるかどうかを確認してください:
| ユーザー ニーズ | Foldkit コンポーネント | 無料で取得できるもの |
|---|---|---|
| モーダル/ダイアログ/確認 | Dialog | フォーカス トラップ、Escape で閉じる、スクロール ロック、バックドロップ |
| タブ付きコンテンツ | Tabs | 矢印キー ナビゲーション、aria-selected、roving tabindex |
| ドロップダウン メニュー | Menu | 矢印キー、先行入力検索、aria-expanded、クリック外部 |
| オートコンプリート/タグ入力 | Combobox | フィルタリング、矢印キー選択、aria-activedescendant |
| 選択ドロップダウン | Select | キーボード選択、aria-selected、配置 |
| オプションから単一選択 | RadioGroup | 矢印キー サイクル、aria-checked |
| オン/オフ トグル | Switch | スペースバー トグル、aria-checked |
| ブール オプション | Checkbox | スペースバー トグル、aria-checked、不確定 |
| 展開可能なセクション | Disclosure | Enter/Space トグル、aria-expanded |
| ホバー/クリックでフローティング コンテンツ | Popover | 配置、クリック外部、フォーカス管理 |
| ホバー ツールチップ | Tooltip | 表示遅延、キーボード を閉じる、配置、aria-describedby |
| 単一選択リスト | Listbox | 矢印キー、先行入力、aria-selected |
| テキスト入力 | Input | 一貫したスタイリング/動作 ラッパー |
| 複数行テキスト | Textarea | 自動サイズ変更、一貫したスタイリング |
| フォーム グループ | Fieldset | 無効化状態の伝播、グループ化 |
| スタイル付きボタン | Button | 一貫したクリック/キーボード処理 |
| インライン カレンダー グリッド | Calendar | 月ナビゲーション、キーボード ナビ、aria-selected、日付制約 |
| 日付入力 + ポップオーバー | DatePicker | カレンダー ポップオーバー、入力マスク、キーボード ナビ、制約 |
| ファイル アップロード ゾーン | FileDrop | ドラッグ アンド ドロップ、クリックして参照、accept フィルター、検証 |
| 並べ替え可能なリスト | DragAndDrop | ポインター + キーボード ドラッグ、ドロップ ゾーン、アナウンスメント領域 |
| 一時的な通知 | Toast | 自動消去、一時停止時のホバー、スタッキング、role=status/alert |
各コンポーネントは、独自の Model、Message、init、update、view を持つ Foldkit Submodel です。使用するには:
- Model に Model を追加します:
confirmDialog: Ui.Dialog.Model Got*Message を追加します:GotConfirmDialogMessageに{ message: Ui.Dialog.Message }- init で初期化します:
confirmDialog: Ui.Dialog.init({ id: 'confirm-dialog' }) - update で委譲します:
GotConfirmDialogMessage: ({ message }) => ... - view でレンダリングします:
Ui.Dialog.view(model.confirmDialog, ...)
手書きのインタラクティブ ウィジェットより常に Foldkit UI コンポーネントを優先してください。 アクセシビリティを後付けではなくデフォルトにします。
フォーム入力の場合特に: フォーム内のすべてのテキスト入力、textarea、ボタンは Ui.Input、Ui.Textarea、Ui.Button をそれぞれ使用する必要があります。これはオプションではありません (生の input/textarea HTML 要素が html<Message>() から利用可能でも)。フォーム例 (examples/form/src/main.ts:347-403) は、Ui.Input.view と Ui.Textarea.view をラベル + 検証フィードバックでラップする inputFieldView と textareaFieldView ヘルパーを定義します。そのヘルパー パターンをコピーしてください。生の input/textarea は、UI コンポーネント層の下で意図的に作業する非フォーム ケース (検索フィールド、インライン エディター) 用で、それでも UI コンポーネントを最初に使ってください。
アプリが UI コンポーネントを使用している場合、コンポーネントの配線方法を理解するために まず ui-showcase 例を読んでください — これは Foldkit UI 統合パターンの標準的なリファレンスです:
${CLAUDE_SKILL_DIR}/../../examples/ui-showcase/src/main.ts— ルート配線、Got*委譲、toParentMessageヘルパー${CLAUDE_SKILL_DIR}/../../examples/ui-showcase/src/message.ts— UI コンポーネント Message の構造化方法${CLAUDE_SKILL_DIR}/../../examples/ui-showcase/src/model.ts— UI コンポーネント Model の構成方法${CLAUDE_SKILL_DIR}/../../examples/ui-showcase/src/update.ts— UI コンポーネント更新の委譲方法${CLAUDE_SKILL_DIR}/../../examples/ui-showcase/src/toast.ts—Ui.Toastを使用する場合に読んでください: Toast はUi.Toast.make(PayloadSchema)経由でペイロード スキーマで型付けされるという点でユニークで、インポートする型付きモジュールを返します
Ui.DatePicker、Ui.FileDrop、または他の最近追加されたコンポーネントを使用するアプリの場合、job-application 例も読んでください (下の層 6 を参照) — これらのコンポーネントの最も完全なリアルワールド統合です。
フェーズ 3: ファイル組織を決定する
ファイル構造をアプリの複雑さと一致させてください。アーキテクチャはすべてのスケールで同じままです。ファイル組織だけが変わります。
どのファイルに何があるか
層別のレイアウト以上に、model.ts の膨らみを避けるために以下の「スキーマ配置」ルールに従ってください:
model.tsはModelスキーマと、Model のフィールド (またはフィールドに構成されるスキーマ) である任意のスキーマを保持します (フォーム状態 / 送信状態の和など)。何も他に。command.tsは、コマンドが外部システムに送受信するペイロードのスキーマを保持します。特に、saveStateがシリアル化しflagsが逆シリアル化する永続性スキーマ。永続性スキーマはコマンド層の関心事で、Model の関心事ではありません。Model のサブセットのように見えることが多いですが、Model の一部ではありません。domain/*.tsはドメイン エンティティ スキーマと、それらに対する純粋な操作を保持します。message.tsはメッセージのみを保持します。route.tsはルート バリアントとルーター パイプラインを保持します。
一般的な間違い (kanban が SavedBoard を model.ts に共置するため): 永続性スキーマを model.ts に入れることです。これは使用場所に合った永続性層のスキーマです。移動してください。
シングル ファイル (層 1-2、~300 行未満):
src/main.ts ← Model、Message、init、update、view すべてインライン
コマンド + メッセージ分割 (層 3、非同期操作がある):
src/main.ts ← Model、init、update、view
src/message.ts ← Message 定義
src/command.ts ← コマンド関数
重要なルール: command.ts を抽出する場合、message.ts も抽出する必要があります。コマンドは Message コンストラクタを参照します (例: SucceededFetchWeather({...})) を返値として。Message が main.ts に存在し、コマンドが command.ts に存在する場合、command.ts は main.ts から インポートし、main.ts は command.ts からコマンドを使用します — 循環インポート。最初に Message を出力して、その後 main.ts と command.ts の両方が message.ts からインポートします。
フル分割 (層 4-5、複数の関心事):
src/main.ts ← init、update、view、アプリ エントリ
src/model.ts ← Model スキーマ
src/message.ts ← Message 定義
src/command.ts ← コマンド関数
src/route.ts ← ルート パーサー (ルーティングの場合)
src/view.ts ← ビュー関数 (ビューが大きい場合)
src/domain/ ← 共有ドメイン スキーマ (複数のエンティティの場合)
Submodel ディレクトリ (層 6-7、独立したモジュール):
src/main.ts ← ルート init、update、view
src/model.ts ← ルート モデル (submodels を含む)
src/message.ts ← ルート メッセージ + Got* ブリッジ
src/command.ts ← 共有コマンド
src/route.ts ← ルート パーサー
src/domain/ ← 共有ドメイン スキーマ
src/page/
featureA/
main.ts ← Submodel init、update、view
message.ts ← Submodel メッセージ + OutMessage
command.ts ← Submodel コマンド
featureB/
...
フェーズ 3.3: アーキテクチャ スケッチ (層 4 以上のみ)
層 4 以上のアプリ — ルーティング、ドメイン モジュール、複数のエンティティ、submodels — 実装を生成する前に、コンパクト スケッチを作成してください。層 1-3 アプリは小さいため、1 パスで生成できます。層 4 以上のアプリは、構造が間違っていると多くの努力を燃やします。
スケッチは 5 つの部分で構成されています。会話にインラインで発行してから、確認してスカフォルドしてください:
- ファイル ツリー — 作成する正確なパス。フェーズ 3 の組織と一致します。
- Model の形状 — トップレベルの
S.Structフィールドとその型。完全なスキーマではなく、形状のみ。 - Message リスト — 定義するすべての Message のカテゴリ別グループ (クリック、入力、コマンド、アウト メッセージ)。
- ルート リスト — ルーティングの場合、すべての
r('...', {...})パラメータと各パスがマップされるパス。 - ドメイン操作 —
domain/内の各ファイルについて、公開する操作 (Link.byNewest、Link.filterByTagなど)。
層 4 リンク セーバーの例:
### スケッチ
ファイル:
src/main.ts、model.ts、message.ts、command.ts、route.ts
src/domain/link.ts、index.ts
src/main.story.test.ts、main.scene.test.ts
Model:
route: AppRoute
links: ReadonlyArray<Link>
newLinkForm: NewLinkForm (url: Field、title/description/tagsInput: string、submitState)
メッセージ:
クリック: ClickedSaveLink、ClickedDeleteLink
入力: UpdatedLinkUrl、UpdatedLinkTitle、UpdatedLinkDescription、UpdatedLinkTagsInput、BlurredLinkUrl
コマンド: SubmittedNewLinkForm、SucceededSaveLinks、FailedSaveLinks
ルーティング: ClickedLink、ChangedUrl、CompletedNavigateInternal、CompletedLoadExternal
トグル: ToggledFavorite
ルート:
HomeRoute → /
NewLinkRoute → /new
TagFilterRoute → /tag/:tag
NotFoundRoute → /* フォールバック
ドメイン:
Link: スキーマ + byNewest、filterByTag、toggleFavorite、remove、updateById
スケッチの発行後、ユーザーに確認または調整を求めてください。スカフォルドまたは生成を開始する前に、実行しないでください。ユーザーが静かに確認する場合 (例: 「良さそう、続けて」)、進めてください。調整する場合、スケッチを繰り返します — 承認していないバージョンに対してコードを書かないでください。
このステップは、エージェントが「何をしているかわかっている」ため、スキップするのは魅力的です。スキップして、完全に生成されたアプリを出荷し、構造的な変更が必要であることが判明します — それが高い形式の反復です。スケッチが安い形式です。
フェーズ 3.5: プロジェクトをスカフォルドする
コードを生成する前に、create-foldkit-app を使用して実行可能なプロジェクトをスカフォルドします:
npx create-foldkit-app@latest
フラグなしで実行してインタラクティブ プロンプトを表示します。ベースとして counter の例を選択して (最もシンプルな開始点)、ユーザーの優先パッケージ マネージャーを選択してください。生成されたプロジェクトには:
- すべての Foldkit と Effect 依存関係を含む
package.json - Tailwind と Foldkit Vite プラグインを使用した
vite.config.ts - 厳格な TypeScript 設定を使用した
tsconfig.json - ルート コンテナを持つ
index.html - Tailwind インポートを持つ
src/styles.css - Foldkit コンベンションを持つ
AGENTS.md
Foldkit submodule を提供する
スカフォルドした後、Foldkit を git submodule として追加し、将来の AI セッションが完全なソース、例、ドキュメントをユーザーのプロジェクトから直接参照できるようにすることを提案してください:
git init # git リポジトリがまだない場合
git submodule add https://github.com/foldkit/foldkit.git repos/foldkit
これはオプションですが、強く推奨されます。スカフォルドされた AGENTS.md には、エージェントが将来のセッションで確認する submodule_prompted: false 行が含まれます。submodule がなく、このフラグが false の場合、エージェントは追加を提案します。ここで事前に処理すると、ユーザーの次の AI セッションは既に完全なコンテキストを持ちます。ユーザーが辞退する場合は、行を submodule_prompted: true に更新して、もう聞かれることがないようにしてください。
submodule を後で更新するには: git submodule update --remote repos/foldkit。
スカフォルドを置き換える
次に、src/main.ts (および必要に応じて追加のソース ファイルを追加) の counter の例コードを生成されたアプリ コードで置き換えます。
フェーズ 3.7: Foldkit API をグラウンド化する
コードを書く前に、使用するすべての Foldkit モジュールの型シグネチャを読んでください。シグネチャを推測すると、サイクルが無駄になります: 各間違った推測は tsc エラー、再読み、編集、別のタイプチェック。5 分の読みで 30 分の反復を防ぎます。
読むべき正確なファイル
使用する予定の各 Foldkit モジュールについて、下のパスで .d.ts を読んでください。公開サーフェスを読んでください。内部は必要ありません。生成中に再チェックする必要がないように、ワーキング ノートに短いシグネチャの概要を書いてください。
# すべてのアプリ
<project>/node_modules/foldkit/dist/index.d.ts # トップレベルの再エクスポート
<project>/node_modules/foldkit/dist/html/index.d.ts # html<M>()、要素シグネチャ、Attribute<M>、empty、keyed
<project>/node_modules/foldkit/dist/message/index.d.ts # m()
<project>/node_modules/foldkit/dist/schema/index.d.ts # ts()、r()
<project>/node_modules/foldkit/dist/struct/index.d.ts # evo() — ネストされた更新シグネチャを確認
<project>/node_modules/foldkit/dist/runtime/runtime.d.ts # ProgramInit、RoutingProgramInit、makeProgram
# ルーティングを使用している場合
<project>/node_modules/foldkit/dist/route/parser.d.ts # literal、slash、string、int、Route.root、Route.mapTo、Route.oneOf、Route.parseUrlWithFallback
<project>/node_modules/foldkit/dist/url/index.d.ts # toString
<project>/node_modules/foldkit/dist/navigation/index.d.ts # pushUrl、load — すべて Effect<void> を返す (Effect.ignore は不要)
# 非同期/副作用を使用している場合
<project>/node_modules/foldkit/dist/command/index.d.ts # Command.define — 結果スキーマは必須
<project>/node_modules/foldkit/dist/task/index.d.ts # focus、uuid、getTime (DateTime.Utc を返す、数値ではない)、delay、scrollIntoView、showModal
# Subscription を使用している場合
<project>/node_modules/foldkit/dist/subscription/index.d.ts # Subscription.makeSubscriptions(Deps)<Model, Message>
# フォームを使用している場合
<project>/node_modules/foldkit/dist/fieldValidation/public.d.ts # Field (tagged union)、makeRules({required?、rules: Rule[]})、validate、url(options)、email、minLength、allValid
# Rule は [Predicate、RuleMessage]、{test、message} ではありません。Field.Invalid は `errors: NonEmptyArray<RuleMessage>` を持ち、`error: string` ではありません。
# いずれかの UI コンポーネントを使用している場合
<project>/node_modules/foldkit/dist/ui/<component>/public.d.ts # Model、Message、init、update、view、ViewConfig
# ViewConfig は必要なプロップを持ちますか? toView は label/input/description/button 属性グループを分解しますか?
# 日付を使用している場合
<project>/node_modules/foldkit/dist/calendar/index.d.ts # CalendarDate、today.local (DateTime を返す、raw millis には Clock.currentTimeMillis を使用)
概要に記録するもの
各呼び出すシンボルについて、1 行を書いてください:
html<M>(): { div、input (VOID)、textarea、button、Class、Href、For、Id、Role、OnClick(M)、OnInput(value=>M)、OnBlur(M)、OnSubmit(M)、keyed、empty、... }
Route.mapTo(schema)(parser) — カリー化
pushUrl(path): Effect<void> // フォールイブルではない、Effect.ignore は不要
urlToString(url: Url): string
Ui.Input.view({ id、value、onInput、isInvalid?、type?、placeholder?、toView: (attrs) => Html })
// attrs: { label: Attribute<M>[]、input: Attribute<M>[]、description: Attribute<M>[] }
Field (スキーマ): NotValidated | Validating | Valid | Invalid(errors: NonEmpty<Rule Message>)
生成部が何度も当たる特定の API の落とし穴
概要に記録して、生成中に表示したままにしてください:
inputとbrと他の void 要素は属性のみを取ります —input([...])、決してinput([...], [])。textareaとbuttonは子を取ります。UrlRequestタグはInternalとExternal、InternalUrl/ExternalUrlではありません。OnClickとOnSubmitは Message を直接取ります、() => Messageではありません。OnInputのみが(value) => Messageを取ります (入力値が必要です)。keyed、emptyはhtml<M>()が返すレコード上のプロパティ —h.keyedとh.emptyとしてアクセスされます (const h = html<M>()の後)。これらはfoldkit/htmlのトップレベル エクスポートではありません。- 属性ヘルパーは具体的 —
Value(...)、Type(...)、Placeholder(...)、Href(...)、Target(...)、Rel(...)、Rows(n)、Id(...)、For(...)、Role(...)、AriaLabel(...)があります。汎用Attr('...'、'...')はありません。 ProgramInit<Model, Message, Flags>には URL パラメータがありません。 ルート付きアプリの場合、RoutingProgramInit<Model, Message, Flags>を使用します — 2 番目の引数はurl: Urlです。Route.mapToはルート スキーマを取ります、ファクトリ関数ではありません。pipe(literal('new')、Route.mapTo(NewLinkRoute))—Route.mapTo(() => NewLinkRoute())ではありません。Effect.ignoreはフォールイブル Effect のみです。pushUrl(path).pipe(Effect.as(Message()))—pushUrlはEffect<void>を返すためEffect.ignoreはありません。Command.defineは名前の後に結果 Message スキーマが必要:Command.define('Fetch'、SucceededFetch、FailedFetch)です。フォールイブル でないコマンドは 1 つの結果のみ必要です:Command.define('ReadClock'、RecordedTime)です。makeRulesは{ required?: RuleMessage、rules: Rule[] }を取ります (Rule = [Predicate、RuleMessage]) — タプル、{ test、message }ではありません。 組み込みルール コンストラクタを使用します (url({ message })、email(message?)、minLength(n、message?)、pattern(regex、message?))。Field.Invalidはerrors: NonEmptyArray<RuleMessage>を持ちます、error: stringではありません。 最初のメッセージを取得するにはArray.headNonEmpty(errors)を使用します。最終文字列を取得するにはresolveMessage(rule、value)を使用します。- ルート バリアントは
HomeRoute、NewLinkRouteなど です —Routeサフィックス付き。 すべての例はこのコンベンションを使用します。 - ルーターは呼び出し可能です:
homeRouter()は'/'を返し、tagFilterRouter({ tag: 'foo' })は'/tag/foo'を返します。URL を手作業で構築しないでください。
フェーズ 4: アプリを生成する
アーキテクチャとコンベンション ガイドに正確に従って、ファイルを生成します。すべてのソース ファイルをスカフォルドされたプロジェクトの src/ ディレクトリに書き込みます。各ファイルについて、以下のルールに従ってください:
モデル
S.Structを Effect Schema 型として定義- 状態には判別共用体を使用:
Idle | Loading | Error | Ok、複数値状態にはブール値を使用しない - 欠落している可能性のあるフィールドに
Optionを使用 — 空の文字列や null は使用しない - Option 型のフィールドに
maybeプレフィックスを付ける:maybeCurrentUser、maybeError - 非同期データについて、
Idle、Loading、Error、Okバリアントをts()で定義し、S.Unionに構成します —conventions.mdの「Discriminated Unions for State」を参照 - 複数のドメイン エンティティを参照する複数のモジュールを持つアプリについて、共有スキーマを
src/domain/に抽出します (例:domain/product.ts、domain/session.ts)。このパターンについては shopping-cart と auth の例を参照し、ドメイン モジュールを構成するタイミングと方法については${CLAUDE_SKILL_DIR}/../../packages/website/src/page/projectOrganization.tsを参照
メッセージ
4 グループのレイアウトに厳密に従います:
// グループ 1: すべての m() 宣言、空行なし
const ClickedSubmit = m('ClickedSubmit')
const UpdatedEmail = m('UpdatedEmail', { value: S.String })
const SucceededLogin = m('SucceededLogin', { user: User })
const FailedLogin = m('FailedLogin', { error: S.String })
const CompletedFocusInput = m('CompletedFocusInput')
// グループ 2: Union + 型 (空行なし)
const Message = S.Union([
ClickedSubmit,
UpdatedEmail,
SucceededLogin,
FailedLogin,
CompletedFocusInput,
])
type Message = typeof Message.Type
メッセージをカテゴリ別に命名します:
Clicked*— ボタン/リンク クリックUpdated*— 入力値の変更 ({ value: S.String }付き) と subscription からの外部状態更新 (UpdatedRoom、UpdatedPlayerProgress)Submitted*— フォーム送信Succeeded*/Failed*— ペア、意味のある失敗ができるコマンド用Completed*— ファイア アンド フォーゲット (動詞+オブジェクト:CompletedFocusInput)Got*— OutMessage パターン経由のチャイルド モジュール結果Loaded*— ストレージから復元されたデータPressed*— キーボード入力Blurred*— フォーカス喪失Selected*— リストから選択されたToggled*— バイナリ状態フリップ
すべてのメッセージは意味を持つ必要があります。NoOp なし。
Flags (初期 Model が副作用を必要とする場合)
- 初期 Model が副作用から必要とするデータについて
Flagsスキーマを定義 flagsを、値を計算するEffect<Flags>として定義 (localStorage 読み取り、現在時刻など)- 結果を init に渡す — モジュール レベルまたは init 内で直接副作用を実行しない
architecture.mdの flags セクションの完全なパターンを参照
Init
[Model、ReadonlyArray<Command<Message>>]を返す- flags が使用されている場合、最初のパラメータとして受け入れます:
(flags: Flags) => [Model、Commands]または(flags: Flags、url: Url) => [Model、Commands] - 起動時コマンド (初期フェッチ、最初の入力にフォーカスなど) を含める
- 初期 Model の呼び出し可能なスキーマ コンストラクタを使用:
Model({ field: value })
Update
M.value(message).pipe(withUpdateReturn、M.tagsExhaustive({...}))を使用 — switch は使用しない- すべてのケースは
[Model、ReadonlyArray<Command<Message>>]を返す - 不変更新には
evo(model、{ field: () => newValue })を使用 - ケースが ~15 行を超える場合は、複雑なハンドラーを別の関数に抽出
- Submodels の場合:
[Model、ReadonlyArray<Command<Message>>、Option.Option<OutMessage>]を返す architecture.mdで OutMessage パターンを参照 — チャイルド モジュールはOption.some(OutMessage)経由で親にシグナルを送信し、親はGot*Message とM.tagsExhaustiveで処理
コマンド
Command.defineでコマンド ID を定義し、名前の後に結果 Message スキーマを渡す — 結果型は必須- 常にパスカルケース定数に割り当てます — パイプ チェーンにインラインしない
- 定義は作成された場所に存在し、update 関数と共置
- TypeScript に戻り型を推論させます — 明示的な
Command<typeof A>アノテーション なし - マルチステップ非同期には
Effect.genを使用 - 常に
Effect.catch(() => Effect.succeed(FailedX(...)))でフォールイブル Effect に対応 — コマンドは投げない。例外: Effect がタイプレベルでフォールイブルでない場合 (Clock.currentTimeMillis、Task.getTime、Task.randomInt、Task.uuidなど)、catchは不要で、Failed*Message も不要。型に従います — エラー チャネルがない場合、キャッチするものはありません。 Effect.provideをサービスに使用- アクション別に命名されたファクトリ関数:
fetchWeather、fetchWeatherCommandではなく - ファイア アンド フォーゲット コマンドは
Completed*メッセージを返す - DOM 操作には
Taskヘルパーを使用 (Task.focus、Task.scrollIntoView、Task.showModal、Task.delay、Task.uuidなど) —architecture.mdの Task ヘルパーを参照 - HTTP リクエストについては、
@effect/platformのHttpClientを使用 — weather の例のパターンを参照
フォーム検証
アプリにフォーム入力があり、検証が必要な場合 (必須フィールド、形式チェック、非同期一意性チェック)、foldkit/fieldValidation を使用 — 検証状態を手書きしない。
import {
Field,
Invalid,
NotValidated,
Valid,
Validating,
allValid,
email,
makeRules,
minLength,
validate,
} from 'foldkit/fieldValidation'
const nameRules = makeRules({
rules: [minLength(2, '名前は最低 2 文字である必要があります')],
})
const emailRules = makeRules({
required: 'メール アドレスは必須です',
rules: [email('有効なメール アドレスを入力してください')],
})
const Model = S.Struct({
name: Field,
email: Field,
// ...
})
Field スキーマは tagged union: NotValidated | Validating | Valid | Invalid です。update ハンドラーでフィールドを遷移させるには validate(rules、value) を使用し、送信をゲートするには allValid(fields) を使用し、オプション フィールド ルール セットを構築するには FieldValidation.optional(rules) を使用してください。
標準的なリファレンス: ${CLAUDE_SKILL_DIR}/../../examples/form/src/main.ts (バージョン ベースのキャンセルで非同期メール一意性チェック) と ${CLAUDE_SKILL_DIR}/../../examples/job-application/src/step/ (submodels 全体で検証されたマルチステップ フォーム)。
日付とファイル アップロード
日付処理 (誕生日、期限、スケジューリング) の場合:
Calendarモジュールを使用:Calendar.CalendarDate、Calendar.today.local(ユーザーのタイムゾーンで今日を返す Effect)、Calendar.make(year、month、day)、Calendar.addDaysなど- UI には
Ui.DatePicker(入力 + ポップオーバー カレンダー) またはUi.Calendar(インライン グリッド) を使用 - 必要に応じて flags 経由で初期日付をシード — 「job-application」の例を参照し、
Calendar.today.localを flags Effect で使用
ファイル アップロード (レジュメ、画像、添付ファイル) の場合:
Fileモジュールをファイル プリミティブに使用Ui.FileDropをドラッグ アンド ドロップ + クリック参照ゾーンで使用 (検証付き)Ui.FileDrop.ReceivedFilesはNonEmptyArray<File>OutMessage — 空の選択は決して発火しない- 標準的なリファレンス:
${CLAUDE_SKILL_DIR}/../../examples/job-application/src/step/attachments.ts
ビュー
- html ファクトリを モジュールごとに 1 回バインド:
const h = html<Message>()です。h:h.div、h.Class、h.OnClickの要素、属性、イベント ハンドラーを手に入れます。親の Message で汎用的な子ビューの場合、<ParentMessage>を関数ジェネリックとして取得し、関数本体内でconst h = html<ParentMessage>()をバインドしてください。 - Tailwind クラスには
h.Class(...)を使用 clsxパッケージからclsxを使用して条件付きクラスの構成:h.Class(clsx('base-classes'、{ 'active-class': isActive、'bg-blue-500': variant === 'Primary' }))です。モデル状態、ブール フラグ、または判別共用体タグに依存するクラスにclsxを使用してください。文字列連結、テンプレート リテラル、または&&式は決して使用しないでください。- モデル状態でパターン マッチ:
M.value(model.state).pipe(M.tagsExhaustive({...})) Option.matchを Option フィールドに基づいた条件付きレンダリングに使用- レイアウト ブランチで
h.keyed('div')(routeOrStateTag、attrs、children)を使用 - 複雑なセクションを抽出されたビュー関数に委譲
- イベントをメッセージに配線:
h.OnClick(ClickedSubmit())(Message 直接、コールバック ではなく)、h.OnInput(value => UpdatedEmail({ value }))(値をメッセージにマップするコールバック) - インタラクションがマッチする場合、Foldkit UI コンポーネントを使用 (モーダル用の Dialog、タブ付きコンテンツ用の Tabs など)
ランタイム配線
Runtime.makeProgramを使用 — URL ルーティングのあるアプリについてrouting: { onUrlRequest、onUrlChange }を追加- すべてのレンダリング後に
document.titleを設定するためにtitle: model => ...を追加 — ルートまたは任意のモデル状態から派生させます architecture.mdの「URL ルーティングなし/ありのプログラム」セクションを参照 (完全なパターン)- ルーティングを持つプログラムについて
ClickedLinkとChangedUrlMessage を含めます。適切なInternalUrl/ExternalUrl処理を update に含めます Runtime.run(program)で終わる
ルート (複数ページの場合)
- 双方向パーサーを使用:
r()、string()、int()、literal()、slash()、Route.mapTo()、Route.oneOf() r('RouteName'、{ param: S.String })でルート スキーマを定義- ルート バリアント定数に
Routeサフィックスを付ける:HomeRoute、NewLinkRoute、NotFoundRouteです。すべての例 (auth、shopping-cart、routing) はこれを行います。ルート スキーマをビュー、モデル、または同じタグ名の UI コンポーネントから区別します。 - 各ルートをルーターとして構築:
const homeRouter = pipe(Route.root、Route.mapTo(HomeRoute))です。ルーターは呼び出し可能 —homeRouter()は'/'を返し、tagFilterRouter({ tag: 'foo' })は'/tag/foo'を返します。これは双方向パーサーのプリント側です。 - パスをテンプレート文字列で手作業で構築しないでください。
Href(homeRouter())ではなくHref('/')です。navigateInternal(newLinkRouter())ではなくnavigateInternal('/new')です。Href(tagFilterRouter({ tag: tagName }))ではなくHref(`/tag/${encodeURIComponent(tagName)}`). ルーターはエンコーディングを処理し、URL の形状を 1 か所に保ちます。リファクター時に複数のファイルではなく 1 つのファイルが変わります。 - ビュー コンテンツを
model.route._tagでキー付け - コマンドの
pushUrlを使用してfoldkit/navigationからプログラムをナビゲート。ClickedLinkハンドラーのInternalケースで、foldkit/urlからurlToString(url)を使用 —url.pathname + search + hashから手作業で URL を再構築しないでください。そのパスは?プレフィックスとハッシュを静かにドロップします。 ClickedLinkハンドラーで、model.routeを事前に更新しないでください。 ランタイムはpushUrlが解決された後にChangedUrlを発火し、ルートを更新します。事前更新により、ダブル ライトが作成されます。
Subscription (リアルタイムの場合)
Subscription.makeSubscriptions(Deps)<Model、Message>で定義modelToDependenciesは Model から Subscription パラメータを抽出dependenciesToStreamは依存関係からStream<Message>を構築- Subscription は Model の状態に基づいて自動開始/停止 — 手動管理しない
- Model 依存関係のない Subscription (常に有効) の場合、依存関係型として
S.Nullを使用し、modelToDependenciesからnullを返す
フェーズ 4.5: 検証前のセルフ チェック
tsc を実行またはブラウザを開く前に、生成されたファイルについて簡単な機械的なパスを行ってください。フェーズ 6 の見直し者はこれらをキャッチしますが、ライト時にキャッチする方が、完全なレビュー ラウンド後にキャッチするより安いです。スキップして、ラウンド 1 レビュー ノイズを防ぐことができる項目で膨らませます。
checklist.md の「機械的スキャン」ブロックを src/ に対して実行 — これはすべての grep のリストです。空のオブジェクト コンストラクタ、ハード コード化されたルート パス、手書きのフォーム入力、.length > 0 チェック、evo の冗長スプレッド、コンストラクタ戻り値の as キャスト、ペアになっていないラベル、非 Option の maybe*、span([], []) プレースホルダー、冗長な Effect.ignore、フォーカス アウトライン リセットをカバーしています。各ヒットは修正またはジャスティフィケーション // NOTE: です。
次に、作成した各ファイルに目を通してください:
- 各ファイルのインポート: 実際にすべてのシンボルを使用しますか? (Lint がこれをキャッチします、しかしここでキャッチするのは lint ラウンドを後で回避します。)
- Union 内のすべてのメッセージ: update にケースはありますか? ビューはそれをディスパッチしますか?
- すべての状態バリアント: 入力されることはありますか? ビューはアクティブ状態で異なる何かをレンダリングしますか?
- すべてのコマンド: テストされていますか? その
Succeeded*またはCompleted*Message は update にハンドラーを持ちますか?
これはファイルごとに ~2 分の読み取り。それは ~15 分の未解決の項目あたりのレビュー ループを保存します。
フェーズ 5: 検証とテスト
ゲート: 4 つのコマンドがすべて成功する必要があります (フェーズ 5 完了前に)
フェーズ 5 完了前に、これらすべて 4 つを実行して、発生したすべての問題を修正してください。1 つではなく、3 つでもなく — すべて 4 つです:
npm run format # または: npx prettier -w . (最初に実行 — ファイルを書き込み)
npm run lint # または: npx eslint .
npm run typecheck # または: npx tsc --noEmit
npm run test # または: npx vitest run
format を最初に実行 (ファイルを書き直すため)。最後に実行すると、lint/typecheck/test はフォーマット解除されていないコードに対して実行され、pre-commit フックが後でフォーマットするため、ユーザーは
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- foldkit
- リポジトリ
- foldkit/foldkit
- ライセンス
- MIT
- 最終更新
- 2026/5/12
Source: https://github.com/foldkit/foldkit / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。