robodev 0.23.0 → 0.24.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 (176) hide show
  1. package/package.json +1 -1
  2. package/src/emit-starter.test.ts +36 -0
  3. package/templates/auth-chat/package.json +1 -1
  4. package/templates/backend/package.json +1 -1
  5. package/templates/catalog.json +5 -0
  6. package/templates/empty/package.json +1 -1
  7. package/templates/marketplace/.config/local.spa.template.yml +27 -0
  8. package/templates/marketplace/.oxlint-base.json +29 -0
  9. package/templates/marketplace/.oxlintrc.json +78 -0
  10. package/templates/marketplace/.rulesync/rules/frontend-api-boundary.md +36 -0
  11. package/templates/marketplace/.rulesync/rules/frontend-component-structure.md +38 -0
  12. package/templates/marketplace/.rulesync/rules/frontend-forms.md +32 -0
  13. package/templates/marketplace/.rulesync/rules/frontend-notifications.md +24 -0
  14. package/templates/marketplace/.rulesync/rules/frontend-povio-components.md +42 -0
  15. package/templates/marketplace/.rulesync/rules/frontend-povio-ui.md +45 -0
  16. package/templates/marketplace/.rulesync/rules/frontend-query-autocomplete.md +29 -0
  17. package/templates/marketplace/.rulesync/rules/frontend-tables-and-lists.md +34 -0
  18. package/templates/marketplace/.rulesync/rules/frontend-translations.md +26 -0
  19. package/templates/marketplace/.rulesync/rules/project-overview.md +32 -0
  20. package/templates/marketplace/.rulesync/rules/robodev-api.md +30 -0
  21. package/templates/marketplace/.rulesync/rules/robodev-auth.md +40 -0
  22. package/templates/marketplace/.rulesync/rules/robodev-database.md +22 -0
  23. package/templates/marketplace/.rulesync/rules/role-based-app-structure.md +36 -0
  24. package/templates/marketplace/.rulesync/skills/media-feature/SKILL.md +21 -0
  25. package/templates/marketplace/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
  26. package/templates/marketplace/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
  27. package/templates/marketplace/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
  28. package/templates/marketplace/.rulesync/skills/robodev-table/SKILL.md +12 -0
  29. package/templates/marketplace/README.md +40 -0
  30. package/templates/marketplace/api/_lib.ts +677 -0
  31. package/templates/marketplace/api/cart/[id].ts +51 -0
  32. package/templates/marketplace/api/cart.ts +70 -0
  33. package/templates/marketplace/api/categories.ts +11 -0
  34. package/templates/marketplace/api/checkout.ts +157 -0
  35. package/templates/marketplace/api/health.ts +7 -0
  36. package/templates/marketplace/api/listings/[id].ts +101 -0
  37. package/templates/marketplace/api/listings/paginate.ts +29 -0
  38. package/templates/marketplace/api/listings.ts +90 -0
  39. package/templates/marketplace/api/me.ts +14 -0
  40. package/templates/marketplace/api/orders/[id]/complete.ts +27 -0
  41. package/templates/marketplace/api/orders/[id]/ship.ts +24 -0
  42. package/templates/marketplace/api/orders/[id].ts +15 -0
  43. package/templates/marketplace/api/purchases.ts +19 -0
  44. package/templates/marketplace/api/reviews.ts +58 -0
  45. package/templates/marketplace/api/sales.ts +19 -0
  46. package/templates/marketplace/api/watches/[listingId].ts +18 -0
  47. package/templates/marketplace/api/watches.ts +51 -0
  48. package/templates/marketplace/apps/fe/index.html +24 -0
  49. package/templates/marketplace/apps/fe/openapi-codegen.config.ts +72 -0
  50. package/templates/marketplace/apps/fe/package.json +84 -0
  51. package/templates/marketplace/apps/fe/public/apple-touch-icon.png +0 -0
  52. package/templates/marketplace/apps/fe/public/favicon-96x96.png +0 -0
  53. package/templates/marketplace/apps/fe/public/favicon.ico +0 -0
  54. package/templates/marketplace/apps/fe/public/favicon.svg +3 -0
  55. package/templates/marketplace/apps/fe/public/site.webmanifest +21 -0
  56. package/templates/marketplace/apps/fe/public/web-app-manifest-192x192.png +0 -0
  57. package/templates/marketplace/apps/fe/public/web-app-manifest-512x512.png +0 -0
  58. package/templates/marketplace/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
  59. package/templates/marketplace/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
  60. package/templates/marketplace/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
  61. package/templates/marketplace/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
  62. package/templates/marketplace/apps/fe/src/assets/locales/en/translation.json +335 -0
  63. package/templates/marketplace/apps/fe/src/assets/locales/sl/translation.json +335 -0
  64. package/templates/marketplace/apps/fe/src/clients/app-rest-client.ts +15 -0
  65. package/templates/marketplace/apps/fe/src/clients/auth-token-store.ts +101 -0
  66. package/templates/marketplace/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
  67. package/templates/marketplace/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
  68. package/templates/marketplace/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
  69. package/templates/marketplace/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
  70. package/templates/marketplace/apps/fe/src/components/404.tsx +18 -0
  71. package/templates/marketplace/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
  72. package/templates/marketplace/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
  73. package/templates/marketplace/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
  74. package/templates/marketplace/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
  75. package/templates/marketplace/apps/fe/src/components/features/marketplace/BrowsePage.tsx +76 -0
  76. package/templates/marketplace/apps/fe/src/components/features/marketplace/CartPage.tsx +88 -0
  77. package/templates/marketplace/apps/fe/src/components/features/marketplace/CheckoutPage.tsx +193 -0
  78. package/templates/marketplace/apps/fe/src/components/features/marketplace/ListingCard.tsx +50 -0
  79. package/templates/marketplace/apps/fe/src/components/features/marketplace/ListingDetailsPage.tsx +152 -0
  80. package/templates/marketplace/apps/fe/src/components/features/marketplace/ListingFormPage.tsx +243 -0
  81. package/templates/marketplace/apps/fe/src/components/features/marketplace/MyListingsPage.tsx +84 -0
  82. package/templates/marketplace/apps/fe/src/components/features/marketplace/OrdersPage.tsx +224 -0
  83. package/templates/marketplace/apps/fe/src/components/features/marketplace/WatchlistPage.tsx +56 -0
  84. package/templates/marketplace/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
  85. package/templates/marketplace/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
  86. package/templates/marketplace/apps/fe/src/components/layout/AppLayout.tsx +19 -0
  87. package/templates/marketplace/apps/fe/src/components/layout/app-header/AppHeader.tsx +95 -0
  88. package/templates/marketplace/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +55 -0
  89. package/templates/marketplace/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
  90. package/templates/marketplace/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
  91. package/templates/marketplace/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
  92. package/templates/marketplace/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
  93. package/templates/marketplace/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
  94. package/templates/marketplace/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
  95. package/templates/marketplace/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
  96. package/templates/marketplace/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
  97. package/templates/marketplace/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
  98. package/templates/marketplace/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
  99. package/templates/marketplace/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
  100. package/templates/marketplace/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
  101. package/templates/marketplace/apps/fe/src/components/shared/ui/Card.tsx +60 -0
  102. package/templates/marketplace/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
  103. package/templates/marketplace/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
  104. package/templates/marketplace/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
  105. package/templates/marketplace/apps/fe/src/config/app.config.ts +35 -0
  106. package/templates/marketplace/apps/fe/src/config/i18n.ts +43 -0
  107. package/templates/marketplace/apps/fe/src/config/inits/a11y.ts +14 -0
  108. package/templates/marketplace/apps/fe/src/config/inits/logger.ts +7 -0
  109. package/templates/marketplace/apps/fe/src/config/inits/sentry.ts +21 -0
  110. package/templates/marketplace/apps/fe/src/config/jwt.config.ts +2 -0
  111. package/templates/marketplace/apps/fe/src/config/query.config.ts +20 -0
  112. package/templates/marketplace/apps/fe/src/hooks/useAuth.ts +5 -0
  113. package/templates/marketplace/apps/fe/src/main.tsx +52 -0
  114. package/templates/marketplace/apps/fe/src/pages/(guest)/login.tsx +23 -0
  115. package/templates/marketplace/apps/fe/src/pages/(guest)/register.tsx +23 -0
  116. package/templates/marketplace/apps/fe/src/pages/(guest)/route.tsx +18 -0
  117. package/templates/marketplace/apps/fe/src/pages/(private)/cart.tsx +22 -0
  118. package/templates/marketplace/apps/fe/src/pages/(private)/checkout.tsx +22 -0
  119. package/templates/marketplace/apps/fe/src/pages/(private)/index.tsx +22 -0
  120. package/templates/marketplace/apps/fe/src/pages/(private)/listings/$id/index.tsx +37 -0
  121. package/templates/marketplace/apps/fe/src/pages/(private)/my-listings.tsx +22 -0
  122. package/templates/marketplace/apps/fe/src/pages/(private)/orders/$id.tsx +40 -0
  123. package/templates/marketplace/apps/fe/src/pages/(private)/orders/index.tsx +22 -0
  124. package/templates/marketplace/apps/fe/src/pages/(private)/profile.tsx +23 -0
  125. package/templates/marketplace/apps/fe/src/pages/(private)/route.tsx +18 -0
  126. package/templates/marketplace/apps/fe/src/pages/(private)/sales.tsx +22 -0
  127. package/templates/marketplace/apps/fe/src/pages/(private)/sell/$id.tsx +44 -0
  128. package/templates/marketplace/apps/fe/src/pages/(private)/sell/index.tsx +19 -0
  129. package/templates/marketplace/apps/fe/src/pages/(private)/watchlist.tsx +22 -0
  130. package/templates/marketplace/apps/fe/src/pages/(public)/auth.tsx +37 -0
  131. package/templates/marketplace/apps/fe/src/pages/(public)/route.tsx +9 -0
  132. package/templates/marketplace/apps/fe/src/pages/__root.tsx +164 -0
  133. package/templates/marketplace/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
  134. package/templates/marketplace/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
  135. package/templates/marketplace/apps/fe/src/providers/index.tsx +44 -0
  136. package/templates/marketplace/apps/fe/src/providers/jwt.provider.tsx +95 -0
  137. package/templates/marketplace/apps/fe/src/routeTree.gen.ts +435 -0
  138. package/templates/marketplace/apps/fe/src/styles/base.css +103 -0
  139. package/templates/marketplace/apps/fe/src/styles/fonts/fonts.tsx +10 -0
  140. package/templates/marketplace/apps/fe/src/styles/fonts/general-sans.css +31 -0
  141. package/templates/marketplace/apps/fe/src/styles/globals.css +28 -0
  142. package/templates/marketplace/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
  143. package/templates/marketplace/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
  144. package/templates/marketplace/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
  145. package/templates/marketplace/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
  146. package/templates/marketplace/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
  147. package/templates/marketplace/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
  148. package/templates/marketplace/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
  149. package/templates/marketplace/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
  150. package/templates/marketplace/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
  151. package/templates/marketplace/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
  152. package/templates/marketplace/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
  153. package/templates/marketplace/apps/fe/src/styles/theme.css +2177 -0
  154. package/templates/marketplace/apps/fe/src/types/i18next.d.ts +12 -0
  155. package/templates/marketplace/apps/fe/src/types/table.d.ts +17 -0
  156. package/templates/marketplace/apps/fe/src/types/ui.d.ts +26 -0
  157. package/templates/marketplace/apps/fe/src/types/vite-env.d.ts +20 -0
  158. package/templates/marketplace/apps/fe/src/utils/date.utils.ts +17 -0
  159. package/templates/marketplace/apps/fe/src/utils/image-fallback.ts +9 -0
  160. package/templates/marketplace/apps/fe/src/utils/listing-form.test.ts +69 -0
  161. package/templates/marketplace/apps/fe/src/utils/listing-form.ts +77 -0
  162. package/templates/marketplace/apps/fe/src/utils/listing-submit.ts +29 -0
  163. package/templates/marketplace/apps/fe/src/utils/number.utils.ts +12 -0
  164. package/templates/marketplace/apps/fe/src/utils/string.utils.ts +5 -0
  165. package/templates/marketplace/apps/fe/src/vite-env.d.ts +1 -0
  166. package/templates/marketplace/apps/fe/tsconfig.app.json +32 -0
  167. package/templates/marketplace/apps/fe/tsconfig.json +9 -0
  168. package/templates/marketplace/apps/fe/tsconfig.node.json +31 -0
  169. package/templates/marketplace/apps/fe/vite.config.ts +125 -0
  170. package/templates/marketplace/database.ts +154 -0
  171. package/templates/marketplace/openapi.json +2303 -0
  172. package/templates/marketplace/oxfmt.config.js +23 -0
  173. package/templates/marketplace/package.json +18 -0
  174. package/templates/marketplace/rulesync.jsonc +18 -0
  175. package/templates/marketplace/tsconfig.json +11 -0
  176. package/templates/space/package.json +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robodev",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "CLI for Robodev Starbase — create, auth, link, and deploy hosted apps",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -77,6 +77,42 @@ test("collectFiles for auth-chat starter is Tiny APIs without apps/fe", async ()
77
77
  assert.ok(files.length < 200);
78
78
  });
79
79
 
80
+ test("emitStarter marketplace FE build runs openapi:gen before vite", async () => {
81
+ const dest = await mkdtemp(join(tmpdir(), "rd-emit-"));
82
+ try {
83
+ await emitStarter(join(cliRoot(), "..", "starters", "marketplace"), dest, {
84
+ name: "Bazaar",
85
+ packageName: "bazaar-app",
86
+ title: "Marketplace",
87
+ versions,
88
+ });
89
+ const fePkg = JSON.parse(await readFile(join(dest, "apps/fe/package.json"), "utf8")) as {
90
+ dependencies?: Record<string, string>;
91
+ scripts?: { build?: string };
92
+ };
93
+ assert.equal(fePkg.dependencies?.["@hookform/resolvers"], "^5.2.2");
94
+ assert.equal(fePkg.dependencies?.["react-hook-form"], "^7.65.0");
95
+ assert.match(fePkg.scripts?.build ?? "", /openapi:gen.*vite build/);
96
+ } finally {
97
+ await rm(dest, { recursive: true, force: true });
98
+ }
99
+ });
100
+
101
+ test("collectFiles for marketplace starter is Tiny APIs without apps/fe", async () => {
102
+ const files = await collectFiles(join(cliRoot(), "..", "starters", "marketplace"));
103
+ const paths = new Set(files.map((file) => file.path));
104
+ assert.ok(paths.has("database.ts"));
105
+ assert.ok(paths.has("api/listings.ts"));
106
+ assert.ok(paths.has("api/cart.ts"));
107
+ assert.ok(paths.has("api/checkout.ts"));
108
+ assert.ok(paths.has("api/health.ts"));
109
+ assert.ok(paths.has("api/me.ts"));
110
+ assert.ok(!paths.has("index.html"));
111
+ assert.ok(!paths.has("src/main.tsx"));
112
+ assert.ok([...paths].every((path) => !path.startsWith("apps/")));
113
+ assert.ok(files.length < 200);
114
+ });
115
+
80
116
  test("collectFiles for space starter is Tiny APIs without apps/fe", async () => {
81
117
  const files = await collectFiles(join(cliRoot(), "..", "starters", "space"));
82
118
  const paths = new Set(files.map((file) => file.path));
@@ -11,7 +11,7 @@
11
11
  "@robodev-ai/sdk": "^0.6.0"
12
12
  },
13
13
  "devDependencies": {
14
- "robodev": "^0.23.0",
14
+ "robodev": "^0.24.0",
15
15
  "rulesync": "^8.18.0",
16
16
  "typescript": "^5.9.2"
17
17
  }
@@ -10,7 +10,7 @@
10
10
  "@robodev-ai/sdk": "^0.6.0"
11
11
  },
12
12
  "devDependencies": {
13
- "robodev": "^0.23.0",
13
+ "robodev": "^0.24.0",
14
14
  "typescript": "^5.9.2"
15
15
  }
16
16
  }
@@ -10,6 +10,11 @@
10
10
  "title": "Auth chat",
11
11
  "description": "Tiny chat APIs plus a local Povio UI in apps/fe, with invite email"
12
12
  },
13
+ {
14
+ "id": "marketplace",
15
+ "title": "Marketplace",
16
+ "description": "Tiny C2C marketplace APIs plus a local Povio UI in apps/fe"
17
+ },
13
18
  {
14
19
  "id": "backend",
15
20
  "title": "Backend",
@@ -10,7 +10,7 @@
10
10
  "@robodev-ai/sdk": "^0.6.0"
11
11
  },
12
12
  "devDependencies": {
13
- "robodev": "^0.23.0",
13
+ "robodev": "^0.24.0",
14
14
  "typescript": "^5.9.2"
15
15
  }
16
16
  }
@@ -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.
@@ -0,0 +1,42 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend interactive controls and typography must use Povio UI primitives instead of native replacements."
5
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when creating or editing React components, controls, forms, tables, overlays, or user-facing text."
9
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
10
+ ---
11
+
12
+ # Use Povio UI Components
13
+
14
+ Use a component from `@povio/ui` whenever it provides the required primitive. Do not recreate an available Povio component with a native element plus Tailwind or custom CSS.
15
+
16
+ Required replacements include:
17
+
18
+ - `Button`, `TextButton`, or the appropriate Povio action component instead of a styled `<button>`.
19
+ - `TextInput`, `PasswordInput`, `TextArea`, checkbox, radio, select, autocomplete, and other Povio form controls instead of native form controls.
20
+ - `Typography` instead of directly styling user-facing headings, paragraphs, labels, or spans.
21
+ - `Table` or `InfiniteTable` instead of a hand-built native data table.
22
+ - `Modal`, `Confirmation`, `Drawer`, `BottomSheet`, `Menu`, `Tooltip`, or `ResponsivePopover` instead of custom overlay primitives.
23
+ - `FileUpload` instead of a directly exposed file input.
24
+ - Povio hooks such as `useForm` and `useToast` instead of parallel custom infrastructure.
25
+
26
+ Before creating a control:
27
+
28
+ 1. Search `@povio/ui` usage in the repository.
29
+ 2. Inspect existing feature components for the same primitive.
30
+ 3. Use the existing Povio props and variants.
31
+ 4. Apply the `povio-ui-styling` skill when choosing between local Tailwind, `UIConfig`, `UIOverrides`, a shared wrapper, or scoped CSS.
32
+
33
+ Native semantic and structural elements such as `<main>`, `<section>`, `<article>`, `<nav>`, `<form>`, `<div>`, and list elements remain appropriate for document structure and layout. Use TanStack Router navigation primitives for routing. This rule prohibits native replacements for available Povio UI behavior; it does not prohibit semantic HTML.
34
+
35
+ If Povio UI has no suitable primitive:
36
+
37
+ - Compose existing Povio components first.
38
+ - Put a reusable project-specific primitive under `apps/fe/src/components/shared/ui`.
39
+ - Preserve keyboard behavior, focus visibility, accessible names, disabled state, validation state, and loading state.
40
+ - Keep the exception narrow; do not introduce another general-purpose UI library.
41
+
42
+ When touching an existing native control that has a Povio equivalent, migrate it when the change is safely within scope. Do not expand a narrowly requested fix into a broad unrelated rewrite.
@@ -0,0 +1,45 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend UI uses Povio UI primitives first and semantic Tailwind tokens."
5
+ globs: ["apps/fe/src/**/*.{ts,tsx,css}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when editing frontend UI, styling, layout, components, or CSS."
9
+ globs: ["apps/fe/src/**/*.{ts,tsx,css}"]
10
+ ---
11
+
12
+ # Frontend UI
13
+
14
+ ## Use `@povio/ui` First
15
+
16
+ Always use components and hooks from `@povio/ui` when a suitable primitive exists, including `Button`, `Typography`, `Table`, `Modal`, `Confirmation`, `TextButton`, `TextInput`, `TextArea`, `PasswordInput`, `FileUpload`, `useForm`, `useToast`, and `Tag`.
17
+
18
+ Create project-specific UI only when `@povio/ui` does not provide the needed primitive. Shared custom primitives live under `apps/fe/src/components/shared/ui`.
19
+
20
+ Feature components should compose existing layout and shared primitives such as `PageHeader`, `BackHeader`, `LoadingState`, `ErrorText`, and `Card` before introducing new wrappers.
21
+
22
+ When implementing a real app, remove the demo planets and aliens links from app navigation such as `apps/fe/src/components/layout/app-header/AppHeader.tsx` so they are not directly accessible in the product UI, but keep the example code available as a reference unless the team intentionally deletes the demo layer.
23
+
24
+ ## Tailwind Tokens
25
+
26
+ The default Tailwind color palette is removed in `apps/fe/src/styles/base.css`. Do not use default color utilities such as:
27
+
28
+ - `text-red-500`
29
+ - `bg-blue-600`
30
+ - `border-gray-200`
31
+ - `ring-emerald-400`
32
+
33
+ Use semantic tokens exported from Figma in `apps/fe/src/styles/theme.css` and exposed through Tailwind, for example:
34
+
35
+ - Surface, fill, and outline: `bg-elevation-fill-default-1`, `border-elevation-outline-default-1`
36
+ - Text: `text-text-default-1`, `text-text-default-2`, `text-text-error-1`
37
+ - Interactive: `bg-interactive-contained-primary-idle`, `hover:bg-interactive-contained-primary-hover`
38
+
39
+ Do not manually edit `apps/fe/src/styles/theme.css`; it is exported from Figma. If a hand-written semantic token or Tailwind theme mapping is needed, add it only in `apps/fe/src/styles/base.css` before referencing it from JSX or CSS.
40
+
41
+ ## Forms And API Data
42
+
43
+ For forms backed by API requests, prefer generated OpenAPI model schemas with `@povio/ui` `useForm`, for example `useForm({ zodSchema: UserModels.UpdateProfileBodySchema })`.
44
+
45
+ Use generated OpenAPI queries and mutations for all API state. Keep API side effects, toast feedback, loading states, and query invalidation close to the feature that owns the interaction. For mutation invalidation, pass generated mutation options such as `invalidateModules`; do not import `queryClient` into feature code for generated API queries.
@@ -0,0 +1,29 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend QueryAutocomplete usage for database-entity dropdowns with generated label queries and queryParams."
5
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when a frontend form or filter needs a dropdown/search picker of database entities."
9
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
10
+ ---
11
+
12
+ # Frontend QueryAutocomplete
13
+
14
+ Use this pattern whenever the UI needs a dropdown/search picker of entities from the database, such as selecting a alien, company, project, user, category, or any other related row in a form or filter.
15
+
16
+ Use `QueryAutocomplete` for these database-entity dropdowns. It should call a generated labels endpoint such as `AliensQueries.useListLabels`, not load full related records into the form.
17
+
18
+ Reference examples:
19
+
20
+ - Create form: `apps/fe/src/components/features/planets/list/PlanetCreateModal.tsx`
21
+ - Edit form: `apps/fe/src/components/features/planets/details/PlanetEditPage.tsx`
22
+ - Filter control: `apps/fe/src/components/features/planets/list/PlanetsFilters.tsx`
23
+ The matching labels endpoint input schema must define `search`, usually as `search: z.string().optional()`, because `QueryAutocomplete` passes search text through that parameter.
24
+
25
+ If a labels endpoint needs extra context, pass it through `QueryAutocomplete` with `queryParams`. The matching labels endpoint input schema can include optional parameters beyond `search`, such as extra filters or flags that control how rows map into `{ id, label }`.
26
+
27
+ When the autocomplete belongs to a form, import it from `@povio/ui/tanstack` and prefer `field={{ form, name }}`. When it belongs to filters, read and write through the `useFilters` store.
28
+
29
+ Real app features should use generated labels queries from `@/openapi`.
@@ -0,0 +1,34 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend list and table patterns for grids, Table, InfiniteTable, filters, and sorting."
5
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when building or changing list pages, grids, tables, filters, sorting, pagination, or row actions."
9
+ globs: ["apps/fe/src/**/*.{ts,tsx}"]
10
+ ---
11
+
12
+ # Frontend Tables And Lists
13
+
14
+ The planets feature intentionally shows multiple list patterns while the template is still a prototype:
15
+
16
+ - Demo switcher: `apps/fe/src/components/features/planets/list/PlanetsPage.tsx`
17
+ - Grid list: `apps/fe/src/components/features/planets/list/PlanetsGridPage.tsx`
18
+ - Table list: `apps/fe/src/components/features/planets/list/PlanetsTablePage.tsx`
19
+ - Infinite table list: `apps/fe/src/components/features/planets/list/PlanetsInfiniteTablePage.tsx`
20
+ - Table wrappers and actions: `apps/fe/src/components/features/planets/list/table/*`
21
+
22
+ The `Segment` in `PlanetsPage` is demo-only. Real feature pages should usually choose one list pattern and use it consistently.
23
+
24
+ Prefer paginated API queries for database-backed collections. Use regular generated list queries only for data that is guaranteed to stay short, such as enums, compact option lists, or label-style endpoints.
25
+
26
+ Use `Table` from `@povio/ui` for small bounded table lists and `InfiniteTable` when incremental loading is useful for the feature. Infinite tables require a paginated backend endpoint and generated `use<Endpoint>Infinite` query. Even non-infinite collection views should normally be backed by a paginated endpoint unless the collection is intentionally bounded.
27
+
28
+ When a feature needs filters, define a filter schema on the API model, expose it through OpenAPI when useful, and use `useFilters` on the frontend. Put filter controls in a focused component such as `PlanetsFilters`. Use `as="filter"` on Povio UI filter controls.
29
+
30
+ When a feature needs sorting, expose an enum of sortable keys from the API layer and pass the generated enum schema into `dynamicColumns({ options: { sortable } })`. Use `useSorting` on the frontend and pass its `order` string to the generated query.
31
+
32
+ Use wrapper components around table primitives. A table wrapper should accept `TableWrapperProps<T>` or `InfiniteTableWrapperProps<T>` from `@povio/ui` and spread props into `Table` or `InfiniteTable`, while defining columns locally with a `getColumns` helper above the component.
33
+
34
+ Keep per-row actions in a small action component, such as `PlanetsTableActions`, and use a shared row-action wrapper when available so clicks do not accidentally trigger row navigation.
@@ -0,0 +1,26 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend translation key structure and no-hardcoded-text conventions."
5
+ globs: ["apps/fe/src/**/*.{ts,tsx,json}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when adding or changing frontend user-facing text, locale files, or translation keys."
9
+ globs: ["apps/fe/src/**/*.{ts,tsx,json}"]
10
+ ---
11
+
12
+ # Frontend Translations
13
+
14
+ Do not hardcode user-facing frontend text. Add copy to locale files and access it through `useTranslation`.
15
+
16
+ Translation key structure should generally follow the feature/component tree. The planets feature is the current reference example:
17
+
18
+ - `planets.page.*`
19
+ - `planets.views.*`
20
+ - `planets.filters.*`
21
+ - `planets.createModal.*`
22
+ - `planets.editModal.*`
23
+ - `planets.table.*`
24
+ - `planets.detail.*`
25
+
26
+ When Slovenian translations are required, preserve real Slovenian characters with carons, especially Unicode `U+0161`, `U+010D`, and `U+017E`. Do not leave mojibake or replacement-character artifacts in locale files.
@@ -0,0 +1,32 @@
1
+ ---
2
+ root: true
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Project overview and Rulesync source-of-truth guidance."
5
+ globs: ["**/*"]
6
+ cursor:
7
+ alwaysApply: true
8
+ description: "Project overview and Rulesync source-of-truth guidance."
9
+ globs: ["**/*"]
10
+ ---
11
+
12
+ # Marketplace starter AI rules
13
+
14
+ This project is a Vite React frontend in `apps/fe` plus a Robodev backend at the **repository root** (`database.ts` + `api/**/*.ts` using `@robodev-ai/sdk`). There is no Expo / mobile app in this starter. End-user auth is reserved on the project host at `/api/user/*`. There is no in-browser fake backend.
15
+
16
+ `robodev deploy` from this root uploads schema and APIs. It does not host the Povio SPA. Run the UI with bun in `apps/fe`.
17
+
18
+ Shared AI rules and skills are authored in `.rulesync/`. Generated files for Codex, Claude Code, and Cursor are local outputs and should be regenerated with `bun rules:gen`.
19
+
20
+ This starter is a signed-in C2C Buy-It-Now goods marketplace. Listings, cart, checkout, orders, watches, and reviews live in `api/` and `apps/fe/src/components/features`.
21
+
22
+ When an app has multiple user roles with distinct UI or business workflows, use the conditional role-based app structure rule. Do not apply role splitting to simple single-role apps or small permission differences.
23
+
24
+ Use the more specific generated rules for:
25
+
26
+ - Robodev `defineDatabase` tables in `database.ts`.
27
+ - Robodev `defineApi` routes in `api/`.
28
+ - Reserved `/api/user/*` auth contracts (do not implement `api/user/**` in the project).
29
+ - Frontend API boundaries around generated `@/openapi` queries and models.
30
+ - Povio UI and semantic Tailwind token usage.
31
+ - Frontend forms, tables/lists, `QueryAutocomplete`, translations, and component structure.
32
+ - Role-based frontend structure when an app has multiple distinct user roles.
@@ -0,0 +1,30 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Robodev defineApi file routes and handler conventions."
5
+ globs: ["api/**/*.ts"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when editing Starbase API routes."
9
+ globs: ["api/**/*.ts"]
10
+ ---
11
+
12
+ # Robodev API
13
+
14
+ Project APIs live in `api/`. `api/planets.ts` is served at `/planets` and `/api/planets`. Prefer the `/api/...` path in OpenAPI and frontend clients.
15
+
16
+ ## File layout
17
+
18
+ - `api/foo.ts` → `/api/foo`
19
+ - `api/foo/[id].ts` → `/api/foo/:id`
20
+ - Export `get` / `post` / `put` / `patch` / `delete` as `defineApi(...)`.
21
+ - Shared helpers belong in `api/_lib.ts`. Do not export `defineApi` from `_lib.ts`.
22
+ - Do not add `api/user/**`. Those paths are reserved for Robodev Auth.
23
+
24
+ ## Handlers
25
+
26
+ - Use `auth: "required"` on routes that Tiny marked authenticated.
27
+ - Handler context includes `db`, `user`, `email`, `storage`, `params`, `query`, `body`.
28
+ - Return JSON for 200, or `{ status, body }` for errors (404/403/400).
29
+ - Keep request/response shapes compatible with `openapi.json` so OpenAPI codegen stays stable.
30
+ - After changing public contracts, update `openapi.json` if needed and run `bun openapi:gen` from `apps/fe`.