@zerotal/arch 1.7.4 → 1.8.0

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 (126) hide show
  1. package/CHANGELOG.md +86 -2
  2. package/README.md +4 -2
  3. package/api-surface.md +2 -0
  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 +268 -1
  32. package/docs/client/index.md +13 -13
  33. package/docs/commands.md +36 -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 +28 -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 +99 -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 +5 -5
  55. package/docs/flow/index.md +21 -21
  56. package/docs/flow/layouts.md +99 -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 +6 -6
  70. package/docs/inertia/props.md +21 -21
  71. package/docs/inertia/rendering.md +9 -9
  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 +23 -10
  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 +29 -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/config.ts +8 -0
  120. package/src/index.ts +25 -1
  121. package/src/install/ArchInstallCommand.ts +46 -3
  122. package/src/install/guidelines.ts +84 -0
  123. package/src/install/shape.ts +177 -0
  124. package/src/install/skills.ts +271 -0
  125. package/src/provider/ArchProvider.ts +148 -0
  126. package/src/tools/searchDocs.ts +7 -0
@@ -18,7 +18,7 @@ in `@zerotal/core` signs the link, and `ValidateSignatureMiddleware` in
18
18
  `@zerotal/auth` checks it. There is no extra package and no extra table:
19
19
 
20
20
  ```typescript
21
- import { Url } from "zerotal";
21
+ import { Url } from "zerotal/http";
22
22
  import { ValidateSignatureMiddleware } from "@zerotal/auth";
23
23
  ```
24
24
 
@@ -51,7 +51,7 @@ register ──▶ Url.sign(/auth/verify?id&email, ttl) ──▶ email link
51
51
 
52
52
  Add a nullable `email_verified_at` column to your users table:
53
53
 
54
- ```typescript
54
+ ```typescript fragment
55
55
  // database/migrations/002_add_email_verified_at.ts
56
56
  export default class AddEmailVerifiedAt extends Migration {
57
57
  async up(schema: Schema) {
@@ -140,7 +140,7 @@ The verify route is already guarded by `ValidateSignatureMiddleware`, so the
140
140
  controller can trust that the URL is intact — it only checks that the `id`/`email`
141
141
  pair still resolves to a real user:
142
142
 
143
- ```typescript
143
+ ```typescript fragment
144
144
  // app/controllers/VerificationController.ts
145
145
  import { Auth } from "@zerotal/auth";
146
146
  import { Notify } from "@zerotal/notifications";
@@ -199,7 +199,7 @@ export class VerificationController {
199
199
 
200
200
  ## Routes
201
201
 
202
- ```typescript
202
+ ```typescript fragment
203
203
  // routes/web.ts
204
204
  import { ValidateSignatureMiddleware } from "@zerotal/auth";
205
205
  import { RequireAuthMiddleware } from "#app/middleware/RequireAuth.ts";
@@ -220,7 +220,7 @@ Router.post("/auth/verify/resend", VerificationController, "resend", [RequireAut
220
220
  Call `Notify.queue()` at the end of your registration handler so the HTTP response
221
221
  returns immediately while delivery happens in the background:
222
222
 
223
- ```typescript
223
+ ```typescript fragment
224
224
  // app/controllers/AuthController.ts
225
225
  import { Notify } from "@zerotal/notifications";
226
226
  import { VerifyEmailNotification } from "#app/notifications/VerifyEmailNotification.ts";
@@ -242,7 +242,7 @@ Write a `VerifiedMiddleware` once and apply it to routes that must only be
242
242
  accessible to verified users. It extends `BaseMiddleware` (from `zerotal`)
243
243
  and reads the current user via `Auth.userOrNull()`:
244
244
 
245
- ```typescript
245
+ ```typescript fragment
246
246
  // app/middleware/Verified.ts
247
247
  import { BaseMiddleware } from "zerotal";
248
248
  import type { HttpContext, NextFn } from "zerotal";
@@ -273,7 +273,7 @@ export class VerifiedMiddleware extends BaseMiddleware {
273
273
  Apply it alongside `RequireAuthMiddleware` so unverified users are bounced to the
274
274
  notice page:
275
275
 
276
- ```typescript
276
+ ```typescript fragment
277
277
  // routes/web.ts
278
278
  Router.group({ middleware: [RequireAuthMiddleware, VerifiedMiddleware] }, () => {
279
279
  Router.get("/dashboard", DashboardController, "index");
@@ -284,7 +284,7 @@ Router.group({ middleware: [RequireAuthMiddleware, VerifiedMiddleware] }, () =>
284
284
 
285
285
  ## Testing
286
286
 
287
- ```typescript
287
+ ```typescript fragment
288
288
  // tests/email-verification.test.ts
289
289
  it("unverified user is redirected to notice page", async () => {
290
290
  const user = await UserFactory.create({ emailVerifiedAt: null });
@@ -81,7 +81,7 @@ Crypt.decrypt<{ userId: number }>(blob).userId; // 7
81
81
  algorithm is auto-detected on verify, so changing the default never breaks
82
82
  existing hashes.
83
83
 
84
- ```ts
84
+ ```ts fragment
85
85
  // in a controller or service
86
86
  import { Hash } from "zerotal/security";
87
87
 
@@ -147,7 +147,7 @@ thread a signing key through your code.
147
147
 
148
148
  ### Generating a signed URL
149
149
 
150
- ```ts
150
+ ```ts fragment
151
151
  function sign(
152
152
  base: string,
153
153
  params?: Record<string, string>,
@@ -173,7 +173,7 @@ per-feature invite secret): `Url.sign(base, params, minutes, inviteSecret)`.
173
173
 
174
174
  ### Verifying a signed URL
175
175
 
176
- ```ts
176
+ ```ts fragment
177
177
  // in a controller
178
178
  import { Url } from "zerotal/http";
179
179
 
@@ -209,7 +209,7 @@ Apply `ValidateSignatureMiddleware` to any route that receives signed URLs — i
209
209
  rejects invalid or expired links with a 403 JSON response, so your controller
210
210
  only runs when the signature is good:
211
211
 
212
- ```ts
212
+ ```ts fragment
213
213
  // routes/web.ts
214
214
  import { ValidateSignatureMiddleware } from "@zerotal/auth";
215
215
 
@@ -277,7 +277,7 @@ that a round-trip test alone would never notice.
277
277
  **Tampering must throw**, and proving it is what tells you the payload is
278
278
  authenticated rather than merely scrambled:
279
279
 
280
- ```typescript
280
+ ```typescript fragment
281
281
  // tests/security/Crypt.test.ts
282
282
  import { DecryptionError } from "zerotal/security";
283
283
 
@@ -292,7 +292,7 @@ test("a modified payload is rejected", () => {
292
292
  **`encrypt()` round-trips any JSON value**, so a test covering an object confirms
293
293
  the serialisation as well as the cipher:
294
294
 
295
- ```typescript
295
+ ```typescript fragment
296
296
  // tests/security/Crypt.test.ts
297
297
  const payload = Crypt.encrypt({ userId: 7, scopes: ["read"] });
298
298
 
@@ -303,7 +303,7 @@ Hashing has its own trap: every `Hash` method except `needsRehash` is async, and
303
303
  forgotten `await` yields a `Promise`, which is always truthy — the one mistake in
304
304
  this API that silently passes.
305
305
 
306
- ```typescript
306
+ ```typescript fragment
307
307
  // tests/security/Hash.test.ts
308
308
  import { Hash } from "zerotal/security";
309
309
 
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++"