robodev 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/package.json +1 -1
  2. package/src/collect-files.test.ts +19 -0
  3. package/src/collect-files.ts +1 -0
  4. package/src/config.test.ts +26 -0
  5. package/src/config.ts +34 -5
  6. package/src/emit-starter.test.ts +68 -0
  7. package/src/emit-starter.ts +47 -13
  8. package/src/index.ts +45 -4
  9. package/templates/auth-chat/.config/local.spa.template.yml +27 -0
  10. package/templates/auth-chat/.oxlint-base.json +29 -0
  11. package/templates/auth-chat/.oxlintrc.json +78 -0
  12. package/templates/auth-chat/.rulesync/rules/frontend-api-boundary.md +36 -0
  13. package/templates/auth-chat/.rulesync/rules/frontend-component-structure.md +38 -0
  14. package/templates/auth-chat/.rulesync/rules/frontend-forms.md +32 -0
  15. package/templates/auth-chat/.rulesync/rules/frontend-notifications.md +24 -0
  16. package/templates/auth-chat/.rulesync/rules/frontend-povio-components.md +42 -0
  17. package/templates/auth-chat/.rulesync/rules/frontend-povio-ui.md +45 -0
  18. package/templates/auth-chat/.rulesync/rules/frontend-query-autocomplete.md +29 -0
  19. package/templates/auth-chat/.rulesync/rules/frontend-tables-and-lists.md +34 -0
  20. package/templates/auth-chat/.rulesync/rules/frontend-translations.md +26 -0
  21. package/templates/auth-chat/.rulesync/rules/project-overview.md +32 -0
  22. package/templates/auth-chat/.rulesync/rules/robodev-api.md +30 -0
  23. package/templates/auth-chat/.rulesync/rules/robodev-auth.md +40 -0
  24. package/templates/auth-chat/.rulesync/rules/robodev-database.md +22 -0
  25. package/templates/auth-chat/.rulesync/rules/role-based-app-structure.md +36 -0
  26. package/templates/auth-chat/.rulesync/skills/media-feature/SKILL.md +21 -0
  27. package/templates/auth-chat/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
  28. package/templates/auth-chat/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
  29. package/templates/auth-chat/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
  30. package/templates/auth-chat/.rulesync/skills/robodev-table/SKILL.md +12 -0
  31. package/templates/auth-chat/README.md +26 -7
  32. package/templates/auth-chat/api/invite.ts +43 -0
  33. package/templates/auth-chat/apps/fe/index.html +24 -0
  34. package/templates/auth-chat/apps/fe/openapi-codegen.config.ts +37 -0
  35. package/templates/auth-chat/apps/fe/package.json +82 -0
  36. package/templates/auth-chat/apps/fe/public/apple-touch-icon.png +0 -0
  37. package/templates/auth-chat/apps/fe/public/favicon-96x96.png +0 -0
  38. package/templates/auth-chat/apps/fe/public/favicon.ico +0 -0
  39. package/templates/auth-chat/apps/fe/public/favicon.svg +3 -0
  40. package/templates/auth-chat/apps/fe/public/site.webmanifest +21 -0
  41. package/templates/auth-chat/apps/fe/public/web-app-manifest-192x192.png +0 -0
  42. package/templates/auth-chat/apps/fe/public/web-app-manifest-512x512.png +0 -0
  43. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
  44. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
  45. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
  46. package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
  47. package/templates/auth-chat/apps/fe/src/assets/locales/en/translation.json +202 -0
  48. package/templates/auth-chat/apps/fe/src/assets/locales/sl/translation.json +202 -0
  49. package/templates/auth-chat/apps/fe/src/clients/app-rest-client.ts +15 -0
  50. package/templates/auth-chat/apps/fe/src/clients/auth-token-store.ts +101 -0
  51. package/templates/auth-chat/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
  52. package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
  53. package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
  54. package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
  55. package/templates/auth-chat/apps/fe/src/components/404.tsx +18 -0
  56. package/templates/auth-chat/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
  57. package/templates/auth-chat/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
  58. package/templates/auth-chat/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
  59. package/templates/auth-chat/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
  60. package/templates/auth-chat/apps/fe/src/components/features/chat/ChatPage.tsx +105 -0
  61. package/templates/auth-chat/apps/fe/src/components/features/chat/InviteForm.tsx +84 -0
  62. package/templates/auth-chat/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
  63. package/templates/auth-chat/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
  64. package/templates/auth-chat/apps/fe/src/components/layout/AppLayout.tsx +19 -0
  65. package/templates/auth-chat/apps/fe/src/components/layout/app-header/AppHeader.tsx +91 -0
  66. package/templates/auth-chat/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +51 -0
  67. package/templates/auth-chat/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
  68. package/templates/auth-chat/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
  69. package/templates/auth-chat/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
  70. package/templates/auth-chat/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
  71. package/templates/auth-chat/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
  72. package/templates/auth-chat/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
  73. package/templates/auth-chat/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
  74. package/templates/auth-chat/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
  75. package/templates/auth-chat/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
  76. package/templates/auth-chat/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
  77. package/templates/auth-chat/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
  78. package/templates/auth-chat/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
  79. package/templates/auth-chat/apps/fe/src/components/shared/ui/Card.tsx +60 -0
  80. package/templates/auth-chat/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
  81. package/templates/auth-chat/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
  82. package/templates/auth-chat/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
  83. package/templates/auth-chat/apps/fe/src/config/app.config.ts +35 -0
  84. package/templates/auth-chat/apps/fe/src/config/i18n.ts +43 -0
  85. package/templates/auth-chat/apps/fe/src/config/inits/a11y.ts +14 -0
  86. package/templates/auth-chat/apps/fe/src/config/inits/logger.ts +7 -0
  87. package/templates/auth-chat/apps/fe/src/config/inits/sentry.ts +21 -0
  88. package/templates/auth-chat/apps/fe/src/config/jwt.config.ts +2 -0
  89. package/templates/auth-chat/apps/fe/src/config/query.config.ts +20 -0
  90. package/templates/auth-chat/apps/fe/src/hooks/useAuth.ts +5 -0
  91. package/templates/auth-chat/apps/fe/src/main.tsx +52 -0
  92. package/templates/auth-chat/apps/fe/src/pages/(guest)/login.tsx +23 -0
  93. package/templates/auth-chat/apps/fe/src/pages/(guest)/register.tsx +23 -0
  94. package/templates/auth-chat/apps/fe/src/pages/(guest)/route.tsx +18 -0
  95. package/templates/auth-chat/apps/fe/src/pages/(private)/index.tsx +23 -0
  96. package/templates/auth-chat/apps/fe/src/pages/(private)/profile.tsx +23 -0
  97. package/templates/auth-chat/apps/fe/src/pages/(private)/route.tsx +18 -0
  98. package/templates/auth-chat/apps/fe/src/pages/(public)/auth.tsx +37 -0
  99. package/templates/auth-chat/apps/fe/src/pages/(public)/route.tsx +9 -0
  100. package/templates/auth-chat/apps/fe/src/pages/__root.tsx +164 -0
  101. package/templates/auth-chat/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
  102. package/templates/auth-chat/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
  103. package/templates/auth-chat/apps/fe/src/providers/index.tsx +44 -0
  104. package/templates/auth-chat/apps/fe/src/providers/jwt.provider.tsx +95 -0
  105. package/templates/auth-chat/apps/fe/src/routeTree.gen.ts +225 -0
  106. package/templates/auth-chat/apps/fe/src/styles/base.css +103 -0
  107. package/templates/auth-chat/apps/fe/src/styles/fonts/fonts.tsx +10 -0
  108. package/templates/auth-chat/apps/fe/src/styles/fonts/general-sans.css +31 -0
  109. package/templates/auth-chat/apps/fe/src/styles/globals.css +28 -0
  110. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
  111. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
  112. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
  113. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
  114. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
  115. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
  116. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
  117. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
  118. package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
  119. package/templates/auth-chat/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
  120. package/templates/auth-chat/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
  121. package/templates/auth-chat/apps/fe/src/styles/theme.css +2177 -0
  122. package/templates/auth-chat/apps/fe/src/types/i18next.d.ts +12 -0
  123. package/templates/auth-chat/apps/fe/src/types/table.d.ts +17 -0
  124. package/templates/auth-chat/apps/fe/src/types/ui.d.ts +26 -0
  125. package/templates/auth-chat/apps/fe/src/types/vite-env.d.ts +20 -0
  126. package/templates/auth-chat/apps/fe/src/utils/date.utils.ts +17 -0
  127. package/templates/auth-chat/apps/fe/src/utils/number.utils.ts +12 -0
  128. package/templates/auth-chat/apps/fe/src/utils/string.utils.ts +5 -0
  129. package/templates/auth-chat/apps/fe/src/vite-env.d.ts +1 -0
  130. package/templates/auth-chat/apps/fe/tsconfig.app.json +32 -0
  131. package/templates/auth-chat/apps/fe/tsconfig.json +9 -0
  132. package/templates/auth-chat/apps/fe/tsconfig.node.json +31 -0
  133. package/templates/auth-chat/apps/fe/vite.config.ts +94 -0
  134. package/templates/auth-chat/openapi.json +1016 -0
  135. package/templates/auth-chat/oxfmt.config.js +23 -0
  136. package/templates/auth-chat/package.json +6 -13
  137. package/templates/auth-chat/rulesync.jsonc +18 -0
  138. package/templates/auth-chat/tsconfig.json +2 -4
  139. package/templates/backend/README.md +2 -2
  140. package/templates/backend/api/_lib.ts +359 -0
  141. package/templates/backend/api/aliens/labels.ts +19 -0
  142. package/templates/backend/api/files/presigned-url.ts +48 -0
  143. package/templates/backend/api/files/upload.ts +29 -0
  144. package/templates/backend/api/planets/[id]/like.ts +36 -0
  145. package/templates/backend/api/planets/[id].ts +56 -13
  146. package/templates/backend/api/planets/paginate.ts +36 -0
  147. package/templates/backend/api/planets.ts +52 -44
  148. package/templates/backend/database.ts +33 -13
  149. package/templates/backend/package.json +1 -1
  150. package/templates/catalog.json +3 -3
  151. package/templates/empty/package.json +1 -1
  152. package/templates/space/.config/local.spa.template.yml +27 -0
  153. package/templates/space/.oxlint-base.json +29 -0
  154. package/templates/space/.oxlintrc.json +78 -0
  155. package/templates/space/.rulesync/rules/frontend-api-boundary.md +36 -0
  156. package/templates/space/.rulesync/rules/frontend-component-structure.md +38 -0
  157. package/templates/space/.rulesync/rules/frontend-forms.md +32 -0
  158. package/templates/space/.rulesync/rules/frontend-notifications.md +24 -0
  159. package/templates/space/.rulesync/rules/frontend-povio-components.md +42 -0
  160. package/templates/space/.rulesync/rules/frontend-povio-ui.md +45 -0
  161. package/templates/space/.rulesync/rules/frontend-query-autocomplete.md +29 -0
  162. package/templates/space/.rulesync/rules/frontend-tables-and-lists.md +34 -0
  163. package/templates/space/.rulesync/rules/frontend-translations.md +26 -0
  164. package/templates/space/.rulesync/rules/project-overview.md +32 -0
  165. package/templates/space/.rulesync/rules/robodev-api.md +30 -0
  166. package/templates/space/.rulesync/rules/robodev-auth.md +40 -0
  167. package/templates/space/.rulesync/rules/robodev-database.md +22 -0
  168. package/templates/space/.rulesync/rules/role-based-app-structure.md +36 -0
  169. package/templates/space/.rulesync/skills/media-feature/SKILL.md +21 -0
  170. package/templates/space/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
  171. package/templates/space/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
  172. package/templates/space/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
  173. package/templates/space/.rulesync/skills/robodev-table/SKILL.md +12 -0
  174. package/templates/space/README.md +26 -7
  175. package/templates/space/api/_lib.ts +359 -0
  176. package/templates/space/api/aliens/labels.ts +19 -0
  177. package/templates/space/api/files/presigned-url.ts +48 -0
  178. package/templates/space/api/files/upload.ts +29 -0
  179. package/templates/space/api/planets/[id]/like.ts +36 -0
  180. package/templates/space/api/planets/[id].ts +56 -13
  181. package/templates/space/api/planets/paginate.ts +36 -0
  182. package/templates/space/api/planets.ts +52 -44
  183. package/templates/space/apps/fe/index.html +24 -0
  184. package/templates/space/apps/fe/openapi-codegen.config.ts +37 -0
  185. package/templates/space/apps/fe/package.json +82 -0
  186. package/templates/space/apps/fe/public/apple-touch-icon.png +0 -0
  187. package/templates/space/apps/fe/public/favicon-96x96.png +0 -0
  188. package/templates/space/apps/fe/public/favicon.ico +0 -0
  189. package/templates/space/apps/fe/public/favicon.svg +3 -0
  190. package/templates/space/apps/fe/public/site.webmanifest +21 -0
  191. package/templates/space/apps/fe/public/web-app-manifest-192x192.png +0 -0
  192. package/templates/space/apps/fe/public/web-app-manifest-512x512.png +0 -0
  193. package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
  194. package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
  195. package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
  196. package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
  197. package/templates/space/apps/fe/src/assets/locales/en/translation.json +301 -0
  198. package/templates/space/apps/fe/src/assets/locales/sl/translation.json +301 -0
  199. package/templates/space/apps/fe/src/clients/app-rest-client.ts +15 -0
  200. package/templates/space/apps/fe/src/clients/auth-token-store.ts +101 -0
  201. package/templates/space/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
  202. package/templates/space/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
  203. package/templates/space/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
  204. package/templates/space/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
  205. package/templates/space/apps/fe/src/components/404.tsx +18 -0
  206. package/templates/space/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
  207. package/templates/space/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
  208. package/templates/space/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
  209. package/templates/space/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
  210. package/templates/space/apps/fe/src/components/features/home/HomePage.tsx +21 -0
  211. package/templates/space/apps/fe/src/components/features/planets/details/PlanetDetailsPage.tsx +139 -0
  212. package/templates/space/apps/fe/src/components/features/planets/details/PlanetEditPage.tsx +266 -0
  213. package/templates/space/apps/fe/src/components/features/planets/feed/PlanetFeedCard.tsx +131 -0
  214. package/templates/space/apps/fe/src/components/features/planets/feed/PlanetsFeedPage.tsx +119 -0
  215. package/templates/space/apps/fe/src/components/features/planets/list/PlanetCard.tsx +89 -0
  216. package/templates/space/apps/fe/src/components/features/planets/list/PlanetCreateModal.tsx +179 -0
  217. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.css +3 -0
  218. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.tsx +73 -0
  219. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsGridPage.tsx +51 -0
  220. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsInfiniteTablePage.tsx +79 -0
  221. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsPage.tsx +65 -0
  222. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTablePage.tsx +72 -0
  223. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTableSummary.tsx +29 -0
  224. package/templates/space/apps/fe/src/components/features/planets/list/PlanetsViewTabs.tsx +79 -0
  225. package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTable.tsx +30 -0
  226. package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableActions.tsx +64 -0
  227. package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableInfinite.tsx +36 -0
  228. package/templates/space/apps/fe/src/components/features/planets/list/table/planetsTableColumns.tsx +72 -0
  229. package/templates/space/apps/fe/src/components/features/planets/list/table/usePlanetTableFilters.tsx +102 -0
  230. package/templates/space/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
  231. package/templates/space/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
  232. package/templates/space/apps/fe/src/components/layout/AppLayout.tsx +19 -0
  233. package/templates/space/apps/fe/src/components/layout/app-header/AppHeader.tsx +93 -0
  234. package/templates/space/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +53 -0
  235. package/templates/space/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
  236. package/templates/space/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
  237. package/templates/space/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
  238. package/templates/space/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
  239. package/templates/space/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
  240. package/templates/space/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
  241. package/templates/space/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
  242. package/templates/space/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
  243. package/templates/space/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
  244. package/templates/space/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
  245. package/templates/space/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
  246. package/templates/space/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
  247. package/templates/space/apps/fe/src/components/shared/ui/Card.tsx +60 -0
  248. package/templates/space/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
  249. package/templates/space/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
  250. package/templates/space/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
  251. package/templates/space/apps/fe/src/config/app.config.ts +35 -0
  252. package/templates/space/apps/fe/src/config/i18n.ts +43 -0
  253. package/templates/space/apps/fe/src/config/inits/a11y.ts +14 -0
  254. package/templates/space/apps/fe/src/config/inits/logger.ts +7 -0
  255. package/templates/space/apps/fe/src/config/inits/sentry.ts +21 -0
  256. package/templates/space/apps/fe/src/config/jwt.config.ts +2 -0
  257. package/templates/space/apps/fe/src/config/query.config.ts +20 -0
  258. package/templates/space/apps/fe/src/hooks/useAuth.ts +5 -0
  259. package/templates/space/apps/fe/src/main.tsx +52 -0
  260. package/templates/space/apps/fe/src/pages/(guest)/login.tsx +23 -0
  261. package/templates/space/apps/fe/src/pages/(guest)/register.tsx +23 -0
  262. package/templates/space/apps/fe/src/pages/(guest)/route.tsx +18 -0
  263. package/templates/space/apps/fe/src/pages/(private)/index.tsx +23 -0
  264. package/templates/space/apps/fe/src/pages/(private)/planets/$id/edit.tsx +53 -0
  265. package/templates/space/apps/fe/src/pages/(private)/planets/$id/index.tsx +41 -0
  266. package/templates/space/apps/fe/src/pages/(private)/planets/index.tsx +24 -0
  267. package/templates/space/apps/fe/src/pages/(private)/planets-feed.tsx +23 -0
  268. package/templates/space/apps/fe/src/pages/(private)/profile.tsx +23 -0
  269. package/templates/space/apps/fe/src/pages/(private)/route.tsx +18 -0
  270. package/templates/space/apps/fe/src/pages/(public)/auth.tsx +37 -0
  271. package/templates/space/apps/fe/src/pages/(public)/route.tsx +9 -0
  272. package/templates/space/apps/fe/src/pages/__root.tsx +164 -0
  273. package/templates/space/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
  274. package/templates/space/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
  275. package/templates/space/apps/fe/src/providers/index.tsx +44 -0
  276. package/templates/space/apps/fe/src/providers/jwt.provider.tsx +95 -0
  277. package/templates/space/apps/fe/src/routeTree.gen.ts +309 -0
  278. package/templates/space/apps/fe/src/styles/base.css +103 -0
  279. package/templates/space/apps/fe/src/styles/fonts/fonts.tsx +10 -0
  280. package/templates/space/apps/fe/src/styles/fonts/general-sans.css +31 -0
  281. package/templates/space/apps/fe/src/styles/globals.css +28 -0
  282. package/templates/space/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
  283. package/templates/space/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
  284. package/templates/space/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
  285. package/templates/space/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
  286. package/templates/space/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
  287. package/templates/space/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
  288. package/templates/space/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
  289. package/templates/space/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
  290. package/templates/space/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
  291. package/templates/space/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
  292. package/templates/space/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
  293. package/templates/space/apps/fe/src/styles/theme.css +2177 -0
  294. package/templates/space/apps/fe/src/types/i18next.d.ts +12 -0
  295. package/templates/space/apps/fe/src/types/table.d.ts +17 -0
  296. package/templates/space/apps/fe/src/types/ui.d.ts +26 -0
  297. package/templates/space/apps/fe/src/types/vite-env.d.ts +20 -0
  298. package/templates/space/apps/fe/src/utils/date.utils.ts +17 -0
  299. package/templates/space/apps/fe/src/utils/image-fallback.ts +9 -0
  300. package/templates/space/apps/fe/src/utils/media-upload.ts +63 -0
  301. package/templates/space/apps/fe/src/utils/number.utils.ts +12 -0
  302. package/templates/space/apps/fe/src/utils/string.utils.ts +5 -0
  303. package/templates/space/apps/fe/src/vite-env.d.ts +1 -0
  304. package/templates/space/apps/fe/tsconfig.app.json +32 -0
  305. package/templates/space/apps/fe/tsconfig.json +9 -0
  306. package/templates/space/apps/fe/tsconfig.node.json +31 -0
  307. package/templates/space/apps/fe/vite.config.ts +94 -0
  308. package/templates/space/database.ts +33 -13
  309. package/templates/space/openapi.json +1819 -0
  310. package/templates/space/oxfmt.config.js +23 -0
  311. package/templates/space/package.json +6 -13
  312. package/templates/space/rulesync.jsonc +18 -0
  313. package/templates/space/tsconfig.json +2 -4
  314. package/templates/auth-chat/index.html +0 -201
  315. package/templates/auth-chat/src/main.tsx +0 -282
  316. package/templates/auth-chat/vite.config.ts +0 -7
  317. package/templates/backend/api/health.ts +0 -7
  318. package/templates/backend/api/launch.ts +0 -29
  319. package/templates/backend/api/me.ts +0 -14
  320. package/templates/backend/api/motto.ts +0 -9
  321. package/templates/backend/api/rockets.ts +0 -43
  322. package/templates/space/api/health.ts +0 -7
  323. package/templates/space/api/launch.ts +0 -29
  324. package/templates/space/api/me.ts +0 -14
  325. package/templates/space/api/motto.ts +0 -9
  326. package/templates/space/api/rockets.ts +0 -43
  327. package/templates/space/index.html +0 -138
  328. package/templates/space/src/main.tsx +0 -229
  329. package/templates/space/vite.config.ts +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robodev",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "CLI for Robodev Starbase — create, auth, link, and deploy hosted apps",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,3 +45,22 @@ test("collectFiles includes hosted sources and skips denied paths", async () =>
45
45
  await rm(root, { recursive: true, force: true });
46
46
  }
47
47
  });
48
+
49
+ test("collectFiles skips nested apps/fe sources", async () => {
50
+ const root = await mkdtemp(join(tmpdir(), "rd-cli-"));
51
+ try {
52
+ await writeFile(join(root, "database.ts"), "export default {}");
53
+ await mkdir(join(root, "apps/fe/src"), { recursive: true });
54
+ await writeFile(join(root, "apps/fe/src/main.tsx"), "export {}");
55
+ await writeFile(join(root, "apps/fe/index.html"), "<html></html>");
56
+ await writeFile(join(root, "apps/fe/package.json"), "{}");
57
+ const files = await collectFiles(root);
58
+ const paths = new Set(files.map((file) => file.path));
59
+ assert.ok(paths.has("database.ts"));
60
+ assert.ok(!paths.has("apps/fe/src/main.tsx"));
61
+ assert.ok(!paths.has("apps/fe/index.html"));
62
+ assert.ok(!paths.has("apps/fe/package.json"));
63
+ } finally {
64
+ await rm(root, { recursive: true, force: true });
65
+ }
66
+ });
@@ -86,6 +86,7 @@ function isUnsafe(path: string): boolean {
86
86
  function isDenied(path: string): boolean {
87
87
  const top = path.split("/")[0];
88
88
  if (SKIP_DIRS.has(top)) return true;
89
+ if (path === "apps" || path.startsWith("apps/")) return true;
89
90
  if (path === ".env" || path.startsWith(".env.")) return true;
90
91
  const name = basename(path);
91
92
  if (name.startsWith(".") && name !== ".gitignore") return true;
@@ -0,0 +1,26 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { test } from "node:test";
6
+ import { writeViteApiUrl } from "./config.js";
7
+
8
+ test("writeViteApiUrl sets Vite and Povio public API URLs", async () => {
9
+ const root = await mkdtemp(join(tmpdir(), "rd-env-"));
10
+ try {
11
+ await mkdir(join(root, ".config"), { recursive: true });
12
+ await writeFile(
13
+ join(root, ".config/local.spa.template.yml"),
14
+ `APP_PUBLIC_API_URL: &APP_PUBLIC_API_URL "http://localhost:4000"\nVITE_PUBLIC_API_URL: *APP_PUBLIC_API_URL\n`,
15
+ );
16
+ await writeViteApiUrl("https://prj.example.test", root);
17
+ const env = await readFile(join(root, ".env"), "utf8");
18
+ assert.match(env, /^VITE_API_URL=https:\/\/prj\.example\.test$/m);
19
+ assert.match(env, /^VITE_PUBLIC_API_URL=https:\/\/prj\.example\.test$/m);
20
+ assert.match(env, /^APP_PUBLIC_API_URL=https:\/\/prj\.example\.test$/m);
21
+ const spa = await readFile(join(root, ".config/local.spa.yml"), "utf8");
22
+ assert.match(spa, /APP_PUBLIC_API_URL: &APP_PUBLIC_API_URL "https:\/\/prj\.example\.test"/);
23
+ } finally {
24
+ await rm(root, { recursive: true, force: true });
25
+ }
26
+ });
package/src/config.ts CHANGED
@@ -65,7 +65,17 @@ export async function writeLink(link: ProjectLink, cwd = process.cwd()): Promise
65
65
  await writeFile(linkPath(cwd), `${JSON.stringify(link, null, 2)}\n`);
66
66
  }
67
67
 
68
- /** Sets `VITE_API_URL` so the starter UI talks to the deployed project host. */
68
+ const PROJECT_API_ENV_KEYS = ["VITE_API_URL", "VITE_PUBLIC_API_URL", "APP_PUBLIC_API_URL"] as const;
69
+
70
+ function upsertEnvKey(content: string, key: string, value: string): string {
71
+ const line = `${key}=${value}`;
72
+ const pattern = new RegExp(`^${key}=.*$`, "m");
73
+ if (pattern.test(content)) return content.replace(pattern, line);
74
+ const trimmed = content.replace(/\s*$/, "");
75
+ return `${trimmed}${trimmed ? "\n" : ""}${line}\n`;
76
+ }
77
+
78
+ /** Sets Vite / Povio API URLs so starter UIs talk to the deployed project host. */
69
79
  export async function writeViteApiUrl(projectApiUrl: string, cwd = process.cwd()): Promise<void> {
70
80
  const envPath = join(cwd, ".env");
71
81
  let content = "";
@@ -74,10 +84,29 @@ export async function writeViteApiUrl(projectApiUrl: string, cwd = process.cwd()
74
84
  } catch {
75
85
  content = "";
76
86
  }
77
- if (/^VITE_API_URL=/m.test(content)) {
78
- content = content.replace(/^VITE_API_URL=.*$/m, `VITE_API_URL=${projectApiUrl}`);
79
- } else {
80
- content = `${content.replace(/\s*$/, "")}${content ? "\n" : ""}VITE_API_URL=${projectApiUrl}\n`;
87
+ for (const key of PROJECT_API_ENV_KEYS) {
88
+ content = upsertEnvKey(content, key, projectApiUrl);
81
89
  }
82
90
  await writeFile(envPath, content.endsWith("\n") ? content : `${content}\n`);
91
+ await rewriteLocalSpaConfig(projectApiUrl, cwd);
92
+ }
93
+
94
+ async function rewriteLocalSpaConfig(projectApiUrl: string, cwd: string): Promise<void> {
95
+ const configDir = join(cwd, ".config");
96
+ const templatePath = join(configDir, "local.spa.template.yml");
97
+ const destPath = join(configDir, "local.spa.yml");
98
+ let source: string;
99
+ try {
100
+ source = await readFile(templatePath, "utf8");
101
+ } catch {
102
+ try {
103
+ source = await readFile(destPath, "utf8");
104
+ } catch {
105
+ return;
106
+ }
107
+ }
108
+ const quoted = JSON.stringify(projectApiUrl);
109
+ const next = source.replace(/^(APP_PUBLIC_API_URL:\s*&APP_PUBLIC_API_URL\s+).+$/m, `$1${quoted}`);
110
+ await mkdir(configDir, { recursive: true });
111
+ await writeFile(destPath, next.endsWith("\n") ? next : `${next}\n`);
83
112
  }
@@ -0,0 +1,68 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { test } from "node:test";
6
+ import { collectFiles } from "./collect-files.js";
7
+ import { cliRoot, emitStarter } from "./emit-starter.js";
8
+
9
+ const versions = { robodev: "0.12.0", sdk: "0.12.0", client: "0.12.0" };
10
+
11
+ test("emitStarter rewrites root package.json name only", async () => {
12
+ const dest = await mkdtemp(join(tmpdir(), "rd-emit-"));
13
+ try {
14
+ await emitStarter(join(cliRoot(), "..", "starters", "space"), dest, {
15
+ name: "Orbit",
16
+ packageName: "orbit-app",
17
+ title: "Space",
18
+ versions,
19
+ });
20
+ const rootPkg = JSON.parse(await readFile(join(dest, "package.json"), "utf8")) as {
21
+ name: string;
22
+ };
23
+ const fePkg = JSON.parse(await readFile(join(dest, "apps/fe/package.json"), "utf8")) as {
24
+ name: string;
25
+ dependencies?: Record<string, string>;
26
+ devDependencies?: Record<string, string>;
27
+ };
28
+ assert.equal(rootPkg.name, "orbit-app");
29
+ assert.equal(fePkg.name, "fe");
30
+ assert.equal(fePkg.dependencies?.robodev, undefined);
31
+ assert.equal(fePkg.devDependencies?.robodev, undefined);
32
+ const overview = await readFile(join(dest, ".rulesync/rules/project-overview.md"), "utf8");
33
+ assert.match(overview, /^# Space starter AI rules/m);
34
+ const readme = await readFile(join(dest, "README.md"), "utf8");
35
+ assert.match(readme, /^# Orbit/m);
36
+ } finally {
37
+ await rm(dest, { recursive: true, force: true });
38
+ }
39
+ });
40
+
41
+ test("collectFiles for auth-chat starter is Tiny APIs without apps/fe", async () => {
42
+ const files = await collectFiles(join(cliRoot(), "..", "starters", "auth-chat"));
43
+ const paths = new Set(files.map((file) => file.path));
44
+ assert.ok(paths.has("database.ts"));
45
+ assert.ok(paths.has("api/messages.ts"));
46
+ assert.ok(paths.has("api/invite.ts"));
47
+ assert.ok(paths.has("api/health.ts"));
48
+ assert.ok(paths.has("api/me.ts"));
49
+ assert.ok(!paths.has("index.html"));
50
+ assert.ok(!paths.has("src/main.tsx"));
51
+ assert.ok([...paths].every((path) => !path.startsWith("apps/")));
52
+ assert.ok(files.length < 200);
53
+ });
54
+
55
+ test("collectFiles for space starter is Tiny APIs without apps/fe", async () => {
56
+ const files = await collectFiles(join(cliRoot(), "..", "starters", "space"));
57
+ const paths = new Set(files.map((file) => file.path));
58
+ assert.ok(paths.has("database.ts"));
59
+ assert.ok(paths.has("api/planets.ts"));
60
+ assert.ok(paths.has("api/planets/paginate.ts"));
61
+ assert.ok(paths.has("api/aliens/labels.ts"));
62
+ assert.ok(paths.has("api/files/presigned-url.ts"));
63
+ assert.ok(paths.has("api/files/upload.ts"));
64
+ assert.ok(!paths.has("index.html"));
65
+ assert.ok(!paths.has("src/main.tsx"));
66
+ assert.ok([...paths].every((path) => !path.startsWith("apps/")));
67
+ assert.ok(files.length < 200);
68
+ });
@@ -1,5 +1,5 @@
1
- import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
1
+ import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import { dirname, extname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
5
  export type Starter = {
@@ -23,6 +23,20 @@ export type EmitContext = {
23
23
  };
24
24
 
25
25
  const SKIP_DIRS = new Set(["node_modules", ".robodev", ".git"]);
26
+ const BINARY_COPY_EXTENSIONS = new Set([
27
+ ".png",
28
+ ".jpg",
29
+ ".jpeg",
30
+ ".gif",
31
+ ".webp",
32
+ ".ico",
33
+ ".bmp",
34
+ ".woff",
35
+ ".woff2",
36
+ ".ttf",
37
+ ".eot",
38
+ ".otf",
39
+ ]);
26
40
 
27
41
  type DepMap = Record<string, string>;
28
42
 
@@ -103,22 +117,35 @@ export async function loadPackageVersions(): Promise<PackageVersions> {
103
117
  }
104
118
 
105
119
  /** Copies a starter tree and applies package + display rewrites used by create and prepack. */
106
- export async function emitStarter(src: string, dest: string, ctx: EmitContext): Promise<void> {
120
+ export async function emitStarter(
121
+ src: string,
122
+ dest: string,
123
+ ctx: EmitContext,
124
+ isRoot = true,
125
+ ): Promise<void> {
107
126
  await mkdir(dest, { recursive: true });
108
127
  const entries = await readdir(src, { withFileTypes: true });
109
128
  for (const entry of entries) {
110
129
  if (entry.isDirectory()) {
111
130
  if (SKIP_DIRS.has(entry.name)) continue;
112
- await emitStarter(join(src, entry.name), join(dest, entry.name), ctx);
131
+ await emitStarter(join(src, entry.name), join(dest, entry.name), ctx, false);
113
132
  continue;
114
133
  }
115
134
  if (!entry.isFile() || entry.name === ".env") continue;
116
- const content = await readFile(join(src, entry.name), "utf8");
135
+ const srcPath = join(src, entry.name);
136
+ const destPath = join(dest, entry.name);
137
+ if (BINARY_COPY_EXTENSIONS.has(extname(entry.name).toLowerCase())) {
138
+ await copyFile(srcPath, destPath);
139
+ continue;
140
+ }
141
+ const content = await readFile(srcPath, "utf8");
117
142
  const next =
118
143
  entry.name === "package.json"
119
- ? rewritePackageJson(content, ctx)
120
- : rewriteDisplay(content, ctx);
121
- await writeFile(join(dest, entry.name), next);
144
+ ? rewritePackageJson(content, ctx, isRoot)
145
+ : shouldRewriteDisplay(entry.name, isRoot)
146
+ ? rewriteDisplay(content, ctx)
147
+ : content;
148
+ await writeFile(destPath, next);
122
149
  }
123
150
  }
124
151
 
@@ -154,9 +181,14 @@ async function readPackageVersion(path: string, fallback?: string): Promise<stri
154
181
  throw new Error(`Missing version in ${path}`);
155
182
  }
156
183
 
157
- function rewritePackageJson(raw: string, ctx: EmitContext): string {
184
+ function shouldRewriteDisplay(fileName: string, isRoot: boolean): boolean {
185
+ if (!isRoot) return false;
186
+ return fileName === "README.md" || fileName.endsWith(".html");
187
+ }
188
+
189
+ function rewritePackageJson(raw: string, ctx: EmitContext, isRoot: boolean): string {
158
190
  const pkg = JSON.parse(raw) as PackageJson;
159
- pkg.name = ctx.packageName;
191
+ if (isRoot) pkg.name = ctx.packageName;
160
192
 
161
193
  const pins: Record<string, string> = {
162
194
  robodev: `^${ctx.versions.robodev}`,
@@ -172,9 +204,11 @@ function rewritePackageJson(raw: string, ctx: EmitContext): string {
172
204
  }
173
205
 
174
206
  const moved = pkg.dependencies?.robodev;
175
- if (pkg.dependencies) delete pkg.dependencies.robodev;
176
- pkg.devDependencies = pkg.devDependencies ?? {};
177
- pkg.devDependencies.robodev = pkg.devDependencies.robodev ?? moved ?? pins.robodev;
207
+ if (isRoot) {
208
+ if (pkg.dependencies) delete pkg.dependencies.robodev;
209
+ pkg.devDependencies = pkg.devDependencies ?? {};
210
+ pkg.devDependencies.robodev = pkg.devDependencies.robodev ?? moved ?? pins.robodev;
211
+ }
178
212
 
179
213
  return `${JSON.stringify(pkg, null, 2)}\n`;
180
214
  }
package/src/index.ts CHANGED
@@ -397,6 +397,36 @@ async function npmInstall(root: string): Promise<void> {
397
397
  });
398
398
  }
399
399
 
400
+ async function bunAvailable(): Promise<boolean> {
401
+ try {
402
+ await execFileAsync("bun", ["--version"]);
403
+ return true;
404
+ } catch {
405
+ return false;
406
+ }
407
+ }
408
+
409
+ async function hasAppsFe(root: string): Promise<boolean> {
410
+ try {
411
+ return (await stat(join(root, "apps/fe"))).isDirectory();
412
+ } catch {
413
+ return false;
414
+ }
415
+ }
416
+
417
+ function printFeNextSteps(displayPath: string): void {
418
+ if (displayPath !== ".") {
419
+ console.log(` cd ${displayPath}`);
420
+ }
421
+ console.log(" cd apps/fe");
422
+ console.log(" bun install");
423
+ console.log(" bun openapi:gen");
424
+ console.log(" bun dev");
425
+ console.log(
426
+ " Open the Vite URL (default http://localhost:3000). The project App URL is APIs and Swagger, not this UI.",
427
+ );
428
+ }
429
+
400
430
  /** Creates a Starbase project, copies the chosen starter, links, deploys, and installs deps. */
401
431
  async function runCreate(args: string[]): Promise<void> {
402
432
  const parsed = parseCreateArgs(args);
@@ -442,6 +472,9 @@ async function runCreate(args: string[]): Promise<void> {
442
472
  "npm install failed. Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.",
443
473
  );
444
474
  console.log("https://robodev.povio.dev/docs/frontend");
475
+ } else if (await hasAppsFe(root)) {
476
+ console.log("Run `npm install` in the project folder.");
477
+ console.log("bun is required for apps/fe. APIs are already deployed.");
445
478
  } else {
446
479
  console.log("Run `npm install` in the project folder, then `npm run dev`.");
447
480
  }
@@ -451,14 +484,22 @@ async function runCreate(args: string[]): Promise<void> {
451
484
  const displayPath = relative(process.cwd(), root) || ".";
452
485
  console.log("");
453
486
  console.log("Next:");
454
- if (displayPath !== ".") {
455
- console.log(` cd ${displayPath}`);
456
- }
457
- if (starter.kind === "backend") {
487
+ if (await hasAppsFe(root)) {
488
+ if (!(await bunAvailable())) {
489
+ console.log("bun is required for apps/fe. APIs are already deployed.");
490
+ }
491
+ printFeNextSteps(displayPath);
492
+ } else if (starter.kind === "backend") {
493
+ if (displayPath !== ".") {
494
+ console.log(` cd ${displayPath}`);
495
+ }
458
496
  console.log(" Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.");
459
497
  console.log(" Do not run robodev deploy or npm run build inside those tools.");
460
498
  console.log(" https://robodev.povio.dev/docs/frontend");
461
499
  } else {
500
+ if (displayPath !== ".") {
501
+ console.log(` cd ${displayPath}`);
502
+ }
462
503
  console.log(" npm run dev");
463
504
  }
464
505
  }
@@ -0,0 +1,27 @@
1
+ STAGE: "local"
2
+ OUTPUT: "export"
3
+
4
+ ANALYZE: "false"
5
+ NEXT_TELEMETRY_DISABLED: "1"
6
+
7
+ VITE_PUBLIC_RELEASE: ${env:RELEASE}
8
+ VITE_PUBLIC_STAGE: ${func:stage}
9
+
10
+ APP_PUBLIC_API_URL: &APP_PUBLIC_API_URL "http://localhost:4000"
11
+ APP_PUBLIC_API_MODE: &APP_PUBLIC_API_MODE "real"
12
+ VITE_PUBLIC_API_URL: *APP_PUBLIC_API_URL
13
+ VITE_PUBLIC_API_MODE: *APP_PUBLIC_API_MODE
14
+
15
+ VITE_PUBLIC_LOG_LEVEL: "trace"
16
+
17
+ VITE_PUBLIC_SENTRY_DSN: ""
18
+ VITE_PUBLIC_SENTRY_ENVIRONMENT: ${func:stage}
19
+ VITE_PUBLIC_SENTRY_TRACES_SAMPLE_RATE: "0"
20
+ VITE_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE: "0"
21
+ VITE_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE: "0"
22
+
23
+ VITE_PUBLIC_AUTH_CUSTOM_JWT_ENABLE_MAGIC_LINK: "true"
24
+
25
+ VITE_PUBLIC_GOOGLE_ANALYTICS_MEASUREMENT_ID: ""
26
+
27
+ VITE_DEV_PORT: "3000"
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "apps/fe/node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["eslint", "typescript", "unicorn", "import", "oxc", "promise", "vitest"],
4
+ "categories": {
5
+ "correctness": "error",
6
+ "perf": "error",
7
+ "pedantic": "off",
8
+ "style": "off",
9
+ "restriction": "off",
10
+ "suspicious": "off",
11
+ "nursery": "off"
12
+ },
13
+ "env": {
14
+ "builtin": true,
15
+ "node": true,
16
+ "shared-node-browser": true
17
+ },
18
+ "rules": {
19
+ "eslint/eqeqeq": [
20
+ "error",
21
+ "always",
22
+ {
23
+ "null": "ignore"
24
+ }
25
+ ],
26
+ "typescript/no-floating-promises": "off",
27
+ "eslint/capitalized-comments": "off"
28
+ }
29
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "$schema": "apps/fe/node_modules/oxlint/configuration_schema.json",
3
+ "extends": [".oxlint-base.json"],
4
+ "plugins": [
5
+ "eslint",
6
+ "typescript",
7
+ "unicorn",
8
+ "import",
9
+ "vitest",
10
+ "react",
11
+ "react-perf",
12
+ "jsx-a11y"
13
+ ],
14
+ "categories": {
15
+ "style": "error"
16
+ },
17
+ "ignorePatterns": [
18
+ "openapi-codegen.config.ts",
19
+ "postcss.config.js",
20
+ "out",
21
+ "dist",
22
+ "public",
23
+ "scripts",
24
+ ".vscode",
25
+ ".turbo",
26
+ "node_modules",
27
+ "apps/fe/src/routeTree.gen.ts",
28
+ "apps/fe/src/openapi",
29
+ "apps/fe/public"
30
+ ],
31
+ "overrides": [
32
+ {
33
+ "files": ["vite-plugin-*.ts", "apps/fe/vite-plugin-*.ts"],
34
+ "rules": {
35
+ "import/no-nodejs-modules": "off"
36
+ }
37
+ }
38
+ ],
39
+ "rules": {
40
+ "eslint/no-implicit-coercion": "off",
41
+ "no-map-spread": "off",
42
+ "eslint/sort-keys": "off",
43
+ "eslint/sort-imports": "off",
44
+ "eslint/no-ternary": "off",
45
+ "eslint/id-length": "off",
46
+ "eslint/no-magic-numbers": "off",
47
+ "eslint/arrow-body-style": "off",
48
+ "eslint/max-params": "off",
49
+ "eslint/func-style": "off",
50
+ "eslint/new-cap": "off",
51
+ "eslint/no-continue": "off",
52
+ "eslint/max-statements": "off",
53
+ "eslint/no-duplicate-imports": [
54
+ "error",
55
+ {
56
+ "allowSeparateTypeImports": true
57
+ }
58
+ ],
59
+ "react/exhaustive-deps": "off",
60
+ "react/jsx-props-no-spreading": "off",
61
+ "react/jsx-max-depth": "off",
62
+ "react-perf/jsx-no-new-object-as-prop": "off",
63
+ "react-perf/jsx-no-new-function-as-prop": "off",
64
+ "react-perf/jsx-no-new-array-as-prop": "off",
65
+ "react-perf/jsx-no-jsx-as-prop": "off",
66
+ "typescript/no-empty-interface": "off",
67
+ "import/no-named-export": "off",
68
+ "import/group-exports": "off",
69
+ "import/prefer-default-export": "off",
70
+ "import/exports-last": "off",
71
+ "import/consistent-type-specifier-style": "off",
72
+ "jest/require-hook": "off",
73
+ "unicorn/filename-case": "off",
74
+ "unicorn/prefer-global-this": "off",
75
+ "unicorn/no-await-expression-member": "off",
76
+ "unicorn/no-null": "off"
77
+ }
78
+ }
@@ -0,0 +1,36 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend must consume generated OpenAPI queries and models, not backend internals."
5
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when editing frontend components, hooks, providers, pages, or clients that call the API."
9
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
10
+ ---
11
+
12
+ # Frontend API Boundary
13
+
14
+ Frontend application code must consume the API through generated files in `apps/fe/src/openapi`.
15
+
16
+ Use generated modules such as:
17
+
18
+ - `@/openapi/<domain>/<domain>.queries`
19
+ - `@/openapi/<domain>/<domain>.models`
20
+ - `@/openapi/queryModules`
21
+
22
+ For generated mutations, pass invalidation through the mutation options instead of importing `queryClient` or calling `invalidateQueries` with generated query keys. The current module is invalidated by default through `invalidateCurrentModule: true`; invalidate dependent modules explicitly with `invalidateModules: [QueryModule.someModule]`.
23
+
24
+ After mutations, derive follow-up UI state from canonical API response or query state instead of leaving action controls unchanged. Disable duplicate submissions when the API state already represents a pending or completed action, and surface mutation errors through the default generated-client error handler or a feature-specific message.
25
+
26
+ Do not import `@robodev-ai/sdk`, `database.ts`, Drizzle tables, or API helper modules into frontend features.
27
+
28
+ `apps/fe/src/openapi` is generated by `@povio/openapi-codegen-cli` from root `openapi.json` (or a live project `/openapi.json` when `OPENAPI_LIVE=true`). Do not hand-edit generated OpenAPI client files.
29
+
30
+ When API contracts change:
31
+
32
+ 1. Update `api/` handlers and `openapi.json` if the documented contract changed.
33
+ 2. Run `bun openapi:gen` from `apps/fe`.
34
+ 3. Update frontend code to use the regenerated queries and models.
35
+
36
+ Point `VITE_PUBLIC_API_URL` at the Starbase project host origin only. Do not append `/api`.
@@ -0,0 +1,38 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend component extraction and feature subcomponent structure conventions."
5
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when organizing frontend feature components, page sections, mapped items, or reusable UI."
9
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
10
+ ---
11
+
12
+ # Frontend Component Structure
13
+
14
+ Extract repeatable UI into subcomponents. Every named React component should live in its own file, even when it is currently used only once. Keep the component file near the feature, layout, page, or shared UI area that owns it.
15
+
16
+ Extract large or complex sections of pages into named components so route and page components stay readable. Anything mapped from an array should usually be a component, such as cards, table row actions, repeated sections, or list items.
17
+
18
+ The planets feature is the reference example while the template is still a prototype:
19
+
20
+ - `PlanetCard` for mapped grid cards.
21
+ - `PlanetsFilters` for shared filter controls.
22
+ - `PlanetsTableActions` for repeated row actions.
23
+ - `PlanetsTable` and `PlanetsTableInfinite` for table wrappers.
24
+ - `PlanetDetailsPage` and `PlanetEditPage` for large route-owned views.
25
+
26
+ When implementing a real app, remove the planets and aliens links from app navigation such as `apps/fe/src/components/layout/AppHeader.tsx` so these examples are not directly accessible in the product UI, but keep the example implementation available as a reference unless the team intentionally deletes the demo layer.
27
+
28
+ Route files should own routing, document metadata, and route data boundaries. Feature components should own the page UI and interaction details.
29
+
30
+ Exception: route-local wrappers such as `PageComponent` or layout guard components may stay in route files when they only connect routing primitives to feature components.
31
+
32
+ ## Shared Utilities
33
+
34
+ Keep generic formatting, parsing, calculation, and data-shaping helpers in `apps/fe/src/utils/*.utils.ts` files instead of defining them inside component files. Export helpers through namespaces such as `DateUtils.formatDate(...)` or `NumberUtils.formatInteger(...)` so call sites stay explicit and related helpers stay grouped.
35
+
36
+ Component files may keep UI-local glue helpers only when the logic is tightly coupled to that component's JSX, such as adapting one component's prop shape or handling a local event. If the helper describes dates, numbers, arrays, strings, IDs, or other domain-neutral data, move it into a shared utility file.
37
+
38
+ For apps with multiple user roles that require distinct UI or business workflows, also apply the role-based app structure rule.
@@ -0,0 +1,32 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend form patterns with Povio UI useForm, generated schemas, and formControl wiring."
5
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when building or changing frontend forms, modals, edit pages, or input components."
9
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
10
+ ---
11
+
12
+ # Frontend Forms
13
+
14
+ Use `useForm` and form-aware inputs from `@povio/ui/tanstack` with generated OpenAPI Zod schemas for API-backed forms. The planets feature is the reference example while the template is still a prototype:
15
+
16
+ - Create form: `apps/fe/src/components/features/planets/list/PlanetCreateModal.tsx`
17
+ - Edit form: `apps/fe/src/components/features/planets/details/PlanetEditPage.tsx`
18
+
19
+ Prefer this shape:
20
+
21
+ ```tsx
22
+ const form = useForm({
23
+ zodSchema: PlanetsModels.PlanetsCreateInputSchema,
24
+ defaultValues: { name: "" },
25
+ });
26
+ ```
27
+
28
+ Pass form state into Povio UI inputs with `field={{ form, name: "fieldName" }}`. Use `form.handleSubmit`, `form.reset`, and `form.setFieldValue` for form actions, and `useFormValue(form, selector)` for reactive field reads.
29
+
30
+ Avoid local `useState`, custom `value`, and custom `onChange` plumbing for fields that belong to the form. Use TanStack Form's `form.Field` adapter only when a component cannot accept the Povio UI `field` binding.
31
+
32
+ Use generated OpenAPI queries and mutations for submit handlers. Keep toast feedback and navigation close to the feature interaction that owns them.
@@ -0,0 +1,24 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend notification inbox, preferences, push permission, service worker, and token registration boundaries."
5
+ globs: ["apps/fe/src/**/*.{ts,tsx}", "apps/fe/public/**/*.{js,ts}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when frontend work handles notifications, push permission, service workers, push tokens, unread counts, or notification preferences."
9
+ globs: ["apps/fe/src/**/*.{ts,tsx}", "apps/fe/public/**/*.{js,ts}"]
10
+ ---
11
+
12
+ # Frontend Notifications
13
+
14
+ Use generated `@/openapi` queries, mutations, and models for notification history, preferences, and push-token registration. Do not import `@robodev-ai/sdk` or `database.ts` into frontend feature code.
15
+
16
+ Request push permission only after an explanatory UI and explicit user gesture. Never request it automatically during initial application load. Handle unsupported, default, denied, granted, registered, expired, and failed-registration states.
17
+
18
+ Register the service worker and provider subscription through a small browser integration, then persist the token/subscription through the generated authenticated user API. Clean up or detach the current device registration on logout when required by the security model.
19
+
20
+ Keep push payloads minimal and privacy-safe. Validate internal deep links before navigation. Never depend on push as the only record of an important event; provide a paginated in-app notification list and unread state.
21
+
22
+ Use generated mutation invalidation options for unread counts, mark-read actions, preferences, and token changes. Do not access the query client directly for generated API state.
23
+
24
+ Use `@povio/ui` for permission prompts, notification lists, menus/popovers, tags, forms, confirmations, and toasts. Use `povio-ui-styling` for styling-layer decisions.