@voltro/cli 0.27.0 → 0.29.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.
- package/CHANGELOG.md +513 -0
- package/THIRD-PARTY-NOTICES.md +88 -2
- package/dist/{apiBuild-D22_EpoR.js → apiBuild-BESZbTjA.js} +4 -4
- package/dist/apiBuild-C8d74T6g.js +2 -0
- package/dist/bin.js +3 -3
- package/dist/checkCommand-BPAtyWs8.js +2420 -0
- package/dist/checkCommand-DBvZvCh1.js +2 -0
- package/dist/cliRuntime-Oh517vCV.js +96 -0
- package/dist/{commands-jBX8no1I.js → commands-82BDEktg.js} +6476 -7672
- package/dist/dbCommand-CaIVTp-a.js +2 -0
- package/dist/{dbCommand-uuNCrFAb.js → dbCommand-bCBbk6tz.js} +3 -3
- package/dist/{dev-DcbIJrWg.js → dev-Cg4BKLmi.js} +3034 -2619
- package/dist/dev-DeEJj5T4.js +3 -0
- package/dist/fileConventions-Cof68_BL.js +33 -0
- package/dist/{frameworkTableAssembly-BwHU9Euq.js → frameworkTableAssembly-4Db40V23.js} +2 -2
- package/dist/frameworkTableAssembly-BTNirAAk.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{inspect-CUCCzw2I.js → inspect-BMvQpwqy.js} +211 -190
- package/dist/inspect-DHeaMZak.js +2 -0
- package/dist/{inspectMetrics-BU90mvJN.js → inspectMetrics-DHYygE4s.js} +999 -961
- package/dist/{manifestBuild-BnzAxp2O.js → manifestBuild-C4R1EdHn.js} +1 -1
- package/dist/manifestBuild-Ck_90gZy.js +2 -0
- package/dist/sdkgen-CW5NbYF0.js +582 -0
- package/dist/{seedRunner-D6eu-u5U.js → seedRunner-Bqxgp7HZ.js} +60 -59
- package/dist/serveCommand-BXl8mhZD.js +1489 -0
- package/dist/serveEntry.js +3 -3
- package/dist/{start-BGXIf6zT.js → start-CsCIaW4-.js} +282 -269
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.md +2 -1
- package/templates/agent-docs/_index.md +2 -1
- package/templates/agent-docs/_manifest.json +10 -1
- package/templates/agent-docs/ai.md +135 -0
- package/templates/agent-docs/cli.md +124 -6
- package/templates/agent-docs/configuration.md +32 -0
- package/templates/agent-docs/data.md +282 -0
- package/templates/agent-docs/database/migrations.md +47 -0
- package/templates/agent-docs/database/misc.md +57 -0
- package/templates/agent-docs/database/schema.md +3 -1
- package/templates/agent-docs/deployment.md +56 -0
- package/templates/agent-docs/internationalization.md +161 -1
- package/templates/agent-docs/local-first-mobile.md +414 -0
- package/templates/agent-docs/routing.md +93 -2
- package/templates/agent-docs/schema-driven-ui.md +12 -0
- package/templates/agent-docs/templates/apibackends.md +1 -1
- package/templates/agent-docs/testing.md +42 -0
- package/templates/agent-docs/whats-new.md +165 -131
- package/templates/agent-docs/workflows.md +11 -0
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/.env.example +19 -0
- package/templates/apps/api-backend-sqlite/README.md +38 -0
- package/templates/apps/api-backend-sqlite/app.config.ts +35 -0
- package/templates/apps/api-backend-sqlite/database/schema.ts +54 -0
- package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-sqlite/package.json +30 -0
- package/templates/apps/api-backend-sqlite/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-sqlite/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-sqlite/template.json +6 -0
- package/templates/apps/api-backend-sqlite/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-sqlite/tsconfig.json +5 -0
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/README.md +104 -0
- package/templates/apps/api-cms/actions/content.get.action.server.ts +27 -0
- package/templates/apps/api-cms/actions/content.get.action.ts +19 -0
- package/templates/apps/api-cms/actions/content.types.action.server.ts +26 -0
- package/templates/apps/api-cms/actions/content.types.action.ts +40 -0
- package/templates/apps/api-cms/actions/me.action.server.ts +18 -0
- package/templates/apps/api-cms/actions/me.action.ts +16 -0
- package/templates/apps/api-cms/app.config.ts +61 -0
- package/templates/apps/api-cms/content/blogPost.contentType.ts +39 -0
- package/templates/apps/api-cms/content/index.ts +18 -0
- package/templates/apps/api-cms/content/page.contentType.ts +24 -0
- package/templates/apps/api-cms/database/schema.ts +64 -0
- package/templates/apps/api-cms/mutations/content.publish.mutation.server.ts +19 -0
- package/templates/apps/api-cms/mutations/content.publish.mutation.ts +15 -0
- package/templates/apps/api-cms/mutations/content.saveDraft.mutation.server.ts +36 -0
- package/templates/apps/api-cms/mutations/content.saveDraft.mutation.ts +32 -0
- package/templates/apps/api-cms/mutations/content.unpublish.mutation.server.ts +19 -0
- package/templates/apps/api-cms/mutations/content.unpublish.mutation.ts +11 -0
- package/templates/apps/api-cms/package.json +32 -0
- package/templates/apps/api-cms/queries/content.list.query.server.ts +0 -0
- package/templates/apps/api-cms/queries/content.list.query.ts +27 -0
- package/templates/apps/api-cms/template.json +6 -0
- package/templates/apps/api-cms/tests/content.descriptors.test.ts +64 -0
- package/templates/apps/api-cms/tests/content.write.test.ts +85 -0
- package/templates/apps/api-cms/tsconfig.json +5 -0
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/README.md +103 -0
- package/templates/apps/api-saas-starter/actions/me.action.server.ts +18 -0
- package/templates/apps/api-saas-starter/actions/me.action.ts +20 -0
- package/templates/apps/api-saas-starter/app.config.ts +87 -0
- package/templates/apps/api-saas-starter/database/schema.ts +57 -0
- package/templates/apps/api-saas-starter/mutations/invites.create.mutation.server.ts +26 -0
- package/templates/apps/api-saas-starter/mutations/invites.create.mutation.ts +18 -0
- package/templates/apps/api-saas-starter/mutations/projects.create.mutation.server.ts +29 -0
- package/templates/apps/api-saas-starter/mutations/projects.create.mutation.ts +18 -0
- package/templates/apps/api-saas-starter/package.json +32 -0
- package/templates/apps/api-saas-starter/queries/invites.list.query.server.ts +14 -0
- package/templates/apps/api-saas-starter/queries/invites.list.query.ts +17 -0
- package/templates/apps/api-saas-starter/queries/projects.list.query.server.ts +14 -0
- package/templates/apps/api-saas-starter/queries/projects.list.query.ts +18 -0
- package/templates/apps/api-saas-starter/template.json +6 -0
- package/templates/apps/api-saas-starter/tests/projects.create.test.ts +62 -0
- package/templates/apps/api-saas-starter/tests/session.test.ts +33 -0
- package/templates/apps/api-saas-starter/tsconfig.json +5 -0
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/README.md +88 -0
- package/templates/apps/api-status/app.config.ts +36 -0
- package/templates/apps/api-status/authz.ts +33 -0
- package/templates/apps/api-status/database/schema.ts +70 -0
- package/templates/apps/api-status/mutations/components.create.mutation.server.ts +19 -0
- package/templates/apps/api-status/mutations/components.create.mutation.ts +16 -0
- package/templates/apps/api-status/mutations/incidents.create.mutation.server.ts +25 -0
- package/templates/apps/api-status/mutations/incidents.create.mutation.ts +22 -0
- package/templates/apps/api-status/mutations/incidents.resolve.mutation.server.ts +24 -0
- package/templates/apps/api-status/mutations/incidents.resolve.mutation.ts +19 -0
- package/templates/apps/api-status/mutations/incidents.update.mutation.server.ts +26 -0
- package/templates/apps/api-status/mutations/incidents.update.mutation.ts +21 -0
- package/templates/apps/api-status/package.json +30 -0
- package/templates/apps/api-status/queries/components.list.query.server.ts +14 -0
- package/templates/apps/api-status/queries/components.list.query.ts +13 -0
- package/templates/apps/api-status/queries/incidents.live.query.server.ts +14 -0
- package/templates/apps/api-status/queries/incidents.live.query.ts +20 -0
- package/templates/apps/api-status/queries/updates.list.query.server.ts +14 -0
- package/templates/apps/api-status/queries/updates.list.query.ts +17 -0
- package/templates/apps/api-status/template.json +6 -0
- package/templates/apps/api-status/tests/status.test.ts +70 -0
- package/templates/apps/api-status/tsconfig.json +5 -0
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-auth/README.md +78 -0
- package/templates/apps/frontend-auth/app.config.ts +34 -0
- package/templates/apps/frontend-auth/package.json +32 -0
- package/templates/apps/frontend-auth/src/components/AuthShell.tsx +35 -0
- package/templates/apps/frontend-auth/src/components/PasswordStrength.tsx +33 -0
- package/templates/apps/frontend-auth/src/config.ts +11 -0
- package/templates/apps/frontend-auth/src/globals.css +105 -0
- package/templates/apps/frontend-auth/src/globals.d.ts +6 -0
- package/templates/apps/frontend-auth/src/lib/auth.ts +34 -0
- package/templates/apps/frontend-auth/src/lib/redirect.test.ts +24 -0
- package/templates/apps/frontend-auth/src/lib/redirect.ts +29 -0
- package/templates/apps/frontend-auth/src/locales/de.ts +66 -0
- package/templates/apps/frontend-auth/src/locales/en.ts +76 -0
- package/templates/apps/frontend-auth/src/locales/index.ts +14 -0
- package/templates/apps/frontend-auth/src/pages/forgot/page.tsx +51 -0
- package/templates/apps/frontend-auth/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-auth/src/pages/login/page.test.tsx +53 -0
- package/templates/apps/frontend-auth/src/pages/login/page.tsx +67 -0
- package/templates/apps/frontend-auth/src/pages/logout/page.tsx +28 -0
- package/templates/apps/frontend-auth/src/pages/magic/page.tsx +51 -0
- package/templates/apps/frontend-auth/src/pages/page.tsx +26 -0
- package/templates/apps/frontend-auth/src/pages/reset/page.test.tsx +51 -0
- package/templates/apps/frontend-auth/src/pages/reset/page.tsx +63 -0
- package/templates/apps/frontend-auth/src/pages/signup/page.tsx +60 -0
- package/templates/apps/frontend-auth/src/pages/verify/page.tsx +50 -0
- package/templates/apps/frontend-auth/template.json +6 -0
- package/templates/apps/frontend-auth/tsconfig.json +5 -0
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/README.md +47 -0
- package/templates/apps/frontend-cms/app.config.ts +37 -0
- package/templates/apps/frontend-cms/package.json +33 -0
- package/templates/apps/frontend-cms/src/config.ts +8 -0
- package/templates/apps/frontend-cms/src/globals.css +105 -0
- package/templates/apps/frontend-cms/src/globals.d.ts +6 -0
- package/templates/apps/frontend-cms/src/lib/api.ts +58 -0
- package/templates/apps/frontend-cms/src/locales/de.ts +50 -0
- package/templates/apps/frontend-cms/src/locales/en.ts +55 -0
- package/templates/apps/frontend-cms/src/locales/index.ts +14 -0
- package/templates/apps/frontend-cms/src/pages/(app)/error.tsx +18 -0
- package/templates/apps/frontend-cms/src/pages/(app)/layout.test.tsx +31 -0
- package/templates/apps/frontend-cms/src/pages/(app)/layout.tsx +62 -0
- package/templates/apps/frontend-cms/src/pages/(app)/not-found.tsx +14 -0
- package/templates/apps/frontend-cms/src/pages/(app)/page.test.tsx +110 -0
- package/templates/apps/frontend-cms/src/pages/(app)/page.tsx +159 -0
- package/templates/apps/frontend-cms/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-cms/src/pages/login/page.test.tsx +58 -0
- package/templates/apps/frontend-cms/src/pages/login/page.tsx +93 -0
- package/templates/apps/frontend-cms/template.json +6 -0
- package/templates/apps/frontend-cms/tsconfig.json +5 -0
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/README.md +71 -0
- package/templates/apps/frontend-portal/app.config.ts +37 -0
- package/templates/apps/frontend-portal/package.json +32 -0
- package/templates/apps/frontend-portal/src/config.ts +8 -0
- package/templates/apps/frontend-portal/src/globals.css +93 -0
- package/templates/apps/frontend-portal/src/globals.d.ts +6 -0
- package/templates/apps/frontend-portal/src/lib/api.ts +62 -0
- package/templates/apps/frontend-portal/src/locales/de.ts +86 -0
- package/templates/apps/frontend-portal/src/locales/en.ts +93 -0
- package/templates/apps/frontend-portal/src/locales/index.ts +14 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/api-keys/page.test.tsx +71 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/api-keys/page.tsx +124 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/billing/page.test.tsx +86 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/billing/page.tsx +97 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/error.tsx +19 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/invoices/page.tsx +45 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/layout.test.tsx +31 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/layout.tsx +63 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/not-found.tsx +15 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/page.tsx +39 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/profile/page.tsx +79 -0
- package/templates/apps/frontend-portal/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-portal/src/pages/login/page.test.tsx +58 -0
- package/templates/apps/frontend-portal/src/pages/login/page.tsx +96 -0
- package/templates/apps/frontend-portal/template.json +6 -0
- package/templates/apps/frontend-portal/tsconfig.json +5 -0
- package/templates/apps/frontend-saas/README.md +73 -0
- package/templates/apps/frontend-saas/app.config.ts +46 -0
- package/templates/apps/frontend-saas/package.json +32 -0
- package/templates/apps/frontend-saas/src/config.ts +8 -0
- package/templates/apps/frontend-saas/src/globals.css +85 -0
- package/templates/apps/frontend-saas/src/globals.d.ts +6 -0
- package/templates/apps/frontend-saas/src/lib/api.ts +47 -0
- package/templates/apps/frontend-saas/src/locales/de.ts +72 -0
- package/templates/apps/frontend-saas/src/locales/en.ts +81 -0
- package/templates/apps/frontend-saas/src/locales/index.ts +14 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/layout.tsx +30 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/login/page.test.tsx +58 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/login/page.tsx +96 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/page.tsx +27 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/billing/page.tsx +72 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/error.tsx +20 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/layout.test.tsx +32 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/layout.tsx +69 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/not-found.tsx +17 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/page.test.tsx +113 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/page.tsx +76 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/team/page.tsx +73 -0
- package/templates/apps/frontend-saas/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-saas/template.json +6 -0
- package/templates/apps/frontend-saas/tsconfig.json +5 -0
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/README.md +51 -0
- package/templates/apps/frontend-status/app.config.ts +39 -0
- package/templates/apps/frontend-status/package.json +32 -0
- package/templates/apps/frontend-status/src/config.ts +7 -0
- package/templates/apps/frontend-status/src/globals.css +70 -0
- package/templates/apps/frontend-status/src/globals.d.ts +6 -0
- package/templates/apps/frontend-status/src/lib/status.ts +81 -0
- package/templates/apps/frontend-status/src/locales/de.ts +41 -0
- package/templates/apps/frontend-status/src/locales/en.ts +45 -0
- package/templates/apps/frontend-status/src/locales/index.ts +13 -0
- package/templates/apps/frontend-status/src/pages/layout.tsx +27 -0
- package/templates/apps/frontend-status/src/pages/page.test.tsx +123 -0
- package/templates/apps/frontend-status/src/pages/page.tsx +136 -0
- package/templates/apps/frontend-status/template.json +6 -0
- package/templates/apps/frontend-status/tsconfig.json +11 -0
- package/templates/baselines/compose/docker-compose.prod.yml +15 -0
- package/templates/baselines/compose-mariadb/docker-compose.prod.yml +15 -0
- package/dist/apiBuild-BrjrVJJh.js +0 -2
- package/dist/dbCommand-DrzXimKf.js +0 -2
- package/dist/dev-DNkso403.js +0 -3
- package/dist/fileConventions-3bffWssN.js +0 -30
- package/dist/frameworkTableAssembly-lrjZtk0G.js +0 -2
- package/dist/inspect-gt8bq-Tz.js +0 -2
- package/dist/manifestBuild-ifczArzr.js +0 -2
- package/dist/serveCommand-DfkisVWP.js +0 -1310
|
@@ -785,6 +785,38 @@ export default function NotePage(): ReactNode {
|
|
|
785
785
|
|
|
786
786
|
`useLoaderData<T>()` returns the loader's resolved value, typed via the generic.
|
|
787
787
|
|
|
788
|
+
**On a page that declares a `loader`, the value is always there.** The router
|
|
789
|
+
never renders such a page without its data: a settled loader commits its data
|
|
790
|
+
and the displayed route together, a pending one shows the `Pending` skeleton
|
|
791
|
+
(or keeps the previous page), and one that threw renders the error subtree. You
|
|
792
|
+
do not need a guard, and adding one only hides a real mistake behind a `?.`.
|
|
793
|
+
|
|
794
|
+
**Calling it where no `loader` exists is an error, and it says so.** A level with
|
|
795
|
+
no `loader` export throws:
|
|
796
|
+
|
|
797
|
+
```text
|
|
798
|
+
useLoaderData() was called at a level that declares no `loader`. Export `loader`
|
|
799
|
+
from this page/layout, or — if this component is shared between routes that have
|
|
800
|
+
one and routes that do not — read it with `useOptionalLoaderData()` …
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
Note what is NOT an absence: an empty **result**. A loader returning
|
|
804
|
+
`{ items: [] }` returns exactly that. `undefined` never means "the query found
|
|
805
|
+
nothing" — it means "there is no loader at this level".
|
|
806
|
+
|
|
807
|
+
### `useOptionalLoaderData()` — for a component on both kinds of route
|
|
808
|
+
|
|
809
|
+
One case needs it: a component genuinely mounted both under routes that declare
|
|
810
|
+
a loader and routes that do not. It returns `undefined` instead of throwing.
|
|
811
|
+
|
|
812
|
+
```tsx
|
|
813
|
+
const data = useOptionalLoaderData<Data>()
|
|
814
|
+
const project = data?.project
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
A loader that legitimately resolves to `undefined` is not an error — both hooks
|
|
818
|
+
hand that `undefined` back. Only the missing `loader` throws.
|
|
819
|
+
|
|
788
820
|
## When loaders run
|
|
789
821
|
|
|
790
822
|
| renderMode | When loader runs |
|
|
@@ -830,6 +862,23 @@ export default function Profile() {
|
|
|
830
862
|
|
|
831
863
|
Because the SSR markup and the hydration render read the same loader value, they match — no hydration flicker — and you don't hand-build a seed store to bridge the two. This is distinct from `fallback`, whose value never came from the server and so keeps `loading: true`; use exactly one of the two.
|
|
832
864
|
|
|
865
|
+
> **Seed whatever the screen checks FIRST, not whatever is most interesting.**
|
|
866
|
+
> A component's server render stops at its OUTERMOST unsatisfied gate, so an
|
|
867
|
+
> unseeded subscription in an early branch hides every seeded one below it:
|
|
868
|
+
>
|
|
869
|
+
> ```tsx
|
|
870
|
+
> // Seeding `roadmap` changes nothing while this branch is the first one.
|
|
871
|
+
> if (availableYearsIdle || availableYearsLoading) return <Spinner />
|
|
872
|
+
> return <Roadmap data={roadmap} />
|
|
873
|
+
> ```
|
|
874
|
+
>
|
|
875
|
+
> The symptom is a page that still server-renders a spinner after you seeded
|
|
876
|
+
> the data you care about. Walk the component's early returns from the top and
|
|
877
|
+
> seed each subscription they read, or move the gate below the render you want.
|
|
878
|
+
> The same applies to an auth gate: a `useSubscription`-backed
|
|
879
|
+
> `AuthenticationProvider` has no data during a server render, so every page
|
|
880
|
+
> under it renders its loading state until that subscription is seeded too.
|
|
881
|
+
|
|
833
882
|
Client-side navigation is unchanged: moving to another route runs that route's
|
|
834
883
|
loaders in the browser as usual. A `spa` page has no server render, so its
|
|
835
884
|
loader runs on the client on first mount.
|
|
@@ -1152,14 +1201,22 @@ The third context field — `locale: string` — is the active i18n locale for t
|
|
|
1152
1201
|
|
|
1153
1202
|
```tsx
|
|
1154
1203
|
import { getCatalog } from '../lib/locale'
|
|
1155
|
-
import {
|
|
1204
|
+
import { seoAlternates } from '@voltro/web'
|
|
1156
1205
|
|
|
1157
1206
|
export const meta = ({ locale }: { locale: string }): PageMeta => {
|
|
1158
1207
|
const c = getCatalog(locale)
|
|
1159
1208
|
return {
|
|
1160
1209
|
title: c['seo.notes.title'] as string,
|
|
1161
1210
|
description: c['seo.notes.description'] as string,
|
|
1162
|
-
|
|
1211
|
+
// Canonical URL for THIS locale + a reciprocal `hreflang` alternate for
|
|
1212
|
+
// every locale (incl. `x-default`), spread straight into the meta.
|
|
1213
|
+
...seoAlternates({
|
|
1214
|
+
siteUrl: 'https://notes.example.com',
|
|
1215
|
+
path: '/notes',
|
|
1216
|
+
locale,
|
|
1217
|
+
locales: ['en', 'de'],
|
|
1218
|
+
defaultLocale: 'en',
|
|
1219
|
+
}),
|
|
1163
1220
|
}
|
|
1164
1221
|
}
|
|
1165
1222
|
```
|
|
@@ -1168,6 +1225,40 @@ export const meta = ({ locale }: { locale: string }): PageMeta => {
|
|
|
1168
1225
|
|
|
1169
1226
|
`loaderData` is the PAGE loader's result. Because `meta` runs server-side after the loader, the SSR'd `<head>` is already correct on first paint — no client-side title patching, no flash.
|
|
1170
1227
|
|
|
1228
|
+
## Technical SEO: canonical, hreflang, sitemap & robots
|
|
1229
|
+
|
|
1230
|
+
The framework ships the cheap technical-SEO primitives so an indexable app gets them without app-level plumbing.
|
|
1231
|
+
|
|
1232
|
+
**Canonical + hreflang helpers** (`@voltro/web`) are pure functions you call from `meta`. They are browser-safe, so importing them into a `*.page.tsx` never drags a server module into the client bundle:
|
|
1233
|
+
|
|
1234
|
+
- `canonicalUrl(siteUrl, path)` — one absolute canonical URL.
|
|
1235
|
+
- `seoAlternates({ siteUrl, path, locale, locales, defaultLocale })` — returns `{ canonical, links }` where `canonical` is this locale's URL and `links` is one `rel="alternate"` per locale (each an **absolute** URL, as Google requires) plus `hreflang="x-default"`. The alternate set is reciprocal across every locale — exactly what Google's [hreflang rules](https://developers.google.com/search/docs/specialty/international/localized-versions) want. The locale model is URL-PREFIX routing: the default locale on the bare path (`/notes`), other locales prefixed (`/de/notes`).
|
|
1236
|
+
|
|
1237
|
+
**Keeping a page out of the index** — set `noIndex` on its meta. It emits `<meta name="robots" content="noindex, nofollow">` AND excludes the route from the generated `sitemap.xml`:
|
|
1238
|
+
|
|
1239
|
+
```tsx
|
|
1240
|
+
export const meta: PageMeta = { title: 'Checkout', noIndex: true }
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
**`sitemap.xml` + `robots.txt`** are generated at `voltro build` from the prerendered routes. Turn them on with a `seo.siteUrl` in `app.config.ts`:
|
|
1244
|
+
|
|
1245
|
+
```ts
|
|
1246
|
+
export default {
|
|
1247
|
+
type: 'web' as const,
|
|
1248
|
+
name: 'Notes',
|
|
1249
|
+
locales: ['en', 'de'],
|
|
1250
|
+
defaultLocale: 'en',
|
|
1251
|
+
seo: {
|
|
1252
|
+
siteUrl: 'https://notes.example.com',
|
|
1253
|
+
disallow: ['/admin'], // extra robots Disallow prefixes (optional)
|
|
1254
|
+
},
|
|
1255
|
+
}
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
- **`sitemap.xml`** lists every prerendered route (minus `noIndex` ones). With 2+ `locales`, each URL carries the full `xhtml:link` alternate set. Written only when `seo.siteUrl` is set — absolute URLs are required.
|
|
1259
|
+
- **`robots.txt`** is generated even without `siteUrl`. Production allows all and advertises the sitemap; `voltro dev` — and any build with `VOLTRO_SEO_NOINDEX=1` (staging / preview deploys) — disallows everything, so a non-production surface never gets indexed by default.
|
|
1260
|
+
- A user-authored `public/sitemap.xml` / `public/robots.txt` always wins — the generator never overwrites one.
|
|
1261
|
+
|
|
1171
1262
|
## Examples
|
|
1172
1263
|
|
|
1173
1264
|
### Authenticated dashboard with cookie-driven loader
|
|
@@ -202,6 +202,18 @@ A combined create-or-edit screen is a three-line wrapper:
|
|
|
202
202
|
`{ fields, values, errors, isValid, pending, setValue, submit, reset }` for
|
|
203
203
|
100% custom JSX — the binding stays.
|
|
204
204
|
|
|
205
|
+
### Accessible by default
|
|
206
|
+
|
|
207
|
+
The built-in widgets render accessible HTML without any extra work: a `<label htmlFor>` tied to the control, `aria-required` on required fields (the visual `*` is `aria-hidden` — assistive tech learns "required" from the control, not from a spoken "star"), and, on error, `aria-invalid` + a `role="alert"` message associated via `aria-describedby`. A radio group ties its error to the whole `<fieldset>`.
|
|
208
|
+
|
|
209
|
+
Add **help text** by putting a `description` on the field's Schema — it renders as a hint and is associated to the control via `aria-describedby`, so a screen reader announces it with the label:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
Schema.Struct({
|
|
213
|
+
handle: Schema.String.annotations({ description: 'Shown on your public profile' }),
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
205
217
|
## `<DataTable>` — bind to a QUERY
|
|
206
218
|
|
|
207
219
|
Columns come from the query's output `Schema`; rows are a LIVE subscription
|
|
@@ -649,7 +649,7 @@ const execute = async (
|
|
|
649
649
|
// Post-commit fan-out → the `order.placed` trigger starts the
|
|
650
650
|
// `orders.fulfill` workflow. Guard `ctx.events` so a unit-test ctx
|
|
651
651
|
// without it doesn't throw.
|
|
652
|
-
await ctx.events?.
|
|
652
|
+
await ctx.events?.publish(orderPlaced, { orderId: row['id'] as string }, {
|
|
653
653
|
orderId: row['id'] as string, tenantId: input.tenantId,
|
|
654
654
|
})
|
|
655
655
|
|
|
@@ -372,6 +372,48 @@ What it fills and what it leaves alone:
|
|
|
372
372
|
|
|
373
373
|
There is no flag to turn the validation off — a test store that accepts rows production rejects is a fake testing itself. `fixtureRow` is a runtime filler for the loose `store.insert(name, row)` path. For **compile-time** payload typing (a missing required column caught as a type error at the call), use [`insertRow` / `upsertRow`](/docs/data/mutations) from `@voltro/database`, which check against `InferInsertRow<T>`.
|
|
374
374
|
|
|
375
|
+
## Subscribers — `makeSubscribeContext` + the change constructors
|
|
376
|
+
|
|
377
|
+
A `*.subscribe.ts` handler does not receive an `AppContext`. It receives a
|
|
378
|
+
change EVENT and a `SubscribeContext`, and both have constructors:
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
import { changeInsert, changeUpdate, changeDelete, changeSoftDelete, makeSubscribeContext, makeTestContext } from '@voltro/testing'
|
|
382
|
+
import subscriber from '../src/attendance.subscribe'
|
|
383
|
+
|
|
384
|
+
const app = makeTestContext({ store: { attendance: [{ id: 'a1', employeeId: 'e1' }] } })
|
|
385
|
+
const ctx = makeSubscribeContext({ id: 'attendance', store: app.store })
|
|
386
|
+
|
|
387
|
+
await subscriber.handler(changeUpdate('attendance', { id: 'a1', state: 'in' }, { id: 'a1', state: 'out' }), ctx)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**Build the event with a constructor, not with an object literal.** The
|
|
391
|
+
constructors put the semantics in the name, which is the half a literal cannot
|
|
392
|
+
give you:
|
|
393
|
+
|
|
394
|
+
| | `op` | `old` | `new` |
|
|
395
|
+
| --- | --- | --- | --- |
|
|
396
|
+
| `changeInsert(t, row)` | `insert` | `null` | the row |
|
|
397
|
+
| `changeUpdate(t, before, after)` | `update` | before | after |
|
|
398
|
+
| `changeDelete(t, row)` | `delete` | the row | `null` |
|
|
399
|
+
| `changeSoftDelete(t, row)` | **`update`** | `deletedAt: null` | `deletedAt` set |
|
|
400
|
+
|
|
401
|
+
`changeSoftDelete` is the reason this exists. There is no `op: 'softDelete'` and
|
|
402
|
+
there never will be — a soft delete is an ordinary update that sets `deletedAt`
|
|
403
|
+
— so a test author who does not know that writes a delete, and the test passes
|
|
404
|
+
against a stream the framework never emits. We shipped exactly that defect: a
|
|
405
|
+
feature that keys off soft deletes was inert in production while its own tests
|
|
406
|
+
were green, because they asserted against an event shape that does not exist.
|
|
407
|
+
|
|
408
|
+
`ctx.store` has **no default and throws when touched**. Pass
|
|
409
|
+
`makeTestContext().store` so the subscriber and the code under test share one; a
|
|
410
|
+
silent empty store would let a subscriber reading the wrong table pass its test,
|
|
411
|
+
which is the same silent-nothing the constructors exist to remove.
|
|
412
|
+
|
|
413
|
+
`ctx.publish` is absent unless you pass one, mirroring the real context — where
|
|
414
|
+
it is optional precisely so that reaching for it in an app that declares no
|
|
415
|
+
event is a type error.
|
|
416
|
+
|
|
375
417
|
## Subject + tenant re-scoping
|
|
376
418
|
|
|
377
419
|
`withSubject` and `withTenant` re-scope to a different principal for one block, sharing the **same** underlying data — so cross-subject reads exercise real tenant scoping, not a closure stub. This is how you prove isolation: write as one tenant, then assert another tenant can't see the row.
|