@voltro/cli 0.53.0 → 0.54.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 +195 -0
- package/dist/{agentsMd-0l980yhL.js → agentsMd-BaLC10Na.js} +110 -82
- package/dist/agentsMd-DCY1RSs8.js +2 -0
- package/dist/apiBuild-CeUN55uk.js +2 -0
- package/dist/{apiBuild-CaPfoWku.js → apiBuild-DTWp0S_q.js} +2 -2
- package/dist/bin.js +1 -1
- package/dist/{build-D-OnvNMf.js → build-D4ygSbnV.js} +114 -114
- package/dist/{checkCommand-C5elt0tW.js → checkCommand-Dg1G7Gwd.js} +6 -6
- package/dist/{checkCommand-D2ZduVlh.js → checkCommand-L7DTlpIF.js} +1 -1
- package/dist/{cloudCmd-QUXh-b5w.js → cloudCmd-DzKcSYuy.js} +1 -1
- package/dist/{codegen-FEk8AZHb.js → codegen-DSLM8Su9.js} +2 -2
- package/dist/codegen-DjgxEOnD.js +2 -0
- package/dist/codegenCommand-CG_Vx4lc.js +41 -0
- package/dist/{codemodRunner-BjtB2lq6.js → codemodRunner-Cd4xkC6u.js} +9 -9
- package/dist/{commands-DyxAmhP0.js → commands-6Kzi92Np.js} +96 -73
- package/dist/{dashboardCommand-BdKTyT13.js → dashboardCommand-Cq1PWvI1.js} +3 -3
- package/dist/{dataCommand-Bab9X7s8.js → dataCommand-DYzW8vkv.js} +3 -3
- package/dist/{dbCommand-06O2finM.js → dbCommand-B4NWZtGL.js} +3 -3
- package/dist/dbCommand-CSFWs9ev.js +2 -0
- package/dist/{dev-C6LGF4iY.js → dev-CmuvUKRq.js} +2236 -2219
- package/dist/{dev-GjJWAYo2.js → dev-cKUiZZsB.js} +1 -1
- package/dist/{doctorCommand-etMkflRc.js → doctorCommand-DCiFVMtZ.js} +21 -21
- package/dist/doctorCommand-J3qu4E0Y.js +2 -0
- package/dist/{dormancyCommand-UwZ1AZzB.js → dormancyCommand-w1TrmgYP.js} +1 -1
- package/dist/{embeddingsCommand-C70zWHwo.js → embeddingsCommand-CMgPyRTr.js} +1 -1
- package/dist/{envCommand-dSyKvRkM.js → envCommand-Cyynmcfa.js} +15 -15
- package/dist/{evolveCommand-CG0_ebO5.js → evolveCommand-BwvQ8dVH.js} +2 -2
- package/dist/fileConventions-l-RIXbx8.js +36 -0
- package/dist/{fileTaxonomy-B7uxipWS.js → fileTaxonomy-CbyMQYx_.js} +37 -37
- package/dist/{frameworkTableAssembly-DKx3ba3S.js → frameworkTableAssembly-D7LJuALW.js} +5 -5
- package/dist/frameworkTableAssembly-IPD1pUnZ.js +2 -0
- package/dist/index.js +2 -2
- package/dist/{infoCommand-_53iOc_j.js → infoCommand-DlYlUPqs.js} +1 -1
- package/dist/{metaCommands-Cn2oboG4.js → metaCommands-x7RCi2AF.js} +2 -2
- package/dist/{migrate-Cko9rswM.js → migrate-BK_Bbx-_.js} +2 -2
- package/dist/mobileCommand-D9O6iq3D.js +428 -0
- package/dist/mobileCommand-DAum7tsG.js +2 -0
- package/dist/{pageConvention-C938S8oC.js → pageConvention-CMpfDN6r.js} +1 -1
- package/dist/{privacyCommand-DWTQMC6R.js → privacyCommand-BCa2OoZG.js} +2 -2
- package/dist/{probeCommand-DkGGLknv.js → probeCommand-_C0YU207.js} +1 -1
- package/dist/{projectScaffold-EzlErR4E.js → projectScaffold-CJfP-xbT.js} +1 -1
- package/dist/{projectScaffold-B4dmTlwT.js → projectScaffold-CSN0OzBV.js} +2 -2
- package/dist/renderModeScan-43yQ2opo.js +147 -0
- package/dist/{renderProfile-CskIgAfn.js → renderProfile-DvrhVJHa.js} +2 -2
- package/dist/{runtimeTrace-c0APJz7E.js → runtimeTrace-CGWx1Q6l.js} +1 -1
- package/dist/{sdkgen-BiQCgIEr.js → sdkgen-CDGHQUFj.js} +1 -1
- package/dist/serveCommand-BiPe8BJm.js +2 -0
- package/dist/{serveCommand-CueKQgzl.js → serveCommand-Bje09q1v.js} +708 -708
- package/dist/serveEntry.js +1 -1
- package/dist/start-B0bnJgxI.js +3 -0
- package/dist/{start-ekPan8BT.js → start-Clz-1BHB.js} +511 -504
- package/dist/startEntry.js +1 -1
- package/dist/{staticCommand-xlSL-IWk.js → staticCommand-ey0kYmOT.js} +1 -1
- package/dist/{subcommandNames-DpYs3DXr.js → subcommandNames-CDzfEtKV.js} +3 -3
- package/dist/{templates-BR-fb4SP.js → templates-BTWZkJJT.js} +41 -9
- package/dist/{test-BWPQcRoB.js → test-D_kW4KMj.js} +1 -1
- package/dist/updateCommand-CIoVDKnj.js +2 -0
- package/dist/{updateCommand-C_8I8Rzo.js → updateCommand-CRJlAOaM.js} +1 -1
- package/dist/{webDev-oczpugbx.js → webDev-DSI9SOhs.js} +1127 -1090
- package/dist/{webDev-C7jWJ5dX.js → webDev-DlvZO30c.js} +1 -1
- package/dist/{webhooksCommand-4SVPDjKg.js → webhooksCommand-BvzXNHji.js} +1 -1
- package/dist/workspaceDeps-RKEkX92S.js +45 -0
- package/package.json +19 -19
- package/templates/AGENTS.core.md +2 -0
- package/templates/AGENTS.md +4 -2
- package/templates/agent-docs/_index.md +2 -2
- package/templates/agent-docs/_manifest.json +1 -1
- package/templates/agent-docs/ai.md +4 -4
- package/templates/agent-docs/authentication.md +72 -0
- package/templates/agent-docs/cli.md +4 -1
- package/templates/agent-docs/data.md +61 -12
- package/templates/agent-docs/database/advancedqueries.md +1 -1
- package/templates/agent-docs/database/migrations.md +1 -1
- package/templates/agent-docs/database/seedsdialects.md +64 -2
- package/templates/agent-docs/internationalization.md +2 -0
- package/templates/agent-docs/introduction.md +25 -0
- package/templates/agent-docs/local-first-mobile.md +139 -41
- package/templates/agent-docs/observability.md +4 -2
- package/templates/agent-docs/plugins/atlassian.md +2 -2
- package/templates/agent-docs/plugins/audit.md +2 -2
- package/templates/agent-docs/plugins/billing.md +1 -1
- package/templates/agent-docs/plugins/cdc-out.md +8 -3
- package/templates/agent-docs/plugins/comments.md +22 -0
- package/templates/agent-docs/plugins/presence.md +32 -3
- package/templates/agent-docs/plugins/prometheus.md +2 -0
- package/templates/agent-docs/plugins/queue.md +47 -4
- package/templates/agent-docs/plugins.md +6 -6
- package/templates/agent-docs/reference.md +20 -1
- package/templates/agent-docs/routing.md +63 -9
- package/templates/agent-docs/scheduling.md +1 -1
- package/templates/agent-docs/schema-driven-ui.md +12 -1
- package/templates/agent-docs/templates/appshells.md +36 -4
- package/templates/agent-docs/whats-new.md +109 -135
- package/templates/apps/api-ai/package.json +6 -6
- 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/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/README.md +3 -3
- package/templates/apps/api-collab/app.config.ts +1 -1
- package/templates/apps/api-collab/database/schema.ts +12 -8
- package/templates/apps/api-collab/mutations/documents.create.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.server.ts +1 -1
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +1 -1
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-collab/template.json +1 -1
- package/templates/apps/api-collab/tests/documents.setBody.test.ts +10 -2
- 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-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +11 -10
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +7 -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 +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/README.md +43 -24
- package/templates/apps/frontend-collab/app.config.ts +3 -3
- package/templates/apps/frontend-collab/package.json +14 -10
- package/templates/apps/frontend-collab/src/locales/de.ts +1 -2
- package/templates/apps/frontend-collab/src/locales/en.ts +1 -2
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +72 -76
- package/templates/apps/frontend-collab/src/pages/page.tsx +45 -22
- package/templates/apps/frontend-collab/template.json +2 -2
- 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 +8 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/README.md +48 -0
- package/templates/apps/frontend-landing/app.config.ts +28 -0
- package/templates/apps/frontend-landing/package.json +7 -6
- package/templates/apps/frontend-landing/src/assets/hero.jpg +0 -0
- package/templates/apps/frontend-landing/src/fonts/Geist-Variable.woff2 +0 -0
- package/templates/apps/frontend-landing/src/fonts/LICENSE-Geist.txt +92 -0
- package/templates/apps/frontend-landing/src/globals.css +15 -0
- package/templates/apps/frontend-landing/src/globals.d.ts +17 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +3 -2
- package/templates/apps/frontend-landing/src/locales/en.ts +5 -2
- package/templates/apps/frontend-landing/src/pages/page.test.tsx +79 -0
- package/templates/apps/frontend-landing/src/pages/page.tsx +26 -3
- package/templates/apps/frontend-landing/template.json +2 -2
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- 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 +8 -7
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +12 -11
- package/templates/apps/mobile-app/src/lib/deeplinks.ts +29 -17
- package/templates/apps/mobile-app/tests/deeplinks.test.ts +16 -0
- package/dist/agentsMd-SDDSkyl4.js +0 -2
- package/dist/apiBuild-DHtLXYx9.js +0 -2
- package/dist/codegen-BWpt3VgF.js +0 -2
- package/dist/codegenCommand-BOiWQ5hz.js +0 -137
- package/dist/dbCommand-B1EXBC6f.js +0 -2
- package/dist/doctorCommand-B0hX0tdz.js +0 -2
- package/dist/fileConventions-DASGEmj-.js +0 -35
- package/dist/frameworkTableAssembly-C_7Z-rMs.js +0 -2
- package/dist/renderModeScan-CUbOeOAg.js +0 -122
- package/dist/serveCommand-DsnrVN3U.js +0 -2
- package/dist/start-BJzZLbt8.js +0 -3
- package/dist/updateCommand-Bqql_rsQ.js +0 -2
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Copyright (c) 2023 Vercel, in collaboration with basement.studio
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
-----------------------------------------------------------
|
|
8
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
9
|
+
-----------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
PREAMBLE
|
|
12
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
13
|
+
development of collaborative font projects, to support the font creation
|
|
14
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
15
|
+
open framework in which fonts may be shared and improved in partnership
|
|
16
|
+
with others.
|
|
17
|
+
|
|
18
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
19
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
20
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
21
|
+
redistributed and/or sold with any software provided that any reserved
|
|
22
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
23
|
+
however, cannot be released under any other type of license. The
|
|
24
|
+
requirement for fonts to remain under this license does not apply
|
|
25
|
+
to any document created using the fonts or their derivatives.
|
|
26
|
+
|
|
27
|
+
DEFINITIONS
|
|
28
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
29
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
30
|
+
include source files, build scripts and documentation.
|
|
31
|
+
|
|
32
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
33
|
+
copyright statement(s).
|
|
34
|
+
|
|
35
|
+
"Original Version" refers to the collection of Font Software components as
|
|
36
|
+
distributed by the Copyright Holder(s).
|
|
37
|
+
|
|
38
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
39
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
40
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
41
|
+
new environment.
|
|
42
|
+
|
|
43
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
44
|
+
writer or other person who contributed to the Font Software.
|
|
45
|
+
|
|
46
|
+
PERMISSION AND CONDITIONS
|
|
47
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
48
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
49
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
50
|
+
Software, subject to the following conditions:
|
|
51
|
+
|
|
52
|
+
1) Neither the Font Software nor any of its individual components,
|
|
53
|
+
in Original or Modified Versions, may be sold by itself.
|
|
54
|
+
|
|
55
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
56
|
+
redistributed and/or sold with any software, provided that each copy
|
|
57
|
+
contains the above copyright notice and this license. These can be
|
|
58
|
+
included either as stand-alone text files, human-readable headers or
|
|
59
|
+
in the appropriate machine-readable metadata fields within text or
|
|
60
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
61
|
+
|
|
62
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
63
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
64
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
65
|
+
presented to the users.
|
|
66
|
+
|
|
67
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
68
|
+
Software shall not be used to promote, endorse or advertise any
|
|
69
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
70
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
71
|
+
permission.
|
|
72
|
+
|
|
73
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
74
|
+
must be distributed entirely under this license, and must not be
|
|
75
|
+
distributed under any other license. The requirement for fonts to
|
|
76
|
+
remain under this license does not apply to any document created
|
|
77
|
+
using the Font Software.
|
|
78
|
+
|
|
79
|
+
TERMINATION
|
|
80
|
+
This license becomes null and void if any of the above conditions are
|
|
81
|
+
not met.
|
|
82
|
+
|
|
83
|
+
DISCLAIMER
|
|
84
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
85
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
86
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
87
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
88
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
89
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
90
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
91
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
92
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@@ -11,8 +11,23 @@
|
|
|
11
11
|
* vite-plugin auto-detect stops firing. */
|
|
12
12
|
@source "./**/*.{tsx,ts,jsx,js}";
|
|
13
13
|
|
|
14
|
+
/* Point the kit's `--font-sans` at the family the framework's font pipeline
|
|
15
|
+
* self-hosts. `--font-geist` is defined by the generated shell (the
|
|
16
|
+
* `<style data-voltro-fonts>` block) from the `fonts:` declaration in
|
|
17
|
+
* app.config.ts, and expands to `'Geist', 'Geist Fallback', Arial,
|
|
18
|
+
* sans-serif` — the second entry being the size-adjusted metric fallback the
|
|
19
|
+
* pipeline computes, which is what keeps the swap from moving any text.
|
|
20
|
+
*
|
|
21
|
+
* Without this line the declaration would still self-host the file, but no
|
|
22
|
+
* rule would reference the family: the page would ship a font it never uses. */
|
|
23
|
+
:root {
|
|
24
|
+
--font-sans: var(--font-geist);
|
|
25
|
+
}
|
|
26
|
+
|
|
14
27
|
html, body { min-height: 100vh; }
|
|
15
28
|
|
|
29
|
+
body { font-family: var(--font-sans); }
|
|
30
|
+
|
|
16
31
|
/* Gradient title — use on hero <h1> via `className="gradient-title"`. */
|
|
17
32
|
.gradient-title {
|
|
18
33
|
background: linear-gradient(180deg, var(--foreground) 0%, oklch(0.7 0.18 290) 100%);
|
|
@@ -4,3 +4,20 @@
|
|
|
4
4
|
// rejects a bare `import './globals.css'` unless the asset module is
|
|
5
5
|
// declared. Vite resolves it at build time; the declaration is type-only.
|
|
6
6
|
declare module '*.css'
|
|
7
|
+
|
|
8
|
+
// `declare module '*?image'`: the build-time image pipeline. The `?image`
|
|
9
|
+
// suffix on an image import is an explicit opt-in — it turns the import into
|
|
10
|
+
// this optimized-asset OBJECT (srcSet variants + intrinsic size + a blur data
|
|
11
|
+
// URI) instead of vite's plain hashed-URL string, so a bare image import keeps
|
|
12
|
+
// its usual semantics. `<Image src={asset}>` consumes exactly this shape.
|
|
13
|
+
declare module '*?image' {
|
|
14
|
+
const asset: {
|
|
15
|
+
readonly src: string
|
|
16
|
+
readonly width: number
|
|
17
|
+
readonly height: number
|
|
18
|
+
readonly blurDataURL: string
|
|
19
|
+
readonly srcSet: string
|
|
20
|
+
readonly sources: ReadonlyArray<{ readonly type: string; readonly srcSet: string }>
|
|
21
|
+
}
|
|
22
|
+
export default asset
|
|
23
|
+
}
|
|
@@ -8,8 +8,9 @@ export default defineLocale<typeof en>()({
|
|
|
8
8
|
'nav.home': 'Start',
|
|
9
9
|
'nav.language': 'Sprache',
|
|
10
10
|
|
|
11
|
-
'hero.tagline':
|
|
12
|
-
'hero.cta':
|
|
11
|
+
'hero.tagline': 'Ersetze diesen Hero-Text durch dein Nutzenversprechen.',
|
|
12
|
+
'hero.cta': 'Mehr erfahren →',
|
|
13
|
+
'hero.imageAlt': 'Abstrakte Verlaufsgrafik',
|
|
13
14
|
|
|
14
15
|
'features.heading': 'Funktionen',
|
|
15
16
|
'features.reactive': '<strong>Reaktive Subscriptions.</strong> Server-Status streamt per WebSocket zu den Clients.',
|
|
@@ -10,8 +10,11 @@ export default defineCatalog({
|
|
|
10
10
|
'nav.home': 'Home',
|
|
11
11
|
'nav.language': 'Language',
|
|
12
12
|
|
|
13
|
-
'hero.tagline':
|
|
14
|
-
'hero.cta':
|
|
13
|
+
'hero.tagline': 'Replace this hero copy with your value prop.',
|
|
14
|
+
'hero.cta': 'Learn more →',
|
|
15
|
+
// `alt` is an attribute, so the page reads this with `useT` (a string), not
|
|
16
|
+
// <T> (an element). Decorative-only images take alt="" instead.
|
|
17
|
+
'hero.imageAlt': 'Abstract gradient artwork',
|
|
15
18
|
|
|
16
19
|
'features.heading': 'Features',
|
|
17
20
|
'features.reactive': '<strong>Reactive subscriptions.</strong> Server state streams to clients via WebSocket.',
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
// @voltro/i18n (<T>); we mock it to resolve against the real catalog for a
|
|
11
11
|
// controllable "current locale". The hero <h1> carries a `{{capProjectName}}`
|
|
12
12
|
// scaffold placeholder, so we assert the heading EXISTS, not its text.
|
|
13
|
+
// 3. the hero image renders as a <picture> with the pipeline's variants.
|
|
14
|
+
//
|
|
15
|
+
// WHY THE `?image` IMPORT IS MOCKED. `voltro test` runs vitest WITHOUT the
|
|
16
|
+
// CLI's image plugin (it is a `voltro dev` / `voltro build` transform), so a
|
|
17
|
+
// `?image` specifier resolves to a plain URL STRING under the unit runner —
|
|
18
|
+
// <Image> would then fall back to the loader path and warn about the missing
|
|
19
|
+
// intrinsic size. Substituting the asset OBJECT the pipeline produces keeps the
|
|
20
|
+
// page code idiomatic (no hand-written width/height) and makes the <picture> /
|
|
21
|
+
// blur / srcSet assertions below mean what they say.
|
|
13
22
|
|
|
14
23
|
import { afterEach, describe, expect, test, vi } from 'vitest'
|
|
15
24
|
import { act, createElement, type ReactNode } from 'react'
|
|
@@ -37,8 +46,27 @@ vi.mock('@voltro/i18n', () => ({
|
|
|
37
46
|
c,
|
|
38
47
|
T: ({ id, values }: { id: string; values?: Record<string, unknown> }) =>
|
|
39
48
|
fill(catalogs[currentLocale]?.[id] ?? id, values),
|
|
49
|
+
// The string form, for message ids used as ATTRIBUTES (the image's `alt`).
|
|
50
|
+
useT: (id: string, values?: Record<string, unknown>): string =>
|
|
51
|
+
fill(catalogs[currentLocale]?.[id] ?? id, values),
|
|
40
52
|
}))
|
|
41
53
|
|
|
54
|
+
// What the `?image` pipeline hands <Image> at build time, in miniature: two
|
|
55
|
+
// modern-format sources, a same-family fallback srcSet, the intrinsic size read
|
|
56
|
+
// off the file, and the inlined blur placeholder.
|
|
57
|
+
const HERO_ASSET = {
|
|
58
|
+
src: '/assets/hero.abc123.jpeg',
|
|
59
|
+
width: 1200,
|
|
60
|
+
height: 630,
|
|
61
|
+
blurDataURL: 'data:image/jpeg;base64,BLUR',
|
|
62
|
+
srcSet: '/assets/hero.640.jpeg 640w, /assets/hero.abc123.jpeg 1200w',
|
|
63
|
+
sources: [
|
|
64
|
+
{ type: 'image/avif', srcSet: '/assets/hero.640.avif 640w, /assets/hero.1200.avif 1200w' },
|
|
65
|
+
{ type: 'image/webp', srcSet: '/assets/hero.640.webp 640w, /assets/hero.1200.webp 1200w' },
|
|
66
|
+
],
|
|
67
|
+
} as const
|
|
68
|
+
vi.mock('../assets/hero.jpg?image', () => ({ default: HERO_ASSET }))
|
|
69
|
+
|
|
42
70
|
const { default: Index, meta, renderMode, interactive } = await import('./page')
|
|
43
71
|
|
|
44
72
|
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
@@ -108,3 +136,54 @@ describe('index — localized render', () => {
|
|
|
108
136
|
expect(container.textContent).toContain('Bereit loszulegen?')
|
|
109
137
|
})
|
|
110
138
|
})
|
|
139
|
+
|
|
140
|
+
describe('index — the hero image + the self-hosted font', () => {
|
|
141
|
+
test('renders a <picture> with one <source> per modern format, avif first', () => {
|
|
142
|
+
render(createElement(Index))
|
|
143
|
+
const picture = container.querySelector('picture')
|
|
144
|
+
expect(picture).toBeTruthy()
|
|
145
|
+
const types = Array.from(picture!.querySelectorAll('source')).map((s) => s.getAttribute('type'))
|
|
146
|
+
expect(types).toEqual(['image/avif', 'image/webp'])
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('the <img> carries the fallback srcSet + the intrinsic size off the asset', () => {
|
|
150
|
+
render(createElement(Index))
|
|
151
|
+
const img = container.querySelector('picture img') as HTMLImageElement
|
|
152
|
+
// width/height are NOT props on the page — they come off the imported
|
|
153
|
+
// asset, which is what reserves the box (CLS) without hand-written numbers.
|
|
154
|
+
expect(img.getAttribute('width')).toBe('1200')
|
|
155
|
+
expect(img.getAttribute('height')).toBe('630')
|
|
156
|
+
expect(img.getAttribute('srcset')).toBe(HERO_ASSET.srcSet)
|
|
157
|
+
expect(img.getAttribute('src')).toBe(HERO_ASSET.src)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('priority makes it the LCP image: eager, not lazy', () => {
|
|
161
|
+
render(createElement(Index))
|
|
162
|
+
const img = container.querySelector('picture img') as HTMLImageElement
|
|
163
|
+
expect(img.getAttribute('loading')).toBe('eager')
|
|
164
|
+
expect(img.getAttribute('fetchpriority')).toBe('high')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('the blur placeholder is painted behind it until it loads', () => {
|
|
168
|
+
render(createElement(Index))
|
|
169
|
+
const img = container.querySelector('picture img') as HTMLImageElement
|
|
170
|
+
expect(img.style.backgroundImage).toContain(HERO_ASSET.blurDataURL)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('alt text is localized — it goes through useT, not <T>', () => {
|
|
174
|
+
currentLocale = 'de'
|
|
175
|
+
render(createElement(Index))
|
|
176
|
+
const img = container.querySelector('picture img') as HTMLImageElement
|
|
177
|
+
expect(img.getAttribute('alt')).toBe(de['hero.imageAlt'])
|
|
178
|
+
expect(img.getAttribute('alt')).not.toBe(en['hero.imageAlt'])
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('the hero heading references the declared family through its CSS variable', () => {
|
|
182
|
+
render(createElement(Index))
|
|
183
|
+
// `localFont('Geist')` maps the family declared in app.config.ts `fonts:`
|
|
184
|
+
// to the variable the generated shell defines. Asserting the VARIABLE (not
|
|
185
|
+
// a literal 'Geist') is the point: the slug rule stays the pipeline's.
|
|
186
|
+
const h1 = container.querySelector('main h1') as HTMLElement
|
|
187
|
+
expect(h1.style.fontFamily).toBe('var(--font-geist)')
|
|
188
|
+
})
|
|
189
|
+
})
|
|
@@ -8,9 +8,17 @@
|
|
|
8
8
|
// so each variant's <title> / <description> is localised in the emitted HTML.
|
|
9
9
|
|
|
10
10
|
import type { ReactNode } from 'react'
|
|
11
|
-
import type
|
|
12
|
-
import { T } from '@voltro/i18n'
|
|
11
|
+
import { Image, localFont, type PageMeta } from '@voltro/web'
|
|
12
|
+
import { T, useT } from '@voltro/i18n'
|
|
13
13
|
import { getCatalog } from '../lib/locale'
|
|
14
|
+
// The `?image` suffix opts this import into the BUILD-TIME pipeline: instead of
|
|
15
|
+
// vite's plain hashed URL it resolves to an optimized-asset object — AVIF+WebP
|
|
16
|
+
// variants at every ladder width up to the intrinsic 1200px plus a jpeg
|
|
17
|
+
// fallback, the intrinsic width/height, and an inlined 16px blur data URI.
|
|
18
|
+
// `voltro build` encodes them into dist/assets/ under content hashes (through a
|
|
19
|
+
// persistent cache, so a rebuild re-encodes nothing); `voltro dev` transforms on
|
|
20
|
+
// demand. Nothing here requires JS on the page — it renders as a <picture>.
|
|
21
|
+
import hero from '../assets/hero.jpg?image'
|
|
14
22
|
|
|
15
23
|
// Pre-render at build time. Removes the runtime dependency entirely
|
|
16
24
|
// for plain-content pages — no rpc client, no react hydration. If you
|
|
@@ -32,14 +40,29 @@ export const meta = ({ locale }: { locale: string }): PageMeta => {
|
|
|
32
40
|
const strong = (chunks: ReactNode): ReactNode => <strong>{chunks}</strong>
|
|
33
41
|
const code = (chunks: ReactNode): ReactNode => <code>{chunks}</code>
|
|
34
42
|
|
|
43
|
+
// The typed way to name a family declared in app.config.ts `fonts:` from TSX.
|
|
44
|
+
// It is a pure NAME MAPPER — `{ variable: '--font-geist', fontFamily:
|
|
45
|
+
// 'var(--font-geist)' }` — so it never has to be kept in sync with the slug
|
|
46
|
+
// rule by hand. src/globals.css points the kit's `--font-sans` at the same
|
|
47
|
+
// variable app-wide; this is the per-component form.
|
|
48
|
+
const brand = localFont('Geist')
|
|
49
|
+
|
|
35
50
|
export default function Index(): ReactNode {
|
|
51
|
+
// `alt` is an ATTRIBUTE, not children, so it needs a plain string — that is
|
|
52
|
+
// what `useT` is for; `<T>` returns an element and cannot be used here.
|
|
53
|
+
const heroAlt = useT('hero.imageAlt')
|
|
36
54
|
return (
|
|
37
55
|
<main>
|
|
38
56
|
<section>
|
|
39
|
-
<h1>{{capProjectName}}</h1>
|
|
57
|
+
<h1 style={{ fontFamily: brand.fontFamily }}>{{capProjectName}}</h1>
|
|
40
58
|
<p>
|
|
41
59
|
<T id="hero.tagline" />
|
|
42
60
|
</p>
|
|
61
|
+
{/* `priority` marks this the LCP image: eager load + high fetch
|
|
62
|
+
* priority instead of the lazy default. width/height/blur are NOT
|
|
63
|
+
* hand-written — they come off the imported asset, which is what
|
|
64
|
+
* reserves the box and keeps CLS at zero. */}
|
|
65
|
+
<Image src={hero} alt={heroAlt} priority sizes="(max-width: 900px) 100vw, 900px" />
|
|
43
66
|
<a href="#features">
|
|
44
67
|
<T id="hero.cta" />
|
|
45
68
|
</a>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "frontend-landing",
|
|
3
3
|
"kind": "web",
|
|
4
|
-
"summary": "Bilingual (en/de) marketing landing page: hero, features grid, CTA. Static-render mode with zero JS on the wire; URL-prefix i18n (`/` + `/de`) pre-renders one HTML file per locale. Flip interactive: 'islands' for interactive pieces.",
|
|
5
|
-
"tags": ["web", "frontend", "landing", "marketing", "static", "i18n", "intl", "localization"]
|
|
4
|
+
"summary": "Bilingual (en/de) marketing landing page: hero, features grid, CTA. Static-render mode with zero JS on the wire; URL-prefix i18n (`/` + `/de`) pre-renders one HTML file per locale. Ships the asset pipelines a marketing page actually needs: a local hero image through `?image` + `<Image>` (AVIF/WebP srcSet, inferred dimensions, blur placeholder) and a self-hosted woff2 declared under `fonts:` (hashed, size-adjusted fallback face, preload — no font CDN). Flip interactive: 'islands' for interactive pieces.",
|
|
5
|
+
"tags": ["web", "frontend", "landing", "marketing", "static", "i18n", "intl", "localization", "images", "fonts", "assets"]
|
|
6
6
|
}
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.54.0",
|
|
16
|
+
"@voltro/cli": "0.54.0",
|
|
17
|
+
"@voltro/client": "0.54.0",
|
|
18
|
+
"@voltro/env": "0.54.0",
|
|
19
|
+
"@voltro/i18n": "0.54.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
21
|
+
"@voltro/web": "0.54.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
26
|
+
"@voltro/devtools": "0.54.0",
|
|
27
27
|
"@types/react": "^19.0.0",
|
|
28
28
|
"@types/react-dom": "^19.0.0",
|
|
29
29
|
"jsdom": "^30.0.0",
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.54.0",
|
|
16
|
+
"@voltro/cli": "0.54.0",
|
|
17
|
+
"@voltro/client": "0.54.0",
|
|
18
|
+
"@voltro/env": "0.54.0",
|
|
19
|
+
"@voltro/i18n": "0.54.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
21
|
+
"@voltro/web": "0.54.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
26
|
+
"@voltro/devtools": "0.54.0",
|
|
27
27
|
"@types/react": "^19.0.0",
|
|
28
28
|
"@types/react-dom": "^19.0.0",
|
|
29
29
|
"jsdom": "^30.0.0",
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/client": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/i18n": "0.
|
|
19
|
-
"@voltro/ui-shadcn": "0.
|
|
20
|
-
"@voltro/web": "0.
|
|
15
|
+
"@voltro/cli": "0.54.0",
|
|
16
|
+
"@voltro/client": "0.54.0",
|
|
17
|
+
"@voltro/env": "0.54.0",
|
|
18
|
+
"@voltro/i18n": "0.54.0",
|
|
19
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
20
|
+
"@voltro/web": "0.54.0",
|
|
21
21
|
"react": "^19.0.0",
|
|
22
22
|
"react-dom": "^19.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/devtools": "0.
|
|
25
|
+
"@voltro/devtools": "0.54.0",
|
|
26
26
|
"@types/react": "^19.0.0",
|
|
27
27
|
"@types/react-dom": "^19.0.0",
|
|
28
28
|
"jsdom": "^30.0.0",
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/client": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/i18n": "0.
|
|
19
|
-
"@voltro/ui-shadcn": "0.
|
|
20
|
-
"@voltro/web": "0.
|
|
15
|
+
"@voltro/cli": "0.54.0",
|
|
16
|
+
"@voltro/client": "0.54.0",
|
|
17
|
+
"@voltro/env": "0.54.0",
|
|
18
|
+
"@voltro/i18n": "0.54.0",
|
|
19
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
20
|
+
"@voltro/web": "0.54.0",
|
|
21
21
|
"react": "^19.0.0",
|
|
22
22
|
"react-dom": "^19.0.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/devtools": "0.
|
|
25
|
+
"@voltro/devtools": "0.54.0",
|
|
26
26
|
"@types/react": "^19.0.0",
|
|
27
27
|
"@types/react-dom": "^19.0.0",
|
|
28
28
|
"jsdom": "^30.0.0",
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.54.0",
|
|
16
|
+
"@voltro/cli": "0.54.0",
|
|
17
|
+
"@voltro/client": "0.54.0",
|
|
18
|
+
"@voltro/env": "0.54.0",
|
|
19
|
+
"@voltro/i18n": "0.54.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
21
|
+
"@voltro/web": "0.54.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
26
|
+
"@voltro/devtools": "0.54.0",
|
|
27
27
|
"@types/react": "^19.0.0",
|
|
28
28
|
"@types/react-dom": "^19.0.0",
|
|
29
29
|
"jsdom": "^30.0.0",
|
|
@@ -12,18 +12,19 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@voltro/
|
|
15
|
+
"@voltro/cli": "0.54.0",
|
|
16
|
+
"@voltro/client": "0.54.0",
|
|
17
|
+
"@voltro/content": "0.54.0",
|
|
18
|
+
"@voltro/env": "0.54.0",
|
|
19
|
+
"@voltro/i18n": "0.54.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
21
|
+
"@voltro/web": "0.54.0",
|
|
16
22
|
"effect": "^3.22.0",
|
|
17
|
-
"@voltro/cli": "0.53.0",
|
|
18
|
-
"@voltro/client": "0.53.0",
|
|
19
|
-
"@voltro/env": "0.53.0",
|
|
20
|
-
"@voltro/i18n": "0.53.0",
|
|
21
|
-
"@voltro/web": "0.53.0",
|
|
22
23
|
"react": "^19.0.0",
|
|
23
24
|
"react-dom": "^19.0.0"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
27
|
+
"@voltro/devtools": "0.54.0",
|
|
27
28
|
"@types/react": "^19.0.0",
|
|
28
29
|
"@types/react-dom": "^19.0.0",
|
|
29
30
|
"jsdom": "^30.0.0",
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"test": "voltro test"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@{{projectName}}/api": "0.
|
|
16
|
-
"@voltro/cli": "0.
|
|
17
|
-
"@voltro/client": "0.
|
|
18
|
-
"@voltro/env": "0.
|
|
19
|
-
"@voltro/i18n": "0.
|
|
20
|
-
"@voltro/ui-shadcn": "0.
|
|
21
|
-
"@voltro/web": "0.
|
|
15
|
+
"@{{projectName}}/api": "0.54.0",
|
|
16
|
+
"@voltro/cli": "0.54.0",
|
|
17
|
+
"@voltro/client": "0.54.0",
|
|
18
|
+
"@voltro/env": "0.54.0",
|
|
19
|
+
"@voltro/i18n": "0.54.0",
|
|
20
|
+
"@voltro/ui-shadcn": "0.54.0",
|
|
21
|
+
"@voltro/web": "0.54.0",
|
|
22
22
|
"react": "^19.0.0",
|
|
23
23
|
"react-dom": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/devtools": "0.
|
|
26
|
+
"@voltro/devtools": "0.54.0",
|
|
27
27
|
"@types/react": "^19.0.0",
|
|
28
28
|
"@types/react-dom": "^19.0.0",
|
|
29
29
|
"jsdom": "^30.0.0",
|
|
@@ -4,20 +4,21 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"main": "expo-router/entry",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"start": "voltro codegen . && expo start",
|
|
8
|
-
"ios": "voltro codegen . && expo run:ios",
|
|
9
|
-
"android": "voltro codegen . && expo run:android",
|
|
10
|
-
"web": "voltro codegen . && expo start --web",
|
|
11
|
-
"test": "vitest run",
|
|
12
|
-
"typecheck": "tsc --noEmit",
|
|
13
|
-
"codegen": "voltro codegen ."
|
|
7
|
+
"start": "voltro mobile codegen . && expo start",
|
|
8
|
+
"ios": "voltro mobile codegen . && expo run:ios",
|
|
9
|
+
"android": "voltro mobile codegen . && expo run:android",
|
|
10
|
+
"web": "voltro mobile codegen . && expo start --web",
|
|
11
|
+
"test": "voltro mobile codegen . && vitest run",
|
|
12
|
+
"typecheck": "voltro mobile codegen . && tsc --noEmit",
|
|
13
|
+
"codegen": "voltro mobile codegen .",
|
|
14
|
+
"links": "voltro mobile links ."
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
17
|
"@react-native-async-storage/async-storage": "1.23.1",
|
|
17
18
|
"@react-native-community/netinfo": "11.4.1",
|
|
18
|
-
"@voltro/client": "0.
|
|
19
|
-
"@voltro/react-native": "0.
|
|
20
|
-
"@{{projectName}}/api": "0.
|
|
19
|
+
"@voltro/client": "0.54.0",
|
|
20
|
+
"@voltro/react-native": "0.54.0",
|
|
21
|
+
"@{{projectName}}/api": "0.54.0",
|
|
21
22
|
"expo": "^52.0.0",
|
|
22
23
|
"expo-constants": "~17.0.0",
|
|
23
24
|
"expo-linking": "~7.0.0",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"@types/node": "^22.0.0",
|
|
33
34
|
"@types/react": "^19.0.0",
|
|
34
35
|
"@vitest/coverage-v8": "^4.1.10",
|
|
35
|
-
"@voltro/cli": "0.
|
|
36
|
+
"@voltro/cli": "0.54.0",
|
|
36
37
|
"typescript": "^6.0.3",
|
|
37
38
|
"vitest": "^4.1.10"
|
|
38
39
|
}
|
|
@@ -1,27 +1,39 @@
|
|
|
1
|
-
// The app's deep-link table
|
|
1
|
+
// The app's deep-link table — GENERATED, not hand-written.
|
|
2
2
|
//
|
|
3
3
|
// A deep link maps an inbound URL — a universal link (`https://…/orders/42`), a
|
|
4
4
|
// custom-scheme URL (`{{appName}}://orders/42`), or a push payload's
|
|
5
|
-
// `data.deepLink` — to an in-app navigation.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// `data.deepLink` — to an in-app navigation. Notice what is NOT in this file: a
|
|
6
|
+
// list of links. `voltro mobile codegen` derives one link per Expo Router screen
|
|
7
|
+
// under `src/app/`, because a screen's route path and a deep-link pattern are
|
|
8
|
+
// the same path in two syntaxes (`app/orders/[id].tsx` ↔ `/orders/:id`). Add a
|
|
9
|
+
// screen and its link exists.
|
|
8
10
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from '@voltro/react-native'
|
|
11
|
+
// **When you need a link file.** Write a `*.deepLink.ts` (default-exporting
|
|
12
|
+
// `defineDeepLink`) when a link needs something the derived one cannot do —
|
|
13
|
+
// param coercion, an auth check, or a side effect before navigation:
|
|
14
|
+
//
|
|
15
|
+
// // src/lib/invoice.deepLink.ts — NOT under src/app/: Expo Router routes
|
|
16
|
+
// // every .ts/.tsx there, so a link file placed inside it ships a screen.
|
|
17
|
+
// import { router } from 'expo-router'
|
|
18
|
+
// import { defineDeepLink } from '@voltro/react-native'
|
|
19
|
+
//
|
|
20
|
+
// export default defineDeepLink({
|
|
21
|
+
// pattern: '/invoices/:number',
|
|
22
|
+
// handler: ({ number }) => router.push(`/invoices/${number.padStart(6, '0')}`),
|
|
23
|
+
// })
|
|
24
|
+
//
|
|
25
|
+
// Declared links are matched BEFORE derived ones, so one for `/orders/:id`
|
|
26
|
+
// replaces the screen-derived link for that pattern rather than racing it.
|
|
27
|
+
//
|
|
28
|
+
// This module stays a thin adapter on purpose: it is browser/RN-safe (no Metro,
|
|
29
|
+
// no screens), which is what makes the routing unit-testable without a simulator.
|
|
30
|
+
import { dispatchDeepLink, type DeepLinkDescriptor } from '@voltro/react-native'
|
|
31
|
+
import { deepLinks } from '../../.framework/deepLinks.generated'
|
|
17
32
|
|
|
18
|
-
/**
|
|
33
|
+
/** The app's deep links, bound to a navigate function (expo-router's `push`). */
|
|
19
34
|
export const makeDeepLinks = (
|
|
20
35
|
navigate: (href: string) => void,
|
|
21
|
-
): ReadonlyArray<DeepLinkDescriptor> =>
|
|
22
|
-
defineDeepLink({ pattern: '/orders/:id', handler: ({ id }) => navigate(`/orders/${id}`) }),
|
|
23
|
-
defineDeepLink({ pattern: '/settings', handler: () => navigate('/settings') }),
|
|
24
|
-
]
|
|
36
|
+
): ReadonlyArray<DeepLinkDescriptor> => deepLinks(navigate)
|
|
25
37
|
|
|
26
38
|
/**
|
|
27
39
|
* Route an inbound URL through the link table. Returns `true` when a link
|