@zerotal/arch 1.7.3 → 1.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/README.md +4 -2
  3. package/api-surface.md +4 -4
  4. package/docs/about.md +16 -16
  5. package/docs/admin/actions.md +9 -9
  6. package/docs/admin/auth.md +2 -2
  7. package/docs/admin/dashboard.md +5 -5
  8. package/docs/admin/extending-ui.md +12 -12
  9. package/docs/admin/extending.md +16 -7
  10. package/docs/admin/forms.md +4 -4
  11. package/docs/admin/index.md +2 -2
  12. package/docs/admin/operations.md +9 -9
  13. package/docs/admin/resources.md +3 -3
  14. package/docs/admin/structure.md +6 -6
  15. package/docs/admin/tables.md +8 -8
  16. package/docs/admin/testing.md +4 -4
  17. package/docs/ai.md +118 -11
  18. package/docs/application.md +12 -12
  19. package/docs/arch.md +25 -3
  20. package/docs/assets.md +11 -11
  21. package/docs/audit.md +13 -13
  22. package/docs/authentication.md +39 -39
  23. package/docs/authorization.md +13 -13
  24. package/docs/broadcasting/channels.md +2 -2
  25. package/docs/broadcasting/client.md +2 -2
  26. package/docs/broadcasting/events.md +9 -10
  27. package/docs/broadcasting/index.md +1 -1
  28. package/docs/broadcasting/testing.md +5 -5
  29. package/docs/cache.md +15 -13
  30. package/docs/carbon.md +35 -35
  31. package/docs/changelog.md +165 -1
  32. package/docs/client/index.md +277 -70
  33. package/docs/commands.md +4 -4
  34. package/docs/components.md +116 -115
  35. package/docs/config-system.md +4 -4
  36. package/docs/container.md +27 -27
  37. package/docs/context.md +27 -27
  38. package/docs/contributing.md +46 -0
  39. package/docs/controllers.md +10 -10
  40. package/docs/conventions.md +23 -15
  41. package/docs/cookies.md +3 -3
  42. package/docs/csrf.md +6 -6
  43. package/docs/database.md +22 -17
  44. package/docs/deployment.md +2 -2
  45. package/docs/devtools.md +16 -16
  46. package/docs/email-verification.md +8 -8
  47. package/docs/encryption.md +7 -7
  48. package/docs/errors.md +6 -6
  49. package/docs/events.md +12 -11
  50. package/docs/flow/components.md +32 -32
  51. package/docs/flow/decorators.md +18 -18
  52. package/docs/flow/events.md +16 -16
  53. package/docs/flow/forms.md +17 -17
  54. package/docs/flow/icons.md +199 -0
  55. package/docs/flow/index.md +20 -20
  56. package/docs/flow/layouts.md +35 -35
  57. package/docs/flow/lifecycle.md +16 -16
  58. package/docs/flow/models.md +9 -9
  59. package/docs/flow/pagination.md +8 -8
  60. package/docs/flow/performance.md +7 -7
  61. package/docs/flow/references.md +3 -3
  62. package/docs/flow/routing.md +26 -26
  63. package/docs/flow/testing.md +20 -20
  64. package/docs/getting-started.md +18 -13
  65. package/docs/health.md +17 -8
  66. package/docs/helpers.md +17 -17
  67. package/docs/i18n.md +10 -10
  68. package/docs/inertia/devtools.md +4 -4
  69. package/docs/inertia/index.md +12 -7
  70. package/docs/inertia/props.md +22 -22
  71. package/docs/inertia/rendering.md +86 -7
  72. package/docs/inertia/ssr.md +2 -2
  73. package/docs/lifecycle.md +2 -2
  74. package/docs/lock.md +11 -11
  75. package/docs/logger.md +12 -12
  76. package/docs/media.md +22 -22
  77. package/docs/middleware.md +17 -17
  78. package/docs/migrations.md +13 -13
  79. package/docs/monitor.md +6 -6
  80. package/docs/notifications.md +24 -24
  81. package/docs/orm/casts.md +14 -14
  82. package/docs/orm/factories.md +8 -8
  83. package/docs/orm/index.md +22 -20
  84. package/docs/orm/lifecycle.md +13 -13
  85. package/docs/orm/queries.md +28 -28
  86. package/docs/orm/relationships.md +16 -16
  87. package/docs/orm/serialization.md +11 -11
  88. package/docs/package-development.md +10 -10
  89. package/docs/pagination.md +14 -14
  90. package/docs/password-reset.md +6 -6
  91. package/docs/providers.md +11 -11
  92. package/docs/query-builder.md +18 -18
  93. package/docs/queue.md +14 -12
  94. package/docs/rate-limiting.md +6 -6
  95. package/docs/responses.md +14 -14
  96. package/docs/roles-and-2fa.md +9 -9
  97. package/docs/routing.md +44 -44
  98. package/docs/scaffolding.md +1 -1
  99. package/docs/scheduler.md +10 -10
  100. package/docs/seeding.md +6 -6
  101. package/docs/session.md +16 -14
  102. package/docs/social.md +14 -14
  103. package/docs/storage.md +25 -13
  104. package/docs/structure.md +2 -2
  105. package/docs/support-policy.md +36 -8
  106. package/docs/telemetry.md +12 -12
  107. package/docs/tenancy.md +34 -40
  108. package/docs/testing/browser.md +6 -6
  109. package/docs/testing/console.md +3 -3
  110. package/docs/testing/database.md +10 -10
  111. package/docs/testing/flow-browser.md +6 -6
  112. package/docs/testing/http.md +12 -12
  113. package/docs/testing/index.md +3 -3
  114. package/docs/testing/mocking.md +8 -8
  115. package/docs/upgrade.md +1 -1
  116. package/docs/validator.md +19 -19
  117. package/docs/view.md +14 -14
  118. package/package.json +4 -4
  119. package/src/index.ts +25 -1
  120. package/src/tools/searchDocs.ts +7 -0
  121. package/docs/client/auth.md +0 -113
  122. package/docs/client/errors.md +0 -139
  123. package/docs/client/files.md +0 -118
  124. package/docs/client/references.md +0 -58
  125. package/docs/client/requests.md +0 -131
  126. package/docs/client/resilience.md +0 -141
  127. package/docs/client/testing.md +0 -146
package/docs/errors.md CHANGED
@@ -123,7 +123,7 @@ export class QuotaExceededError extends HttpError {
123
123
  }
124
124
  ```
125
125
 
126
- ```typescript
126
+ ```typescript fragment
127
127
  // in a controller
128
128
  throw new PaymentFailedError(charge.id);
129
129
  ```
@@ -135,7 +135,7 @@ then register it in `bootstrap/app.ts` with `app.withExceptionHandler(Handler)`.
135
135
  The framework calls `report()` first, then `render()`, for every unhandled
136
136
  exception:
137
137
 
138
- ```typescript
138
+ ```typescript fragment
139
139
  // app/exceptions/Handler.ts
140
140
  import { ExceptionHandler, NotFoundError, ForbiddenError } from "zerotal";
141
141
  import type { HttpContext } from "zerotal";
@@ -169,7 +169,7 @@ export class Handler extends ExceptionHandler {
169
169
  }
170
170
  ```
171
171
 
172
- ```typescript
172
+ ```typescript fragment
173
173
  // bootstrap/app.ts
174
174
  import { Application } from "zerotal";
175
175
  import providers from "./providers.ts";
@@ -193,7 +193,7 @@ reach `console.error`.
193
193
  To silence your own classes, list them on the `dontReport` array instead of
194
194
  filtering by hand:
195
195
 
196
- ```typescript
196
+ ```typescript fragment
197
197
  // app/exceptions/Handler.ts
198
198
  import { ExceptionHandler, ForbiddenError } from "zerotal";
199
199
  import { PaymentFailedError } from "./PaymentFailedError.ts";
@@ -227,7 +227,7 @@ The validation system throws automatically — you don't catch anything. It thro
227
227
  `ValidationJsonError` (a 422 JSON body for API clients), and both flow straight
228
228
  through the handler:
229
229
 
230
- ```typescript
230
+ ```typescript fragment
231
231
  // in a controller
232
232
  // Browser → 303 redirect back with flashed errors
233
233
  // API → 422 JSON: { message: 'Validation failed', errors: {...} }
@@ -241,7 +241,7 @@ See the [Validator](/docs/validator) guide for details.
241
241
  Every `ZerotalError` carries a machine-readable `code` string alongside the HTTP
242
242
  `status`. Use it on the client to distinguish errors that share a status:
243
243
 
244
- ```typescript
244
+ ```typescript fragment
245
245
  // the API client receives:
246
246
  // { "message": "Payment failed for charge ch_xxx", "code": "E_PAYMENT_FAILED" }
247
247
 
package/docs/events.md CHANGED
@@ -36,7 +36,7 @@ never use framework events to drive business logic.
36
36
  If you want the full side-by-side, here it is — but you can skip it and come back
37
37
  once you've read the section for the one you need:
38
38
 
39
- | | Framework events | Application events |
39
+ | Aspect | Framework events | Application events |
40
40
  | ------------- | --------------------------------- | --------------------------- |
41
41
  | Purpose | Observing the framework | Reacting to your domain |
42
42
  | Who emits | The framework | Your code |
@@ -53,8 +53,9 @@ talks through the bus instead.
53
53
 
54
54
  ### API
55
55
 
56
- ```typescript
57
- import { FrameworkEvents, QueryExecuted } from "zerotal";
56
+ ```typescript fragment
57
+ import { FrameworkEvents } from "zerotal";
58
+ import { QueryExecuted } from "@zerotal/orm";
58
59
 
59
60
  // Subscribe — returns an unsubscribe function.
60
61
  const off = FrameworkEvents.on(QueryExecuted, (event) => {
@@ -87,7 +88,7 @@ dispatch a queue job) from outside the handler.
87
88
  Subscribe in `onBooted`, keep the unsubscribe functions, and release them in
88
89
  `onStopping` so handlers never leak between boots or test suites:
89
90
 
90
- ```typescript
91
+ ```typescript fragment
91
92
  import { ServiceProvider, FrameworkEvents, RequestFailed } from "zerotal";
92
93
 
93
94
  export class MetricsProvider extends ServiceProvider {
@@ -185,7 +186,7 @@ the emitting packages knowing you exist.
185
186
  Some events carry `ctx`; for those that do not (mail/cache/queue happen outside the
186
187
  HTTP frame of reference), resolve the current request lazily:
187
188
 
188
- ```typescript
189
+ ```typescript fragment
189
190
  import { FrameworkEvents, RequestContext } from "zerotal";
190
191
  import { CacheQueried } from "@zerotal/cache";
191
192
 
@@ -204,7 +205,7 @@ app should react to. Events are plain classes; listeners are classes with a
204
205
 
205
206
  ### Define an event and a listener
206
207
 
207
- ```typescript
208
+ ```typescript fragment
208
209
  // app/events/UserRegistered.ts
209
210
  export class UserRegistered {
210
211
  constructor(
@@ -227,7 +228,7 @@ export class SendWelcomeEmail {
227
228
 
228
229
  ### Register and emit
229
230
 
230
- ```typescript
231
+ ```typescript fragment
231
232
  // usually a provider's onBooted(), or bootstrap code
232
233
  import { Events } from "zerotal";
233
234
  import { UserRegistered } from "./app/events/UserRegistered.ts";
@@ -266,7 +267,7 @@ You usually don't call `Events.on(...)` yourself. A listener placed in `app/list
266
267
  declares the event(s) it handles via `static listens`, and the framework binds it on the
267
268
  emitter at boot — no registration code required.
268
269
 
269
- ```typescript
270
+ ```typescript fragment
270
271
  // app/listeners/SendWelcomeEmail.ts
271
272
  import { UserRegistered } from "../events/UserRegistered.ts";
272
273
 
@@ -284,7 +285,7 @@ export class SendWelcomeEmail {
284
285
  `static listens` accepts an array — the listener binds to each event and `handle()`
285
286
  receives whichever one fired:
286
287
 
287
- ```typescript
288
+ ```typescript fragment
288
289
  // app/listeners/GrantWelcomeBonus.ts
289
290
  import { UserRegistered } from "../events/UserRegistered.ts";
290
291
  import { UserReactivated } from "../events/UserReactivated.ts";
@@ -309,7 +310,7 @@ job, so `emit()` returns without waiting for the work to finish. Set `queue` to
309
310
  the default queue, or to a queue name. Two optional fields tune the retry behaviour of the
310
311
  dispatched job:
311
312
 
312
- ```typescript
313
+ ```typescript fragment
313
314
  // app/listeners/SendWelcomeEmail.ts
314
315
  import { UserRegistered } from "../events/UserRegistered.ts";
315
316
 
@@ -360,7 +361,7 @@ authorization, and the client side.
360
361
 
361
362
  Both buses expose a reset so one test never leaks subscriptions into the next:
362
363
 
363
- ```typescript
364
+ ```typescript fragment
364
365
  import { Events, FrameworkEvents } from "zerotal";
365
366
 
366
367
  // Remove every application listener
@@ -7,7 +7,7 @@ description: The component library that ships with Flow — forms, overlays, tab
7
7
 
8
8
  Flow ships a library of ready-made native components and unstyled headless primitives. All are imported from `@zerotal/flow`.
9
9
 
10
- > **Tip** — See them live: the example app's component gallery at `/pulse/components` (`apps/example/app/flow/ComponentsPage.tsx`) shows every component, and `/pulse/users` (`UsersPage.tsx`) is a realistic admin screen composing them together.
10
+ > **Tip** — See them live: [the component gallery](/showcase/flow/components) shows modals, drawers, tabs and menus driven by a real server, and [the flow-ui kit](/showcase/flow/ui-kit) walks the styled set. The [full catalogue](/docs/components) documents every component with props.
11
11
 
12
12
  ## Navigation
13
13
 
@@ -54,7 +54,7 @@ cached either way.
54
54
 
55
55
  `data-current` matches by **prefix** — a link to `/posts` stays active on `/posts/42` — which is what you want for a section parent. For an index link that should be active only on its own exact URL (an "Overview" tab that shouldn't light up on the section's sub-pages), add `exact`:
56
56
 
57
- ```tsx
57
+ ```tsx fragment
58
58
  <Link href="/dashboard" exact class="data-[current]:font-bold">
59
59
  Overview
60
60
  </Link>
@@ -72,7 +72,7 @@ Some links aren't really going anywhere, though: a sort header, a filter chip, a
72
72
  tab strip partway down a long page. Jumping to the top for those loses the
73
73
  control the user was just looking at. `preserveScroll` leaves the viewport alone:
74
74
 
75
- ```tsx
75
+ ```tsx fragment
76
76
  <Link href={this.currentUrl({ query: { sort: "title" } })} preserveScroll>
77
77
  Title
78
78
  </Link>
@@ -101,7 +101,7 @@ import { Head } from "@zerotal/flow";
101
101
 
102
102
  Shorthand for `<Head><title>…</title></Head>`. Supports interpolation:
103
103
 
104
- ```tsx
104
+ ```tsx fragment
105
105
  import { Title } from "@zerotal/flow";
106
106
 
107
107
  <Title>{`${this.unreadCount} unread — Inbox`}</Title>;
@@ -113,7 +113,7 @@ import { Title } from "@zerotal/flow";
113
113
 
114
114
  Preserve an element — and its live state (a playing `<audio>`, an embedded widget) — across `navigate` page visits. Give it a stable `name`; on navigation the bridge re-uses the existing DOM node instead of replacing it. Best placed in your layout:
115
115
 
116
- ```tsx
116
+ ```tsx fragment
117
117
  import { Persist } from "@zerotal/flow";
118
118
 
119
119
  {
@@ -132,7 +132,7 @@ Elements inside `<Persist>` also survive server-patch morphs (they carry `flow:i
132
132
 
133
133
  A dialog that bundles reactive visibility, a backdrop, a panel, a transition, a close button, and Escape-to-close — all wired to one boolean prop. Clicking the backdrop, the × button, or pressing Escape sets that prop back to `false`:
134
134
 
135
- ```tsx
135
+ ```tsx fragment
136
136
  import { Modal } from "@zerotal/flow";
137
137
 
138
138
  export class ContactsPage extends Component {
@@ -175,7 +175,7 @@ export class ContactsPage extends Component {
175
175
 
176
176
  A slide-over panel — the edge-anchored sibling of `<Modal>`. Same binding and close model (backdrop, × and Escape all close it client-side with no round-trip; focus-trapped), but slides in from an edge:
177
177
 
178
- ```tsx
178
+ ```tsx fragment
179
179
  import { Drawer } from "@zerotal/flow";
180
180
 
181
181
  <button onClick={() => (this.cartOpen = true)}>Cart ({this.cartCount})</button>
@@ -219,7 +219,7 @@ Levels `success` / `error` / `warning` / `info` map to distinct colors. Click a
219
219
 
220
220
  A dismissible inline alert. `variant` sets the palette and ARIA role (error/warning announce assertively). Dismissal is client-only — no round-trip:
221
221
 
222
- ```tsx
222
+ ```tsx fragment
223
223
  import { Alert } from "@zerotal/flow";
224
224
 
225
225
  <Alert variant="success" dismissible>
@@ -237,7 +237,7 @@ import { Alert } from "@zerotal/flow";
237
237
 
238
238
  Shows its children only while a server action is in flight — and, so a fast action never flashes a spinner, loading **indicators wait out a short delay** (~200ms) by default: an action that finishes inside that window shows nothing at all. This applies to the whole loading family — `<Loading>`, `showOnLoading`, `hideOnLoading`, and `loadingClass`. Only `loadingAttr` (e.g. `loadingAttr="disabled"`) is applied immediately, so a submit button still guards against a double-click even on a sub-100ms action. The `delay` prop is now the default behaviour and kept only for clarity/back-compat:
239
239
 
240
- ```tsx
240
+ ```tsx fragment
241
241
  import { Loading } from "@zerotal/flow";
242
242
 
243
243
  <button onClick={this.save}>Save</button>
@@ -262,7 +262,7 @@ import { Loading } from "@zerotal/flow";
262
262
 
263
263
  Renders the component's entire validation error bag as a list. Hidden when there are none:
264
264
 
265
- ```tsx
265
+ ```tsx fragment
266
266
  import { Errors } from "@zerotal/flow";
267
267
 
268
268
  <Errors /> {/* every current error */}
@@ -273,7 +273,7 @@ import { Errors } from "@zerotal/flow";
273
273
 
274
274
  A single field's first error message as a self-hiding `<span>`. Equivalent to `<span error={this.errors.field} />`:
275
275
 
276
- ```tsx
276
+ ```tsx fragment
277
277
  import { ErrorMessage } from "@zerotal/flow";
278
278
 
279
279
  <input value={this.form.email} />
@@ -286,7 +286,7 @@ Contains a failure in a nested component so it costs that component rather than
286
286
  one, a child that throws while mounting or rendering takes the whole response with it — one broken
287
287
  widget blanks the dashboard.
288
288
 
289
- ```tsx
289
+ ```tsx fragment
290
290
  import { ErrorBoundary } from "@zerotal/flow";
291
291
 
292
292
  <ErrorBoundary fallback={<p class="text-sm text-red-600">Sales data unavailable.</p>}>
@@ -297,7 +297,7 @@ import { ErrorBoundary } from "@zerotal/flow";
297
297
  `fallback` may be a function, which receives the thrown error. `onError` reports it (for logging or
298
298
  an error tracker) without changing what renders:
299
299
 
300
- ```tsx
300
+ ```tsx fragment
301
301
  <ErrorBoundary fallback={(e) => <p>{(e as Error).message}</p>} onError={(e) => Log.error(e)}>
302
302
  <RiskyWidget />
303
303
  </ErrorBoundary>
@@ -317,7 +317,7 @@ affect the other.
317
317
 
318
318
  A data table with URL-driven sortable headers. Clicking a sortable header navigates to `?sortBy=key&sortDir=asc|desc`. Pair with `@url sortBy`/`@url sortDir` and sort the rows server-side in `render()`:
319
319
 
320
- ```tsx
320
+ ```tsx fragment
321
321
  import { Table, Pager } from "@zerotal/flow";
322
322
 
323
323
  export class UsersPage extends Component.using(Pagination) {
@@ -362,7 +362,7 @@ export class UsersPage extends Component.using(Pagination) {
362
362
 
363
363
  Renders a Prev / numbered / Next pager from either paginator — `Model.paginate()` or the in-memory `paginate()` helper. Links are `navigate` anchors to `?page=N`, so they pair with `@url page` automatically. (The `Pagination` export is the page-state mixin; `<Pager>` is the links UI.)
364
364
 
365
- ```tsx
365
+ ```tsx fragment
366
366
  import { Pager } from "@zerotal/flow";
367
367
 
368
368
  const users = await User.paginate(this.perPage);
@@ -375,7 +375,7 @@ const users = await User.paginate(this.perPage);
375
375
 
376
376
  A sentinel element that calls a server action when it scrolls into view. Pass `show` to stop loading when there are no more items:
377
377
 
378
- ```tsx
378
+ ```tsx fragment
379
379
  import { InfiniteScroll } from "@zerotal/flow";
380
380
 
381
381
  @expose async loadMore(): Promise<void> {
@@ -396,7 +396,7 @@ elements; spacers above and below hold the scrollbar at the size the full collec
396
396
  As the viewport moves, `onWindow` is called with `(start, count)` and your action loads that slice.
397
397
  The collection never has to reach the client in full.
398
398
 
399
- ```tsx
399
+ ```tsx fragment
400
400
  import { Virtualize } from "@zerotal/flow";
401
401
 
402
402
  @expose rows: Row[] = [];
@@ -440,7 +440,7 @@ fetch latency.
440
440
 
441
441
  A click-to-open menu, entirely client-side (no round-trip). Fully keyboard-navigable: Down/Up/Enter opens (focusing first/last item), arrow keys + Home/End move between items, Escape closes and returns focus to the trigger, click-outside dismisses:
442
442
 
443
- ```tsx
443
+ ```tsx fragment
444
444
  import { Dropdown } from "@zerotal/flow";
445
445
 
446
446
  <Dropdown label="Options" align="right">
@@ -457,7 +457,7 @@ import { Dropdown } from "@zerotal/flow";
457
457
 
458
458
  Client-side tabbed panels. Pass `items`, each with a `label` and the `content` to show when selected:
459
459
 
460
- ```tsx
460
+ ```tsx fragment
461
461
  import { Tabs } from "@zerotal/flow";
462
462
 
463
463
  <Tabs
@@ -485,7 +485,7 @@ import { Tabs } from "@zerotal/flow";
485
485
 
486
486
  A dropzone bound to an `@expose` property. Choosing a file POSTs the bytes to `/__flow/upload` over HTTP, shows live upload progress, and resolves to a signed `TemporaryUploadedFile` reference. Compose the `FileUploads` mixin for the `removeUpload` action:
487
487
 
488
- ```tsx
488
+ ```tsx fragment
489
489
  import { Component, expose, FileUpload, FileUploads, TemporaryUploadedFile } from "@zerotal/flow";
490
490
 
491
491
  export class AvatarPage extends Component.using(FileUploads) {
@@ -525,7 +525,7 @@ export class AvatarPage extends Component.using(FileUploads) {
525
525
 
526
526
  For multiple files, use `multiple`:
527
527
 
528
- ```tsx
528
+ ```tsx fragment
529
529
  @expose photos: TemporaryUploadedFile[] = [];
530
530
 
531
531
  <FileUpload bind={this.photos} multiple accept="image/*" />
@@ -538,7 +538,7 @@ For multiple files, use `multiple`:
538
538
 
539
539
  Common Alpine plugins are bundled and exposed as props:
540
540
 
541
- ```tsx
541
+ ```tsx fragment
542
542
  {/* Input masking */}
543
543
  <input mask="(999) 999-9999" value={this.phone} live />
544
544
  <input mask="9999 9999 9999 9999" value={this.cardNumber} live />
@@ -576,7 +576,7 @@ Unstyled, fully accessible interactive primitives that expose state through `dat
576
576
 
577
577
  An accessible on/off toggle (`role="switch"`, keyboard-operable). Style the on-state with `data-[checked]:…`:
578
578
 
579
- ```tsx
579
+ ```tsx fragment
580
580
  import { Switch } from "@zerotal/flow";
581
581
 
582
582
  <Switch
@@ -593,7 +593,7 @@ The Switch button is a Tailwind `group`, so the inner knob reacts with `group-da
593
593
 
594
594
  An accessible checkbox (`role="checkbox"`) bound to a boolean. Style the checked state with `data-[checked]:…`:
595
595
 
596
- ```tsx
596
+ ```tsx fragment
597
597
  import { Checkbox } from "@zerotal/flow";
598
598
 
599
599
  <Checkbox bind={this.agree} class="h-5 w-5 rounded border border-gray-700 data-[checked]:bg-indigo-600 data-[checked]:border-indigo-600">
@@ -608,7 +608,7 @@ import { Checkbox } from "@zerotal/flow";
608
608
 
609
609
  A styled native `<select>`. Fully accessible out of the box. Use `<Listbox>` only when you need custom option markup:
610
610
 
611
- ```tsx
611
+ ```tsx fragment
612
612
  import { Select } from "@zerotal/flow";
613
613
 
614
614
  <Select
@@ -627,7 +627,7 @@ import { Select } from "@zerotal/flow";
627
627
 
628
628
  `role="radiogroup"` with arrow-key roving. Style the selected option with `data-[checked]:…`:
629
629
 
630
- ```tsx
630
+ ```tsx fragment
631
631
  import { RadioGroup } from "@zerotal/flow";
632
632
 
633
633
  <RadioGroup
@@ -645,7 +645,7 @@ import { RadioGroup } from "@zerotal/flow";
645
645
 
646
646
  A fully keyboard-navigable custom select. Arrow keys, Home/End, Enter/Escape, `aria-activedescendant`. `multiple` makes the value an array:
647
647
 
648
- ```tsx
648
+ ```tsx fragment
649
649
  import { Listbox } from "@zerotal/flow";
650
650
 
651
651
  <Listbox
@@ -662,7 +662,7 @@ States: `data-[selected]`, `data-[active]`, `data-[open]`.
662
662
 
663
663
  An autocomplete input + filtered dropdown list. Two modes:
664
664
 
665
- ```tsx
665
+ ```tsx fragment
666
666
  import { Combobox } from "@zerotal/flow";
667
667
 
668
668
  {
@@ -744,7 +744,7 @@ import { Popover } from "@zerotal/flow";
744
744
 
745
745
  Accessibility glue around a single control. `<Field>` wires `for` / `id` / `aria-describedby` between the label, the control, the description, and the error — keeping screen-reader semantics correct without hand-wiring IDs:
746
746
 
747
- ```tsx
747
+ ```tsx fragment
748
748
  import { Field } from "@zerotal/flow";
749
749
 
750
750
  <Field label="Email" description="We'll never share it." error={this.errors.email}>
@@ -758,7 +758,7 @@ All three props (`label`, `description`, `error`) are optional. You can also com
758
758
 
759
759
  Group related fields. A native `<fieldset disabled>` cascades the disabled state to every control inside:
760
760
 
761
- ```tsx
761
+ ```tsx fragment
762
762
  import { Fieldset } from "@zerotal/flow";
763
763
 
764
764
  <Fieldset legend="Billing address" disabled={this.saving}>
@@ -775,7 +775,7 @@ import { Fieldset } from "@zerotal/flow";
775
775
 
776
776
  Shows `content` on hover/focus of its children, wired with `aria-describedby` and `role="tooltip"`. Client-only:
777
777
 
778
- ```tsx
778
+ ```tsx fragment
779
779
  import { Tooltip } from "@zerotal/flow";
780
780
 
781
781
  <Tooltip content="Copy link to clipboard" placement="top">
@@ -825,7 +825,7 @@ ZT_FLOW_CSP_SAFE=true
825
825
 
826
826
  When on, the client runtime swaps Alpine's evaluator for an eval-free interpreter, the bridge avoids `new Function`, and the AOT compiler emits only CSP-safe expressions:
827
827
 
828
- ```tsx
828
+ ```tsx fragment
829
829
  <button onClick={() => this.count++}>+</button>
830
830
  // standard mode → flow:click="() => $flow.count++"
831
831
  // CSP mode → flow:click="$flow.count++"
@@ -16,7 +16,7 @@ Marks a property or method as part of the public contract with the browser.
16
16
  - **On a property** — the value is included in the signed snapshot and synced to the client on every patch. The client can update it via a bound `value`/`checked` attribute or a client expression like `onClick={() => this.count++}`.
17
17
  - **On a method** — makes it callable from the browser over the WebSocket via event bindings like `onClick={this.save}`.
18
18
 
19
- ```typescript
19
+ ```typescript fragment
20
20
  import { expose } from "@zerotal/flow";
21
21
 
22
22
  export class CounterPage extends Component {
@@ -48,7 +48,7 @@ A `value={this.x}` binding on a `@locked` property renders as a read-only displa
48
48
 
49
49
  Use `@locked` for anything the server owns: a record the route resolved, results loaded in `onMount()`, computed totals, child props from the parent:
50
50
 
51
- ```tsx
51
+ ```tsx fragment
52
52
  export class PostsPage extends Component {
53
53
  @locked user!: User; // /users/:user — the record, already found
54
54
  @locked posts: Post[] = [];
@@ -87,7 +87,7 @@ with no arguments, and used for [real-time validation](#real-time-validation) on
87
87
 
88
88
  Combine with `@expose` for a property that is both two-way bound and validated:
89
89
 
90
- ```typescript
90
+ ```typescript fragment
91
91
  @expose @validate((rule) => rule.required().email()) email: string = "";
92
92
  @expose @validate((rule) => rule.required().min(8)) password: string = "";
93
93
  @expose @validate((rule) => rule.required().min(2).max(50)) name: string = "";
@@ -102,7 +102,7 @@ Combine with `@expose` for a property that is both two-way bound and validated:
102
102
 
103
103
  The validation runs when you call `this.validate()` inside an action:
104
104
 
105
- ```typescript
105
+ ```typescript fragment
106
106
  @expose async register(): Promise<void> {
107
107
  await this.validate(); // reads all @validate rules on the class
108
108
 
@@ -120,7 +120,7 @@ The validation runs when you call `this.validate()` inside an action:
120
120
 
121
121
  You can also pass rules directly to `this.validate()` — they override the decorator-based rules:
122
122
 
123
- ```typescript
123
+ ```typescript fragment
124
124
  @expose async update(): Promise<void> {
125
125
  await this.validate({
126
126
  email: (rule) => rule.required().email(),
@@ -142,7 +142,7 @@ as the user edits, with no action call and without affecting any other field.
142
142
  > form is not writable in TSX: the `.` in an attribute name is a parse error (`TS1003`), so
143
143
  > copying it out of the emitted HTML into a component will not build.
144
144
 
145
- ```tsx
145
+ ```tsx fragment
146
146
  @expose @validate((rule) => rule.required().email()) email = "";
147
147
 
148
148
  async render() {
@@ -165,7 +165,7 @@ See [Forms & Validation](/docs/flow/forms) for the full validation rules referen
165
165
 
166
166
  Syncs a property to the browser URL query string. Initialised from the query string on the initial page load; updated in the URL on every patch. The URL becomes shareable and bookmark-friendly automatically:
167
167
 
168
- ```typescript
168
+ ```typescript fragment
169
169
  // Page and search stay in the URL: /posts?page=2&search=TypeScript
170
170
  @url page: number = 1;
171
171
  @url search: string = "";
@@ -189,14 +189,14 @@ Binds a property to the HTTP session, so the value survives a browser refresh. R
189
189
 
190
190
  The field reads and writes the session key of the same name — the same value a controller or another component sees:
191
191
 
192
- ```typescript
192
+ ```typescript fragment
193
193
  @session userId: string = ""; // the session's `userId`
194
194
  @session preferredTheme: string = "light";
195
195
  ```
196
196
 
197
197
  Pass options to change the key, or to keep it to this component:
198
198
 
199
- ```typescript
199
+ ```typescript fragment
200
200
  // Read a differently-named key
201
201
  @session({ key: "s" }) whatever: number = 0;
202
202
 
@@ -216,7 +216,7 @@ A `@session` field is **not** in the WebSocket snapshot: it is read from and wri
216
216
 
217
217
  A getter derived from other state. Not stored in the snapshot — recomputed on every render pass. The result is **memoized for the duration of a single render**, so an expensive getter read multiple times in the same template runs only once:
218
218
 
219
- ```typescript
219
+ ```typescript fragment
220
220
  @computed get fullName(): string {
221
221
  return `${this.firstName} ${this.lastName}`;
222
222
  }
@@ -241,7 +241,7 @@ Use them freely in a template as a text child — `{this.fullName}` — where th
241
241
 
242
242
  Excludes a property from the snapshot entirely. Reset to its class-level default on every WebSocket round-trip. Use for ephemeral UI state that shouldn't persist between server calls:
243
243
 
244
- ```typescript
244
+ ```typescript fragment
245
245
  @transient isUploading: boolean = false;
246
246
  @transient dropzoneActive: boolean = false;
247
247
  @transient tempMessage: string = "";
@@ -254,7 +254,7 @@ The pattern: start an upload, set `this.isUploading = true`, send a response. On
254
254
 
255
255
  An `@expose`d method that runs on the server but **skips the re-render cycle**. Use for side-effects that don't change the UI: file downloads, external API calls, jobs that just need to fire:
256
256
 
257
- ```typescript
257
+ ```typescript fragment
258
258
  @expose @renderless async exportCsv(): Promise<void> {
259
259
  const rows = await Report.all();
260
260
  const csv = rows.map((r) => `${r.id},${r.name},${r.email}`).join("\n");
@@ -282,7 +282,7 @@ Because the render cycle is skipped, `@renderless` actions are faster and cheape
282
282
 
283
283
  Registers a method as a listener for cross-component events dispatched via `this.dispatch()`. The method is implicitly exposed — no separate `@expose` needed:
284
284
 
285
- ```typescript
285
+ ```typescript fragment
286
286
  @on("post-created")
287
287
  async handlePostCreated(data: { id: number; title: string }): Promise<void> {
288
288
  this.posts = await Post.query().orderBy("created_at", "desc").limit(10).get();
@@ -319,7 +319,7 @@ For child components — marks a prop the parent can re-push whenever its value
319
319
 
320
320
  Declare the reactive prop on the child:
321
321
 
322
- ```tsx
322
+ ```tsx fragment
323
323
  export class PriceTag extends Component {
324
324
  @reactive currency = "USD";
325
325
  @reactive amount = 0;
@@ -339,7 +339,7 @@ export class PriceTag extends Component {
339
339
 
340
340
  Pass the prop from the parent:
341
341
 
342
- ```tsx
342
+ ```tsx fragment
343
343
  // When this.currency changes, PriceTag re-renders automatically:
344
344
  <PriceTag currency={this.currency} amount={this.subtotal} />
345
345
  ```
@@ -350,7 +350,7 @@ Unlike `@locked`, `@reactive` props are **live** — the parent keeps them curre
350
350
 
351
351
  A reactive prop that **also syncs back to the parent** (two-way). The parent property and the child prop stay in lock-step. Use for reusable input/control components that need to write a value back to their parent:
352
352
 
353
- ```tsx
353
+ ```tsx fragment
354
354
  export class StarRating extends Component {
355
355
  @modelable rating: number = 0; // two-way bound to parent
356
356
 
@@ -409,7 +409,7 @@ See [Layouts & Composition](/docs/flow/layouts#two-way-props) for the full `@mod
409
409
 
410
410
  These helpers are available inside any `@expose`d method:
411
411
 
412
- ```typescript
412
+ ```typescript fragment
413
413
  // Flash a toast notification to the browser
414
414
  this.flash("Saved successfully!"); // level defaults to "success"
415
415
  this.flash("Could not connect.", "error");
@@ -442,7 +442,7 @@ this.download("export.json", JSON.stringify(data, null, 2), "application/json");
442
442
 
443
443
  `this.errors` is a typed proxy over the validation error bag. It is populated by `this.validate()` and `this.addError()`.
444
444
 
445
- ```tsx
445
+ ```tsx fragment
446
446
  // Check if any validation errors exist
447
447
  if (this.errors.any()) {
448
448
  return; // stop processing