@voltro/cli 0.1.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 +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +20236 -0
- package/bin/voltro.mjs +43 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +9 -0
- package/dist/commands-CfPH2Wf4.js +18061 -0
- package/dist/frameworkInspectState-CX2250XB.js +86 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +5 -0
- package/dist/inspectState.d.ts +116 -0
- package/dist/inspectState.js +2 -0
- package/dist/startup.d.ts +26 -0
- package/dist/startup.js +2 -0
- package/dist/startupRunner-CRhuUl91.js +71 -0
- package/package.json +88 -0
- package/templates/AGENTS.core.md +258 -0
- package/templates/AGENTS.md +351 -0
- package/templates/agent-docs/_index.md +93 -0
- package/templates/agent-docs/_manifest.json +655 -0
- package/templates/agent-docs/ai.md +1845 -0
- package/templates/agent-docs/authentication.md +1788 -0
- package/templates/agent-docs/caching.md +624 -0
- package/templates/agent-docs/cli.md +1650 -0
- package/templates/agent-docs/configuration.md +295 -0
- package/templates/agent-docs/data.md +2172 -0
- package/templates/agent-docs/database/advancedqueries.md +1583 -0
- package/templates/agent-docs/database/columntypes.md +1200 -0
- package/templates/agent-docs/database/hosting.md +881 -0
- package/templates/agent-docs/database/migrations.md +2938 -0
- package/templates/agent-docs/database/misc.md +270 -0
- package/templates/agent-docs/database/overview.md +108 -0
- package/templates/agent-docs/database/querying.md +1622 -0
- package/templates/agent-docs/database/scaling.md +331 -0
- package/templates/agent-docs/database/schema.md +1458 -0
- package/templates/agent-docs/database/seedsdialects.md +1235 -0
- package/templates/agent-docs/database/transactions.md +285 -0
- package/templates/agent-docs/deployment.md +999 -0
- package/templates/agent-docs/internationalization.md +359 -0
- package/templates/agent-docs/introduction.md +438 -0
- package/templates/agent-docs/multi-tenancy.md +610 -0
- package/templates/agent-docs/observability.md +350 -0
- package/templates/agent-docs/plugins.md +1273 -0
- package/templates/agent-docs/reference.md +978 -0
- package/templates/agent-docs/routing.md +1553 -0
- package/templates/agent-docs/scheduling.md +666 -0
- package/templates/agent-docs/schema-driven-ui.md +607 -0
- package/templates/agent-docs/security.md +42 -0
- package/templates/agent-docs/templates/apibackends.md +3214 -0
- package/templates/agent-docs/templates/appshells.md +2062 -0
- package/templates/agent-docs/templates/custom.md +128 -0
- package/templates/agent-docs/templates/overview.md +122 -0
- package/templates/agent-docs/templates/serverless.md +315 -0
- package/templates/agent-docs/testing.md +376 -0
- package/templates/agent-docs/workflows.md +1351 -0
- package/templates/apps/api-ai/README.md +105 -0
- package/templates/apps/api-ai/actions/summarize.action.server.tsx +32 -0
- package/templates/apps/api-ai/actions/summarize.action.ts +33 -0
- package/templates/apps/api-ai/agents/support.agent.server.tsx +31 -0
- package/templates/apps/api-ai/agents/support.agent.tsx +27 -0
- package/templates/apps/api-ai/app.config.ts +57 -0
- package/templates/apps/api-ai/database/schema.ts +67 -0
- package/templates/apps/api-ai/package.json +27 -0
- package/templates/apps/api-ai/seeds/docs.seed.ts +67 -0
- package/templates/apps/api-ai/template.json +6 -0
- package/templates/apps/api-ai/tests/summarize.test.ts +50 -0
- package/templates/apps/api-ai/tools/searchDocs.tool.tsx +60 -0
- package/templates/apps/api-ai/tsconfig.json +5 -0
- package/templates/apps/api-auth/.env +17 -0
- package/templates/apps/api-auth/README.md +109 -0
- package/templates/apps/api-auth/actions/me.action.server.ts +20 -0
- package/templates/apps/api-auth/actions/me.action.ts +19 -0
- package/templates/apps/api-auth/app.config.ts +59 -0
- package/templates/apps/api-auth/database/schema.ts +26 -0
- package/templates/apps/api-auth/package.json +28 -0
- package/templates/apps/api-auth/template.json +6 -0
- package/templates/apps/api-auth/tests/me.test.ts +37 -0
- package/templates/apps/api-auth/tsconfig.json +5 -0
- package/templates/apps/api-backend/README.md +25 -0
- package/templates/apps/api-backend/app.config.ts +39 -0
- package/templates/apps/api-backend/database/schema.ts +57 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend/package.json +28 -0
- package/templates/apps/api-backend/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend/template.json +6 -0
- package/templates/apps/api-backend/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend/tsconfig.json +5 -0
- package/templates/apps/api-backend-deactivation/README.md +49 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.server.ts +17 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +18 -0
- package/templates/apps/api-backend-deactivation/app.config.ts +20 -0
- package/templates/apps/api-backend-deactivation/database/schema.ts +41 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.server.ts +6 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +16 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.server.ts +10 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +19 -0
- package/templates/apps/api-backend-deactivation/package.json +28 -0
- package/templates/apps/api-backend-deactivation/template.json +6 -0
- package/templates/apps/api-backend-deactivation/tests/users.deactivate.test.ts +47 -0
- package/templates/apps/api-backend-deactivation/tsconfig.json +5 -0
- package/templates/apps/api-backend-mail/README.md +39 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.server.ts +15 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +21 -0
- package/templates/apps/api-backend-mail/app.config.ts +34 -0
- package/templates/apps/api-backend-mail/database/schema.ts +57 -0
- package/templates/apps/api-backend-mail/emails/welcome.email.tsx +106 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mail/package.json +30 -0
- package/templates/apps/api-backend-mail/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mail/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mail/template.json +6 -0
- package/templates/apps/api-backend-mail/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mail/tsconfig.json +5 -0
- package/templates/apps/api-backend-mariadb/.env.example +35 -0
- package/templates/apps/api-backend-mariadb/README.md +31 -0
- package/templates/apps/api-backend-mariadb/app.config.ts +68 -0
- package/templates/apps/api-backend-mariadb/database/schema.ts +57 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mariadb/package.json +30 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mariadb/template.json +6 -0
- package/templates/apps/api-backend-mariadb/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mariadb/tsconfig.json +5 -0
- package/templates/apps/api-backend-storage/README.md +86 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +20 -0
- package/templates/apps/api-backend-storage/app.config.ts +38 -0
- package/templates/apps/api-backend-storage/database/schema.ts +57 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-storage/package.json +27 -0
- package/templates/apps/api-backend-storage/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-storage/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-storage/template.json +6 -0
- package/templates/apps/api-backend-storage/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-storage/tsconfig.json +5 -0
- package/templates/apps/api-data-advanced/.env +17 -0
- package/templates/apps/api-data-advanced/README.md +88 -0
- package/templates/apps/api-data-advanced/app.config.ts +56 -0
- package/templates/apps/api-data-advanced/database/actors.entity.ts +14 -0
- package/templates/apps/api-data-advanced/database/authors.entity.ts +30 -0
- package/templates/apps/api-data-advanced/database/authors.relations.ts +15 -0
- package/templates/apps/api-data-advanced/database/books.entity.ts +59 -0
- package/templates/apps/api-data-advanced/database/books.relations.ts +10 -0
- package/templates/apps/api-data-advanced/database/index.ts +23 -0
- package/templates/apps/api-data-advanced/database/tenants.entity.ts +11 -0
- package/templates/apps/api-data-advanced/package.json +28 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.server.ts +18 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +40 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.server.ts +17 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.ts +39 -0
- package/templates/apps/api-data-advanced/seeds/catalog.seed.ts +101 -0
- package/templates/apps/api-data-advanced/template.json +6 -0
- package/templates/apps/api-data-advanced/tests/queries.test.ts +124 -0
- package/templates/apps/api-data-advanced/tsconfig.json +5 -0
- package/templates/apps/api-durable/README.md +81 -0
- package/templates/apps/api-durable/aggregates/orderStats.aggregate.ts +49 -0
- package/templates/apps/api-durable/app.config.ts +41 -0
- package/templates/apps/api-durable/database/schema.ts +65 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.server.ts +43 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +23 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +45 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.ts +40 -0
- package/templates/apps/api-durable/package.json +28 -0
- package/templates/apps/api-durable/schedules/nightlyReport.cron.tsx +37 -0
- package/templates/apps/api-durable/startup/warm.startup.tsx +33 -0
- package/templates/apps/api-durable/subscribers/orderChanges.subscribe.ts +27 -0
- package/templates/apps/api-durable/template.json +6 -0
- package/templates/apps/api-durable/tests/orders.place.test.ts +91 -0
- package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +21 -0
- package/templates/apps/api-durable/tsconfig.json +5 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.server.tsx +103 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.tsx +25 -0
- package/templates/apps/api-feature-flags/README.md +63 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.server.ts +11 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.ts +18 -0
- package/templates/apps/api-feature-flags/app.config.ts +41 -0
- package/templates/apps/api-feature-flags/database/schema.ts +37 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.server.ts +29 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +28 -0
- package/templates/apps/api-feature-flags/package.json +29 -0
- package/templates/apps/api-feature-flags/template.json +6 -0
- package/templates/apps/api-feature-flags/tests/notes.create.test.ts +67 -0
- package/templates/apps/api-feature-flags/tsconfig.json +5 -0
- package/templates/apps/api-governance/.env +4 -0
- package/templates/apps/api-governance/README.md +61 -0
- package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -0
- package/templates/apps/api-governance/actions/profiles.get.action.ts +17 -0
- package/templates/apps/api-governance/app.config.ts +35 -0
- package/templates/apps/api-governance/database/schema.ts +41 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.server.ts +17 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +21 -0
- package/templates/apps/api-governance/package.json +29 -0
- package/templates/apps/api-governance/template.json +6 -0
- package/templates/apps/api-governance/tests/profiles.create.test.ts +59 -0
- package/templates/apps/api-governance/tsconfig.json +5 -0
- package/templates/apps/api-kv/README.md +100 -0
- package/templates/apps/api-kv/actions/sync.pull.action.server.ts +75 -0
- package/templates/apps/api-kv/actions/sync.pull.action.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.server.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.ts +22 -0
- package/templates/apps/api-kv/actions/sync.status.action.server.ts +28 -0
- package/templates/apps/api-kv/actions/sync.status.action.ts +17 -0
- package/templates/apps/api-kv/app.config.ts +48 -0
- package/templates/apps/api-kv/database/schema.ts +62 -0
- package/templates/apps/api-kv/package.json +28 -0
- package/templates/apps/api-kv/queries/events.list.query.server.ts +14 -0
- package/templates/apps/api-kv/queries/events.list.query.ts +23 -0
- package/templates/apps/api-kv/template.json +6 -0
- package/templates/apps/api-kv/tests/sync.test.ts +103 -0
- package/templates/apps/api-kv/tsconfig.json +5 -0
- package/templates/apps/api-moderation/README.md +46 -0
- package/templates/apps/api-moderation/app.config.ts +33 -0
- package/templates/apps/api-moderation/database/schema.ts +45 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.server.ts +8 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +20 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.server.ts +9 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +25 -0
- package/templates/apps/api-moderation/package.json +29 -0
- package/templates/apps/api-moderation/template.json +6 -0
- package/templates/apps/api-moderation/tests/posts.create.test.ts +79 -0
- package/templates/apps/api-moderation/tsconfig.json +5 -0
- package/templates/apps/api-observability/README.md +106 -0
- package/templates/apps/api-observability/app.config.ts +32 -0
- package/templates/apps/api-observability/database/schema.ts +38 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.server.ts +33 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.ts +15 -0
- package/templates/apps/api-observability/package.json +29 -0
- package/templates/apps/api-observability/queries/notes.list.query.server.ts +7 -0
- package/templates/apps/api-observability/queries/notes.list.query.ts +14 -0
- package/templates/apps/api-observability/template.json +6 -0
- package/templates/apps/api-observability/tests/notes.create.test.ts +36 -0
- package/templates/apps/api-observability/tsconfig.json +5 -0
- package/templates/apps/api-ratelimit/README.md +49 -0
- package/templates/apps/api-ratelimit/app.config.ts +42 -0
- package/templates/apps/api-ratelimit/database/schema.ts +37 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.server.ts +16 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +34 -0
- package/templates/apps/api-ratelimit/package.json +29 -0
- package/templates/apps/api-ratelimit/template.json +6 -0
- package/templates/apps/api-ratelimit/tests/notes.create.test.ts +69 -0
- package/templates/apps/api-ratelimit/tsconfig.json +5 -0
- package/templates/apps/api-rbac/README.md +59 -0
- package/templates/apps/api-rbac/app.config.ts +48 -0
- package/templates/apps/api-rbac/database/schema.ts +37 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.server.ts +22 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.ts +26 -0
- package/templates/apps/api-rbac/package.json +29 -0
- package/templates/apps/api-rbac/template.json +6 -0
- package/templates/apps/api-rbac/tests/notes.create.test.ts +80 -0
- package/templates/apps/api-rbac/tsconfig.json +5 -0
- package/templates/apps/api-rest/README.md +85 -0
- package/templates/apps/api-rest/app.config.ts +70 -0
- package/templates/apps/api-rest/database/schema.ts +52 -0
- package/templates/apps/api-rest/lib/product.ts +30 -0
- package/templates/apps/api-rest/package.json +27 -0
- package/templates/apps/api-rest/routes/v1/products.create.route.tsx +45 -0
- package/templates/apps/api-rest/routes/v1/products.delete.route.tsx +25 -0
- package/templates/apps/api-rest/routes/v1/products.get.route.tsx +29 -0
- package/templates/apps/api-rest/routes/v1/products.list.route.tsx +43 -0
- package/templates/apps/api-rest/template.json +6 -0
- package/templates/apps/api-rest/tests/products.create.test.ts +53 -0
- package/templates/apps/api-rest/tsconfig.json +5 -0
- package/templates/apps/api-saas/README.md +106 -0
- package/templates/apps/api-saas/app.config.ts +51 -0
- package/templates/apps/api-saas/database/schema.ts +50 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.server.ts +52 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.ts +18 -0
- package/templates/apps/api-saas/package.json +31 -0
- package/templates/apps/api-saas/queries/projects.list.query.server.ts +7 -0
- package/templates/apps/api-saas/queries/projects.list.query.ts +18 -0
- package/templates/apps/api-saas/template.json +6 -0
- package/templates/apps/api-saas/tests/projects.create.test.ts +47 -0
- package/templates/apps/api-saas/tsconfig.json +5 -0
- package/templates/apps/api-search/README.md +68 -0
- package/templates/apps/api-search/app.config.ts +31 -0
- package/templates/apps/api-search/database/schema.ts +47 -0
- package/templates/apps/api-search/lib/search.ts +27 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.server.ts +19 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.ts +35 -0
- package/templates/apps/api-search/package.json +29 -0
- package/templates/apps/api-search/queries/articles.list.query.server.ts +14 -0
- package/templates/apps/api-search/queries/articles.list.query.ts +19 -0
- package/templates/apps/api-search/seeds/articles.seed.ts +41 -0
- package/templates/apps/api-search/startup/searchBackfill.startup.tsx +24 -0
- package/templates/apps/api-search/template.json +6 -0
- package/templates/apps/api-search/tests/articles.create.test.ts +69 -0
- package/templates/apps/api-search/tsconfig.json +5 -0
- package/templates/apps/api-versioning/README.md +51 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.server.ts +13 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.ts +14 -0
- package/templates/apps/api-versioning/actions/documents.history.action.server.ts +15 -0
- package/templates/apps/api-versioning/actions/documents.history.action.ts +17 -0
- package/templates/apps/api-versioning/app.config.ts +24 -0
- package/templates/apps/api-versioning/database/schema.ts +38 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.server.ts +8 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +21 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.server.ts +9 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +21 -0
- package/templates/apps/api-versioning/package.json +29 -0
- package/templates/apps/api-versioning/template.json +6 -0
- package/templates/apps/api-versioning/tests/documents.create.test.ts +37 -0
- package/templates/apps/api-versioning/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/.env +6 -0
- package/templates/apps/api-webhooks/README.md +106 -0
- package/templates/apps/api-webhooks/app.config.ts +16 -0
- package/templates/apps/api-webhooks/database/schema.ts +49 -0
- package/templates/apps/api-webhooks/events/order.completed.webhook.tsx +22 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.server.ts +37 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +15 -0
- package/templates/apps/api-webhooks/package.json +28 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.server.ts +7 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.ts +15 -0
- package/templates/apps/api-webhooks/template.json +6 -0
- package/templates/apps/api-webhooks/tests/orders.fulfill.test.ts +51 -0
- package/templates/apps/api-webhooks/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/webhooks/orders.webhook.tsx +35 -0
- package/templates/apps/changelog/README.md +77 -0
- package/templates/apps/changelog/app.config.ts +27 -0
- package/templates/apps/changelog/content/releases/0.1.0.mdx +19 -0
- package/templates/apps/changelog/content/releases/0.2.0.mdx +28 -0
- package/templates/apps/changelog/package.json +30 -0
- package/templates/apps/changelog/scripts/generate-rss.mjs +38 -0
- package/templates/apps/changelog/src/globals.css +66 -0
- package/templates/apps/changelog/src/globals.d.ts +16 -0
- package/templates/apps/changelog/src/lib/locale.test.ts +72 -0
- package/templates/apps/changelog/src/lib/locale.ts +55 -0
- package/templates/apps/changelog/src/lib/releases.ts +21 -0
- package/templates/apps/changelog/src/locales/de.ts +22 -0
- package/templates/apps/changelog/src/locales/en.ts +30 -0
- package/templates/apps/changelog/src/pages/[locale]/[slug].tsx +24 -0
- package/templates/apps/changelog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +95 -0
- package/templates/apps/changelog/src/pages/[slug].test.tsx +127 -0
- package/templates/apps/changelog/src/pages/[slug].tsx +52 -0
- package/templates/apps/changelog/src/pages/index.test.tsx +112 -0
- package/templates/apps/changelog/src/pages/index.tsx +55 -0
- package/templates/apps/changelog/src/pages/layout.tsx +84 -0
- package/templates/apps/changelog/template.json +6 -0
- package/templates/apps/changelog/tsconfig.json +11 -0
- package/templates/apps/edge-functions/README.md +55 -0
- package/templates/apps/edge-functions/functions/aiComplete.serverless.ts +58 -0
- package/templates/apps/edge-functions/functions/currencyConvert.serverless.ts +47 -0
- package/templates/apps/edge-functions/functions/geoGreeting.serverless.ts +44 -0
- package/templates/apps/edge-functions/functions/health.serverless.ts +29 -0
- package/templates/apps/edge-functions/functions/resolveLink.serverless.ts +32 -0
- package/templates/apps/edge-functions/functions/shareLink.serverless.ts +30 -0
- package/templates/apps/edge-functions/functions/slackNotify.serverless.ts +40 -0
- package/templates/apps/edge-functions/functions/verifySignature.serverless.ts +52 -0
- package/templates/apps/edge-functions/package.json +21 -0
- package/templates/apps/edge-functions/template.json +6 -0
- package/templates/apps/edge-functions/tsconfig.json +10 -0
- package/templates/apps/frontend-admin/README.md +40 -0
- package/templates/apps/frontend-admin/app.config.ts +37 -0
- package/templates/apps/frontend-admin/package.json +30 -0
- package/templates/apps/frontend-admin/src/config.ts +8 -0
- package/templates/apps/frontend-admin/src/globals.css +76 -0
- package/templates/apps/frontend-admin/src/globals.d.ts +6 -0
- package/templates/apps/frontend-admin/src/lib/admin.ts +16 -0
- package/templates/apps/frontend-admin/src/lib/auth.ts +24 -0
- package/templates/apps/frontend-admin/src/locales/de.ts +67 -0
- package/templates/apps/frontend-admin/src/locales/en.ts +79 -0
- package/templates/apps/frontend-admin/src/locales/index.ts +15 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.tsx +32 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +29 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.test.tsx +73 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.tsx +35 -0
- package/templates/apps/frontend-admin/src/pages/admin/[entity].tsx +121 -0
- package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +119 -0
- package/templates/apps/frontend-admin/src/pages/admin/error.tsx +20 -0
- package/templates/apps/frontend-admin/src/pages/admin/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.test.tsx +88 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.tsx +65 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.test.tsx +114 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +97 -0
- package/templates/apps/frontend-admin/src/pages/admin/loading.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/admin/not-found.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-admin/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-admin/template.json +6 -0
- package/templates/apps/frontend-admin/tsconfig.json +11 -0
- package/templates/apps/frontend-app/README.md +98 -0
- package/templates/apps/frontend-app/app.config.ts +43 -0
- package/templates/apps/frontend-app/package.json +30 -0
- package/templates/apps/frontend-app/src/locales/de.ts +36 -0
- package/templates/apps/frontend-app/src/locales/en.ts +43 -0
- package/templates/apps/frontend-app/src/locales/index.ts +15 -0
- package/templates/apps/frontend-app/src/pages/index.test.tsx +167 -0
- package/templates/apps/frontend-app/src/pages/index.tsx +136 -0
- package/templates/apps/frontend-app/src/pages/layout.tsx +41 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.test.tsx +99 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.tsx +74 -0
- package/templates/apps/frontend-app/template.json +6 -0
- package/templates/apps/frontend-app/tsconfig.json +11 -0
- package/templates/apps/frontend-blank/README.md +18 -0
- package/templates/apps/frontend-blank/app.config.ts +29 -0
- package/templates/apps/frontend-blank/package.json +29 -0
- package/templates/apps/frontend-blank/src/locales/de.ts +15 -0
- package/templates/apps/frontend-blank/src/locales/en.ts +22 -0
- package/templates/apps/frontend-blank/src/locales/index.ts +15 -0
- package/templates/apps/frontend-blank/src/pages/index.test.tsx +55 -0
- package/templates/apps/frontend-blank/src/pages/index.tsx +27 -0
- package/templates/apps/frontend-blank/src/pages/layout.test.tsx +54 -0
- package/templates/apps/frontend-blank/src/pages/layout.tsx +35 -0
- package/templates/apps/frontend-blank/template.json +6 -0
- package/templates/apps/frontend-blank/tsconfig.json +11 -0
- package/templates/apps/frontend-contact/README.md +65 -0
- package/templates/apps/frontend-contact/app.config.ts +25 -0
- package/templates/apps/frontend-contact/functions/sendMessage.serverless.ts +69 -0
- package/templates/apps/frontend-contact/package.json +33 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.test.tsx +142 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +104 -0
- package/templates/apps/frontend-contact/src/config.ts +12 -0
- package/templates/apps/frontend-contact/src/globals.css +84 -0
- package/templates/apps/frontend-contact/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-contact/src/locales/de.ts +26 -0
- package/templates/apps/frontend-contact/src/locales/en.ts +29 -0
- package/templates/apps/frontend-contact/src/pages/[locale]/index.tsx +14 -0
- package/templates/apps/frontend-contact/src/pages/index.test.tsx +70 -0
- package/templates/apps/frontend-contact/src/pages/index.tsx +63 -0
- package/templates/apps/frontend-contact/src/pages/layout.tsx +65 -0
- package/templates/apps/frontend-contact/template.json +6 -0
- package/templates/apps/frontend-contact/tsconfig.json +11 -0
- package/templates/apps/frontend-dashboard/README.md +54 -0
- package/templates/apps/frontend-dashboard/app.config.ts +39 -0
- package/templates/apps/frontend-dashboard/package.json +29 -0
- package/templates/apps/frontend-dashboard/src/config.ts +8 -0
- package/templates/apps/frontend-dashboard/src/globals.css +72 -0
- package/templates/apps/frontend-dashboard/src/globals.d.ts +6 -0
- package/templates/apps/frontend-dashboard/src/lib/auth.ts +27 -0
- package/templates/apps/frontend-dashboard/src/locales/de.ts +49 -0
- package/templates/apps/frontend-dashboard/src/locales/en.ts +60 -0
- package/templates/apps/frontend-dashboard/src/locales/index.ts +15 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.tsx +34 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +32 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.test.tsx +74 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.tsx +40 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/error.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.tsx +38 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.test.tsx +88 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/loading.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/not-found.tsx +19 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.test.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.tsx +35 -0
- package/templates/apps/frontend-dashboard/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-dashboard/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-dashboard/template.json +6 -0
- package/templates/apps/frontend-dashboard/tsconfig.json +11 -0
- package/templates/apps/frontend-docs/README.md +19 -0
- package/templates/apps/frontend-docs/app.config.ts +25 -0
- package/templates/apps/frontend-docs/package.json +29 -0
- package/templates/apps/frontend-docs/src/globals.css +32 -0
- package/templates/apps/frontend-docs/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-docs/src/lib/locale.ts +56 -0
- package/templates/apps/frontend-docs/src/locales/de.ts +24 -0
- package/templates/apps/frontend-docs/src/locales/en.ts +28 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug].tsx +18 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +67 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].test.tsx +115 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].tsx +82 -0
- package/templates/apps/frontend-docs/src/pages/index.test.tsx +90 -0
- package/templates/apps/frontend-docs/src/pages/index.tsx +45 -0
- package/templates/apps/frontend-docs/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-docs/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-docs/template.json +6 -0
- package/templates/apps/frontend-docs/tsconfig.json +11 -0
- package/templates/apps/frontend-i18n/README.md +61 -0
- package/templates/apps/frontend-i18n/app.config.ts +33 -0
- package/templates/apps/frontend-i18n/package.json +28 -0
- package/templates/apps/frontend-i18n/src/globals.css +46 -0
- package/templates/apps/frontend-i18n/src/globals.d.ts +6 -0
- package/templates/apps/frontend-i18n/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-i18n/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-i18n/src/locales/de.ts +24 -0
- package/templates/apps/frontend-i18n/src/locales/en.ts +25 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/about.tsx +11 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +50 -0
- package/templates/apps/frontend-i18n/src/pages/about.test.tsx +75 -0
- package/templates/apps/frontend-i18n/src/pages/about.tsx +31 -0
- package/templates/apps/frontend-i18n/src/pages/index.test.tsx +102 -0
- package/templates/apps/frontend-i18n/src/pages/index.tsx +43 -0
- package/templates/apps/frontend-i18n/src/pages/layout.test.tsx +86 -0
- package/templates/apps/frontend-i18n/src/pages/layout.tsx +70 -0
- package/templates/apps/frontend-i18n/template.json +6 -0
- package/templates/apps/frontend-i18n/tsconfig.json +11 -0
- package/templates/apps/frontend-landing/README.md +17 -0
- package/templates/apps/frontend-landing/app.config.ts +25 -0
- package/templates/apps/frontend-landing/package.json +29 -0
- package/templates/apps/frontend-landing/src/globals.css +23 -0
- package/templates/apps/frontend-landing/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-landing/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +24 -0
- package/templates/apps/frontend-landing/src/locales/en.ts +26 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +37 -0
- package/templates/apps/frontend-landing/src/pages/index.test.tsx +110 -0
- package/templates/apps/frontend-landing/src/pages/index.tsx +75 -0
- package/templates/apps/frontend-landing/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-landing/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-landing/template.json +6 -0
- package/templates/apps/frontend-landing/tsconfig.json +11 -0
- package/templates/apps/frontend-spa/README.md +45 -0
- package/templates/apps/frontend-spa/app.config.ts +27 -0
- package/templates/apps/frontend-spa/package.json +29 -0
- package/templates/apps/frontend-spa/src/globals.css +84 -0
- package/templates/apps/frontend-spa/src/locales/de.ts +22 -0
- package/templates/apps/frontend-spa/src/locales/en.ts +29 -0
- package/templates/apps/frontend-spa/src/locales/index.ts +15 -0
- package/templates/apps/frontend-spa/src/pages/index.test.tsx +137 -0
- package/templates/apps/frontend-spa/src/pages/index.tsx +123 -0
- package/templates/apps/frontend-spa/src/pages/layout.tsx +27 -0
- package/templates/apps/frontend-spa/template.json +6 -0
- package/templates/apps/frontend-spa/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr/README.md +68 -0
- package/templates/apps/frontend-ssr/app.config.ts +32 -0
- package/templates/apps/frontend-ssr/package.json +29 -0
- package/templates/apps/frontend-ssr/src/globals.css +67 -0
- package/templates/apps/frontend-ssr/src/locales/de.ts +41 -0
- package/templates/apps/frontend-ssr/src/locales/en.ts +54 -0
- package/templates/apps/frontend-ssr/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.test.tsx +69 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.tsx +54 -0
- package/templates/apps/frontend-ssr/src/pages/feed.test.tsx +73 -0
- package/templates/apps/frontend-ssr/src/pages/feed.tsx +64 -0
- package/templates/apps/frontend-ssr/src/pages/index.test.tsx +89 -0
- package/templates/apps/frontend-ssr/src/pages/index.tsx +72 -0
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +37 -0
- package/templates/apps/frontend-ssr/template.json +6 -0
- package/templates/apps/frontend-ssr/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr-api/README.md +50 -0
- package/templates/apps/frontend-ssr-api/app.config.ts +43 -0
- package/templates/apps/frontend-ssr-api/package.json +30 -0
- package/templates/apps/frontend-ssr-api/src/globals.css +38 -0
- package/templates/apps/frontend-ssr-api/src/globals.d.ts +6 -0
- package/templates/apps/frontend-ssr-api/src/locales/de.ts +20 -0
- package/templates/apps/frontend-ssr-api/src/locales/en.ts +31 -0
- package/templates/apps/frontend-ssr-api/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.test.tsx +105 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.tsx +83 -0
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +28 -0
- package/templates/apps/frontend-ssr-api/template.json +6 -0
- package/templates/apps/frontend-ssr-api/tsconfig.json +11 -0
- package/templates/apps/frontend-static-blog/README.md +49 -0
- package/templates/apps/frontend-static-blog/app.config.ts +34 -0
- package/templates/apps/frontend-static-blog/package.json +28 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.test.tsx +65 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +35 -0
- package/templates/apps/frontend-static-blog/src/content/posts.ts +64 -0
- package/templates/apps/frontend-static-blog/src/globals.css +75 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-static-blog/src/locales/de.ts +19 -0
- package/templates/apps/frontend-static-blog/src/locales/en.ts +26 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug].tsx +20 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +62 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].test.tsx +116 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].tsx +69 -0
- package/templates/apps/frontend-static-blog/src/pages/index.test.tsx +100 -0
- package/templates/apps/frontend-static-blog/src/pages/index.tsx +58 -0
- package/templates/apps/frontend-static-blog/src/pages/layout.tsx +63 -0
- package/templates/apps/frontend-static-blog/template.json +6 -0
- package/templates/apps/frontend-static-blog/tsconfig.json +11 -0
- package/templates/baselines/bare/.env.example +37 -0
- package/templates/baselines/bare/README.md +47 -0
- package/templates/baselines/bare/baseline.json +31 -0
- package/templates/baselines/bare/deploy/README.md +43 -0
- package/templates/baselines/bare/deploy/voltro.service.example +36 -0
- package/templates/baselines/compose/.env.example +46 -0
- package/templates/baselines/compose/README.md +69 -0
- package/templates/baselines/compose/baseline.json +51 -0
- package/templates/baselines/compose/docker/.dockerignore +38 -0
- package/templates/baselines/compose/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose/docker-compose.dev.yml +89 -0
- package/templates/baselines/compose/docker-compose.prod.yml +87 -0
- package/templates/baselines/compose/docker-compose.yml +41 -0
- package/templates/baselines/compose-mariadb/.env.example +57 -0
- package/templates/baselines/compose-mariadb/README.md +78 -0
- package/templates/baselines/compose-mariadb/baseline.json +51 -0
- package/templates/baselines/compose-mariadb/docker/.dockerignore +38 -0
- package/templates/baselines/compose-mariadb/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose-mariadb/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose-mariadb/docker/mariadb-init.sql +6 -0
- package/templates/baselines/compose-mariadb/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose-mariadb/docker-compose.dev.yml +117 -0
- package/templates/baselines/compose-mariadb/docker-compose.prod.yml +114 -0
- package/templates/baselines/compose-mariadb/docker-compose.yml +79 -0
- package/templates/baselines/helm/.env.example +39 -0
- package/templates/baselines/helm/README.md +98 -0
- package/templates/baselines/helm/baseline.json +53 -0
- package/templates/baselines/helm/charts/voltro-app/.helmignore +10 -0
- package/templates/baselines/helm/charts/voltro-app/Chart.yaml +10 -0
- package/templates/baselines/helm/charts/voltro-app/templates/_helpers.tpl +36 -0
- package/templates/baselines/helm/charts/voltro-app/templates/configmap.yaml +13 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-api.yaml +120 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-web.yaml +45 -0
- package/templates/baselines/helm/charts/voltro-app/templates/ingress.yaml +37 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-service.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-statefulset.yaml +73 -0
- package/templates/baselines/helm/charts/voltro-app/templates/secret.yaml +33 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-api.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-web.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/values-dev.yaml +21 -0
- package/templates/baselines/helm/charts/voltro-app/values-prod.yaml +58 -0
- package/templates/baselines/helm/charts/voltro-app/values-staging.yaml +25 -0
- package/templates/baselines/helm/charts/voltro-app/values.yaml +109 -0
- package/templates/baselines/helm/deploy/README.md +94 -0
- package/templates/patches/@effect__cluster@0.59.0.patch +262 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/` — the SPA bill-splitter. A 100% client-side page (`renderMode='spa'`)
|
|
4
|
+
// built from React state + localStorage; no rpc client, no loader, no
|
|
5
|
+
// framework hooks. We assert the config export, that it renders, and — the
|
|
6
|
+
// real observable behavior — that entering a bill + choosing a tip preset +
|
|
7
|
+
// people count updates the computed per-person total.
|
|
8
|
+
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test } from 'vitest'
|
|
10
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
11
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
12
|
+
import { I18nProvider } from '@voltro/i18n'
|
|
13
|
+
import enCatalog from '../locales/en'
|
|
14
|
+
|
|
15
|
+
import BillSplitter, { renderMode } from './index'
|
|
16
|
+
|
|
17
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
18
|
+
|
|
19
|
+
let container: HTMLDivElement
|
|
20
|
+
let root: Root
|
|
21
|
+
|
|
22
|
+
// The page uses <T>, so it needs an <I18nProvider>. Wrap with the real English
|
|
23
|
+
// catalog (the framework auto-wires this provider in the running app).
|
|
24
|
+
const render = (node: ReactNode): void => {
|
|
25
|
+
container = document.createElement('div')
|
|
26
|
+
document.body.appendChild(container)
|
|
27
|
+
act(() => {
|
|
28
|
+
root = createRoot(container)
|
|
29
|
+
root.render(
|
|
30
|
+
createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Native value setter so React's onChange sees the programmatic update.
|
|
36
|
+
const setInput = (el: HTMLInputElement, value: string): void => {
|
|
37
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
38
|
+
window.HTMLInputElement.prototype,
|
|
39
|
+
'value',
|
|
40
|
+
)!.set!
|
|
41
|
+
act(() => {
|
|
42
|
+
setter.call(el, value)
|
|
43
|
+
el.dispatchEvent(new Event('input', { bubbles: true }))
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// This Node build's jsdom does not provide localStorage (it needs
|
|
48
|
+
// --localstorage-file). The page talks to the standard Web Storage API, so we
|
|
49
|
+
// install a spec-compliant in-memory Storage — the same surface a browser gives
|
|
50
|
+
// it — and exercise the real persistence path against it.
|
|
51
|
+
let hadLocalStorage: boolean
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
hadLocalStorage = typeof window.localStorage !== 'undefined'
|
|
55
|
+
if (!hadLocalStorage) {
|
|
56
|
+
const store = new Map<string, string>()
|
|
57
|
+
const storage: Storage = {
|
|
58
|
+
get length() {
|
|
59
|
+
return store.size
|
|
60
|
+
},
|
|
61
|
+
clear: () => store.clear(),
|
|
62
|
+
getItem: (k) => (store.has(k) ? store.get(k)! : null),
|
|
63
|
+
key: (i) => Array.from(store.keys())[i] ?? null,
|
|
64
|
+
removeItem: (k) => {
|
|
65
|
+
store.delete(k)
|
|
66
|
+
},
|
|
67
|
+
setItem: (k, v) => {
|
|
68
|
+
store.set(k, String(v))
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
Object.defineProperty(window, 'localStorage', { configurable: true, value: storage })
|
|
72
|
+
}
|
|
73
|
+
window.localStorage.clear()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
if (root) act(() => root.unmount())
|
|
78
|
+
container?.remove()
|
|
79
|
+
document.body.innerHTML = ''
|
|
80
|
+
if (!hadLocalStorage) {
|
|
81
|
+
Reflect.deleteProperty(window, 'localStorage')
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('index — config export', () => {
|
|
86
|
+
test('is a client-only SPA page', () => {
|
|
87
|
+
expect(renderMode).toBe('spa')
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('index — render', () => {
|
|
92
|
+
test('renders the bill-splitter heading and the tip presets', () => {
|
|
93
|
+
render(createElement(BillSplitter))
|
|
94
|
+
expect(container.querySelector('h1')!.textContent).toBe('Bill splitter')
|
|
95
|
+
const presetButtons = Array.from(
|
|
96
|
+
container.querySelectorAll('.presets button'),
|
|
97
|
+
).map((b) => b.textContent)
|
|
98
|
+
expect(presetButtons).toEqual(['10%', '15%', '18%', '20%'])
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('index — computed split', () => {
|
|
103
|
+
test('computes the per-person total from bill, tip preset, and people', () => {
|
|
104
|
+
render(createElement(BillSplitter))
|
|
105
|
+
|
|
106
|
+
// $100 bill.
|
|
107
|
+
setInput(container.querySelector('#bill') as HTMLInputElement, '100')
|
|
108
|
+
|
|
109
|
+
// Choose the 20% preset.
|
|
110
|
+
const twentyPct = Array.from(container.querySelectorAll('.presets button')).find(
|
|
111
|
+
(b) => b.textContent === '20%',
|
|
112
|
+
) as HTMLButtonElement
|
|
113
|
+
act(() => { twentyPct.dispatchEvent(new MouseEvent('click', { bubbles: true })) })
|
|
114
|
+
|
|
115
|
+
// 4 people.
|
|
116
|
+
setInput(container.querySelector('#people') as HTMLInputElement, '4')
|
|
117
|
+
|
|
118
|
+
// total = 100 + 20% = 120; per-person = 30.
|
|
119
|
+
const perPerson = container.querySelector('.result .big')!.textContent ?? ''
|
|
120
|
+
expect(perPerson).toContain('30')
|
|
121
|
+
expect(container.textContent).toContain('120') // total tip+bill shown
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('persists the chosen tip % to localStorage', () => {
|
|
125
|
+
render(createElement(BillSplitter))
|
|
126
|
+
const tenPct = Array.from(container.querySelectorAll('.presets button')).find(
|
|
127
|
+
(b) => b.textContent === '10%',
|
|
128
|
+
) as HTMLButtonElement
|
|
129
|
+
act(() => { tenPct.dispatchEvent(new MouseEvent('click', { bubbles: true })) })
|
|
130
|
+
const stored: string[] = []
|
|
131
|
+
for (let i = 0; i < window.localStorage.length; i++) {
|
|
132
|
+
const key = window.localStorage.key(i)
|
|
133
|
+
if (key !== null) stored.push(window.localStorage.getItem(key) ?? '')
|
|
134
|
+
}
|
|
135
|
+
expect(stored).toContain('10')
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// A pure client-side SPA — a bill splitter.
|
|
2
|
+
//
|
|
3
|
+
// `renderMode = 'spa'` tells `voltro build` NOT to pre-render this page:
|
|
4
|
+
// the client renders it on load. That is the right choice here because the
|
|
5
|
+
// whole page is interactive and its state lives in the browser
|
|
6
|
+
// (localStorage) — an SSR'd first paint would just be discarded on
|
|
7
|
+
// hydration, so rendering it on the server buys nothing.
|
|
8
|
+
//
|
|
9
|
+
// No backend, no rpc client, no loader. Just React + the browser.
|
|
10
|
+
|
|
11
|
+
import type { ReactNode } from 'react'
|
|
12
|
+
import { useEffect, useState } from 'react'
|
|
13
|
+
import type { PageMeta } from '@voltro/web'
|
|
14
|
+
import { T } from '@voltro/i18n'
|
|
15
|
+
import { getCatalog } from '../locales'
|
|
16
|
+
|
|
17
|
+
// Locale-aware tab title: @voltro/web resolves meta({ locale }) from the
|
|
18
|
+
// active locale (the `voltro:lang` cookie in this cookie-i18n app).
|
|
19
|
+
export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({
|
|
20
|
+
title: getCatalog(locale)['meta.home.title'],
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export const renderMode = 'spa' as const
|
|
24
|
+
// `interactive` defaults to 'full' — every component hydrates. That is
|
|
25
|
+
// correct for an SPA where the whole tree is interactive (don't reach for
|
|
26
|
+
// islands here; islands only help when most of the page is static).
|
|
27
|
+
|
|
28
|
+
const TIP_PRESETS = [10, 15, 18, 20] as const
|
|
29
|
+
const STORAGE_KEY = '{{appName}}:last-tip'
|
|
30
|
+
|
|
31
|
+
const money = (n: number): string =>
|
|
32
|
+
Number.isFinite(n) ? n.toLocaleString(undefined, { style: 'currency', currency: 'USD' }) : '—'
|
|
33
|
+
|
|
34
|
+
export default function BillSplitter(): ReactNode {
|
|
35
|
+
const [bill, setBill] = useState('')
|
|
36
|
+
const [tipPct, setTipPct] = useState(18)
|
|
37
|
+
const [people, setPeople] = useState(2)
|
|
38
|
+
|
|
39
|
+
// Restore the last tip % from localStorage — client-only, so read it in
|
|
40
|
+
// an effect (it never runs on the server).
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const saved = Number(window.localStorage.getItem(STORAGE_KEY))
|
|
43
|
+
if (Number.isFinite(saved) && saved > 0) setTipPct(saved)
|
|
44
|
+
}, [])
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
window.localStorage.setItem(STORAGE_KEY, String(tipPct))
|
|
48
|
+
}, [tipPct])
|
|
49
|
+
|
|
50
|
+
const billNum = Math.max(0, Number(bill) || 0)
|
|
51
|
+
const headcount = Math.max(1, people)
|
|
52
|
+
const tip = billNum * (tipPct / 100)
|
|
53
|
+
const total = billNum + tip
|
|
54
|
+
const perPerson = total / headcount
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="card">
|
|
58
|
+
<h1><T id="home.heading" /></h1>
|
|
59
|
+
<p className="muted"><T id="home.subtitle" /></p>
|
|
60
|
+
|
|
61
|
+
<label htmlFor="bill"><T id="field.bill" /></label>
|
|
62
|
+
<input
|
|
63
|
+
id="bill"
|
|
64
|
+
type="number"
|
|
65
|
+
inputMode="decimal"
|
|
66
|
+
min="0"
|
|
67
|
+
step="0.01"
|
|
68
|
+
placeholder="0.00"
|
|
69
|
+
value={bill}
|
|
70
|
+
onChange={(e) => setBill(e.target.value)}
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
<label><T id="field.tip" /></label>
|
|
74
|
+
<div className="presets">
|
|
75
|
+
{TIP_PRESETS.map((p) => (
|
|
76
|
+
<button
|
|
77
|
+
key={p}
|
|
78
|
+
type="button"
|
|
79
|
+
className={p === tipPct ? 'active' : ''}
|
|
80
|
+
onClick={() => setTipPct(p)}
|
|
81
|
+
>
|
|
82
|
+
{p}%
|
|
83
|
+
</button>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className="row">
|
|
88
|
+
<div>
|
|
89
|
+
<label htmlFor="custom"><T id="field.custom" /></label>
|
|
90
|
+
<input
|
|
91
|
+
id="custom"
|
|
92
|
+
type="number"
|
|
93
|
+
min="0"
|
|
94
|
+
max="100"
|
|
95
|
+
value={tipPct}
|
|
96
|
+
onChange={(e) => setTipPct(Math.max(0, Number(e.target.value) || 0))}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
<div>
|
|
100
|
+
<label htmlFor="people"><T id="field.people" /></label>
|
|
101
|
+
<input
|
|
102
|
+
id="people"
|
|
103
|
+
type="number"
|
|
104
|
+
min="1"
|
|
105
|
+
value={people}
|
|
106
|
+
onChange={(e) => setPeople(Math.max(1, Math.floor(Number(e.target.value) || 1)))}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div className="result">
|
|
112
|
+
<div className="col">
|
|
113
|
+
<span className="line"><span className="muted"><T id="result.tip" /></span><span>{money(tip)}</span></span>
|
|
114
|
+
<span className="line"><span className="muted"><T id="result.total" /></span><span>{money(total)}</span></span>
|
|
115
|
+
</div>
|
|
116
|
+
<div style={{ textAlign: 'right' }}>
|
|
117
|
+
<div className="muted"><T id="result.perPerson" /></div>
|
|
118
|
+
<div className="big">{money(perPerson)}</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Root layout — imports globals.css. The SPA renders entirely client-side,
|
|
2
|
+
// so there is no SSR document to worry about here.
|
|
3
|
+
//
|
|
4
|
+
// The app is bilingual (see `locales` in app.config.ts): <LocaleSwitcher>
|
|
5
|
+
// writes the `voltro:lang` cookie + reloads so the framework re-renders in the
|
|
6
|
+
// chosen language.
|
|
7
|
+
|
|
8
|
+
import type { ReactNode } from 'react'
|
|
9
|
+
import { useLocale, useT } from '@voltro/i18n'
|
|
10
|
+
import { LocaleSwitcher } from '@voltro/ui-shadcn'
|
|
11
|
+
import '../globals.css'
|
|
12
|
+
|
|
13
|
+
const LOCALES = [
|
|
14
|
+
{ code: 'en', label: 'English' },
|
|
15
|
+
{ code: 'de', label: 'Deutsch' },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
export default function Layout({ children }: { readonly children: ReactNode }): ReactNode {
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<header style={{ display: 'flex', justifyContent: 'flex-end', padding: '12px 16px' }}>
|
|
22
|
+
<LocaleSwitcher locales={LOCALES} current={useLocale()} ariaLabel={useT('lang.label')} />
|
|
23
|
+
</header>
|
|
24
|
+
{children}
|
|
25
|
+
</>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "frontend-spa",
|
|
3
|
+
"kind": "web",
|
|
4
|
+
"summary": "Pure client-rendered SPA (renderMode: 'spa') — a fully interactive standalone tool with NO backend and NO SSR. Shows where a single-page app is the right call: state lives in the browser (localStorage), so server rendering would just be discarded on hydration.",
|
|
5
|
+
"tags": ["web", "frontend", "spa", "client", "interactive", "standalone"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react-jsx",
|
|
5
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler"
|
|
8
|
+
},
|
|
9
|
+
"include": ["src/**/*"],
|
|
10
|
+
"exclude": ["node_modules", "dist", ".framework"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Server-rendered pages (template: **frontend-ssr**) — the **SSR** and **ISR**
|
|
4
|
+
render modes. Unlike a static site, these render on a runtime, so you serve
|
|
5
|
+
them with `voltro start` (or the cloud), NOT a bare CDN.
|
|
6
|
+
|
|
7
|
+
## Boot
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm install
|
|
11
|
+
pnpm --filter @{{projectName}}/{{appName}} dev # dev server with HMR
|
|
12
|
+
|
|
13
|
+
# Production behaviour (the SSR/ISR cache only runs here):
|
|
14
|
+
pnpm --filter @{{projectName}}/{{appName}} build
|
|
15
|
+
pnpm --filter @{{projectName}}/{{appName}} start # → voltro start
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Then watch the response headers (`curl -i`):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
curl -i http://localhost:5190/ | grep x-voltro # x-voltro-rendered-by: ssr
|
|
22
|
+
curl -i http://localhost:5190/feed | grep x-voltro # x-voltro-cache: MISS, then HIT
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## What this template demonstrates
|
|
26
|
+
|
|
27
|
+
| Page | Mode | Concept |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `/` | `ssr` | Fresh per request; `useServerRequest()` reads cookies + headers server-side |
|
|
30
|
+
| `/feed` | `isr` + `revalidate` + `tenantAware` | Cached HTML, revalidated on a window, per-tenant cache key |
|
|
31
|
+
| `/feed-swr` | `isr` + `staleWhileRevalidate` | Serve stale instantly, refresh in the background |
|
|
32
|
+
|
|
33
|
+
Response headers to look for: `x-voltro-rendered-by`, `x-voltro-cache`
|
|
34
|
+
(`HIT` / `MISS` / `STALE` / `BYPASS`), `x-voltro-cache-age-ms`.
|
|
35
|
+
|
|
36
|
+
## Pulling fresh data from your api
|
|
37
|
+
|
|
38
|
+
This template is self-contained (loaders compute their own data), so it boots
|
|
39
|
+
with zero infra. To render from YOUR reactive backend, declare an `apis` entry
|
|
40
|
+
in `app.config.ts` and call `ctx.query` in an ssr/isr loader — it runs
|
|
41
|
+
server-side, invokes the api's rpc directly, and forwards the session cookie:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// app.config.ts: apis: { app: { package: '@{{projectName}}/api' } }
|
|
45
|
+
export const loader: LoaderFn<Data> = async ({ query }) => ({
|
|
46
|
+
post: await query!('posts.get', { id: '…' }), // `query` is server-only
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Scaffold the pair in one command:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
voltro create-project {{projectName}} --api api-backend --web frontend-ssr
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Also available: CDC-driven cache invalidation
|
|
57
|
+
|
|
58
|
+
An ISR page can declare `export const cacheInvalidatesOn = ['todos']` — the
|
|
59
|
+
cached HTML drops the instant ANY write to those tables fires (needs an api
|
|
60
|
+
with CDC). It pairs with a long `revalidate` for "reactive cache" semantics:
|
|
61
|
+
cached arbitrarily long, fresh the moment the source data changes.
|
|
62
|
+
|
|
63
|
+
## Don't ship these to a pure CDN
|
|
64
|
+
|
|
65
|
+
`voltro static deploy` will refuse — SSR/ISR pages need a runtime to render per
|
|
66
|
+
request. That gate is correct; serve them on `voltro start` / a container, and
|
|
67
|
+
push only your `static` pages to a CDN. `voltro deploy plan` tells you which
|
|
68
|
+
tier each page lands in.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Web app for the {{projectName}} project — SERVER-RENDERED pages.
|
|
2
|
+
//
|
|
3
|
+
// Pages here use `renderMode: 'ssr'` (fresh per request) and `'isr'`
|
|
4
|
+
// (cached + revalidated). Unlike a static site, these need a runtime —
|
|
5
|
+
// run them with `voltro start` (NOT a bare CDN). The render-mode gate in
|
|
6
|
+
// `voltro static deploy` will (correctly) refuse to ship them to a pure CDN.
|
|
7
|
+
//
|
|
8
|
+
// Self-contained: no `apis` declared, so it boots with zero infra. To pull
|
|
9
|
+
// fresh data from YOUR reactive backend instead, add an `apis` entry and use
|
|
10
|
+
// `ctx.query(...)` in the loaders (see src/pages/index.tsx).
|
|
11
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
12
|
+
|
|
13
|
+
export const env = defineEnv({
|
|
14
|
+
VOLTRO_PUBLIC_APP_NAME: envVar.string({ access: 'public', default: '{{capProjectName}}' }),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
type: 'web' as const,
|
|
19
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
20
|
+
port: {{port}},
|
|
21
|
+
theme: 'system' as const,
|
|
22
|
+
env,
|
|
23
|
+
// Bilingual by default. The framework auto-wires a cookie-driven
|
|
24
|
+
// <I18nProvider> from these codes and loads the matching
|
|
25
|
+
// `src/locales/<code>.ts` catalog — so `<T id="…" />` / `useT()` /
|
|
26
|
+
// `useLocale()` work app-wide (SSR + ISR too) with no manual setup. The
|
|
27
|
+
// <LocaleSwitcher> in the layout writes the `voltro:lang` cookie + reloads.
|
|
28
|
+
// Grow the app to more languages by adding a code here + a
|
|
29
|
+
// `src/locales/<code>.ts`.
|
|
30
|
+
locales: ['en', 'de'] as const,
|
|
31
|
+
defaultLocale: 'en' as const,
|
|
32
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{projectName}}/{{appName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "voltro dev .",
|
|
8
|
+
"build": "voltro build .",
|
|
9
|
+
"start": "voltro start .",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"test": "voltro test"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@voltro/cli": "0.1.0",
|
|
15
|
+
"@voltro/client": "0.1.0",
|
|
16
|
+
"@voltro/env": "0.1.0",
|
|
17
|
+
"@voltro/i18n": "0.1.0",
|
|
18
|
+
"@voltro/ui-shadcn": "0.1.0",
|
|
19
|
+
"@voltro/web": "0.1.0",
|
|
20
|
+
"react": "^19.0.0",
|
|
21
|
+
"react-dom": "^19.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/react": "^19.0.0",
|
|
25
|
+
"@types/react-dom": "^19.0.0",
|
|
26
|
+
"typescript": "^5.7.0",
|
|
27
|
+
"vitest": "^3.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* Self-contained styles — plain CSS, no Tailwind / kit dependency. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #ffffff;
|
|
5
|
+
--fg: #18181b;
|
|
6
|
+
--muted: #71717a;
|
|
7
|
+
--accent: #ea580c;
|
|
8
|
+
--border: #e4e4e7;
|
|
9
|
+
--code-bg: #f4f4f5;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:root.dark {
|
|
13
|
+
--bg: #0a0a0a;
|
|
14
|
+
--fg: #fafafa;
|
|
15
|
+
--muted: #a1a1aa;
|
|
16
|
+
--accent: #fb923c;
|
|
17
|
+
--border: #27272a;
|
|
18
|
+
--code-bg: #18181b;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* { box-sizing: border-box; }
|
|
22
|
+
html, body { margin: 0; min-height: 100vh; }
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: var(--fg);
|
|
27
|
+
font: 16px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
main { max-width: 40rem; margin: 0 auto; padding: 2rem 1.25rem 5rem; }
|
|
31
|
+
|
|
32
|
+
nav.top {
|
|
33
|
+
display: flex;
|
|
34
|
+
gap: 1rem;
|
|
35
|
+
padding: 1rem 1.25rem;
|
|
36
|
+
border-bottom: 1px solid var(--border);
|
|
37
|
+
}
|
|
38
|
+
nav.top a { color: var(--muted); text-decoration: none; font-size: 0.95rem; }
|
|
39
|
+
nav.top a:hover { color: var(--accent); }
|
|
40
|
+
|
|
41
|
+
h1 { font-size: 1.6rem; margin: 0 0 0.25rem; }
|
|
42
|
+
.lead { color: var(--muted); margin: 0 0 1.5rem; }
|
|
43
|
+
|
|
44
|
+
.fields { list-style: none; padding: 0; margin: 1.5rem 0; }
|
|
45
|
+
.fields li { display: flex; justify-content: space-between; padding: 0.6rem 0; border-bottom: 1px solid var(--border); gap: 1rem; }
|
|
46
|
+
.fields .k { color: var(--muted); }
|
|
47
|
+
.fields .v { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; text-align: right; word-break: break-all; }
|
|
48
|
+
|
|
49
|
+
code { background: var(--code-bg); padding: 0.1rem 0.35rem; border-radius: 5px; font-size: 0.9em; }
|
|
50
|
+
|
|
51
|
+
.note {
|
|
52
|
+
margin-top: 2rem;
|
|
53
|
+
padding: 1rem;
|
|
54
|
+
border: 1px solid var(--border);
|
|
55
|
+
border-radius: 10px;
|
|
56
|
+
color: var(--muted);
|
|
57
|
+
font-size: 0.9rem;
|
|
58
|
+
}
|
|
59
|
+
.badge {
|
|
60
|
+
display: inline-block;
|
|
61
|
+
padding: 0.15rem 0.5rem;
|
|
62
|
+
border-radius: 999px;
|
|
63
|
+
font-size: 0.75rem;
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
background: var(--accent);
|
|
66
|
+
color: #fff;
|
|
67
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// German catalog. Mirrors every key in `en.ts` — `defineLocale<typeof en>()`
|
|
2
|
+
// fails the build if a key is missing or misspelled. Translate the prose only;
|
|
3
|
+
// keep ICU placeholders and rich tags (`<code>`) identical to `en.ts`.
|
|
4
|
+
|
|
5
|
+
import { defineLocale } from '@voltro/i18n'
|
|
6
|
+
import en from './en'
|
|
7
|
+
|
|
8
|
+
export default defineLocale<typeof en>()({
|
|
9
|
+
'meta.ssr.title': '{{capProjectName}} — SSR (frisch pro Anfrage)',
|
|
10
|
+
'meta.ssr.description': 'Bei jeder Anfrage serverseitig gerendert.',
|
|
11
|
+
'meta.isr.title': '{{capProjectName}} — ISR (gecacht + revalidiert)',
|
|
12
|
+
'meta.isr.description': 'Gecachtes HTML, in einem Zeitfenster revalidiert.',
|
|
13
|
+
'meta.swr.title': '{{capProjectName}} — stale-while-revalidate',
|
|
14
|
+
'meta.swr.description': 'Sofort veraltet ausliefern, im Hintergrund aktualisieren.',
|
|
15
|
+
|
|
16
|
+
'nav.ssr': '/ (ssr)',
|
|
17
|
+
'nav.isr': '/feed (isr)',
|
|
18
|
+
'nav.swr': '/feed-swr (isr + swr)',
|
|
19
|
+
'lang.label': 'Sprache',
|
|
20
|
+
|
|
21
|
+
'field.renderedAt': 'Gerendert am',
|
|
22
|
+
'field.nonce': 'Nonce',
|
|
23
|
+
|
|
24
|
+
'ssr.badge': 'renderMode: ssr',
|
|
25
|
+
'ssr.heading': 'Frisch bei jeder Anfrage',
|
|
26
|
+
'ssr.lead': 'Aktualisieren — Zeitstempel und Nonce ändern sich jedes Mal.',
|
|
27
|
+
'ssr.field.acceptLanguage': 'Accept-Language',
|
|
28
|
+
'ssr.field.themeCookie': 'voltro:theme-Cookie',
|
|
29
|
+
'ssr.note': 'SSR-Antworten tragen <code>x-voltro-rendered-by: ssr</code> und werden nie gecacht. Diese Seite braucht eine Laufzeit — liefere sie mit <code>voltro start</code> aus, nicht über ein reines CDN.',
|
|
30
|
+
|
|
31
|
+
'isr.badge': 'renderMode: isr · revalidate 10s',
|
|
32
|
+
'isr.heading': 'Gecacht, dann revalidiert',
|
|
33
|
+
'isr.lead': 'Zweimal schnell aktualisieren — gleiche Nonce (Cache-HIT). 10s+ warten, aktualisieren — frische Nonce.',
|
|
34
|
+
'isr.field.tenant': 'Mandant (aus x-tenant)',
|
|
35
|
+
'isr.note': 'Beobachte die Response-Header: <code>x-voltro-cache: MISS</code> bei der ersten Anfrage (sie wurde gerendert), dann <code>HIT</code> innerhalb des Fensters, dann wieder <code>MISS</code>, nachdem es abgelaufen ist. Mit <code>tenantAware: true</code> enthält der Cache-Schlüssel <code>x-tenant</code>.',
|
|
36
|
+
|
|
37
|
+
'swr.badge': 'renderMode: isr · revalidate 5s · swr 30s',
|
|
38
|
+
'swr.heading': 'Stale-while-revalidate',
|
|
39
|
+
'swr.lead': 'Zuerst frisch; HIT innerhalb von 5s; STALE (sofort) von 5–35s, während im Hintergrund aktualisiert wird.',
|
|
40
|
+
'swr.note': 'Der Response-Header verrät, welcher Pfad die Seite ausgeliefert hat: <code>x-voltro-cache: MISS</code> → <code>HIT</code> → <code>STALE</code> (Hintergrund-Aktualisierung) → wieder <code>MISS</code> nach dem SWR-Fenster.',
|
|
41
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Base catalog — the source of truth for this app's UI strings. Every key here
|
|
2
|
+
// MUST be mirrored in `de.ts` (and any other locale); `defineLocale<typeof en>()`
|
|
3
|
+
// enforces that parity at build time, so a missing translation fails typecheck
|
|
4
|
+
// rather than silently falling back.
|
|
5
|
+
//
|
|
6
|
+
// Messages use ICU syntax: `{name}` placeholders and rich tags like
|
|
7
|
+
// `<code>…</code>` (rendered by passing a matching function to
|
|
8
|
+
// `<T values={{ code: (c) => <code>{c}</code> }} />`).
|
|
9
|
+
|
|
10
|
+
import { defineCatalog } from '@voltro/i18n'
|
|
11
|
+
|
|
12
|
+
export default defineCatalog({
|
|
13
|
+
// Browser tab titles + descriptions — locale-aware via each page's
|
|
14
|
+
// `meta({ locale })`. These are plain lookups (not ICU-formatted), so the
|
|
15
|
+
// `{{capProjectName}}` scaffold token is fine here.
|
|
16
|
+
'meta.ssr.title': '{{capProjectName}} — SSR (fresh per request)',
|
|
17
|
+
'meta.ssr.description': 'Server-rendered on every request.',
|
|
18
|
+
'meta.isr.title': '{{capProjectName}} — ISR (cached + revalidated)',
|
|
19
|
+
'meta.isr.description': 'Cached HTML, revalidated on a window.',
|
|
20
|
+
'meta.swr.title': '{{capProjectName}} — stale-while-revalidate',
|
|
21
|
+
'meta.swr.description': 'Serve stale instantly, refresh in the background.',
|
|
22
|
+
|
|
23
|
+
// ---- Layout shell (pages/layout.tsx) ----
|
|
24
|
+
// Route + render-mode labels — technical tokens, identical across locales.
|
|
25
|
+
'nav.ssr': '/ (ssr)',
|
|
26
|
+
'nav.isr': '/feed (isr)',
|
|
27
|
+
'nav.swr': '/feed-swr (isr + swr)',
|
|
28
|
+
'lang.label': 'Language',
|
|
29
|
+
|
|
30
|
+
// ---- Shared field labels ----
|
|
31
|
+
'field.renderedAt': 'Rendered at',
|
|
32
|
+
'field.nonce': 'Nonce',
|
|
33
|
+
|
|
34
|
+
// ---- SSR page (pages/index.tsx) ----
|
|
35
|
+
'ssr.badge': 'renderMode: ssr',
|
|
36
|
+
'ssr.heading': 'Fresh on every request',
|
|
37
|
+
'ssr.lead': 'Refresh — the timestamp and nonce change each time.',
|
|
38
|
+
'ssr.field.acceptLanguage': 'Accept-Language',
|
|
39
|
+
'ssr.field.themeCookie': 'voltro:theme cookie',
|
|
40
|
+
'ssr.note': 'SSR responses carry <code>x-voltro-rendered-by: ssr</code> and are never cached. This page needs a runtime — serve it with <code>voltro start</code>, not a bare CDN.',
|
|
41
|
+
|
|
42
|
+
// ---- ISR page (pages/feed.tsx) ----
|
|
43
|
+
'isr.badge': 'renderMode: isr · revalidate 10s',
|
|
44
|
+
'isr.heading': 'Cached, then revalidated',
|
|
45
|
+
'isr.lead': 'Refresh twice fast — same nonce (cache HIT). Wait 10s+, refresh — fresh nonce.',
|
|
46
|
+
'isr.field.tenant': 'Tenant (from x-tenant)',
|
|
47
|
+
'isr.note': 'Watch the response headers: <code>x-voltro-cache: MISS</code> on the first request (it rendered), then <code>HIT</code> within the window, then <code>MISS</code> again after it expires. With <code>tenantAware: true</code> the cache key includes <code>x-tenant</code>.',
|
|
48
|
+
|
|
49
|
+
// ---- ISR + stale-while-revalidate page (pages/feed-swr.tsx) ----
|
|
50
|
+
'swr.badge': 'renderMode: isr · revalidate 5s · swr 30s',
|
|
51
|
+
'swr.heading': 'Stale-while-revalidate',
|
|
52
|
+
'swr.lead': 'Fresh first; HIT within 5s; STALE (instant) from 5–35s while a background refresh runs.',
|
|
53
|
+
'swr.note': 'The response header tells you which path served the page: <code>x-voltro-cache: MISS</code> → <code>HIT</code> → <code>STALE</code> (background refresh) → <code>MISS</code> again after the SWR window.',
|
|
54
|
+
} as const)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Catalog lookup OUTSIDE React — for a page's `meta({ locale })` locale-aware
|
|
2
|
+
// <title> / <meta description>. This app is cookie-i18n (no `/<locale>` URL
|
|
3
|
+
// prefix), so @voltro/web hands `meta` the active locale from the `voltro:lang`
|
|
4
|
+
// cookie. Inside React, use `<T>` / `useT()` via the framework's auto-wired
|
|
5
|
+
// <I18nProvider> instead.
|
|
6
|
+
|
|
7
|
+
import en from './en'
|
|
8
|
+
import de from './de'
|
|
9
|
+
|
|
10
|
+
// Both catalogs share `en`'s keys (defineLocale enforces the mirror), so a
|
|
11
|
+
// known-key lookup returns `string` — not `string | undefined` — which is what
|
|
12
|
+
// `PageMeta.title` requires under exactOptionalPropertyTypes.
|
|
13
|
+
export type Messages = Record<keyof typeof en, string>
|
|
14
|
+
|
|
15
|
+
export const getCatalog = (locale?: string): Messages =>
|
|
16
|
+
(locale === 'de' ? de : en) as Messages
|