@h-ai/cli 0.1.0-alpha.20 → 0.1.0-alpha.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +10 -7
  2. package/dist/{chunk-GSMXGUYF.js → chunk-5IGMJVV5.js} +56 -38
  3. package/dist/chunk-5IGMJVV5.js.map +1 -0
  4. package/dist/{chunk-3BBXDQ4Y.js → chunk-CFCXOKGV.js} +24 -2
  5. package/dist/chunk-CFCXOKGV.js.map +1 -0
  6. package/dist/cli-config-templates-5J5VKWWD.js +5 -0
  7. package/dist/{cli-config-templates-IGGO7P45.js.map → cli-config-templates-5J5VKWWD.js.map} +1 -1
  8. package/dist/cli-skill-templates-JMSV2SVW.js +5 -0
  9. package/dist/{cli-skill-templates-IMYZPQRG.js.map → cli-skill-templates-JMSV2SVW.js.map} +1 -1
  10. package/dist/index.js +567 -75
  11. package/dist/index.js.map +1 -1
  12. package/package.json +3 -2
  13. package/templates/README.md +40 -29
  14. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +16 -0
  15. package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +38 -0
  16. package/templates/apps/api/apps/{{projectName}}-contract/src/app-contract.ts.hbs +27 -0
  17. package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +33 -0
  18. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +3 -0
  19. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +12 -0
  20. package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +18 -0
  21. package/templates/apps/api/apps/{{projectName}}-contract/tsconfig.json.hbs +12 -0
  22. package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +11 -0
  23. package/templates/apps/api/apps/{{projectName}}-contract/vitest.config.ts.hbs +9 -0
  24. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +17 -0
  25. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +19 -0
  26. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +26 -0
  27. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +55 -0
  28. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +98 -0
  29. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +21 -0
  30. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +184 -0
  31. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/app-procedures.ts.hbs +49 -0
  32. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +18 -0
  33. package/templates/apps/api/apps/{{projectName}}-service/tests/service.test.ts.hbs +168 -0
  34. package/templates/apps/api/apps/{{projectName}}-service/tsconfig.json.hbs +16 -0
  35. package/templates/apps/api/apps/{{projectName}}-service/tsup.config.ts.hbs +11 -0
  36. package/templates/apps/api/apps/{{projectName}}-service/vitest.config.ts.hbs +9 -0
  37. package/templates/apps/api/e2e/api-service.spec.ts.hbs +18 -0
  38. package/templates/apps/api/eslint.config.js +32 -0
  39. package/templates/apps/api/package.json.hbs +29 -0
  40. package/templates/apps/api/playwright.config.ts.hbs +58 -0
  41. package/templates/apps/api/pnpm-workspace.yaml.hbs +14 -0
  42. package/templates/apps/api/tsconfig.base.json +15 -0
  43. package/templates/apps/fullstack/apps/{{projectName}}-app/.env.example.hbs +10 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -7
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +18 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +1 -1
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +16 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/en-US.json.hbs +6 -0
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/zh-CN.json.hbs +6 -0
  50. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +51 -29
  51. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +186 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.css.hbs +2 -0
  53. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.d.ts.hbs +0 -1
  54. package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +18 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-app/src/main.ts.hbs +14 -0
  56. package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +1 -5
  57. package/templates/apps/fullstack/apps/{{projectName}}-app/tsconfig.json.hbs +10 -4
  58. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +4 -3
  59. package/templates/apps/fullstack/apps/{{projectName}}-desktop/.env.example.hbs +6 -0
  60. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +20 -6
  61. package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +1 -1
  62. package/templates/apps/fullstack/apps/{{projectName}}-desktop/index.html.hbs +15 -0
  63. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +33 -30
  64. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +152 -0
  65. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.d.ts.hbs +1 -2
  66. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/main.ts.hbs +13 -0
  67. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/Cargo.toml.hbs +22 -0
  68. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/build.rs.hbs +6 -0
  69. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/capabilities/default.json.hbs +12 -0
  70. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128.png +0 -0
  71. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128@2x.png +0 -0
  72. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/32x32.png +0 -0
  73. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.icns +0 -0
  74. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.ico +0 -0
  75. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +16 -0
  76. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/main.rs.hbs +8 -0
  77. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +41 -0
  78. package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +2 -8
  79. package/templates/apps/fullstack/apps/{{projectName}}-desktop/tsconfig.json.hbs +9 -4
  80. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +44 -21
  81. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +15 -6
  82. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +1 -1
  83. package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +15 -0
  84. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +25 -27
  85. package/templates/apps/fullstack/apps/{{projectName}}-web/src/App.svelte.hbs +153 -0
  86. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.d.ts.hbs +0 -1
  87. package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +14 -0
  88. package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +1 -5
  89. package/templates/apps/fullstack/apps/{{projectName}}-web/tsconfig.json.hbs +10 -4
  90. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -2
  91. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +2 -1
  92. package/templates/apps/fullstack/eslint.config.js +0 -1
  93. package/templates/apps/fullstack/package.json.hbs +18 -17
  94. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +3 -3
  95. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +2 -2
  96. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +2 -2
  97. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +1 -1
  98. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/{server-app.test.ts.hbs → app-server.test.ts.hbs} +1 -1
  99. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +9 -8
  100. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/index.ts.hbs +1 -1
  101. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +5 -1
  102. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ThemeSwitcher.svelte.hbs +1 -1
  103. package/templates/apps/fullstack/packages/{{projectName}}-shared/tsconfig.json.hbs +1 -1
  104. package/templates/apps/fullstack/playwright.config.ts.hbs +41 -4
  105. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +15 -0
  106. package/templates/apps/mobile-app/.env.hbs +3 -0
  107. package/templates/apps/{android-app → mobile-app}/capacitor.config.ts.hbs +2 -2
  108. package/templates/apps/mobile-app/index.html.hbs +13 -0
  109. package/templates/apps/mobile-app/messages/en-US.json +36 -0
  110. package/templates/apps/mobile-app/messages/zh-CN.json +36 -0
  111. package/templates/apps/mobile-app/src/App.svelte.hbs +162 -0
  112. package/templates/apps/mobile-app/src/lib/api.ts.hbs +54 -0
  113. package/templates/apps/{android-app → mobile-app}/src/lib/capacitor.ts +5 -6
  114. package/templates/apps/mobile-app/src/main.ts.hbs +11 -0
  115. package/templates/base/.gitignore.hbs +16 -0
  116. package/templates/base/{eslint.config.js → eslint.config.js.hbs} +4 -1
  117. package/templates/base/package.json.hbs +52 -33
  118. package/templates/base/playwright.config.ts +30 -3
  119. package/templates/base/pnpm-workspace.yaml.hbs +14 -0
  120. package/templates/base/src/app.d.ts.hbs +3 -0
  121. package/templates/base/src/app.html.hbs +3 -0
  122. package/templates/base/svelte.config.js.hbs +43 -3
  123. package/templates/base/tests/scaffold.test.ts.hbs +7 -0
  124. package/templates/base/tsconfig.json.hbs +50 -0
  125. package/templates/base/vite.config.ts.hbs +10 -0
  126. package/templates/skills/AGENTS.md +57 -24
  127. package/templates/skills/CLAUDE.md +18 -7
  128. package/templates/skills/bridges/admin/AGENTS.md +66 -0
  129. package/templates/skills/bridges/admin/CLAUDE.md +19 -0
  130. package/templates/skills/bridges/admin/copilot-instructions.md +46 -0
  131. package/templates/skills/bridges/api/AGENTS.md +65 -0
  132. package/templates/skills/bridges/api/CLAUDE.md +19 -0
  133. package/templates/skills/bridges/api/copilot-instructions.md +46 -0
  134. package/templates/skills/bridges/fullstack/AGENTS.md +84 -0
  135. package/templates/skills/bridges/fullstack/CLAUDE.md +19 -0
  136. package/templates/skills/bridges/fullstack/copilot-instructions.md +54 -0
  137. package/templates/skills/bridges/h5/AGENTS.md +65 -0
  138. package/templates/skills/bridges/h5/CLAUDE.md +19 -0
  139. package/templates/skills/bridges/h5/copilot-instructions.md +44 -0
  140. package/templates/skills/bridges/mobile-app/AGENTS.md +77 -0
  141. package/templates/skills/bridges/mobile-app/CLAUDE.md +19 -0
  142. package/templates/skills/bridges/mobile-app/copilot-instructions.md +45 -0
  143. package/templates/skills/bridges/website/AGENTS.md +65 -0
  144. package/templates/skills/bridges/website/CLAUDE.md +19 -0
  145. package/templates/skills/bridges/website/copilot-instructions.md +44 -0
  146. package/templates/skills/copilot-instructions.md +27 -64
  147. package/templates/skills/hai-app-create/SKILL.md +1 -0
  148. package/templates/skills/hai-app-review/SKILL.md +1 -1
  149. package/templates/skills/hai-app-tests/SKILL.md +28 -3
  150. package/templates/skills/hai-capacitor/SKILL.md +14 -22
  151. package/templates/skills/hai-ui/SKILL.md +8 -2
  152. package/templates/skills/opencode.json +1 -6
  153. package/dist/chunk-3BBXDQ4Y.js.map +0 -1
  154. package/dist/chunk-GSMXGUYF.js.map +0 -1
  155. package/dist/cli-config-templates-IGGO7P45.js +0 -5
  156. package/dist/cli-skill-templates-IMYZPQRG.js +0 -5
  157. package/templates/apps/android-app/.env.hbs +0 -3
  158. package/templates/apps/android-app/messages/en-US.json +0 -24
  159. package/templates/apps/android-app/messages/zh-CN.json +0 -24
  160. package/templates/apps/android-app/src/lib/api.ts.hbs +0 -32
  161. package/templates/apps/android-app/src/routes/+layout.svelte.hbs +0 -43
  162. package/templates/apps/android-app/src/routes/+layout.ts +0 -7
  163. package/templates/apps/android-app/src/routes/+page.svelte.hbs +0 -38
  164. package/templates/apps/api/src/routes/+layout.svelte +0 -14
  165. package/templates/apps/api/src/routes/+page.svelte +0 -11
  166. package/templates/apps/api/src/routes/api/v1/health/+server.ts +0 -14
  167. package/templates/apps/api/src/routes/api/v1/items/+server.ts +0 -37
  168. package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +0 -30
  169. package/templates/apps/api/tests/health.test.ts.hbs +0 -26
  170. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.html.hbs +0 -14
  171. package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+layout.svelte.hbs +0 -18
  172. package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+page.svelte.hbs +0 -162
  173. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.html.hbs +0 -14
  174. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+layout.svelte.hbs +0 -18
  175. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+page.svelte.hbs +0 -162
  176. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.html.hbs +0 -14
  177. package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+layout.svelte.hbs +0 -18
  178. package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+page.svelte.hbs +0 -162
  179. package/templates/apps/fullstack/pnpm-workspace.yaml +0 -7
  180. package/templates/base/tsconfig.json +0 -20
  181. /package/templates/apps/fullstack/packages/{{projectName}}-serv/src/{server-app.ts.hbs → app-server.ts.hbs} +0 -0
  182. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/{theme.svelte.ts.hbs → theme-store.svelte.ts.hbs} +0 -0
@@ -0,0 +1,153 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.web}}
3
+ <script lang="ts">
4
+ import type { AppInfo } from '{{fullstack.sharedPackageName}}'
5
+ import * as appM from './lib/paraglide/messages.js'
6
+ import { setLocale } from './lib/paraglide/runtime.js'
7
+ import { Alert, Button, Card, Skeleton } from '@h-ai/ui'
8
+ import { onMount } from 'svelte'
9
+ import { AppShell, fetchAppInfo, messages as sharedM } from '{{fullstack.sharedPackageName}}'
10
+
11
+ let info = $state<AppInfo | null>(null)
12
+ let loading = $state(true)
13
+ let errorMessage = $state('')
14
+
15
+ function syncLocale(locale: string) {
16
+ setLocale(locale as Parameters<typeof setLocale>[0])
17
+ }
18
+
19
+ async function refreshStatus() {
20
+ loading = true
21
+ errorMessage = ''
22
+ try {
23
+ info = await fetchAppInfo({ clientName: '{{projectName}}-web' })
24
+ }
25
+ catch {
26
+ info = null
27
+ errorMessage = sharedM.home_error()
28
+ }
29
+ finally {
30
+ loading = false
31
+ }
32
+ }
33
+
34
+ onMount(() => {
35
+ document.title = appM.page_title({ name: sharedM.app_name() })
36
+ void refreshStatus()
37
+ })
38
+ </script>
39
+
40
+ <AppShell platform="web" onLocaleChange={syncLocale}>
41
+ <section class="space-y-8">
42
+ <div class="grid gap-6 lg:grid-cols-[1.2fr_0.8fr]">
43
+ <section class="relative overflow-hidden rounded-[2rem] border border-base-300/70 bg-gradient-to-br from-primary/15 via-base-100 to-secondary/15 p-6 shadow-2xl shadow-primary/10 lg:p-10">
44
+ <div class="absolute right-8 top-8 hidden size-28 rounded-full bg-primary/20 blur-3xl sm:block"></div>
45
+ <div class="relative z-10 max-w-2xl space-y-6">
46
+ <span class="badge badge-primary badge-lg gap-2">
47
+ <span class="icon-[tabler--rocket] size-4"></span>
48
+ {appM.hero_badge()}
49
+ </span>
50
+ <div class="space-y-4">
51
+ <h1 class="text-4xl font-black tracking-tight text-base-content sm:text-5xl">
52
+ {appM.hero_title({ name: sharedM.app_name() })}
53
+ </h1>
54
+ <p class="max-w-xl text-lg leading-8 text-base-content/70">
55
+ {appM.hero_subtitle()}
56
+ </p>
57
+ </div>
58
+ <div class="flex flex-wrap gap-3">
59
+ <a class="btn btn-primary shadow-lg shadow-primary/20" href="#status">
60
+ <span class="icon-[tabler--activity-heartbeat] size-5"></span>
61
+ {appM.hero_primary_action()}
62
+ </a>
63
+ <a class="btn btn-ghost" href="#customize">
64
+ <span class="icon-[tabler--adjustments] size-5"></span>
65
+ {appM.hero_secondary_action()}
66
+ </a>
67
+ </div>
68
+ </div>
69
+ </section>
70
+
71
+ <aside class="rounded-[2rem] border border-base-300/70 bg-base-100/80 p-5 shadow-xl backdrop-blur">
72
+ <div class="mockup-window border border-base-300 bg-base-200/70">
73
+ <div class="space-y-4 bg-base-100 p-5">
74
+ <div class="flex items-center justify-between">
75
+ <div>
76
+ <p class="text-sm font-semibold">{appM.preview_title()}</p>
77
+ <p class="text-xs text-base-content/55">{appM.preview_body()}</p>
78
+ </div>
79
+ <span class="badge badge-success badge-outline">{appM.platform_name()}</span>
80
+ </div>
81
+ <div class="grid gap-3 text-sm">
82
+ <div class="rounded-2xl bg-primary/10 p-4">
83
+ <span class="icon-[tabler--components] mb-2 size-6 text-primary"></span>
84
+ <p class="font-semibold">{appM.preview_metric_shared()}</p>
85
+ </div>
86
+ <div class="grid grid-cols-2 gap-3">
87
+ <div class="rounded-2xl bg-secondary/10 p-4">
88
+ <span class="icon-[tabler--language] mb-2 size-6 text-secondary"></span>
89
+ <p class="font-semibold">{appM.preview_metric_local()}</p>
90
+ </div>
91
+ <div class="rounded-2xl bg-accent/10 p-4">
92
+ <span class="icon-[tabler--palette] mb-2 size-6 text-accent"></span>
93
+ <p class="font-semibold">{appM.preview_metric_theme()}</p>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </aside>
100
+ </div>
101
+
102
+ <div class="grid gap-6 lg:grid-cols-[0.95fr_1.05fr]">
103
+ <div id="status">
104
+ <Card title={appM.status_title()} bordered shadow="md" class="bg-base-100/90 backdrop-blur">
105
+ <p class="mb-5 text-sm text-base-content/65">{appM.status_description()}</p>
106
+ {#if loading}
107
+ <Skeleton variant="title" class="mb-3" />
108
+ <Skeleton count={3} />
109
+ <p class="sr-only" data-testid="service-status">{sharedM.home_loading()}</p>
110
+ {:else if info}
111
+ <Alert variant="success" title={appM.status_online({ name: info.name })}>
112
+ <p data-testid="service-status">{info.name}</p>
113
+ </Alert>
114
+ <dl class="mt-5 grid gap-3 text-sm sm:grid-cols-2">
115
+ <div class="rounded-box border border-base-300/70 bg-base-200/60 p-4">
116
+ <dt class="text-xs text-base-content/55">{appM.status_version()}</dt>
117
+ <dd class="mt-1 font-semibold">{info.version}</dd>
118
+ </div>
119
+ <div class="rounded-box border border-base-300/70 bg-base-200/60 p-4">
120
+ <dt class="text-xs text-base-content/55">{appM.status_started({ time: info.startedAt })}</dt>
121
+ <dd class="mt-1 font-semibold">{appM.status_frontends_count({ count: String(info.frontends.length) })}</dd>
122
+ </div>
123
+ </dl>
124
+ {:else}
125
+ <Alert variant="error" title={sharedM.home_error()}>
126
+ <p data-testid="service-status">{errorMessage}</p>
127
+ </Alert>
128
+ {/if}
129
+
130
+ <div class="mt-5">
131
+ <Button type="button" variant="primary" loading={loading} onclick={refreshStatus}>
132
+ {sharedM.home_refresh()}
133
+ </Button>
134
+ </div>
135
+ </Card>
136
+ </div>
137
+
138
+ <section id="customize" class="grid gap-4 sm:grid-cols-2">
139
+ <div class="rounded-[1.5rem] border border-base-300/70 bg-base-100/90 p-5 shadow-md">
140
+ <span class="icon-[tabler--package] mb-4 size-7 text-primary"></span>
141
+ <h2 class="text-lg font-bold">{appM.custom_shared_title()}</h2>
142
+ <p class="mt-2 text-sm leading-6 text-base-content/65">{appM.custom_shared_body()}</p>
143
+ </div>
144
+ <div class="rounded-[1.5rem] border border-base-300/70 bg-base-100/90 p-5 shadow-md">
145
+ <span class="icon-[tabler--message-language] mb-4 size-7 text-secondary"></span>
146
+ <h2 class="text-lg font-bold">{appM.custom_local_title()}</h2>
147
+ <p class="mt-2 text-sm leading-6 text-base-content/65">{appM.custom_local_body()}</p>
148
+ </div>
149
+ </section>
150
+ </div>
151
+ </section>
152
+ </AppShell>
153
+ {{/if}}
@@ -1,6 +1,5 @@
1
1
  {{! @skipIfEmpty }}
2
2
  {{#if fullstack.frontends.web}}
3
- /// <reference types="@sveltejs/kit" />
4
3
  /// <reference types="vite/client" />
5
4
 
6
5
  import '@h-ai/ui/auto-import'
@@ -0,0 +1,14 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.web}}
3
+ import { mount } from 'svelte'
4
+ import App from './App.svelte'
5
+ import './app.css'
6
+
7
+ const target = document.getElementById('app')
8
+ if (!target)
9
+ throw new Error('Root element #app not found')
10
+
11
+ const app = mount(App, { target })
12
+
13
+ export default app
14
+ {{/if}}
@@ -1,19 +1,15 @@
1
1
  {{! @skipIfEmpty }}
2
2
  {{#if fullstack.frontends.web}}
3
3
  import { autoImportHaiUi } from '@h-ai/ui/auto-import'
4
- import adapter from '@sveltejs/adapter-auto'
5
4
  import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
6
5
 
7
- /** @type {import('@sveltejs/kit').Config} */
6
+ /** @type {import('@sveltejs/vite-plugin-svelte').SvelteConfig} */
8
7
  const config = {
9
8
  preprocess: [autoImportHaiUi(), vitePreprocess()],
10
9
  compilerOptions: {
11
10
  // 使用 Svelte 5 Runes,和 @h-ai/ui 组件库保持一致。
12
11
  runes: true,
13
12
  },
14
- kit: {
15
- adapter: adapter(),
16
- },
17
13
  }
18
14
 
19
15
  export default config
@@ -1,17 +1,23 @@
1
1
  {{! @skipIfEmpty }}
2
2
  {{#if fullstack.frontends.web}}
3
3
  {
4
- "extends": "./.svelte-kit/tsconfig.json",
5
4
  "compilerOptions": {
5
+ "target": "ES2022",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "moduleDetection": "force",
8
+ "module": "ESNext",
6
9
  "moduleResolution": "bundler",
7
- "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "types": ["svelte", "vite/client", "node", "vitest/globals"],
8
12
  "allowJs": true,
9
13
  "checkJs": false,
10
14
  "strict": true,
15
+ "noEmit": true,
16
+ "isolatedModules": true,
11
17
  "verbatimModuleSyntax": true,
12
18
  "skipLibCheck": true
13
19
  },
14
- "include": ["src/**/*", "src/**/*.svelte", "tests/**/*"],
15
- "exclude": ["node_modules", ".svelte-kit", "build"]
20
+ "include": ["src/**/*.ts", "src/**/*.svelte", "tests/**/*.ts", "vite.config.ts", "svelte.config.js"],
21
+ "exclude": ["node_modules", "dist", "build"]
16
22
  }
17
23
  {{/if}}
@@ -1,23 +1,24 @@
1
1
  {{! @skipIfEmpty }}
2
2
  {{#if fullstack.frontends.web}}
3
3
  import { paraglideVitePlugin } from '@inlang/paraglide-js'
4
- import { sveltekit } from '@sveltejs/kit/vite'
4
+ import { svelte } from '@sveltejs/vite-plugin-svelte'
5
5
  import tailwindcss from '@tailwindcss/vite'
6
6
  import { defineConfig } from 'vitest/config'
7
7
 
8
8
  export default defineConfig({
9
9
  plugins: [
10
+ svelte(),
10
11
  paraglideVitePlugin({
11
12
  project: './project.inlang',
12
13
  outdir: './src/lib/paraglide',
13
14
  strategy: ['cookie', 'baseLocale'],
14
15
  }),
15
- sveltekit(),
16
16
  tailwindcss(),
17
17
  ],
18
18
  optimizeDeps: {
19
19
  exclude: ['bits-ui'],
20
20
  },
21
+ envPrefix: ['VITE_', 'PUBLIC_'],
21
22
  ssr: {
22
23
  noExternal: [/@h-ai\//, /{{fullstack.sharedPackageName}}/],
23
24
  },
@@ -1,7 +1,8 @@
1
1
  import { expect, test } from '@playwright/test'
2
2
  import { APP_API_ROUTES } from '{{fullstack.contractPackageName}}'
3
3
 
4
- const apiBaseUrl = 'http://127.0.0.1:3000'
4
+ const DEFAULT_SERVICE_BASE_URL = 'http://127.0.0.1:3000'
5
+ const apiBaseUrl = process.env.SERVICE_BASE_URL || DEFAULT_SERVICE_BASE_URL
5
6
 
6
7
  test('service health endpoint is ready', async ({ request }) => {
7
8
  const response = await request.get(apiBaseUrl + APP_API_ROUTES.health)
@@ -3,7 +3,6 @@ import antfu from '@antfu/eslint-config'
3
3
  export default antfu({
4
4
  formatters: true,
5
5
  ignores: [
6
- '**/.svelte-kit/**',
7
6
  '**/build/**',
8
7
  '**/dist/**',
9
8
  '**/src/lib/paraglide/**',
@@ -9,7 +9,10 @@
9
9
  "i18n:compile": "pnpm --filter {{fullstack.sharedPackageName}} paraglide:compile{{#each fullstack.frontendApps}} && pnpm --filter {{packageName}} paraglide:compile{{/each}}",
10
10
  "postinstall": "pnpm i18n:compile",
11
11
  "dev": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm --parallel --filter {{fullstack.servPackageName}}{{#each fullstack.frontendApps}} --filter {{packageName}}{{/each}} dev",
12
+ "compile": "pnpm typecheck && pnpm build",
12
13
  "build": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present build",
14
+ "package": "pnpm build{{#if fullstack.frontends.web}} && pnpm --filter {{projectName}}-web package{{/if}}{{#if fullstack.frontends.app}} && pnpm --filter {{projectName}}-app package{{/if}}{{#if fullstack.frontends.desktop}} && pnpm --filter {{projectName}}-desktop package{{/if}}",
15
+ "deploy": "pnpm package",
13
16
  "typecheck": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present typecheck",
14
17
  "lint": "pnpm -r --if-present lint",
15
18
  "test": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present test",
@@ -21,22 +24,20 @@
21
24
  "{{fullstack.sharedPackageName}}": "workspace:*"
22
25
  },
23
26
  "devDependencies": {
24
- "@antfu/eslint-config": "{{fullstack.versions.antfuEslintConfig}}",
25
- "@playwright/test": "{{fullstack.versions.playwright}}",
26
- "@sveltejs/adapter-auto": "{{fullstack.versions.adapterAuto}}",
27
- "@sveltejs/kit": "{{fullstack.versions.svelteKit}}",
28
- "@sveltejs/vite-plugin-svelte": "{{fullstack.versions.sveltePlugin}}",
29
- "@types/node": "{{fullstack.versions.nodeTypes}}",
30
- "eslint": "{{fullstack.versions.eslint}}",
31
- "eslint-plugin-format": "{{fullstack.versions.eslintPluginFormat}}",
32
- "eslint-plugin-svelte": "{{fullstack.versions.eslintPluginSvelte}}",
33
- "rimraf": "{{fullstack.versions.rimraf}}",
34
- "svelte": "{{fullstack.versions.svelte}}",
35
- "svelte-check": "{{fullstack.versions.svelteCheck}}",
36
- "svelte-eslint-parser": "{{fullstack.versions.svelteEslintParser}}",
37
- "tsup": "{{fullstack.versions.tsup}}",
38
- "typescript": "{{fullstack.versions.typescript}}",
39
- "vite": "{{fullstack.versions.vite}}",
40
- "vitest": "{{fullstack.versions.vitest}}"
27
+ "@antfu/eslint-config": "{{fullstack.packageSpecifiers.antfuEslintConfig}}",
28
+ "@playwright/test": "{{fullstack.packageSpecifiers.playwright}}",
29
+ "@sveltejs/vite-plugin-svelte": "{{fullstack.packageSpecifiers.sveltePlugin}}",
30
+ "@types/node": "{{fullstack.packageSpecifiers.nodeTypes}}",
31
+ "eslint": "{{fullstack.packageSpecifiers.eslint}}",
32
+ "eslint-plugin-format": "{{fullstack.packageSpecifiers.eslintPluginFormat}}",
33
+ "eslint-plugin-svelte": "{{fullstack.packageSpecifiers.eslintPluginSvelte}}",
34
+ "rimraf": "{{fullstack.packageSpecifiers.rimraf}}",
35
+ "svelte": "{{fullstack.packageSpecifiers.svelte}}",
36
+ "svelte-check": "{{fullstack.packageSpecifiers.svelteCheck}}",
37
+ "svelte-eslint-parser": "{{fullstack.packageSpecifiers.svelteEslintParser}}",
38
+ "tsup": "{{fullstack.packageSpecifiers.tsup}}",
39
+ "typescript": "{{fullstack.packageSpecifiers.typescript}}",
40
+ "vite": "{{fullstack.packageSpecifiers.vite}}",
41
+ "vitest": "{{fullstack.packageSpecifiers.vitest}}"
41
42
  }
42
43
  }
@@ -20,8 +20,8 @@
20
20
  "clean": "rimraf dist .turbo"
21
21
  },
22
22
  "dependencies": {
23
- "@h-ai/api-contract": "{{fullstack.versions.apiContract}}",
24
- "@h-ai/core": "{{fullstack.versions.core}}",
25
- "zod": "{{fullstack.versions.zod}}"
23
+ "@h-ai/api-contract": "{{haiPackageSpecifier}}",
24
+ "@h-ai/core": "{{haiPackageSpecifier}}",
25
+ "zod": "{{fullstack.packageSpecifiers.zod}}"
26
26
  }
27
27
  }
@@ -7,9 +7,9 @@
7
7
  - `config/_core.yml`:应用元信息(名称 / 版本 / 环境 / 语言),启动时由 `core.config` 加载。
8
8
  - `config/_serv.yml`:服务端运行时设置(CORS 来源等)。
9
9
  - `src/index.ts`:进程入口,先 `core.init({ configDir: './config' })` 加载配置再启动监听。
10
- - `src/server-app.ts`:服务装配入口,挂载健康检查、OpenAPI、docs 与 procedures;应用名称 / 版本与 CORS 来源均取自上述配置。
10
+ - `src/app-server.ts`:服务装配入口,挂载健康检查、OpenAPI、docs 与 procedures;应用名称 / 版本与 CORS 来源均取自上述配置。
11
11
  - `src/server/procedures/app-procedures.ts`:app 领域 procedures,对应 contract 中的 `app.info` / `app.echo` 路由。
12
- - `tests/server-app.test.ts`:通过 `ServerApp.request()` 验证健康检查与业务路由。
12
+ - `tests/app-server.test.ts`:通过 `ServerApp.request()` 验证健康检查与业务路由。
13
13
 
14
14
  ## 开发
15
15
 
@@ -22,8 +22,8 @@
22
22
  "start": "node dist/index.js"
23
23
  },
24
24
  "dependencies": {
25
- "@h-ai/core": "{{fullstack.versions.core}}",
26
- "@h-ai/serv": "{{fullstack.versions.serv}}",
25
+ "@h-ai/core": "{{haiPackageSpecifier}}",
26
+ "@h-ai/serv": "{{haiPackageSpecifier}}",
27
27
  "{{fullstack.contractPackageName}}": "workspace:*"
28
28
  }
29
29
  }
@@ -1,7 +1,7 @@
1
1
  import process from 'node:process'
2
2
  import { core } from '@h-ai/core'
3
3
  import { serv } from '@h-ai/serv'
4
- import { createServerApp } from './server-app.js'
4
+ import { createServerApp } from './app-server.js'
5
5
 
6
6
  // 加载 config/ 下的 _core.yml / _serv.yml 等配置文件(约定优于配置)
7
7
  core.init({ configDir: './config', logging: { level: 'info' } })
@@ -1,6 +1,6 @@
1
1
  import { APP_API_ROUTES } from '{{fullstack.contractPackageName}}'
2
2
  import { describe, expect, it } from 'vitest'
3
- import { createServerApp } from '../src/server-app.js'
3
+ import { createServerApp } from '../src/app-server.js'
4
4
 
5
5
  describe('{{fullstack.servPackageName}}', () => {
6
6
  it('returns health status', async () => {
@@ -29,16 +29,17 @@
29
29
  "clean": "rimraf .turbo src/lib/paraglide"
30
30
  },
31
31
  "dependencies": {
32
- "@h-ai/api-client": "{{fullstack.versions.apiClient}}",
33
- "@h-ai/ui": "{{fullstack.versions.ui}}",
32
+ "@h-ai/api-client": "{{haiPackageSpecifier}}",
33
+ "@h-ai/ui": "{{haiPackageSpecifier}}",
34
34
  "{{fullstack.contractPackageName}}": "workspace:*"
35
35
  },
36
36
  "devDependencies": {
37
- "@inlang/paraglide-js": "{{fullstack.versions.inlangParaglide}}",
38
- "@inlang/plugin-message-format": "{{fullstack.versions.inlangMessageFormat}}",
39
- "svelte": "{{fullstack.versions.svelte}}",
40
- "svelte-check": "{{fullstack.versions.svelteCheck}}",
41
- "typescript": "{{fullstack.versions.typescript}}",
42
- "vitest": "{{fullstack.versions.vitest}}"
37
+ "@inlang/paraglide-js": "{{fullstack.packageSpecifiers.inlangParaglide}}",
38
+ "@inlang/plugin-message-format": "{{fullstack.packageSpecifiers.inlangMessageFormat}}",
39
+ "svelte": "{{fullstack.packageSpecifiers.svelte}}",
40
+ "svelte-check": "{{fullstack.packageSpecifiers.svelteCheck}}",
41
+ "typescript": "{{fullstack.packageSpecifiers.typescript}}",
42
+ "vite": "{{fullstack.packageSpecifiers.vite}}",
43
+ "vitest": "{{fullstack.packageSpecifiers.vitest}}"
43
44
  }
44
45
  }
@@ -1,7 +1,7 @@
1
1
  export { default as AppShell } from './lib/components/AppShell.svelte'
2
2
  export { default as LanguageSwitcher } from './lib/components/LanguageSwitcher.svelte'
3
3
  export { default as ThemeSwitcher } from './lib/components/ThemeSwitcher.svelte'
4
- export { AVAILABLE_THEMES, themeStore } from './lib/stores/theme.svelte.js'
4
+ export { AVAILABLE_THEMES, themeStore } from './lib/stores/theme-store.svelte.js'
5
5
  export type { AppInfo, FetchAppInfoOptions } from './lib/api/api-client.js'
6
6
  export { fetchAppInfo } from './lib/api/api-client.js'
7
7
  export * as messages from './lib/paraglide/messages.js'
@@ -14,7 +14,7 @@ export interface FetchAppInfoOptions {
14
14
 
15
15
  export async function fetchAppInfo(options: FetchAppInfoOptions = {}): Promise<AppInfo> {
16
16
  const fetchFn = options.fetchFn ?? fetch
17
- const baseUrl = options.baseUrl ?? DEFAULT_API_ORIGIN
17
+ const baseUrl = options.baseUrl ?? resolveDefaultApiOrigin()
18
18
  const clientName = options.clientName ?? '{{projectName}}-shared'
19
19
 
20
20
  const client = apiClient.create({{fullstack.contractExportName}})
@@ -37,6 +37,10 @@ export async function fetchAppInfo(options: FetchAppInfoOptions = {}): Promise<A
37
37
  }
38
38
  }
39
39
 
40
+ function resolveDefaultApiOrigin(): string {
41
+ return import.meta.env.PUBLIC_API_BASE ?? DEFAULT_API_ORIGIN
42
+ }
43
+
40
44
  function normalizeApiBaseUrl(value: string): string {
41
45
  const trimmed = trimTrailingSlash(value)
42
46
  return trimmed.endsWith(APP_HTTP_ROUTES.apiPrefix)
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import * as m from '../paraglide/messages.js'
3
- import { AVAILABLE_THEMES, themeStore } from '../stores/theme.svelte.js'
3
+ import { AVAILABLE_THEMES, themeStore } from '../stores/theme-store.svelte.js'
4
4
 
5
5
  function handleChange(event: Event) {
6
6
  const target = event.target as HTMLSelectElement
@@ -9,7 +9,7 @@
9
9
  "noEmit": true,
10
10
  "skipLibCheck": true,
11
11
  "strict": true,
12
- "types": ["svelte"],
12
+ "types": ["svelte", "vite/client"],
13
13
  "verbatimModuleSyntax": true
14
14
  },
15
15
  "include": ["src/**/*", "src/**/*.svelte"],
@@ -1,6 +1,39 @@
1
1
  import process from 'node:process'
2
2
  import { defineConfig } from '@playwright/test'
3
3
 
4
+ const defaultBaseURL = '{{#if fullstack.e2eFrontend}}http://127.0.0.1:{{fullstack.e2eFrontend.port}}{{else}}http://127.0.0.1:3000{{/if}}'
5
+ const defaultServiceBaseURL = 'http://127.0.0.1:3000'
6
+ const baseURL = process.env.BASE_URL || defaultBaseURL
7
+ const frontendHost = resolveHostFromBaseUrl(baseURL, '127.0.0.1')
8
+ const frontendPort = resolvePortFromBaseUrl(baseURL, '{{#if fullstack.e2eFrontend}}{{fullstack.e2eFrontend.port}}{{else}}3000{{/if}}')
9
+ const serviceBaseURL = process.env.SERVICE_BASE_URL || defaultServiceBaseURL
10
+ const serviceHost = resolveHostFromBaseUrl(serviceBaseURL, '127.0.0.1')
11
+ const servicePort = resolvePortFromBaseUrl(serviceBaseURL, '3000')
12
+
13
+ function resolveHostFromBaseUrl(urlString: string, fallbackHost: string): string {
14
+ try {
15
+ const url = new URL(urlString)
16
+ return url.hostname || fallbackHost
17
+ }
18
+ catch {
19
+ return fallbackHost
20
+ }
21
+ }
22
+
23
+ function resolvePortFromBaseUrl(urlString: string, fallbackPort: string): string {
24
+ try {
25
+ const url = new URL(urlString)
26
+ if (url.port) {
27
+ return url.port
28
+ }
29
+
30
+ return url.protocol === 'https:' ? '443' : '80'
31
+ }
32
+ catch {
33
+ return fallbackPort
34
+ }
35
+ }
36
+
4
37
  export default defineConfig({
5
38
  testDir: './e2e',
6
39
  fullyParallel: false,
@@ -10,7 +43,7 @@ export default defineConfig({
10
43
  reporter: 'list',
11
44
  timeout: 30_000,
12
45
  use: {
13
- baseURL: '{{#if fullstack.e2eFrontend}}http://127.0.0.1:{{fullstack.e2eFrontend.port}}{{else}}http://127.0.0.1:3000{{/if}}',
46
+ baseURL,
14
47
  channel: 'chrome',
15
48
  trace: 'on-first-retry',
16
49
  screenshot: 'only-on-failure',
@@ -19,14 +52,18 @@ export default defineConfig({
19
52
  {
20
53
  name: '{{fullstack.servPackageName}}',
21
54
  command: 'pnpm --filter {{fullstack.servPackageName}} start',
22
- url: 'http://127.0.0.1:3000/health',
55
+ env: {
56
+ HOST: serviceHost,
57
+ PORT: servicePort,
58
+ },
59
+ url: `${serviceBaseURL}/health`,
23
60
  reuseExistingServer: false,
24
61
  timeout: 120_000,
25
62
  }{{#if fullstack.e2eFrontend}},
26
63
  {
27
64
  name: '{{fullstack.e2eFrontend.packageName}}',
28
- command: 'pnpm --filter {{fullstack.e2eFrontend.packageName}} preview',
29
- url: 'http://127.0.0.1:{{fullstack.e2eFrontend.port}}',
65
+ command: `pnpm --filter {{fullstack.e2eFrontend.packageName}} exec vite preview --host ${frontendHost} --port ${frontendPort} --strictPort`,
66
+ url: baseURL,
30
67
  reuseExistingServer: false,
31
68
  timeout: 120_000,
32
69
  }{{/if}},
@@ -0,0 +1,15 @@
1
+ {{! @skipIfEmpty }}
2
+ shellEmulator: true
3
+
4
+ trustPolicy: no-downgrade
5
+
6
+ packages:
7
+ - packages/*
8
+ - apps/*
9
+
10
+ {{#if useCatalogProtocol}}
11
+ catalog:
12
+ {{#each haiCatalogPackages}}
13
+ '{{@key}}': {{this}}
14
+ {{/each}}
15
+ {{/if}}
@@ -0,0 +1,3 @@
1
+ # {{projectName}} — 环境变量
2
+ # Android 模拟器使用 10.0.2.2 访问宿主机;真机请改为局域网 IP。
3
+ PUBLIC_API_BASE=http://10.0.2.2:3000/api/v1
@@ -3,10 +3,10 @@ import type { CapacitorConfig } from '@capacitor/cli'
3
3
  const config: CapacitorConfig = {
4
4
  appId: 'com.hai.{{projectName}}',
5
5
  appName: '{{projectName}}',
6
- webDir: 'build',
6
+ webDir: 'dist',
7
7
  server: {
8
8
  androidScheme: 'https',
9
9
  },
10
10
  }
11
11
 
12
- export default config
12
+ export default config
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="{{defaultLocale}}">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
+ <meta name="theme-color" content="#2563eb" />
7
+ <title>{{projectName}}</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://inlang.com/schema/inlang-message-format",
3
+ "app_title": "Mobile Workspace",
4
+ "app_description": "A mobile app built with hai Agent Framework + Capacitor + Svelte 5",
5
+ "boot_failed_title": "Boot failed",
6
+ "common_retry": "Retry",
7
+ "common_refresh": "Refresh",
8
+ "common_enabled": "Enabled",
9
+ "common_disabled": "Disabled",
10
+ "nav_home": "Workspace",
11
+ "nav_tasks": "Tasks",
12
+ "nav_profile": "Profile",
13
+ "hero_eyebrow": "Mobile Workspace",
14
+ "hero_title": "A real mobile-first starting point",
15
+ "hero_subtitle": "Capacitor native capabilities, secure token storage, unified API client and @h-ai/ui mobile components are wired in.",
16
+ "feature_native_title": "Native ready",
17
+ "feature_native_desc": "Status bar, safe areas and device runtime are ready for push, camera and more plugins.",
18
+ "feature_api_title": "Unified API",
19
+ "feature_api_desc": "Connect to your backend through PUBLIC_API_BASE. Tokens use secure storage on native devices.",
20
+ "status_title": "Runtime status",
21
+ "status_api_label": "API base",
22
+ "status_runtime_label": "Runtime",
23
+ "status_runtime_native": "Native app",
24
+ "status_runtime_web": "Browser preview",
25
+ "status_token_label": "Token storage",
26
+ "status_token_native": "Secure storage",
27
+ "status_token_memory": "Memory storage",
28
+ "tasks_title": "Today's tasks",
29
+ "tasks_sync": "Wire backend APIs",
30
+ "tasks_polish": "Polish mobile UX",
31
+ "tasks_release": "Prepare native packaging",
32
+ "profile_title": "App settings",
33
+ "profile_language": "Locale follows Paraglide",
34
+ "profile_theme": "Theme follows @h-ai/ui",
35
+ "profile_secure": "Sensitive tokens avoid localStorage"
36
+ }