Skip to content

Commit 6970fd2

Browse files
authored
Merge branch 'main' into feature/2420-video-proposed-interface-and-abstraction-tsx
2 parents 983b3e8 + e9d8fab commit 6970fd2

File tree

1,129 files changed

+47292
-10181
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,129 files changed

+47292
-10181
lines changed

.github/Ivy

Lines changed: 0 additions & 1 deletion
This file was deleted.

.github/workflows/frontend-formatting-linting-checks.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ on:
1010
branches: [main, master]
1111
paths:
1212
- "src/frontend/**"
13+
workflow_dispatch:
1314

1415
jobs:
1516
frontend-checks:

.github/workflows/react-doctor.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: React Doctor Checks
2+
permissions:
3+
contents: read
4+
on:
5+
push:
6+
branches: [main, master]
7+
paths:
8+
- "src/frontend/**"
9+
pull_request:
10+
branches: [main, master]
11+
paths:
12+
- "src/frontend/**"
13+
workflow_dispatch:
14+
15+
jobs:
16+
react-doctor:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Run React Doctor
22+
uses: millionco/react-doctor@main
23+
with:
24+
directory: ./src/frontend
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## Review Checklist
2+
3+
- [ ] Run IvyFeatureTester with a positional record type and verify `DataTableBuilder.Remove()` renders remaining columns correctly
4+
- [ ] Test with EF Core / database-backed `IQueryable` to confirm the expression tree translates to valid SQL (the default values for removed params should be excluded from the SELECT)

AGENTS.md

Lines changed: 168 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
All Ivy documentation pages are listed on: <https://docs.ivy.app/sitemap.xml>.
2-
Add ".md" to the end of any URL to go directly to the Markdown version of the doc.
3-
41
# Introduction to the Ivy Framework for LLMs
52

63
- Ivy is a declarative full-stack UI framework that allows developers to build user interfaces using a component-based approach very similar to React.
7-
- In Ivy you only write one application in pure C# and we don't have a BE and FE distinction.
4+
- In Ivy, you only write one application in pure C# and we don't have a BE and FE distinction.
85
- UI rendering is handled by Ivy.
96
- When programming in Ivy you focus on building the logical structure of your application using a large set of pre-built widgets and views - you rarely need to specify any styling - Ivy just makes it look good by default.
107

@@ -17,6 +14,8 @@ Terminology:
1714

1815
A view is defined as a class that inherits from `ViewBase` and implements a `Build` method. The `Build` method returns either another view or a widget.
1916

17+
> WARNING: There is NO `AppBase` class. ALL views and apps inherit from `ViewBase`.
18+
2019
Widgets can have multiple children, but views can only return a single object (widget or view). To return multiple widgets from a view, you can use a `Fragment` use the Layout helpers. See below.
2120

2221
public class MyView : ViewBase
@@ -30,13 +29,43 @@ public class MyView : ViewBase
3029
}
3130
}
3231

33-
The topmost view in an Ivy application is called an [App](https://docs.ivy.app/onboarding/concepts/apps.md) and is decorated with the `[App]` attribute.
32+
The topmost view in an Ivy application is called an [App](https://docs.ivy.app/onboarding/concepts/apps.md) and is decorated with the `[App]` attribute. The attribute uses **named parameters**:
33+
34+
[App(title: "Customers", icon: Icons.Rocket)]
35+
public class CustomersApp : ViewBase
36+
37+
- `title` is optional — if omitted, it is derived from the class name (e.g. `CustomersApp` → "Customers").
38+
- `icon` uses the `Icons` enum — these are Lucide icons in PascalCase (e.g. `Icons.Link`, `Icons.Settings`, `Icons.Rocket`).
39+
40+
An app is built into a tree of widgets. This is what's rendered to the screen.
41+
42+
## Application Structure
43+
44+
A typical Ivy project has this folder structure:
45+
46+
MyProject/
47+
├── Program.cs # Entry point — configures and starts the Ivy server
48+
├── MyProject.csproj # Project file
49+
├── Apps/ # All app classes go here (convention)
50+
│ ├── DashboardApp.cs
51+
│ └── Settings/ # Subfolder namespaces become URL path segments
52+
│ └── UserProfileApp.cs # → /settings/user-profile
53+
└── Connections/
54+
└── MyDb/
55+
├── MyDbContext.cs
56+
├── MyDbContextFactory.cs
57+
├── MyDbConnection.cs
58+
└── Product.cs # Entity classes
59+
60+
### Dependency Injection
61+
62+
Register services in Program.cs, consume them in views with `UseService<T>()`:
3463

35-
[App()]
36-
public class MyApp : ViewBase
64+
// Program.cs
65+
server.Services.AddSingleton<IMyService, MyService>();
3766

38-
- The convention is to put all apps in the `Apps` folder of your Ivy project.
39-
- An app is built into a tree of widgets. This is what's rendered to the screen.
67+
// In a view
68+
var myService = UseService<IMyService>();
4069

4170
## Common Widgets
4271

@@ -47,7 +76,7 @@ public class MyApp : ViewBase
4776
[Progress](https://docs.ivy.app/widgets/common/progress.md)
4877
[Expandable](https://docs.ivy.app/widgets/common/expandable.md)
4978
[Tooltip](https://docs.ivy.app/widgets/common/tooltip.md)
50-
[DropDownMenu](https://docs.ivy.app/widgets/common/drop-down-menu)
79+
[DropDownMenu](https://docs.ivy.app/widgets/common/drop-down-menu.md)
5180
[Table](https://docs.ivy.app/widgets/common/table.md)
5281
[List](https://docs.ivy.app/widgets/common/list.md)
5382
[Details](https://docs.ivy.app/widgets/common/details.md)
@@ -64,7 +93,8 @@ public class MyApp : ViewBase
6493
- Add Children: Pipe child elements using the | operator to arrange them top-to-bottom (vertical) or left-to-right (horizontal).
6594
- Layouts can be customized with methods like .Gap(int number) to set spacing between children. Use .Left(), .Center(), or .Right() methods to control alignment.
6695
- The number in Gap(int number) works the same as in Tailwind CSS spacing scale (e.g., 1 = 0.25rem, 2 = 0.5rem, etc.).
67-
Layouts have a default gap of 4 (1rem). In general, you very rarely need to set the gap explicitly.
96+
- Layouts have a default gap of 4 (1rem). Do NOT add `.Gap(4)` — it is the default and adds unnecessary noise. Only use `.Gap()` when you need a value other than 4.
97+
- `.Padding()` is rarely needed. Layouts and pages already have appropriate padding by default. Only add `.Padding()` when you need extra inner spacing for a specific design reason.
6898

6999
// Basic Vertical Layout
70100
Layout.Vertical()
@@ -84,8 +114,6 @@ Grids:
84114
Layout.Grid()
85115
.Columns(2)
86116
.Rows(2)
87-
.Gap(4)
88-
.Padding(8)
89117
| Text.Block("Cell 1")
90118
| Text.Block("Cell 2")
91119
...
@@ -103,6 +131,7 @@ The Text helper utility is used to create various semantic text elements.
103131
- Text.P: For standard paragraphs.
104132
- Text.Block: For block-level content (e.g., list items).
105133
- Text.InlineCode: For displaying inline code snippets.
134+
- Test.Muted
106135

107136
Styling Modifiers:
108137
.NoWrap():
@@ -117,11 +146,40 @@ Layout.Vertical()
117146

118147
[Text](https://docs.ivy.app/widgets/primitives/text-block.md)
119148

149+
## Colors
150+
151+
Ivy.Colors enum has the following values:
152+
153+
Black, White, Slate, Gray, Zinc, Neutral, Stone, Red, Orange, Amber, Yellow, Lime, Green, Emerald, Teal, Cyan, Sky, Blue, Indigo, Violet, Purple, Fuchsia, Pink, Rose, Primary, Secondary, Destructive, Success, Warning, Info, Muted
154+
155+
## Density
156+
157+
All widgets support `.Density(Density.Small)`, `.Density(Density.Medium)`, `.Density(Density.Large)`.
158+
Convenience methods: `.Small()`, `.Medium()`, `.Large()`.
159+
Density adjusts the overall visual size of a widget (text, padding, etc.).
160+
There is no `ButtonSize` enum — use `Density` for all widgets.
161+
162+
## Size
163+
164+
`.Width(Size.X)` and `.Height(Size.X)` set widget dimensions.
165+
`.Size(Size.X)` sets both width and height.
166+
167+
Common Size values:
168+
169+
- Size.Units(n) — Tailwind spacing scale (n × 0.25rem)
170+
- Size.Full() — 100%
171+
- Size.Fit() — fit-content
172+
- Size.Auto() — auto
173+
- Size.Px(n) — exact pixels
174+
- Size.Fraction(0.5f) — percentage, Size.Half(), Size.Third()
175+
176+
Size is NOT the same as Density. Size controls dimensions; Density controls visual density.
177+
120178
## Event Handling
121179

122180
new Button("Click Me")
123181
.Primary()
124-
.HandleClick(() => {
182+
.OnClick(() => {
125183
count.Set(count.Value + 1);
126184
})
127185

@@ -138,15 +196,19 @@ new TextInput().Default()
138196
### UseState
139197

140198
var nameState = UseState("World");
141-
var iconsState = this.UseState<Icons[]>();
199+
var iconsState = UseState<Icons[]>();
142200

143201
If you don't specify a value, default(T) is used.
144202

145203
UseState hook returns a state object IState<T> that provides:
146204

205+
> WARNING: UseState returns `IState<T>`, NOT `State<T>`. There is no `State<T>` type in Ivy.
206+
147207
- .Value property to read the current state.
148208
- .Set(newValue) method to update the state in UseEffect or in an event handler.
149209

210+
Always use immutable types (e.g. records) with `UseState` — mutable classes that are modified in-place and passed back via `.Set()` will not trigger a re-render because the reference hasn't changed. Instead, create a new instance (e.g. using `with` expressions on records) before calling `.Set()`.
211+
150212
### UseEffect
151213

152214
void UseEffect(Action effect, params IEffectTriggers[] triggers)
@@ -161,15 +223,74 @@ void UseEffect(Func<Task<IDisposable>> asyncEffectWithCleanup, IEffectTriggers o
161223
- IState<T> is automatically converted to EffectTrigger.OnStateChange
162224
- If no triggers are provided, the effect trigger is assumed to be OnMount.
163225

164-
### Other Hooks
226+
### UseQuery
227+
228+
UseQuery is the **preferred pattern for data fetching** in Ivy. It should be favored over the UseEffect + UseState fetch pattern.
229+
230+
var query = UseQuery(
231+
key: "my-data",
232+
fetcher: async (ct) => await LoadDataAsync(ct)
233+
);
234+
235+
if (query.Loading) return Skeleton.Card();
236+
if (query.Error is { } error) return Callout.Error(error.Message);
237+
238+
// Use query.Value
239+
240+
QueryResult<T> properties:
241+
242+
- .Value — the fetched data (default until loaded)
243+
- .Loading — true during initial fetch (no value yet)
244+
- .Validating — true during background revalidation
245+
- .Error — exception if the fetch failed
246+
- .Mutator — provides .Revalidate(), .Invalidate(), .Mutate(value, revalidate)
247+
248+
Key conventions:
249+
250+
- String: `"my-data"`
251+
- Tuple: `(nameof(MyBlade), entityId)`
252+
253+
Common options (QueryOptions):
254+
255+
- KeepPrevious: true — show stale data while revalidating with a new key
256+
- RevalidateOnMount: false — skip initial fetch when using initialValue
257+
- RefreshInterval: TimeSpan — poll at an interval
258+
- Scope: QueryScope.View — isolate cache to the view instance (default is Server)
165259

166-
UseMemo
167-
UseCallback
168-
UseRef
169-
UseContext
170-
UseReducer
171-
UseQuery
172-
UseSignal
260+
Tag-based invalidation (cross-component):
261+
var queryService = UseService<IQueryService>();
262+
queryService.RevalidateByTag(typeof(Product[])); // collection
263+
queryService.RevalidateByTag((typeof(Product), id)); // single entity
264+
265+
Static "hooks" pattern (reusable across views):
266+
public static QueryResult<T[]> UseMyRecords(IViewContext context, string filter)
267+
{
268+
return context.UseQuery(
269+
key: (nameof(UseMyRecords), filter),
270+
fetcher: async ct => { /*fetch*/ },
271+
tags: [typeof(T[])],
272+
options: new QueryOptions { KeepPrevious = true }
273+
);
274+
}
275+
276+
Dependent fetching (wait for another query):
277+
var user = UseQuery(key: "user", fetcher: async ct => await GetUser(ct));
278+
var projects = UseQuery(
279+
() => user.Value?.Id, // null = idle, no fetch
280+
async (userId, ct) => await GetProjects(userId, ct));
281+
282+
[UseRef](https://docs.ivy.app/hooks/core/use-ref.md)
283+
[UseContext](https://docs.ivy.app/hooks/core/use-context.md)
284+
[UseQuery](https://docs.ivy.app/hooks/core/use-query.md)
285+
[UseMutation](https://docs.ivy.app/hooks/core/use-mutation.md)
286+
[UseSignal](https://docs.ivy.app/hooks/core/use-signal.md)
287+
[UseService](https://docs.ivy.app/hooks/core/use-service.md)
288+
[UseArgs](https://docs.ivy.app/hooks/core/use-args.md)
289+
[UseDownload](https://docs.ivy.app/hooks/core/use-download.md)
290+
[UseRefreshToken](https://docs.ivy.app/hooks/core/use-refresh-token.md)
291+
[UseTrigger](https://docs.ivy.app/hooks/core/use-trigger.md)
292+
[UseWebhook](https://docs.ivy.app/hooks/core/use-webhook.md)
293+
[UseAlert](https://docs.ivy.app/onboarding/concepts/alerts.md)
173294

174295
## Inputs
175296

@@ -179,45 +300,41 @@ extension methods on IState<T> to bind state to inputs.
179300
var userNameState = UseState("");
180301
var input = userNameState.ToTextInput().Placeholder("Enter your name");
181302

182-
ToTextInput()
183-
ToTextAreaInput()
184-
ToPasswordInput()
185-
ToNumberInput()
186-
ToBoolInput()
187-
ToSelectInput(IEnumerable<IAnyOption>)
188-
ToCodeInput(Language)
189-
ToColorInput()
190-
ToDateTimeInput()
191-
ToDateRangeInput()
192-
ToFeedbackInput()
193-
194303
Most inputs have extension methods for common configurations:
195304
userNameState.ToTextInput().Required().MaxLength(50).Placeholder("Enter your name");
196305

197-
## Best Practices
198-
199-
(Basically the same as React best practices)
200-
201-
1. **Keep Views Pure** - Views should be pure functions of their props and state
202-
2. **Use Hooks Correctly** - Call hooks at the top level, never in loops or conditions
203-
3. **Minimize State** - Derive computed values instead of storing them
204-
4. **Handle Loading States** - Always consider loading and error states
205-
5. **Leverage Type Safety** - Use strongly-typed widgets and state
206-
6. **Component Composition** - Build complex UIs from simple, reusable views
306+
[TextInput](https://docs.ivy.app/widgets/inputs/text-input.md)
307+
[NumberInput](https://docs.ivy.app/widgets/inputs/number-input.md)
308+
[BoolInput](https://docs.ivy.app/widgets/inputs/bool-input.md)
309+
[SelectInput](https://docs.ivy.app/widgets/inputs/select-input.md)
310+
[AsyncSelectInput](https://docs.ivy.app/widgets/inputs/async-select-input.md)
311+
[DateTimeInput](https://docs.ivy.app/widgets/inputs/date-time-input.md)
312+
[DateRangeInput](https://docs.ivy.app/widgets/inputs/date-range-input.md)
313+
[ColorInput](https://docs.ivy.app/widgets/inputs/color-input.md)
314+
[CodeInput](https://docs.ivy.app/widgets/inputs/code-input.md)
315+
[FeedbackInput](https://docs.ivy.app/widgets/inputs/feedback-input.md)
316+
[FileInput](https://docs.ivy.app/widgets/inputs/file-input.md)
317+
318+
## Common Hallucinations
319+
320+
- Base class is `ViewBase` (NOT `AppBase` there is no `AppBase` class)
321+
- `Text` is a static helper - use `Text.P()`, `Text.H2()`, ...
322+
- `UseState<T>()` returns `IState<T>`, NOT `State<T>`
323+
- All types are in the `Ivy` namespace
324+
- `Colors` is a flat enum (e.g. `Colors.Red`, `Colors.Blue`) we have no shade levels
325+
- `DbContext` must never be injected directly! Always resolve `IDbContextFactory<T>` via `UseService` and create scoped instances with `CreateDbContextAsync()` inside query/mutation lambdas
207326

208327
## Further Reading
209328

210329
[Forms](https://docs.ivy.app/onboarding/concepts/forms.md)
211330
[DataTable](https://docs.ivy.app/widgets/advanced/data-table.md)
212331
[Table](https://docs.ivy.app/widgets/common/table.md)
213332
[Details](https://docs.ivy.app/widgets/common/details.md) - Display structured label-value pairs
214-
[Services](https://docs.ivy.app/onboarding/concepts/services.md)
215333
[Program.cs](https://docs.ivy.app/onboarding/concepts/program.md)
216-
[Colors](https://docs.ivy.app/api-reference/ivy-shared/colors.md)
217334
[Size](https://docs.ivy.app/api-reference/ivy-shared/size.md)
218335
[Align](https://docs.ivy.app/api-reference/ivy-shared/align.md)
219-
[UseAlert](https://docs.ivy.app/onboarding/concepts/alerts.md)
220-
[RefreshTokens](https://docs.ivy.app/onboarding/concepts/refresh-tokens.md)
221-
[Downloads](https://docs.ivy.app/onboarding/concepts/downloads.md)
222-
[Uploads](https://docs.ivy.app/widgets/inputs/file.md)
336+
[Downloads](https://docs.ivy.app/hooks/core/use-download.md)
223337
[Icons](https://raw.githubusercontent.com/Ivy-Interactive/Ivy-Framework/refs/heads/main/src/Ivy/Shared/Icons.cs)
338+
339+
All Ivy documentation pages are listed on: <https://docs.ivy.app/sitemap.xml>.
340+
Add ".md" to the end of any URL to go directly to the Markdown version of the doc.

src/.releases/CreateNotes.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ else {
4747

4848
if (-not $SkipDownloads) {
4949
$downloadScript = Join-Path $scriptDir "DownloadCommits.ps1"
50-
& $downloadScript -Repo https://github.com/Ivy-Interactive/Ivy -OutputFolder $commitsFolder -LastDays 5 -Prefix ivy
51-
& $downloadScript -Repo https://github.com/Ivy-Interactive/Ivy-Framework -OutputFolder $commitsFolder -LastDays 5 -Prefix ivy-framework
50+
& $downloadScript -Repo https://github.com/Ivy-Interactive/Ivy -OutputFolder $commitsFolder -LastDays 12 -Prefix ivy
51+
& $downloadScript -Repo https://github.com/Ivy-Interactive/Ivy-Framework -OutputFolder $commitsFolder -LastDays 12 -Prefix ivy-framework
5252
}
5353

5454
$promptFile = Join-Path $scriptDir "prompt.md"

src/.releases/Refactors/1.2.11/optional/UseQuery.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ if (productQuery.Loading || productQuery.Value == null)
177177
return productQuery.Value
178178
.ToForm()
179179
.Remove(e => e.Id, e => e.CreatedAt, e => e.UpdatedAt)
180-
.HandleSubmit(OnSubmit)
180+
.OnSubmit(OnSubmit)
181181
.ToSheet(isOpen, "Edit Product");
182182

183183
async Task OnSubmit(Product? request)
@@ -238,7 +238,7 @@ public class ProductCreateDialog(IState<bool> isOpen, RefreshToken refreshToken)
238238
return product
239239
.ToForm()
240240
.Builder(e => e.CategoryId, e => e.ToAsyncSelectInput(...))
241-
.HandleSubmit(OnSubmit)
241+
.OnSubmit(OnSubmit)
242242
.ToDialog(isOpen, title: "Create Product", submitTitle: "Create");
243243

244244
async Task OnSubmit(ProductCreateRequest request)

0 commit comments

Comments
 (0)