ivy-create-crud
Create a CRUD app with list, detail, create, and edit views for an Ivy project.
Pre-flight: Read Learnings
If the file .ivy/learnings/ivy-create-crud.md exists in the project directory, read it first and apply any lessons learned from previous runs of this skill.
Reference Files
Read these before implementing:
- references/AGENTS.md -- Ivy framework API reference (widgets, hooks, layouts, inputs, colors)
- references/ProductsApp.cs -- simple app entry point with UseBlades
- references/OrdersApp.cs -- app entry point for entity with child relationships
- references/ProductListBlade.cs -- list blade with search, UseQuery, FuncView
- references/ProductDetailsBlade.cs -- details blade with ToDetails, delete, edit trigger
- references/ProductCreateDialog.cs -- create dialog with ToForm, validation
- references/ProductEditSheet.cs -- edit sheet with ToForm, Remove, revalidation
- references/OrderListBlade.cs -- list blade with FK resolution via Include
- references/OrderDetailsBlade.cs -- details blade with related child cards
- references/OrderCreateDialog.cs -- create dialog with async select for FK
- references/OrderEditSheet.cs -- edit sheet with FK fields
- references/OrderLinesBlade.cs -- child relationship blade with table
- references/OrderLineCreateDialog.cs -- child create dialog with parent FK
- references/OrderLineEditSheet.cs -- child edit sheet with async select
Workflow
This skill guides you through creating complete CRUD applications:
- Plan - Analyze the data models and propose CRUD views for selected entities
- Review - Present the plan for user approval (accept / request changes / cancel)
- Implement - Generate all CRUD files following reference patterns, one entity at a time
Step 1: Plan the CRUD App
If the user has specified a database connection, read the connection context using ivy cli explain connections/{ConnectionName}. If not, ask the user which connection to use.
Ask the user which entities they want CRUD views for (suggest all top-level entities as options).
For each selected entity, determine:
- Singular name and plural name
- Lucide icon (PascalCase, e.g.
ShoppingBag, Users, FileText)
- Whether it is a top-level entity (gets its own App with list/details/create/edit) or a child entity (shown as a relationship blade on a parent's details view)
- Child entities are those that primarily exist as children of another entity via a required foreign key (e.g., OrderLines belong to Orders)
Detect one-to-many relationships from navigation properties and foreign keys:
- A parent entity's details blade should show a related card linking to child blades
- Child entities get: RelationshipBlade (table view), RelationshipCreateDialog, RelationshipEditSheet
Plan Format
# Plan
## Create App: [PluralName]
**Namespace**: `{ProjectNamespace}.Apps`
**Connection**: `[ConnectionName]`
**Entity**: `[SingularName]`
**Icon**: `[LucideIcon]`
**Group**: `Apps`
**Layout**: `Blades`
**Files**: `Apps\[PluralName]App.cs`
Opens [Singular]ListBlade.
### Create View: [Singular]ListBlade
**Files**: `Apps\[PluralName]\[Singular]ListBlade.cs`
**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`
List view with search. Shows [key fields]. Click opens [Singular]DetailsBlade. Create button opens [Singular]CreateDialog.
### Create View: [Singular]DetailsBlade
**Files**: `Apps\[PluralName]\[Singular]DetailsBlade.cs`
**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`
Detail view showing [interesting fields]. Edit button opens [Singular]EditSheet. Delete button with confirmation.
[If has children: Related card with links to child blades with count badges.]
### Create View: [Singular]CreateDialog
**Files**: `Apps\[PluralName]\[Singular]CreateDialog.cs`
**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`
Create dialog with required fields. [List the fields.]
### Create View: [Singular]EditSheet
**Files**: `Apps\[PluralName]\[Singular]EditSheet.cs`
**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`
Edit sheet with all editable fields. Removes Id, CreatedAt, UpdatedAt. [List notable field customizations.]
### Create Relationship View: [ParentSingular][ChildPlural]Blade
**Files**: `Apps\[PluralName]\[ParentSingular][ChildPlural]Blade.cs`
**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`
Table view of [ChildPlural] for a given [ParentSingular]. Shows [key columns]. Add button opens [ParentSingular][ChildPlural]CreateDialog.
### Create Relationship View: [ParentSingular][ChildPlural]CreateDialog
**Files**: `Apps\[PluralName]\[ParentSingular][ChildPlural]CreateDialog.cs`
**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`
Create dialog for adding a [ChildSingular] to a [ParentSingular]. Parent ID passed via constructor.
### Create Relationship View: [ParentSingular][ChildPlural]EditSheet
**Files**: `Apps\[PluralName]\[ParentSingular][ChildPlural]EditSheet.cs`
**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`
Edit sheet for a [ChildSingular]. Takes child entity ID as parameter.
Repeat the ## Create App block for each top-level entity selected.
Chrome Decision
- If the plan has only one
## Create App block (single entity) and no multi-app navigation needs, add a ### Update Program.cs section that replaces server.UseAppShell(...) with server.UseDefaultApp(typeof(TheAppClass));.
- If the plan has multiple
## Create App blocks or the user explicitly wants sidebar navigation, add a ### Update Program.cs section that ensures server.UseAppShell(new AppShellSettings().DefaultApp<FirstAppClass>().UseTabs(preventDuplicates: true)); is present.
Present the plan to the user and ask them to Accept, Request Changes, or Cancel.
Step 2: Handle Changes
If the user requests changes, apply them to the plan and present the updated plan again.
Step 3: Implement
Once approved, implement each entity's views in order.
Namespace Conventions
- App files (
[PluralName]App.cs): namespace {ProjectNamespace}.Apps;
- View files (all blades, dialogs, sheets):
namespace {ProjectNamespace}.Apps.[PluralName];
Required Using Directives
Every generated .cs file must include explicit using statements. Do NOT rely on global usings.
Connection usings (when using a database connection):
using {ProjectNamespace}.Connections.{ConnectionName}; -- for context factory
using {ProjectNamespace}.Connections.{ConnectionName}.Models; -- for entity classes (only if project uses a Models subfolder)
using Microsoft.EntityFrameworkCore; -- for ToListAsync, FirstOrDefaultAsync, Include, etc.
Common Ivy usings by view type:
| View Type |
Required Namespaces |
| All views |
Ivy, Ivy.Core.Hooks, Ivy.Hooks, Ivy.Shared, Ivy.Views |
| App entry point |
+ Ivy.Apps |
| Blades |
+ Ivy.Views.Blades |
| Details views |
+ Ivy.Views.Builders |
| Forms (dialogs, sheets) |
+ Ivy.Views.Forms, System.ComponentModel.DataAnnotations |
| Tables |
+ Ivy.Views.Tables |
| Alerts/Callouts |
+ Ivy.Views.Alerts |
| Async select inputs |
+ Ivy.Widgets.Inputs |
Include only the namespaces your file actually uses.
Data Access Pattern
Always use the context factory pattern:
var factory = UseService<{ConnectionName}ContextFactory>();
await using var db = factory.CreateDbContext();
Program.cs Chrome Configuration
Check if the approved plan includes a ### Update Program.cs section. If it does:
- If it specifies
UseDefaultApp: Open Program.cs and replace the server.UseAppShell(...) line with server.UseDefaultApp(typeof(AppClassName));. Remove the UseAppShell line entirely.
- If it specifies
UseAppShell: Ensure server.UseAppShell(new AppShellSettings().DefaultApp<AppClassName>().UseTabs(preventDuplicates: true)); is present in Program.cs.
- Follow the approved plan exactly.
Per-View-Type Instructions
App ([PluralName]App.cs)
using {ProjectNamespace}.Apps.[PluralName];
namespace {ProjectNamespace}.Apps;
[App(icon: Icons.[Icon], group: ["[Group]"])]
public class [PluralName]App : ViewBase
{
public override object? Build()
{
return UseBlades(() => new [Singular]ListBlade(), "Search");
}
}
ListBlade ([Singular]ListBlade.cs)
- Define a
private record [Singular]ListRecord(int Id, ...) with the key display fields. For FK properties (e.g., CustomerId, PartnerId), do NOT include the raw ID. Instead, include a resolved name field (e.g., string? CustomerName) and project it via the navigation property in the .Select().
- Use
UseRefreshToken() for create/edit refresh flow.
UseEffect on refreshToken to pop, revalidate, and push to DetailsBlade.
onItemClicked pushes [Singular]DetailsBlade(id).
- Create button:
Icons.Plus.ToButton(...).Ghost().Tooltip("Create [Singular]").ToTrigger(isOpen => new [Singular]CreateDialog(isOpen, refreshToken)).
- Two UseQuery methods:
Use[Singular]ListRecords (filtered list) and Use[Singular]ListRecord (single item for FuncView).
- List query: filter with
.Where(), order by .OrderByDescending(e => e.CreatedAt), .Take(50), tags: [typeof([Entity][])].
- Single item query:
RevalidateOnMount = false, initialValue: record, tags: [(typeof([Entity]), record.Id)].
- Search input + create button in a horizontal header layout.
- If entity has FK properties, ALWAYS
.Include() the navigation property and project the human-readable name into the record -- NEVER expose raw FK IDs as display columns. Explicitly type as IQueryable<Entity> to avoid IIncludableQueryable type issues.
DetailsBlade ([Singular]DetailsBlade.cs)
- Constructor:
(int [entityCamelCase]Id).
- Query the entity with
.Include() for navigation properties.
- Show loading skeleton:
if (query.Loading) return Skeleton.Card();
- Show not found callout if null.
- Delete button with
.WithConfirm(), pops and revalidates typeof([Entity][]).
- Edit button with
.ToTrigger(isOpen => new [Singular]EditSheet(isOpen, [entityCamelCase]Id)).
- Details card using anonymous type
.ToDetails().RemoveEmpty().Builder(e => e.Id, e => e.CopyToClipboard()).
- Only include "interesting" fields. Exclude CreatedAt, UpdatedAt.
- If entity has one-to-many children: add a related card with
ListItem entries that push child blades with count badges.
CreateDialog ([Singular]CreateDialog.cs)
- Constructor:
(IState<bool> isOpen, RefreshToken refreshToken).
- Define a
private record [Singular]CreateRequest with [Required] attributes on required nullable fields (string, int?, Guid?, etc.). Do NOT add [Required] on non-nullable value types (int, decimal, DateTime, bool, etc.).
- CRITICAL: CreateRequest MUST NEVER include these auto-managed fields:
Id -- auto-generated by the database
CreatedAt -- set in OnSubmit: CreatedAt = DateTime.UtcNow
UpdatedAt -- set in OnSubmit: UpdatedAt = DateTime.UtcNow
- Any other auto-increment or computed fields
- Use the SAME types as in the target entity to avoid type mismatches.
UseState(() => new [Singular]CreateRequest()).
.ToForm().OnSubmit(OnSubmit).ToDialog(isOpen, title: "Create [Singular]", submitTitle: "Create").
- Use
.Builder(e => e.ForeignKeyId, e => e.ToAsyncSelectInput(...)) for foreign key fields.
- OnSubmit creates entity, sets CreatedAt/UpdatedAt if they exist, saves, returns ID via
refreshToken.Refresh(id).
EditSheet ([Singular]EditSheet.cs)
- Constructor:
(IState<bool> isOpen, int [entityCamelCase]Id).
- Query entity by ID.
- Loading state:
Skeleton.Form().ToSheet(isOpen, "Edit [Singular]").
.ToForm().Remove(e => e.Id, e => e.CreatedAt, e => e.UpdatedAt) - ONLY remove properties that actually exist on the entity.
- Use
.Builder() for custom inputs (TextArea for long text, AsyncSelect for FKs, etc.).
- OnSubmit sets
UpdatedAt = DateTime.UtcNow if property exists, updates entity, revalidates tags.
RelationshipBlade ([Parent][ChildPlural]Blade.cs)
- Constructor:
(int [parentCamelCase]Id).
- Query child entities filtered by parent ID with
.Where(e => e.[ParentId] == [parentCamelCase]Id).
- For FK properties on child entities,
.Include() the navigation property and project the resolved name -- never show raw FK IDs as table columns.
- Use
.ToTable() with .Totals() for numeric columns and .RemoveEmptyColumns().
- Each row has Delete button with
.WithConfirm() and Edit trigger to [Parent][ChildPlural]EditSheet.
- Add button triggers
[Parent][ChildPlural]CreateDialog(isOpen, refreshToken, [parentCamelCase]Id).
- UseEffect on refreshToken to revalidate both child list and parent tags.
- Empty state: show Callout with info variant.
RelationshipCreateDialog ([Parent][ChildPlural]CreateDialog.cs)
- Constructor:
(IState<bool> isOpen, RefreshToken refreshToken, int [parentCamelCase]Id).
- Parent ID is passed via constructor, NOT part of the form.
- CreateRequest record MUST only have user input fields -- no parent FK, no Id, no CreatedAt, no UpdatedAt.
- Set parent FK directly when creating entity:
[ParentId] = [parentCamelCase]Id.
RelationshipEditSheet ([Parent][ChildPlural]EditSheet.cs)
- Constructor:
(IState<bool> isOpen, RefreshToken refreshToken, int [childCamelCase]Id).
- For regular child entities with their own ID, only the child ID is needed.
- For junction tables with composite keys, accept ALL key fields.
.Remove() the parent FK and ID fields that shouldn't be editable.
Pattern Sources
- Use ONLY the reference documents in the
references/ folder and documentation endpoints for API patterns and code examples.
- Do NOT read existing app files in the project's
Apps/ directory for patterns.
Critical Code Generation Rules
Use Modern C# Features
- Use file-scoped namespaces, primary constructors, collection expressions.
- Use
record for DTOs with { get; init; } properties.
Icon Usage
Only use icons from the Icons enum. Common ones: Icons.Pencil, Icons.Trash, Icons.Plus, Icons.ChevronRight, Icons.Search, Icons.Filter.
Entity Property Verification
- ALWAYS check entity definitions before using any property.
- Don't assume properties exist (CreatedAt, UpdatedAt, FullName, etc.).
- Match property types exactly (int vs int?, DateTime vs string).
- If navigational properties don't exist in the POCOs, don't use
.Include().
ToForm() Rules
- Only
.Remove() properties that actually exist on the model.
- Only
.Builder() with builders you've seen in the reference examples.
- ToMoneyInput MUST be followed by
.Currency("USD") or appropriate currency.
- ToAsyncMultiSelectInput does NOT exist.
- Always provide explicit input builder to avoid CS0411 errors:
.Builder(e => e.Field, e => e.ToTextInput()).
Available Form Builders by Type
string: ToTextInput(), ToTextareaInput(), ToPasswordInput(), ToEmailInput(), ToUrlInput(), ToTelInput(), ToColorInput(), ToCodeInput()
DateTime, DateTime?: ToDateInput(), ToDateTimeInput(). Never use ToTextareaInput() for dates.
int, long, decimal, double, float: ToNumberInput(), ToFeedbackInput(), ToMoneyInput() (MUST chain .Currency())
Foreign keys: ToAsyncSelectInput(searchFn, lookupFn, placeholder) -- single selection only
Async Select Pattern
For foreign key dropdowns, create two static methods:
Use[Entity]Search(IViewContext context, string query) - returns QueryResult<Option<TKey?>[]>
Use[Entity]Lookup(IViewContext context, TKey? id) - returns QueryResult<Option<TKey?>?>
CRITICAL: Return type MUST be QueryResult<>, NOT Task<>. Always use context.UseQuery() which returns QueryResult<>.
FK Validation in EditSheet
When editing, entity FK properties are non-nullable (int, Guid). If the user clears a required FK field, the value becomes 0 or Guid.Empty. Add explicit validation:
if (request.CustomerId == default)
{
client.Error("Please select a customer.");
return;
}
IQueryable Type with Include
When using .Include() followed by .Where(), explicitly type as IQueryable<Entity> to avoid type mismatch:
IQueryable<Order> query = db.Orders.Include(o => o.Customer);
query = query.Where(o => o.StoreName.Contains(filter));
After Implementation
After writing all files for each entity, summarize what was created.
Post-run: Evaluate and Improve
After completing the task:
- Evaluate: Did the build succeed? Were there compilation errors, unexpected behavior, or manual corrections needed during this run?
- Update learnings: If anything required correction or was surprising, append a concise entry to
.ivy/learnings/ivy-create-crud.md (create the file and .ivy/learnings/ directory if they don't exist). Each entry should note: the date, what went wrong, why, and what to do differently next time.
- Skip if clean: If everything succeeded without issues, do not update the learnings file.
1---2name: ivy-create-crud3description: Create a CRUD app with list, detail, create, and edit views for an Ivy project. Use when the user asks for CRUD views, master-detail views, data management screens, entity management, or wants to build views for database tables. Handles blades, dialogs, sheets, foreign key lookups, search, pagination, parent-child relationships, and async select inputs.4---56# ivy-create-crud78Create a CRUD app with list, detail, create, and edit views for an Ivy project.910## Pre-flight: Read Learnings1112If the file `.ivy/learnings/ivy-create-crud.md` exists in the project directory, read it first and apply any lessons learned from previous runs of this skill.1314## Reference Files1516Read these before implementing:17- [references/AGENTS.md](references/AGENTS.md) -- Ivy framework API reference (widgets, hooks, layouts, inputs, colors)18- [references/ProductsApp.cs](references/ProductsApp.cs) -- simple app entry point with UseBlades19- [references/OrdersApp.cs](references/OrdersApp.cs) -- app entry point for entity with child relationships20- [references/ProductListBlade.cs](references/ProductListBlade.cs) -- list blade with search, UseQuery, FuncView21- [references/ProductDetailsBlade.cs](references/ProductDetailsBlade.cs) -- details blade with ToDetails, delete, edit trigger22- [references/ProductCreateDialog.cs](references/ProductCreateDialog.cs) -- create dialog with ToForm, validation23- [references/ProductEditSheet.cs](references/ProductEditSheet.cs) -- edit sheet with ToForm, Remove, revalidation24- [references/OrderListBlade.cs](references/OrderListBlade.cs) -- list blade with FK resolution via Include25- [references/OrderDetailsBlade.cs](references/OrderDetailsBlade.cs) -- details blade with related child cards26- [references/OrderCreateDialog.cs](references/OrderCreateDialog.cs) -- create dialog with async select for FK27- [references/OrderEditSheet.cs](references/OrderEditSheet.cs) -- edit sheet with FK fields28- [references/OrderLinesBlade.cs](references/OrderLinesBlade.cs) -- child relationship blade with table29- [references/OrderLineCreateDialog.cs](references/OrderLineCreateDialog.cs) -- child create dialog with parent FK30- [references/OrderLineEditSheet.cs](references/OrderLineEditSheet.cs) -- child edit sheet with async select3132## Workflow3334This skill guides you through creating complete CRUD applications:35361. **Plan** - Analyze the data models and propose CRUD views for selected entities372. **Review** - Present the plan for user approval (accept / request changes / cancel)383. **Implement** - Generate all CRUD files following reference patterns, one entity at a time3940## Step 1: Plan the CRUD App4142If the user has specified a database connection, read the connection context using `ivy cli explain connections/{ConnectionName}`. If not, ask the user which connection to use.4344Ask the user which entities they want CRUD views for (suggest all top-level entities as options).4546For each selected entity, determine:47- **Singular name** and **plural name**48- **Lucide icon** (PascalCase, e.g. `ShoppingBag`, `Users`, `FileText`)49- Whether it is a **top-level entity** (gets its own App with list/details/create/edit) or a **child entity** (shown as a relationship blade on a parent's details view)50- Child entities are those that primarily exist as children of another entity via a required foreign key (e.g., OrderLines belong to Orders)5152Detect **one-to-many relationships** from navigation properties and foreign keys:53- A parent entity's details blade should show a related card linking to child blades54- Child entities get: RelationshipBlade (table view), RelationshipCreateDialog, RelationshipEditSheet5556### Plan Format5758```59# Plan6061## Create App: [PluralName]6263**Namespace**: `{ProjectNamespace}.Apps`64**Connection**: `[ConnectionName]`65**Entity**: `[SingularName]`66**Icon**: `[LucideIcon]`67**Group**: `Apps`68**Layout**: `Blades`69**Files**: `Apps\[PluralName]App.cs`7071Opens [Singular]ListBlade.7273### Create View: [Singular]ListBlade7475**Files**: `Apps\[PluralName]\[Singular]ListBlade.cs`76**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`7778List view with search. Shows [key fields]. Click opens [Singular]DetailsBlade. Create button opens [Singular]CreateDialog.7980### Create View: [Singular]DetailsBlade8182**Files**: `Apps\[PluralName]\[Singular]DetailsBlade.cs`83**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`8485Detail view showing [interesting fields]. Edit button opens [Singular]EditSheet. Delete button with confirmation.86[If has children: Related card with links to child blades with count badges.]8788### Create View: [Singular]CreateDialog8990**Files**: `Apps\[PluralName]\[Singular]CreateDialog.cs`91**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`9293Create dialog with required fields. [List the fields.]9495### Create View: [Singular]EditSheet9697**Files**: `Apps\[PluralName]\[Singular]EditSheet.cs`98**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`99100Edit sheet with all editable fields. Removes Id, CreatedAt, UpdatedAt. [List notable field customizations.]101102### Create Relationship View: [ParentSingular][ChildPlural]Blade103104**Files**: `Apps\[PluralName]\[ParentSingular][ChildPlural]Blade.cs`105**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`106107Table view of [ChildPlural] for a given [ParentSingular]. Shows [key columns]. Add button opens [ParentSingular][ChildPlural]CreateDialog.108109### Create Relationship View: [ParentSingular][ChildPlural]CreateDialog110111**Files**: `Apps\[PluralName]\[ParentSingular][ChildPlural]CreateDialog.cs`112**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`113114Create dialog for adding a [ChildSingular] to a [ParentSingular]. Parent ID passed via constructor.115116### Create Relationship View: [ParentSingular][ChildPlural]EditSheet117118**Files**: `Apps\[PluralName]\[ParentSingular][ChildPlural]EditSheet.cs`119**Namespace**: `{ProjectNamespace}.Apps.[PluralName]`120121Edit sheet for a [ChildSingular]. Takes child entity ID as parameter.122```123124Repeat the `## Create App` block for each top-level entity selected.125126### Chrome Decision127- If the plan has **only one `## Create App` block** (single entity) and no multi-app navigation needs, add a `### Update Program.cs` section that replaces `server.UseAppShell(...)` with `server.UseDefaultApp(typeof(TheAppClass));`.128- If the plan has **multiple `## Create App` blocks** or the user explicitly wants sidebar navigation, add a `### Update Program.cs` section that ensures `server.UseAppShell(new AppShellSettings().DefaultApp<FirstAppClass>().UseTabs(preventDuplicates: true));` is present.129130Present the plan to the user and ask them to **Accept**, **Request Changes**, or **Cancel**.131132## Step 2: Handle Changes133134If the user requests changes, apply them to the plan and present the updated plan again.135136## Step 3: Implement137138Once approved, implement each entity's views in order.139140### Namespace Conventions141142- App files (`[PluralName]App.cs`): `namespace {ProjectNamespace}.Apps;`143- View files (all blades, dialogs, sheets): `namespace {ProjectNamespace}.Apps.[PluralName];`144145### Required Using Directives146147Every generated `.cs` file must include explicit `using` statements. Do NOT rely on global usings.148149**Connection usings (when using a database connection):**150- `using {ProjectNamespace}.Connections.{ConnectionName};` -- for context factory151- `using {ProjectNamespace}.Connections.{ConnectionName}.Models;` -- for entity classes (only if project uses a Models subfolder)152- `using Microsoft.EntityFrameworkCore;` -- for ToListAsync, FirstOrDefaultAsync, Include, etc.153154**Common Ivy usings by view type:**155156| View Type | Required Namespaces |157|---|---|158| All views | `Ivy`, `Ivy.Core.Hooks`, `Ivy.Hooks`, `Ivy.Shared`, `Ivy.Views` |159| App entry point | + `Ivy.Apps` |160| Blades | + `Ivy.Views.Blades` |161| Details views | + `Ivy.Views.Builders` |162| Forms (dialogs, sheets) | + `Ivy.Views.Forms`, `System.ComponentModel.DataAnnotations` |163| Tables | + `Ivy.Views.Tables` |164| Alerts/Callouts | + `Ivy.Views.Alerts` |165| Async select inputs | + `Ivy.Widgets.Inputs` |166167Include only the namespaces your file actually uses.168169### Data Access Pattern170171Always use the context factory pattern:172```csharp173var factory = UseService<{ConnectionName}ContextFactory>();174await using var db = factory.CreateDbContext();175```176177### Program.cs Chrome Configuration178179Check if the approved plan includes a `### Update Program.cs` section. If it does:180- If it specifies `UseDefaultApp`: Open `Program.cs` and replace the `server.UseAppShell(...)` line with `server.UseDefaultApp(typeof(AppClassName));`. Remove the `UseAppShell` line entirely.181- If it specifies `UseAppShell`: Ensure `server.UseAppShell(new AppShellSettings().DefaultApp<AppClassName>().UseTabs(preventDuplicates: true));` is present in `Program.cs`.182- Follow the approved plan exactly.183184### Per-View-Type Instructions185186#### App (`[PluralName]App.cs`)187188```csharp189using {ProjectNamespace}.Apps.[PluralName];190191namespace {ProjectNamespace}.Apps;192193[App(icon: Icons.[Icon], group: ["[Group]"])]194public class [PluralName]App : ViewBase195{196 public override object? Build()197 {198 return UseBlades(() => new [Singular]ListBlade(), "Search");199 }200}201```202203#### ListBlade (`[Singular]ListBlade.cs`)204205- Define a `private record [Singular]ListRecord(int Id, ...)` with the key display fields. **For FK properties (e.g., `CustomerId`, `PartnerId`), do NOT include the raw ID. Instead, include a resolved name field (e.g., `string? CustomerName`) and project it via the navigation property in the `.Select()`.**206- Use `UseRefreshToken()` for create/edit refresh flow.207- `UseEffect` on `refreshToken` to pop, revalidate, and push to DetailsBlade.208- `onItemClicked` pushes `[Singular]DetailsBlade(id)`.209- Create button: `Icons.Plus.ToButton(...).Ghost().Tooltip("Create [Singular]").ToTrigger(isOpen => new [Singular]CreateDialog(isOpen, refreshToken))`.210- Two UseQuery methods: `Use[Singular]ListRecords` (filtered list) and `Use[Singular]ListRecord` (single item for FuncView).211- List query: filter with `.Where()`, order by `.OrderByDescending(e => e.CreatedAt)`, `.Take(50)`, tags: `[typeof([Entity][])]`.212- Single item query: `RevalidateOnMount = false`, `initialValue: record`, tags: `[(typeof([Entity]), record.Id)]`.213- Search input + create button in a horizontal header layout.214- If entity has FK properties, ALWAYS `.Include()` the navigation property and project the human-readable name into the record -- NEVER expose raw FK IDs as display columns. Explicitly type as `IQueryable<Entity>` to avoid IIncludableQueryable type issues.215216#### DetailsBlade (`[Singular]DetailsBlade.cs`)217218- Constructor: `(int [entityCamelCase]Id)`.219- Query the entity with `.Include()` for navigation properties.220- Show loading skeleton: `if (query.Loading) return Skeleton.Card();`221- Show not found callout if null.222- Delete button with `.WithConfirm()`, pops and revalidates `typeof([Entity][])`.223- Edit button with `.ToTrigger(isOpen => new [Singular]EditSheet(isOpen, [entityCamelCase]Id))`.224- Details card using anonymous type `.ToDetails().RemoveEmpty().Builder(e => e.Id, e => e.CopyToClipboard())`.225- Only include "interesting" fields. Exclude CreatedAt, UpdatedAt.226- If entity has one-to-many children: add a related card with `ListItem` entries that push child blades with count badges.227228#### CreateDialog (`[Singular]CreateDialog.cs`)229230- Constructor: `(IState<bool> isOpen, RefreshToken refreshToken)`.231- Define a `private record [Singular]CreateRequest` with `[Required]` attributes on required **nullable** fields (`string`, `int?`, `Guid?`, etc.). Do NOT add `[Required]` on non-nullable value types (`int`, `decimal`, `DateTime`, `bool`, etc.).232- **CRITICAL: CreateRequest MUST NEVER include these auto-managed fields:**233 - `Id` -- auto-generated by the database234 - `CreatedAt` -- set in OnSubmit: `CreatedAt = DateTime.UtcNow`235 - `UpdatedAt` -- set in OnSubmit: `UpdatedAt = DateTime.UtcNow`236 - Any other auto-increment or computed fields237- Use the SAME types as in the target entity to avoid type mismatches.238- `UseState(() => new [Singular]CreateRequest())`.239- `.ToForm().OnSubmit(OnSubmit).ToDialog(isOpen, title: "Create [Singular]", submitTitle: "Create")`.240- Use `.Builder(e => e.ForeignKeyId, e => e.ToAsyncSelectInput(...))` for foreign key fields.241- OnSubmit creates entity, sets CreatedAt/UpdatedAt if they exist, saves, returns ID via `refreshToken.Refresh(id)`.242243#### EditSheet (`[Singular]EditSheet.cs`)244245- Constructor: `(IState<bool> isOpen, int [entityCamelCase]Id)`.246- Query entity by ID.247- Loading state: `Skeleton.Form().ToSheet(isOpen, "Edit [Singular]")`.248- `.ToForm().Remove(e => e.Id, e => e.CreatedAt, e => e.UpdatedAt)` - ONLY remove properties that actually exist on the entity.249- Use `.Builder()` for custom inputs (TextArea for long text, AsyncSelect for FKs, etc.).250- OnSubmit sets `UpdatedAt = DateTime.UtcNow` if property exists, updates entity, revalidates tags.251252#### RelationshipBlade (`[Parent][ChildPlural]Blade.cs`)253254- Constructor: `(int [parentCamelCase]Id)`.255- Query child entities filtered by parent ID with `.Where(e => e.[ParentId] == [parentCamelCase]Id)`.256- For FK properties on child entities, `.Include()` the navigation property and project the resolved name -- never show raw FK IDs as table columns.257- Use `.ToTable()` with `.Totals()` for numeric columns and `.RemoveEmptyColumns()`.258- Each row has Delete button with `.WithConfirm()` and Edit trigger to `[Parent][ChildPlural]EditSheet`.259- Add button triggers `[Parent][ChildPlural]CreateDialog(isOpen, refreshToken, [parentCamelCase]Id)`.260- UseEffect on refreshToken to revalidate both child list and parent tags.261- Empty state: show Callout with info variant.262263#### RelationshipCreateDialog (`[Parent][ChildPlural]CreateDialog.cs`)264265- Constructor: `(IState<bool> isOpen, RefreshToken refreshToken, int [parentCamelCase]Id)`.266- Parent ID is passed via constructor, NOT part of the form.267- CreateRequest record MUST only have user input fields -- no parent FK, no Id, no CreatedAt, no UpdatedAt.268- Set parent FK directly when creating entity: `[ParentId] = [parentCamelCase]Id`.269270#### RelationshipEditSheet (`[Parent][ChildPlural]EditSheet.cs`)271272- Constructor: `(IState<bool> isOpen, RefreshToken refreshToken, int [childCamelCase]Id)`.273- For regular child entities with their own ID, only the child ID is needed.274- For junction tables with composite keys, accept ALL key fields.275- `.Remove()` the parent FK and ID fields that shouldn't be editable.276277### Pattern Sources278- Use ONLY the reference documents in the `references/` folder and documentation endpoints for API patterns and code examples.279- Do NOT read existing app files in the project's `Apps/` directory for patterns.280281### Critical Code Generation Rules282283#### Use Modern C# Features284- Use file-scoped namespaces, primary constructors, collection expressions.285- Use `record` for DTOs with `{ get; init; }` properties.286287#### Icon Usage288Only use icons from the Icons enum. Common ones: `Icons.Pencil`, `Icons.Trash`, `Icons.Plus`, `Icons.ChevronRight`, `Icons.Search`, `Icons.Filter`.289290#### Entity Property Verification291- ALWAYS check entity definitions before using any property.292- Don't assume properties exist (CreatedAt, UpdatedAt, FullName, etc.).293- Match property types exactly (int vs int?, DateTime vs string).294- If navigational properties don't exist in the POCOs, don't use `.Include()`.295296#### ToForm() Rules297- Only `.Remove()` properties that actually exist on the model.298- Only `.Builder()` with builders you've seen in the reference examples.299- ToMoneyInput MUST be followed by `.Currency("USD")` or appropriate currency.300- ToAsyncMultiSelectInput does NOT exist.301- Always provide explicit input builder to avoid CS0411 errors: `.Builder(e => e.Field, e => e.ToTextInput())`.302303#### Available Form Builders by Type304305**string:** `ToTextInput()`, `ToTextareaInput()`, `ToPasswordInput()`, `ToEmailInput()`, `ToUrlInput()`, `ToTelInput()`, `ToColorInput()`, `ToCodeInput()`306**DateTime, DateTime?:** `ToDateInput()`, `ToDateTimeInput()`. Never use `ToTextareaInput()` for dates.307**int, long, decimal, double, float:** `ToNumberInput()`, `ToFeedbackInput()`, `ToMoneyInput()` (MUST chain `.Currency()`)308**Foreign keys:** `ToAsyncSelectInput(searchFn, lookupFn, placeholder)` -- single selection only309310#### Async Select Pattern311For foreign key dropdowns, create two static methods:312- `Use[Entity]Search(IViewContext context, string query)` - returns `QueryResult<Option<TKey?>[]>`313- `Use[Entity]Lookup(IViewContext context, TKey? id)` - returns `QueryResult<Option<TKey?>?>`314315**CRITICAL: Return type MUST be `QueryResult<>`, NOT `Task<>`.** Always use `context.UseQuery()` which returns `QueryResult<>`.316317#### FK Validation in EditSheet318319When editing, entity FK properties are non-nullable (`int`, `Guid`). If the user clears a required FK field, the value becomes `0` or `Guid.Empty`. Add explicit validation:320```csharp321if (request.CustomerId == default)322{323 client.Error("Please select a customer.");324 return;325}326```327328#### IQueryable Type with Include329When using `.Include()` followed by `.Where()`, explicitly type as `IQueryable<Entity>` to avoid type mismatch:330```csharp331IQueryable<Order> query = db.Orders.Include(o => o.Customer);332query = query.Where(o => o.StoreName.Contains(filter));333```334335### After Implementation336After writing all files for each entity, summarize what was created.337338## Post-run: Evaluate and Improve339340After completing the task:3413421. **Evaluate**: Did the build succeed? Were there compilation errors, unexpected behavior, or manual corrections needed during this run?3432. **Update learnings**: If anything required correction or was surprising, append a concise entry to `.ivy/learnings/ivy-create-crud.md` (create the file and `.ivy/learnings/` directory if they don't exist). Each entry should note: the date, what went wrong, why, and what to do differently next time.3443. **Skip if clean**: If everything succeeded without issues, do not update the learnings file.