- name
- generate-program
- 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..."
- argument-hint
- ["description of the program you want"]
Generate a complete Foldkit program based on this description:
**$ARGUMENTS**
## Phase 1: Analyze the Description
Before writing any code, analyze the description to identify:
1. **Domain entities**: nouns that become Model fields (e.g., "todos", "user", "score")
2. **User interactions**: verbs that become Messages (e.g., "add", "delete", "filter", "submit")
3. **Async operations**: external data that becomes Commands (e.g., "fetch weather", "save to localStorage")
4. **Real-time needs**: streaming data that becomes Subscriptions (e.g., "live updates", "countdown", "WebSocket")
5. **Pages/navigation**: URL structure that becomes routes (e.g., "home page", "detail page")
6. **UI component needs**: interactive widgets that map to Foldkit UI components (e.g., "dropdown" → Menu, "modal" → Dialog, "tabs" → Tabs, "autocomplete" → Combobox, "date picker" → DatePicker, "file upload" → FileDrop, "reorderable list" → DragAndDrop, "toast/notification" → Toast, "hover tooltip" → Tooltip, "range/price filter" → Slider, "long scrolling list" → VirtualList)
7. **Form validation needs**: required fields, format checks, async uniqueness → `foldkit/fieldValidation` module (see Phase 4)
8. **Date handling**: birthdays, deadlines, scheduling → `Calendar` module + `DatePicker` or `Calendar` from `@foldkit/ui`
9. **File handling**: uploads, attachments, images → `File` module + `FileDrop` from `@foldkit/ui`
10. **Remote data**: anything fetched, cached, refreshed, or revalidated → the `AsyncData` module (see Phase 4). Don't hand-roll a loading/error union
11. **Multi-state flows**: a described process that moves through several named steps with rules about which step follows which (checkout, onboarding, multi-step approval, a connection lifecycle) → consider the `Machine` module (`foldkit/experimental`). Writing the transitions as a table makes the edge set enumerable, so `unreachableStates()` and `deadTransitions()` catch a missing or unreachable step by computation instead of by review. Raise it as an option in the analysis you present, noting it is under `experimental/`, and let the user choose. Plain `ts()` unions with one `M.tagsExhaustive` are still right for a flow of two or three states. `@foldkit/examples/state-machine/` is the reference
12. **Host embedding**: the program runs inside another app ("a widget in our React app", "embed this in an existing page", "the host needs to control it") → `Runtime.makeElement` plus the `Runtime.embed` lifecycle handle, with Flags for initial data and Ports for ongoing communication in both directions. `@foldkit/examples/embedding/` is the canonical reference: a plain TypeScript host driving a Foldkit widget end to end
Present this analysis to the user before proceeding.
If the description is detailed and unambiguous, summarize the analysis and confirm before moving on. But if there are gaps (unclear state transitions, vague UI requirements, unspecified error handling, missing edge cases, ambiguous domain boundaries, unclear counter/reset semantics), ask targeted clarifying questions before proceeding. Don't ask open-ended questions like "anything else?". Ask specific questions about the gaps you found.
**UX/behavior gaps:**
- "Should the todo list persist across page reloads (localStorage), or start fresh each session?"
- "When the API call fails, should the app show an inline error or a dialog?"
- "You mentioned 'users can edit items'. Is that inline editing or a separate edit page?"
**Domain-logic gaps (easy to miss, expensive to fix):**
- "When the user skips an interval, does that count as 'completed' for purposes of the streak?"
- "Does a counter that tracks 'completed' increments on successful actions only, or on skipped actions too?"
- "If the user triggers a reset mid-flow, does the counter reset with it, or persist across resets?"
- "You mentioned 'after N events, trigger X'. Is that N events total, or N events since the last X?"
- "On the Nth action in a cycle, which action does it trigger, the cycle's first or last?"
Domain-logic questions often surface off-by-one bugs before they hit the code. If the description has any counter, cycle, streak, or "after N" phrase, ask about edge cases at 0 and 1 and N specifically.
The goal is to resolve ambiguity early so the generated code matches what the user actually wants, not what you assumed.
## Phase 2: Study Reference Examples
Read the architecture and conventions guides to internalize the rules:
- [Architecture guide](architecture.md): TEA structure, file organization, type patterns
- [Conventions guide](conventions.md): naming, Effect-TS patterns, anti-patterns
- [Verification checklist](checklist.md): not just for Phase 5, also the generation bar. Skim the **Quality Bar** section now so you generate code that already meets it rather than code that will fail review.
- [Blind spots](blindSpots.md): what the Phase 6 reviewer will grade you against. Reading it now is cheaper than fixing it later.
These four files are a snapshot of a moving codebase. When they disagree with the live source under `@foldkit` or the `.d.ts` in `node_modules`, the live source is right. Treat the disagreement as a bug in this skill and say so in your final report.
If you have access to a context7 MCP tool, use it to look up Effect-TS documentation when you're unsure about an API. Effect is a large library. Verify function signatures rather than guessing.
### Quality exemplars
Two codebases are the _quality bar_ for generated apps. Not just "patterns to copy" but "the level of craft to match":
- `@foldkit/packages/typing-game/client/src/`: production multi-page app: Submodels, OutMessage, update/view decomposition, curried handler extraction, subscription patterns, domain modules.
- `@foldkit/packages/website/src/`: production Foldkit website: page organization, shared view primitives, route-driven rendering, idiomatic domain separation.
Before generating, spot-check at least ONE file from each (the shape of `update.ts` / how handlers get extracted / how domain files are structured) and match that level of craft in your output. The generated code should be indistinguishable from hand-written exemplar code.
Then read the tier-specific example files that match the app's complexity. **Always read at least one tier-specific example.** Never generate from memory alone.
### Complexity tiers
**Tier 1: Single page, no async, minimal state:**
Read `@foldkit/examples/counter/src/main.ts`
**Tier 2: Timers, subscriptions, simple stateful apps:**
Read `@foldkit/examples/stopwatch/src/main.ts` (timer via subscription, `Duration` field pattern) and `@foldkit/examples/todo/src/main.ts` (CRUD with localStorage via Flags)
**Tier 3: Async operations, loading/error states, API calls, form validation:**
Read `@foldkit/examples/weather/src/main.ts` (HTTP with `HttpClient`) and `@foldkit/examples/form/src/main.ts` (uses `foldkit/fieldValidation`; see the Form Validation section in Phase 4)
**Tier 4: URL routing, multiple pages, query parameters:**
Read `@foldkit/examples/routing/src/main.ts` and `@foldkit/examples/query-sync/src/main.ts`
**Tier 5: Complex state, nested domain models, CRUD, drag-and-drop:**
Read `@foldkit/examples/shopping-cart/src/main.ts` (nested domain schemas, cart state) and `@foldkit/examples/kanban/src/main.ts` (CRUD with `DragAndDrop`, Flags restoring from localStorage, subscriptions)
**Tier 6: Submodels, OutMessage, multi-step forms, auth flows, multi-module apps:**
Read `@foldkit/examples/auth/src/main.ts` (login/signup with Submodels, OutMessage, protected routes) and `@foldkit/examples/job-application/src/main.ts` (multi-step form with deeply nested Submodels in `step/`, `DatePicker`, `FileDrop`, `Listbox`, `Calendar` module for date handling)
**Tier 7: Real-time, WebSocket, Managed Resources, production-grade:**
Read `@foldkit/packages/typing-game/client/src/update.ts`, then explore its `page/home/` and `page/room/` directories for the full Submodel/OutMessage pattern.
Read examples from the target tier AND all lower tiers. A Tier 4 app should reflect patterns from Tiers 1-3 as well.
## Phase 2.5: Identify Foldkit UI Component Opportunities
Foldkit ships accessible UI components that handle keyboard navigation, ARIA attributes, and focus management automatically. They live in a **separate package**, `@foldkit/ui`, and are imported by name:
```ts
import { Button, Dialog, Input } from "@foldkit/ui"
```
There is no `Ui` namespace on the `foldkit` package. Reach for `Dialog.view`, not `Ui.Dialog.view`. Deep imports (`@foldkit/ui/dialog`) work too when you want to keep the barrel out of the bundle.
Before generating, check if any part of the app maps to a built-in component:
| User Need | Component | What you get for free |
| ------------------------------- | ------------- | --------------------------------------------------------------- |
| Modal/dialog/confirmation | `Dialog` | Focus trapping, Escape to close, scroll locking, backdrop |
| Tabbed content | `Tabs` | Arrow key navigation, aria-selected, roving tabindex |
| Dropdown menu | `Menu` | Arrow keys, typeahead search, aria-expanded, click-outside |
| Autocomplete/tag input | `Combobox` | Filtering, arrow key selection, aria-activedescendant |
| Select dropdown | `Select` | Keyboard selection, aria-selected, positioning |
| Single selection from options | `RadioGroup` | Arrow key cycling, aria-checked, read-only navigation |
| On/off toggle | `Switch` | Spacebar toggle, aria-checked |
| Boolean option | `Checkbox` | Spacebar toggle, aria-checked, indeterminate |
| Expandable section | `Disclosure` | Enter/Space toggle, aria-expanded |
| Floating content on hover/click | `Popover` | Positioning, click-outside, focus management |
| Hover tooltip | `Tooltip` | Show-delay, keyboard dismiss, positioning, aria-describedby |
| Single-select list | `Listbox` | Arrow keys, typeahead, aria-selected |
| Text input | `Input` | Consistent styling/behavior wrapper |
| Multi-line text | `Textarea` | Auto-resize, consistent styling |
| Form group | `Fieldset` | Disabled state propagation, grouping |
| Styled button | `Button` | Consistent click/keyboard handling |
| Inline calendar grid | `Calendar` | Month navigation, keyboard nav, aria-selected, date constraints |
| Date input + popover | `DatePicker` | Calendar popover, input masking, keyboard nav, constraints |
| File upload zone | `FileDrop` | Drag-and-drop, click-to-browse, accept filters, validation |
| Reorderable list | `DragAndDrop` | Pointer + keyboard drag, drop zones, announcement region |
| Transient notifications | `Toast` | Auto-dismiss, pause-on-hover, stacking, role=status/alert |
| Numeric range / price filter | `Slider` | Arrow/Home/End keys, aria-valuenow, multi-thumb ranges |
| Long scrolling list | `VirtualList` | Windowed rendering, scroll anchoring, measured item heights |
| Site/section navigation | `Nav` | Current-page marking, keyboard traversal, landmark semantics |
The package is not one shape.
**Stateful Submodels** carry their own Model, Message, update, and (mostly) OutMessage, and are embedded via `h.submodel`: `Menu`, `Listbox`, `Combobox`, `Calendar`, `DatePicker`, `Dialog`, `Popover`, `RadioGroup`, `Tabs`, `Tooltip`, `FileDrop`, `DragAndDrop`, `Slider`, `VirtualList`, plus `Toast` once built through `Toast.make(PayloadSchema)`.
**Stateless render helpers** have no Model at all. You call `view` directly with a ViewConfig and your own `h`, and store the value in your own Model: `Button`, `Input`, `Textarea`, `Select`, `Fieldset`, `Checkbox`, `Switch`, `Disclosure`, `Nav`.
Don't take that split on faith, because components have moved across it (`Checkbox`, `Switch`, and `Disclosure` became controlled render helpers; `RadioGroup` became a Submodel; `Tabs` and `Slider` moved their selection to the parent Model). Read the component's `public.d.ts`: exporting `Model` and `update` means Submodel, exporting only `view` and a `ViewConfig` / `ViewInputs` type means render helper. A render helper does not want a `Got*` Message.
To use a stateful Submodel:
1. Add its Model to your Model: `confirmDialog: Dialog.Model`
2. Add a `Got*` Message: `GotConfirmDialogMessage` with `{ message: Dialog.Message }`
3. Initialize in init: `confirmDialog: Dialog.init({ id: 'confirm-dialog' })`
4. Delegate in update: `GotConfirmDialogMessage: ({ message }) => ...`
5. Embed in view via `h.submodel`: `h.submodel({ slotId: 'confirm-dialog', view: Dialog.view, model: model.confirmDialog, toParentMessage: message => Message.GotConfirmDialogMessage({ message }) })` (add `viewInputs` for components whose view takes them)
**Always prefer Foldkit UI components over hand-rolling interactive widgets.** They make accessibility the default, not an afterthought.
**For form inputs specifically:** every text input, textarea, and button in a form MUST go through `Input`, `Textarea`, and `Button`. This is not optional, even though raw `input`/`textarea` HTML elements are available on the view's builder `h`. The form example (`@foldkit/examples/form/src/main.ts`) defines `inputFieldView` and `textareaFieldView` helpers that wrap `Input.view` and `Textarea.view` with label + validation feedback. Copy that helper pattern. Raw `input`/`textarea` are for non-form cases (search fields, inline editors) where you're intentionally working below the component layer, and even then, reach for the component first.
If the app uses UI components, **always read the ui-showcase example first** to understand how components are wired. This is the canonical reference for Foldkit UI integration patterns:
- `@foldkit/examples/ui-showcase/src/main.ts`: root wiring, `Got*` delegation, `toParentMessage` helpers
- `@foldkit/examples/ui-showcase/src/ui/message.ts`: how component Messages are structured
- `@foldkit/examples/ui-showcase/src/ui/model.ts`: how component Models are composed
- `@foldkit/examples/ui-showcase/src/ui/update.ts`: how component updates are delegated
- `@foldkit/examples/ui-showcase/src/ui/subscriptions.ts`: which components need Subscriptions lifted into the parent (`DragAndDrop`, `Slider`, `VirtualList`)
- `@foldkit/examples/ui-showcase/src/ui/toast.ts`: read when using `Toast`. It's unique in that it's parameterized on a payload schema via `Toast.make(PayloadSchema)`, returning a typed module you import from
Directory names under `@foldkit/examples/ui-showcase/src/` have moved before. List the directory rather than trusting these paths blind.
For apps using `DatePicker`, `FileDrop`, or other recently-added components, also read the `job-application` example (see Tier 6 below). It's the most complete real-world integration of these components together.
## Phase 3: Determine File Organization
Match the file structure to the app's complexity. The architecture stays the same at every scale; only the file organization changes.
### What lives in which file
Beyond the tier-based layouts below, follow these "schema placement" rules to avoid model.ts bloat:
- **`model.ts`** holds the `Model` schema + any schemas that are fields of Model (or composed into fields, like form state / submit state unions). Nothing else.
- **`command.ts`** holds schemas for the payloads commands send to / receive from external systems, in particular the persistence schema that `saveState` serializes and `flags` deserializes. The persistence schema is a command-layer concern, not a model concern; it often looks like a subset of Model but it isn't part of Model.
- **`domain/*.ts`** holds domain entity schemas and pure operations on them.
- **`message.ts`** holds messages (only).
- **`route.ts`** holds route variants + router pipelines.
A common mistake (because kanban colocates `SavedBoard` in `model.ts`): putting the persistence schema in `model.ts` because "it's schema." It's schema for the persistence layer, not for the Model. Move it to where it's used.
**Single file** (Tier 1-2, under ~300 lines):
```
src/main.ts ← Model, Message, init, update, view
src/entry.ts ← Runtime.makeApplication + Runtime.run
```
**Split commands + messages** (Tier 3, has async operations):
```
src/main.ts ← Model, init, update, view
src/entry.ts ← Runtime.makeApplication + Runtime.run
src/message.ts ← Message definitions
src/command.ts ← Command functions
```
在 GitHub 查看