@zerotal/arch 1.7.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 (160) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +67 -0
  4. package/api-surface.md +283 -0
  5. package/docs/about.md +676 -0
  6. package/docs/admin/actions.md +183 -0
  7. package/docs/admin/auth.md +89 -0
  8. package/docs/admin/dashboard.md +155 -0
  9. package/docs/admin/extending-ui.md +229 -0
  10. package/docs/admin/extending.md +205 -0
  11. package/docs/admin/forms.md +155 -0
  12. package/docs/admin/index.md +144 -0
  13. package/docs/admin/operations.md +224 -0
  14. package/docs/admin/references.md +174 -0
  15. package/docs/admin/resources.md +132 -0
  16. package/docs/admin/structure.md +158 -0
  17. package/docs/admin/tables.md +185 -0
  18. package/docs/admin/testing.md +138 -0
  19. package/docs/ai.md +373 -0
  20. package/docs/application.md +435 -0
  21. package/docs/arch.md +263 -0
  22. package/docs/assets.md +755 -0
  23. package/docs/audit.md +367 -0
  24. package/docs/authentication.md +1000 -0
  25. package/docs/authorization.md +529 -0
  26. package/docs/broadcasting/channels.md +170 -0
  27. package/docs/broadcasting/client.md +108 -0
  28. package/docs/broadcasting/events.md +190 -0
  29. package/docs/broadcasting/index.md +163 -0
  30. package/docs/broadcasting/references.md +84 -0
  31. package/docs/broadcasting/testing.md +132 -0
  32. package/docs/cache.md +387 -0
  33. package/docs/carbon.md +830 -0
  34. package/docs/changelog.md +465 -0
  35. package/docs/client/auth.md +113 -0
  36. package/docs/client/errors.md +139 -0
  37. package/docs/client/files.md +118 -0
  38. package/docs/client/index.md +128 -0
  39. package/docs/client/references.md +58 -0
  40. package/docs/client/requests.md +131 -0
  41. package/docs/client/resilience.md +141 -0
  42. package/docs/client/testing.md +146 -0
  43. package/docs/commands.md +450 -0
  44. package/docs/components.md +2427 -0
  45. package/docs/config-system.md +258 -0
  46. package/docs/container.md +596 -0
  47. package/docs/context.md +579 -0
  48. package/docs/contributing.md +164 -0
  49. package/docs/controllers.md +354 -0
  50. package/docs/conventions.md +466 -0
  51. package/docs/cookies.md +101 -0
  52. package/docs/csrf.md +206 -0
  53. package/docs/database.md +476 -0
  54. package/docs/deployment.md +434 -0
  55. package/docs/devtools.md +1013 -0
  56. package/docs/email-verification.md +320 -0
  57. package/docs/encryption.md +380 -0
  58. package/docs/errors.md +317 -0
  59. package/docs/events.md +420 -0
  60. package/docs/flow/components.md +846 -0
  61. package/docs/flow/decorators.md +458 -0
  62. package/docs/flow/events.md +420 -0
  63. package/docs/flow/forms.md +551 -0
  64. package/docs/flow/index.md +568 -0
  65. package/docs/flow/layouts.md +793 -0
  66. package/docs/flow/lifecycle.md +336 -0
  67. package/docs/flow/pagination.md +402 -0
  68. package/docs/flow/performance.md +262 -0
  69. package/docs/flow/references.md +317 -0
  70. package/docs/flow/routing.md +550 -0
  71. package/docs/flow/testing.md +447 -0
  72. package/docs/getting-started.md +286 -0
  73. package/docs/health.md +269 -0
  74. package/docs/helpers.md +423 -0
  75. package/docs/i18n.md +414 -0
  76. package/docs/index.md +65 -0
  77. package/docs/inertia/build.md +95 -0
  78. package/docs/inertia/devtools.md +197 -0
  79. package/docs/inertia/index.md +267 -0
  80. package/docs/inertia/middleware.md +81 -0
  81. package/docs/inertia/props.md +507 -0
  82. package/docs/inertia/references.md +144 -0
  83. package/docs/inertia/rendering.md +189 -0
  84. package/docs/inertia/ssr.md +102 -0
  85. package/docs/inspirations.md +257 -0
  86. package/docs/lifecycle.md +331 -0
  87. package/docs/lock.md +454 -0
  88. package/docs/logger.md +517 -0
  89. package/docs/media.md +582 -0
  90. package/docs/middleware.md +509 -0
  91. package/docs/migrations.md +529 -0
  92. package/docs/monitor.md +377 -0
  93. package/docs/notifications.md +848 -0
  94. package/docs/orm/casts.md +365 -0
  95. package/docs/orm/factories.md +231 -0
  96. package/docs/orm/index.md +663 -0
  97. package/docs/orm/lifecycle.md +421 -0
  98. package/docs/orm/queries.md +692 -0
  99. package/docs/orm/relationships.md +440 -0
  100. package/docs/orm/serialization.md +251 -0
  101. package/docs/package-development.md +536 -0
  102. package/docs/pagination.md +340 -0
  103. package/docs/password-reset.md +335 -0
  104. package/docs/providers.md +447 -0
  105. package/docs/query-builder.md +465 -0
  106. package/docs/queue.md +549 -0
  107. package/docs/rate-limiting.md +253 -0
  108. package/docs/responses.md +336 -0
  109. package/docs/roles-and-2fa.md +418 -0
  110. package/docs/routing.md +1008 -0
  111. package/docs/scaffolding.md +335 -0
  112. package/docs/scheduler.md +533 -0
  113. package/docs/seeding.md +229 -0
  114. package/docs/session.md +451 -0
  115. package/docs/social.md +524 -0
  116. package/docs/storage.md +622 -0
  117. package/docs/structure.md +230 -0
  118. package/docs/support-policy.md +114 -0
  119. package/docs/telemetry.md +505 -0
  120. package/docs/tenancy.md +608 -0
  121. package/docs/testing/browser.md +153 -0
  122. package/docs/testing/console.md +130 -0
  123. package/docs/testing/database.md +238 -0
  124. package/docs/testing/flow-browser.md +216 -0
  125. package/docs/testing/http.md +441 -0
  126. package/docs/testing/index.md +240 -0
  127. package/docs/testing/mocking.md +410 -0
  128. package/docs/upgrade.md +192 -0
  129. package/docs/validator.md +549 -0
  130. package/docs/view.md +453 -0
  131. package/package.json +60 -0
  132. package/src/bin/mcp.ts +73 -0
  133. package/src/config.ts +63 -0
  134. package/src/errors.ts +27 -0
  135. package/src/index.ts +56 -0
  136. package/src/install/ArchInstallCommand.ts +189 -0
  137. package/src/install/detect.ts +98 -0
  138. package/src/install/guidelines.ts +290 -0
  139. package/src/install/markers.ts +77 -0
  140. package/src/install/mcpConfig.ts +81 -0
  141. package/src/mcp/index.ts +27 -0
  142. package/src/mcp/jsonrpc.ts +133 -0
  143. package/src/mcp/server.ts +307 -0
  144. package/src/mcp/stdio.ts +111 -0
  145. package/src/mcp/types.ts +146 -0
  146. package/src/probe/ArchProbeCommand.ts +48 -0
  147. package/src/probe/sentinel.ts +13 -0
  148. package/src/probe/topics.ts +291 -0
  149. package/src/provider/ArchProvider.ts +101 -0
  150. package/src/tools/_probe.ts +164 -0
  151. package/src/tools/apiSurface.ts +241 -0
  152. package/src/tools/appInfo.ts +88 -0
  153. package/src/tools/baselines.ts +210 -0
  154. package/src/tools/context.ts +37 -0
  155. package/src/tools/doctor.ts +93 -0
  156. package/src/tools/index.ts +50 -0
  157. package/src/tools/logs.ts +265 -0
  158. package/src/tools/routes.ts +133 -0
  159. package/src/tools/schema.ts +119 -0
  160. package/src/tools/searchDocs.ts +345 -0
@@ -0,0 +1,205 @@
1
+ ---
2
+ title: Admin Custom Pages & Plugins
3
+ description: Add your own pages, and contribute pages or widgets to the panel from a package.
4
+ ---
5
+
6
+ # Custom pages
7
+
8
+ A resource is the right shape when you're editing rows in a table. When you aren't
9
+ — a settings screen, a report, an ops console — extend `AdminPage` instead. It's a
10
+ Flow component with some extra statics, so `@expose` state, actions and the
11
+ WebSocket round-trip all behave exactly as they do on a resource page.
12
+
13
+ ```ts
14
+ import { AdminPage, Panel } from "@zerotal/admin";
15
+
16
+ class ReportsPage extends AdminPage {
17
+ static override slug = "reports";
18
+ static override title = "Reports";
19
+ static override navigationIcon = "chart";
20
+ static override navigationGroup = "Insights";
21
+ static override ability = "reports.view";
22
+
23
+ override async render() {
24
+ return <div>…</div>;
25
+ }
26
+ }
27
+
28
+ Panel.pages(ReportsPage);
29
+ ```
30
+
31
+ The statics drive everything: `slug` becomes the route under the panel path,
32
+ `title` the heading and the sidebar label (override with `navigationLabel`), and
33
+ `ability` decides who sees it. Set `showInNavigation = false` for a page that
34
+ should be reachable but not listed — a drill-in, say — and `routeParams` to hang
35
+ extra segments off the same page, so `routeParams = [":section"]` serves both
36
+ `/admin/reports` and `/admin/reports/revenue`.
37
+
38
+ ## Abilities
39
+
40
+ Every custom page, contributed widget, nav entry and search provider names an
41
+ ability, and the panel checks it twice: once to decide whether to draw the entry,
42
+ and again in the route guard to decide whether to serve it. Both checks run the
43
+ same resolver, so the sidebar and the router can't drift — what you cannot see,
44
+ you cannot open by typing the URL.
45
+
46
+ Three things can answer an ability check, in order:
47
+
48
+ | Source | Use it when |
49
+ | -------------------------------- | -------------------------------------------------------------------- |
50
+ | `authorize` in `config/admin.ts` | The app models permissions itself. |
51
+ | The `gate` binding | You use `@zerotal/auth` — policies and abilities answer directly. |
52
+ | Neither | Development only. Every ability is denied outside a dev environment. |
53
+
54
+ That last row is the important one. A panel with no authorization wired stays
55
+ closed in production, the same posture the panel guard already takes, which is
56
+ what makes it safe for a package to add pages without the app asking.
57
+
58
+ ```ts
59
+ // config/admin.ts
60
+ export default {
61
+ authorize: (ability) => currentUser()?.permissions.includes(ability) ?? false,
62
+ };
63
+ ```
64
+
65
+ Resources are the exception: they authorize through their own
66
+ `Resource.can("viewAny")`, because record-level checks need context an ability
67
+ string can't carry. See [Authorization](/docs/admin/resources#authorization) above.
68
+
69
+ ## Extending the panel from a package
70
+
71
+ The panel is a **host**. It publishes a write surface, binds it into the container
72
+ as `admin.panel`, and names no contributor at all — so packages add pages, widgets,
73
+ navigation and search results to the admin without the admin knowing they exist,
74
+ and without depending on `@zerotal/admin`.
75
+
76
+ A contributing provider resolves the binding in its `onBooting` and pushes:
77
+
78
+ ```ts
79
+ // packages/queue/src/admin.ts
80
+ interface AdminHost {
81
+ enabled(id: string): boolean;
82
+ page(c: { slug: string; page: unknown; title: string; ability: string }): void;
83
+ }
84
+
85
+ export function installQueueAdmin(app: Application): void {
86
+ const panel = app.container.tryMake("admin.panel") as AdminHost | undefined;
87
+ if (!panel?.enabled("queue")) return;
88
+ panel.page({ slug: "jobs", page: JobsPage, title: "Jobs", ability: "queue.view" });
89
+ }
90
+ ```
91
+
92
+ Declaring the host's shape locally rather than importing it is the point: the
93
+ package compiles and ships with no dependency on the admin, and an app that runs
94
+ the queue without the panel pulls in nothing extra — the binding simply isn't
95
+ there and the function returns.
96
+
97
+ This is the same inversion the observability sinks use, so there is one extension
98
+ idiom across the framework rather than a bespoke plugin API here.
99
+
100
+ ### What a package can contribute
101
+
102
+ | Surface | Adds |
103
+ | ------------------ | ------------------------------------------------------- |
104
+ | `console()` | A tabbed table page with actions, described as data. |
105
+ | `page()` | A route under the panel path, plus a sidebar entry. |
106
+ | `widget()` | A dashboard widget. |
107
+ | `navItem()` | A sidebar link to somewhere the panel doesn't mount. |
108
+ | `searchProvider()` | A source of global-search results beyond the resources. |
109
+ | `topbarSlot()` | A status pill or control in the top bar. |
110
+ | `userMenuItem()` | An entry in the account dropdown. |
111
+
112
+ ### Consoles: pages described rather than built
113
+
114
+ Most packages want the same page — some tables, a few buttons, no bespoke layout.
115
+ `console()` lets a package describe that instead of rendering it, so it needs no
116
+ JSX, no `@zerotal/flow` dependency and no build configuration. The panel owns
117
+ the markup, which also means every console looks like the rest of the admin
118
+ without trying to.
119
+
120
+ ```ts
121
+ panel.console({
122
+ slug: "jobs",
123
+ title: "Jobs",
124
+ ability: "queue.view",
125
+ navigationBadge: async () => (await Queue.failed()).length || null,
126
+ tabs: [
127
+ {
128
+ key: "failed",
129
+ label: "Failed",
130
+ columns: [
131
+ { key: "id", label: "ID", mono: true },
132
+ { key: "className", label: "Job" },
133
+ { key: "error", label: "Error", mono: true, format: firstLine },
134
+ ],
135
+ rows: () => Queue.failed(),
136
+ rowActions: [{ key: "retry", label: "Retry", icon: "undo", run: (row) => retry(row) }],
137
+ headerActions: [
138
+ { key: "clear", label: "Clear failed", danger: true, confirm: "Sure?", run: clearAll },
139
+ ],
140
+ },
141
+ ],
142
+ });
143
+ ```
144
+
145
+ An action returns a string to flash on success, or throws to flash an error;
146
+ either way the table re-reads afterwards so it shows what the action just did.
147
+ Tabs carry their own `badge()` count, and the console as a whole can carry a
148
+ `navigationBadge()` that puts a number beside its sidebar entry.
149
+
150
+ The console's ability is re-checked on every dispatched action, not just when the
151
+ page renders — these are `@expose`d methods reachable from a client frame, so
152
+ drawing the button and running it are separately enforced.
153
+
154
+ Choose between the two doors on layout, not size: reach for `page()` when the
155
+ page genuinely needs its own component — charts, a custom arrangement, its own
156
+ reactive state — and `console()` for everything that is a table and some buttons.
157
+
158
+ Contributed pages keep their own class. The panel hosts a subclass carrying its
159
+ layout rather than assigning one onto the class it was handed, so a package that
160
+ also mounts the page in its own standalone panel is unaffected.
161
+
162
+ ### Switching a contributor off
163
+
164
+ Contributions are automatic — installing both providers is enough. To keep a
165
+ provider installed but drop what it adds to the panel, name it in `plugins`:
166
+
167
+ ```ts
168
+ // config/admin.ts
169
+ export default {
170
+ plugins: { monitor: false },
171
+ };
172
+ ```
173
+
174
+ This belongs in `config/admin.ts` rather than `app/admin.ts` because it has to be
175
+ in place before contributors ask whether they're enabled, and only the config file
176
+ is read early enough.
177
+
178
+ ### Plugins written by the app
179
+
180
+ Application code can name the panel directly, so it doesn't need the container
181
+ dance. Group a set of related contributions behind an `AdminPlugin`:
182
+
183
+ ```ts
184
+ await Panel.plugin({
185
+ id: "billing",
186
+ install: (panel) => {
187
+ panel.page({
188
+ slug: "invoices",
189
+ page: InvoicesPage,
190
+ title: "Invoices",
191
+ ability: "billing.view",
192
+ });
193
+ panel.userMenuItem({ label: "Billing", href: "/admin/invoices", ability: "billing.view" });
194
+ },
195
+ });
196
+ ```
197
+
198
+ Use `Panel.pages()` for a single page, an `AdminPlugin` when a feature adds
199
+ several things at once and you want one switch (`plugins: { billing: false }`) to
200
+ control them together.
201
+
202
+ ## Next steps
203
+
204
+ - [Admin overview](/docs/admin) — the guide's front page and the rest of the sections.
205
+ - [Reference](/docs/admin/references) — the full API surface in one table.
@@ -0,0 +1,155 @@
1
+ ---
2
+ title: Admin Forms & Infolists
3
+ description: Build create and edit forms, and lay out the read-only view screen.
4
+ ---
5
+
6
+ # Forms
7
+
8
+ `form()` returns fields (and layout components). An empty `form()` disables
9
+ Create/Edit for the resource.
10
+
11
+ ```ts
12
+ import {
13
+ textInput, textarea, select, toggle, datePicker, fileUpload, richEditor,
14
+ formSection, formTabs, formTab, wizard, wizardStep,
15
+ } from "@zerotal/admin";
16
+
17
+ static form() {
18
+ return [
19
+ formSection("Content").columns(2).schema([
20
+ textInput("title").required().maxLength(160).columnSpan(2),
21
+ textInput("slug").required()
22
+ .live().afterStateUpdated((v) => ({ slug: slugify(String(v)) })),
23
+ select("status").options({ draft: "Draft", published: "Published" }).required(),
24
+ datePicker("published_at").visible((d) => d.status === "published"),
25
+ ]),
26
+ formSection("Body").schema([
27
+ richEditor("body"),
28
+ fileUpload("cover").image().disk("covers"),
29
+ ]),
30
+ ];
31
+ }
32
+ ```
33
+
34
+ ## Field catalogue
35
+
36
+ | Factory | Renders |
37
+ | ----------------------------------------------------------- | ------------------------------------------------------------------------------- |
38
+ | `textInput(k)` | text — plus `.email()/.password()/.numeric()/.url()/.tel()` |
39
+ | `textarea(k)` | multi-line text (`.rows(n)`) |
40
+ | `select(k)` | select — `.options()/.optionsUsing()/.multiple()/.searchable()/.createOption()` |
41
+ | `checkbox(k)`, `toggle(k)` | boolean |
42
+ | `radio(k)`, `checkboxList(k)` | single / multi choice |
43
+ | `datePicker`, `dateTimePicker`, `timePicker`, `colorPicker` | native pickers |
44
+ | `tagsInput(k)` | tag/token array |
45
+ | `keyValue(k)` | key/value object editor |
46
+ | `fileUpload(k)` | upload — `.image()/.disk(dir)/.accept(mime)/.multiple()` |
47
+ | `slider(k)` | range → number (`.min()/.max()/.step()`) |
48
+ | `toggleButtons(k)` | segmented buttons (`.multiple()`) |
49
+ | `codeEditor`, `markdownEditor`, `richEditor` | code / markdown / WYSIWYG editors |
50
+ | `hidden(k)` | retained-but-hidden value |
51
+ | `repeater(k)`, `builder(k)` | nested object-arrays (see below) |
52
+
53
+ A control the catalogue lacks is `customField(k).render(fn)` — see
54
+ [Extending the UI](/docs/admin/extending-ui#custom-renderers).
55
+
56
+ ## Common modifiers
57
+
58
+ `.label()`, `.placeholder()`, `.helperText()`, `.default(v)`, `.required()`,
59
+ `.minLength()/.maxLength()/.min()/.max()`, `.confirmed()`, `.rule(fn)`,
60
+ `.columnSpan(n)`, `.disabled()`, `.visibleOn("create"|"edit")` / `.hiddenOn(...)`,
61
+ and the reactive `.visible((data) => bool)` / `.disabledWhen((data) => bool)`.
62
+
63
+ `.live()` re-evaluates dependent fields on change; `.afterStateUpdated((v, data) =>
64
+ patch)` runs server-side and merges a patch into the form (e.g. derive a slug).
65
+
66
+ ## Layout components
67
+
68
+ | Component | Purpose |
69
+ | -------------------------------------------- | --------------------------------------- |
70
+ | `formSection(heading).columns(n).schema([])` | Titled, multi-column card. |
71
+ | `formTabs([formTab("X").schema([])])` | Tabbed groups (client-side switching). |
72
+ | `wizard([wizardStep("X").schema([])])` | Stepped form with per-step validation. |
73
+ | `fieldset(legend)` | Bordered `<fieldset>`/`<legend>` group. |
74
+ | `split([sectionA, sectionB])` | Side-by-side sections. |
75
+ | `callout(text).tone().icon().heading()` | A toned notice block. |
76
+ | `prime / primeHtml / primeImage` | Static text / HTML / image display. |
77
+
78
+ ## Repeater & Builder
79
+
80
+ `repeater` edits an array of objects sharing one sub-schema; `builder` edits an array
81
+ of typed blocks. Rows can be added, removed, and reordered.
82
+
83
+ ```ts
84
+ import { repeater, builder, builderBlock, textInput, textarea, fileUpload } from "@zerotal/admin";
85
+
86
+ repeater("contacts").minItems(1).addActionLabel("Add contact").schema([
87
+ textInput("name").required(),
88
+ textInput("email").email(),
89
+ ]),
90
+
91
+ builder("content").blocks([
92
+ builderBlock("paragraph").schema([textarea("text")]),
93
+ builderBlock("image").icon("photo").schema([fileUpload("src").image()]),
94
+ ]),
95
+ ```
96
+
97
+ Repeaters serialize to `[{ ...fields }]`; builders to `[{ type, data }]`.
98
+
99
+ ## Infolists (View page)
100
+
101
+ `infolist()` describes the read-only detail page. Omit it to fall back to a section
102
+ derived from `columns()`.
103
+
104
+ ```ts
105
+ import { section, textEntry, iconEntry } from "@zerotal/admin";
106
+
107
+ static infolist() {
108
+ return [
109
+ section("Overview").columns(2).schema([
110
+ textEntry("title").weight("semibold"),
111
+ textEntry("status").badge().color((v) => (v === "published" ? "success" : "muted")),
112
+ textEntry("email").copyable(),
113
+ textEntry("created_at").since(),
114
+ textEntry("price").money("USD"),
115
+ iconEntry("featured").boolean(),
116
+ ]),
117
+ ];
118
+ }
119
+ ```
120
+
121
+ ### Entry kinds
122
+
123
+ `textEntry` covers most of a view page. The rest exist because some values are not
124
+ usefully read as text:
125
+
126
+ | Factory | Renders |
127
+ | ---------------------- | ------------------------------------------------------------------------- |
128
+ | `textEntry(key)` | A value, with `.badge()`, `.date()`, `.money()`, `.copyable()`, `.url()`. |
129
+ | `iconEntry(key)` | A check or a cross for a boolean. |
130
+ | `imageEntry(key)` | An image from a URL — `.circular()`, `.height(px)`. |
131
+ | `colorEntry(key)` | A swatch beside the value. |
132
+ | `codeEntry(key)` | A monospace block — `.language("json")`. |
133
+ | `keyValueEntry(key)` | An object as a two-column table of its pairs. |
134
+ | `repeatableEntry(key)` | A nested `.schema([…])` rendered once per array item. |
135
+
136
+ `repeatableEntry` is the read side of `repeater`: whatever the form wrote into a
137
+ JSON column, this reads back in the same shape.
138
+
139
+ ```ts
140
+ section("Line items").schema([
141
+ repeatableEntry("lines")
142
+ .placeholder("This order has no line items.")
143
+ .schema([
144
+ textEntry("sku").label("SKU"),
145
+ textEntry("description"),
146
+ textEntry("quantity").label("Qty"),
147
+ textEntry("unitPrice").label("Unit price").money("USD"),
148
+ ]),
149
+ ]),
150
+ ```
151
+
152
+ ## Next steps
153
+
154
+ - [Admin overview](/docs/admin) — the guide's front page and the rest of the sections.
155
+ - [Reference](/docs/admin/references) — the full API surface in one table.
@@ -0,0 +1,144 @@
1
+ ---
2
+ title: Admin Panel
3
+ description: A Filament-style, server-driven admin panel — declare resources as classes and get tables, forms, infolists, actions, relations, dashboards, search, notifications, and auth pages.
4
+ ---
5
+
6
+ # Admin Panel
7
+
8
+ `@zerotal/admin` is a **Filament-style** admin panel for Zerotal. You describe each
9
+ model with a `Resource` class — its table columns, form fields, infolist entries,
10
+ filters, actions, and relations — and the panel renders fully reactive CRUD pages.
11
+
12
+ It is **server-driven**: pages are [`@zerotal/flow`](/docs/flow) components and
13
+ the UI is built from [`@zerotal/flow-ui`](/docs/components), so sorting, filtering,
14
+ inline edits, modals, and live notifications all round-trip over Flow's WebSocket
15
+ morph with no client store to maintain. Light + dark mode ship out of the box.
16
+
17
+ > **Note** — This is the current, class-based admin. The earlier zero-config
18
+ > `Admin.register(Model)` panel has been retired; it remains available in
19
+ > pre-1.1 releases (and on the `admin-legacy` git tag) if you need to reference it.
20
+
21
+ ## Getting Started
22
+
23
+ ```bash
24
+ bun add @zerotal/admin
25
+ ```
26
+
27
+ The panel renders with Flow and flow-ui, and reads/writes through your
28
+ [`@zerotal/orm`](/docs/orm) models. `@zerotal/auth` is an **optional** peer —
29
+ needed only if you enable the built-in auth pages.
30
+
31
+ ## Register the providers
32
+
33
+ Add `AdminProvider` **after** `FlowProvider` (which installs the `Router.flow()`
34
+ macro and the WebSocket runtime the pages depend on):
35
+
36
+ ```ts
37
+ // bootstrap/providers.ts
38
+ import { FlowProvider } from "@zerotal/flow";
39
+ import { AdminProvider } from "@zerotal/admin";
40
+
41
+ export default [FlowProvider, AdminProvider];
42
+ ```
43
+
44
+ On boot the provider auto-discovers `app/admin.ts` (where you configure the panel
45
+ and register resources), then mounts the dashboard, search, notifications, and a
46
+ List / View / Create / Edit page per resource under the configured `path`.
47
+
48
+ ## Quick start
49
+
50
+ ```ts
51
+ // app/admin.ts
52
+ import { Panel, Resource, text, textInput } from "@zerotal/admin";
53
+ import { User } from "./models/User.ts";
54
+
55
+ Panel.configure({ brand: "Acme", path: "/admin" });
56
+
57
+ class UserResource extends Resource {
58
+ static model = User;
59
+ static navigationIcon = "users";
60
+ static navigationGroup = "Access";
61
+
62
+ static columns() {
63
+ return [
64
+ text("id").sortable(),
65
+ text("name").searchable().sortable(),
66
+ text("email").searchable().copyable(),
67
+ text("role").badge((v) => (v === "admin" ? "primary" : "muted")),
68
+ text("created_at").label("Joined").sortable(),
69
+ ];
70
+ }
71
+
72
+ static form() {
73
+ return [textInput("name").required().maxLength(120), textInput("email").email().required()];
74
+ }
75
+ }
76
+
77
+ Panel.register(UserResource);
78
+ ```
79
+
80
+ Visit `/admin`. You now have a searchable, sortable, paginated table with Create /
81
+ Edit / View / Delete — and the form above on the Create and Edit pages.
82
+
83
+ > **Danger** — The panel is **unguarded by default** (fine for local exploration).
84
+ > Set `middleware` in your config before shipping; see [Securing the panel](/docs/admin/auth#securing-the-panel).
85
+
86
+ ## Configuration
87
+
88
+ Configure the panel with `Panel.configure(...)` in `app/admin.ts`, or by exporting
89
+ an `admin` config object the provider merges on boot.
90
+
91
+ ```ts
92
+ Panel.configure({
93
+ path: "/admin",
94
+ brand: "Acme",
95
+ tagline: "Control panel",
96
+ middleware: [AdminGuard], // guard every panel route
97
+ userMenu: {
98
+ // top-bar identity dropdown
99
+ label: "Jane Doe",
100
+ items: [
101
+ { label: "Profile", href: "/admin/profile", icon: "users" },
102
+ { label: "Sign out", href: "/admin/logout", icon: "logout" },
103
+ ],
104
+ },
105
+ theme: {/* see Theming */},
106
+ auth: {/* see Auth pages */},
107
+ });
108
+ ```
109
+
110
+ | Field | Default | Description |
111
+ | ------------ | ----------- | ---------------------------------------------------------------------- |
112
+ | `path` | `"/admin"` | URL prefix the panel mounts under. |
113
+ | `brand` | `"Zerotal"` | Sidebar + login heading. |
114
+ | `tagline` | `"Admin"` | Small text under the brand. |
115
+ | `middleware` | `[]` | Middleware guarding every panel route. **Set this before production.** |
116
+ | `userMenu` | — | Top-bar dropdown: `{ label?, items: [{ label, href, icon? }] }`. |
117
+ | `theme` | CDN | Styling source — Tailwind Play CDN or a prebuilt stylesheet. |
118
+ | `auth` | — | Built-in login / profile / reset / verify pages. |
119
+ | `authorize` | — | Decide the abilities pages and contributions name. |
120
+ | `plugins` | `{}` | Switch contributing packages off by id, e.g. `{ monitor: false }`. |
121
+
122
+ ## The rest of the guide
123
+
124
+ | Page | What it covers |
125
+ | ----------------------------------------------- | -------------------------------------------------------------------------------- |
126
+ | [Resources](/docs/admin/resources) | Declare a resource and get list, create, edit, and view screens for a model. |
127
+ | [Tables](/docs/admin/tables) | Columns, filters, sorting, search, and bulk actions on the list screen. |
128
+ | [Forms & Infolists](/docs/admin/forms) | Build create and edit forms, and lay out the read-only view screen. |
129
+ | [Actions & Relations](/docs/admin/actions) | Row, bulk, and page actions, related-record managers, and soft-delete handling. |
130
+ | [Panel Structure](/docs/admin/structure) | Clusters, nested and singular resources, and running more than one panel. |
131
+ | [Dashboard & Navigation](/docs/admin/dashboard) | Widgets, global search, the command palette, notifications, and the nav tree. |
132
+ | [Extending the UI](/docs/admin/extending-ui) | Custom cells and controls, render hooks, custom data sources, table layouts. |
133
+ | [Operations](/docs/admin/operations) | History, impersonation, saved views, media, roles, and per-user dashboards. |
134
+ | [Custom Pages & Plugins](/docs/admin/extending) | Add your own pages, and contribute pages or widgets to the panel from a package. |
135
+ | [Auth Pages & Theming](/docs/admin/auth) | The built-in login and profile screens, and how to restyle the panel. |
136
+ | [Testing the Admin Panel](/docs/admin/testing) | Drive panel screens in tests and assert on what they render. |
137
+ | [References](/docs/admin/references) | Every resource, table, form, and action API in one table. |
138
+
139
+ ## Next steps
140
+
141
+ - [Flow](/docs/flow) — the reactivity layer the pages are built on.
142
+ - [Components](/docs/components) — the flow-ui kit the panel renders with.
143
+ - [ORM](/docs/orm) — the models, relations, and soft-deletes the panel reads.
144
+ - [Authentication](/docs/authentication) / [Authorization](/docs/authorization) — the auth pages and `can()` policies.