robodev 0.47.2 → 0.48.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 (172) hide show
  1. package/package.json +3 -3
  2. package/src/emit-starter.test.ts +6 -3
  3. package/templates/auth-chat/ROBODEV.md +7 -1
  4. package/templates/auth-chat/package.json +2 -2
  5. package/templates/backend/ROBODEV.md +7 -1
  6. package/templates/backend/package.json +2 -2
  7. package/templates/catalog.json +5 -0
  8. package/templates/empty/ROBODEV.md +7 -1
  9. package/templates/empty/package.json +2 -2
  10. package/templates/marketplace/ROBODEV.md +7 -1
  11. package/templates/marketplace/package.json +2 -2
  12. package/templates/povio/.config/local.spa.template.yml +27 -0
  13. package/templates/povio/.oxlint-base.json +29 -0
  14. package/templates/povio/.oxlintrc.json +78 -0
  15. package/templates/povio/.rulesync/rules/frontend-api-boundary.md +36 -0
  16. package/templates/povio/.rulesync/rules/frontend-component-structure.md +40 -0
  17. package/templates/povio/.rulesync/rules/frontend-forms.md +27 -0
  18. package/templates/povio/.rulesync/rules/frontend-notifications.md +24 -0
  19. package/templates/povio/.rulesync/rules/frontend-povio-components.md +42 -0
  20. package/templates/povio/.rulesync/rules/frontend-povio-ui.md +45 -0
  21. package/templates/povio/.rulesync/rules/frontend-query-autocomplete.md +24 -0
  22. package/templates/povio/.rulesync/rules/frontend-tables-and-lists.md +26 -0
  23. package/templates/povio/.rulesync/rules/frontend-translations.md +25 -0
  24. package/templates/povio/.rulesync/rules/project-overview.md +32 -0
  25. package/templates/povio/.rulesync/rules/robodev-api.md +30 -0
  26. package/templates/povio/.rulesync/rules/robodev-auth.md +40 -0
  27. package/templates/povio/.rulesync/rules/robodev-database.md +22 -0
  28. package/templates/povio/.rulesync/rules/role-based-app-structure.md +36 -0
  29. package/templates/povio/.rulesync/skills/media-feature/SKILL.md +21 -0
  30. package/templates/povio/.rulesync/skills/povio-ui-styling/SKILL.md +211 -0
  31. package/templates/povio/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
  32. package/templates/povio/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
  33. package/templates/povio/.rulesync/skills/robodev-table/SKILL.md +12 -0
  34. package/templates/povio/README.md +40 -0
  35. package/templates/povio/ROBODEV.md +84 -0
  36. package/templates/povio/apps/fe/index.html +24 -0
  37. package/templates/povio/apps/fe/openapi-codegen.config.ts +72 -0
  38. package/templates/povio/apps/fe/package.json +71 -0
  39. package/templates/povio/apps/fe/public/apple-touch-icon.png +0 -0
  40. package/templates/povio/apps/fe/public/favicon-96x96.png +0 -0
  41. package/templates/povio/apps/fe/public/favicon.ico +0 -0
  42. package/templates/povio/apps/fe/public/favicon.svg +3 -0
  43. package/templates/povio/apps/fe/public/site.webmanifest +21 -0
  44. package/templates/povio/apps/fe/public/web-app-manifest-192x192.png +0 -0
  45. package/templates/povio/apps/fe/public/web-app-manifest-512x512.png +0 -0
  46. package/templates/povio/apps/fe/src/assets/locales/en/translation.json +166 -0
  47. package/templates/povio/apps/fe/src/clients/app-rest-client.ts +15 -0
  48. package/templates/povio/apps/fe/src/clients/auth-token-store.ts +101 -0
  49. package/templates/povio/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
  50. package/templates/povio/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
  51. package/templates/povio/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +45 -0
  52. package/templates/povio/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
  53. package/templates/povio/apps/fe/src/components/404.tsx +18 -0
  54. package/templates/povio/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
  55. package/templates/povio/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
  56. package/templates/povio/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
  57. package/templates/povio/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
  58. package/templates/povio/apps/fe/src/components/features/home/HomePage.tsx +21 -0
  59. package/templates/povio/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
  60. package/templates/povio/apps/fe/src/components/layout/AppLayout.tsx +19 -0
  61. package/templates/povio/apps/fe/src/components/layout/app-header/AppHeader.tsx +91 -0
  62. package/templates/povio/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +51 -0
  63. package/templates/povio/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
  64. package/templates/povio/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
  65. package/templates/povio/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
  66. package/templates/povio/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
  67. package/templates/povio/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
  68. package/templates/povio/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
  69. package/templates/povio/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
  70. package/templates/povio/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
  71. package/templates/povio/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
  72. package/templates/povio/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
  73. package/templates/povio/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
  74. package/templates/povio/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
  75. package/templates/povio/apps/fe/src/components/shared/ui/Card.tsx +60 -0
  76. package/templates/povio/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
  77. package/templates/povio/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
  78. package/templates/povio/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
  79. package/templates/povio/apps/fe/src/config/app.config.ts +23 -0
  80. package/templates/povio/apps/fe/src/config/i18n.ts +37 -0
  81. package/templates/povio/apps/fe/src/config/inits/logger.ts +7 -0
  82. package/templates/povio/apps/fe/src/config/jwt.config.ts +2 -0
  83. package/templates/povio/apps/fe/src/config/query.config.ts +20 -0
  84. package/templates/povio/apps/fe/src/hooks/useAuth.ts +5 -0
  85. package/templates/povio/apps/fe/src/main.tsx +52 -0
  86. package/templates/povio/apps/fe/src/pages/(guest)/login.tsx +23 -0
  87. package/templates/povio/apps/fe/src/pages/(guest)/register.tsx +23 -0
  88. package/templates/povio/apps/fe/src/pages/(guest)/route.tsx +18 -0
  89. package/templates/povio/apps/fe/src/pages/(private)/index.tsx +23 -0
  90. package/templates/povio/apps/fe/src/pages/(private)/profile.tsx +23 -0
  91. package/templates/povio/apps/fe/src/pages/(private)/route.tsx +18 -0
  92. package/templates/povio/apps/fe/src/pages/(public)/auth.tsx +37 -0
  93. package/templates/povio/apps/fe/src/pages/(public)/route.tsx +9 -0
  94. package/templates/povio/apps/fe/src/pages/__root.tsx +133 -0
  95. package/templates/povio/apps/fe/src/providers/AppErrorBoundary.tsx +44 -0
  96. package/templates/povio/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
  97. package/templates/povio/apps/fe/src/providers/index.tsx +44 -0
  98. package/templates/povio/apps/fe/src/providers/jwt.provider.tsx +95 -0
  99. package/templates/povio/apps/fe/src/routeTree.gen.ts +225 -0
  100. package/templates/povio/apps/fe/src/styles/base.css +84 -0
  101. package/templates/povio/apps/fe/src/styles/fonts/fonts.tsx +10 -0
  102. package/templates/povio/apps/fe/src/styles/globals.css +26 -0
  103. package/templates/povio/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
  104. package/templates/povio/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
  105. package/templates/povio/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
  106. package/templates/povio/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
  107. package/templates/povio/apps/fe/src/styles/overrides/defaults/modal.override.ts +62 -0
  108. package/templates/povio/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
  109. package/templates/povio/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
  110. package/templates/povio/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
  111. package/templates/povio/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
  112. package/templates/povio/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
  113. package/templates/povio/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
  114. package/templates/povio/apps/fe/src/styles/theme.css +2177 -0
  115. package/templates/povio/apps/fe/src/types/i18next.d.ts +12 -0
  116. package/templates/povio/apps/fe/src/types/table.d.ts +17 -0
  117. package/templates/povio/apps/fe/src/types/ui.d.ts +26 -0
  118. package/templates/povio/apps/fe/src/types/vite-env.d.ts +13 -0
  119. package/templates/povio/apps/fe/src/utils/date.utils.ts +17 -0
  120. package/templates/povio/apps/fe/src/utils/image-fallback.ts +9 -0
  121. package/templates/povio/apps/fe/src/utils/number.utils.ts +12 -0
  122. package/templates/povio/apps/fe/src/utils/string.utils.ts +5 -0
  123. package/templates/povio/apps/fe/src/vite-env.d.ts +1 -0
  124. package/templates/povio/apps/fe/tsconfig.app.json +32 -0
  125. package/templates/povio/apps/fe/tsconfig.json +9 -0
  126. package/templates/povio/apps/fe/tsconfig.node.json +31 -0
  127. package/templates/povio/apps/fe/vite.config.ts +150 -0
  128. package/templates/povio/openapi.json +911 -0
  129. package/templates/povio/oxfmt.config.js +23 -0
  130. package/templates/povio/package.json +19 -0
  131. package/templates/povio/robodev/api/_lib.ts +99 -0
  132. package/templates/povio/robodev/api/health.ts +7 -0
  133. package/templates/povio/robodev/database.ts +28 -0
  134. package/templates/povio/robodev/package.json +4 -0
  135. package/templates/povio/rulesync.jsonc +18 -0
  136. package/templates/povio/tsconfig.json +11 -0
  137. package/templates/shadcn/.rulesync/rules/frontend-api-boundary.md +29 -0
  138. package/templates/shadcn/.rulesync/rules/frontend-component-structure.md +19 -0
  139. package/templates/shadcn/.rulesync/rules/frontend-hosted-ui.md +38 -0
  140. package/templates/shadcn/.rulesync/rules/project-overview.md +29 -0
  141. package/templates/shadcn/.rulesync/rules/robodev-api.md +22 -0
  142. package/templates/shadcn/.rulesync/rules/robodev-auth.md +47 -0
  143. package/templates/shadcn/.rulesync/rules/robodev-database.md +21 -0
  144. package/templates/shadcn/.rulesync/skills/robodev-crud-page/SKILL.md +181 -0
  145. package/templates/shadcn/README.md +9 -4
  146. package/templates/shadcn/ROBODEV.md +14 -6
  147. package/templates/shadcn/package.json +4 -6
  148. package/templates/shadcn/rulesync.jsonc +18 -0
  149. package/templates/shadcn/src/App.tsx +12 -3
  150. package/templates/shadcn/src/app.css +359 -0
  151. package/templates/shadcn/src/components/auth/AuthCard.tsx +29 -0
  152. package/templates/shadcn/src/components/ui/badge.tsx +17 -0
  153. package/templates/shadcn/src/components/ui/card.tsx +28 -0
  154. package/templates/shadcn/src/components/ui/dialog.tsx +38 -0
  155. package/templates/shadcn/src/components/ui/empty-state.tsx +17 -0
  156. package/templates/shadcn/src/components/ui/input.tsx +8 -0
  157. package/templates/shadcn/src/components/ui/label.tsx +8 -0
  158. package/templates/shadcn/src/components/ui/skeleton.tsx +6 -0
  159. package/templates/shadcn/src/components/ui/spinner.tsx +3 -0
  160. package/templates/shadcn/src/components/ui/table.tsx +30 -0
  161. package/templates/shadcn/src/components/ui/textarea.tsx +8 -0
  162. package/templates/shadcn/src/lib/auth.tsx +72 -0
  163. package/templates/shadcn/src/lib/client.ts +8 -0
  164. package/templates/shadcn/src/pages/Home.tsx +15 -0
  165. package/templates/shadcn/src/pages/Login.tsx +70 -0
  166. package/templates/shadcn/src/pages/NotFound.tsx +14 -0
  167. package/templates/shadcn/src/pages/Register.tsx +82 -0
  168. package/templates/space/ROBODEV.md +7 -1
  169. package/templates/space/package.json +2 -2
  170. package/templates/shadcn/components.json +0 -21
  171. package/templates/shadcn/postcss.config.js +0 -6
  172. package/templates/shadcn/tailwind.config.ts +0 -42
@@ -0,0 +1,23 @@
1
+ export default {
2
+ useTabs: false,
3
+ tabWidth: 2,
4
+ printWidth: 120,
5
+ singleQuote: false,
6
+ jsxSingleQuote: false,
7
+ quoteProps: "as-needed",
8
+ trailingComma: "all",
9
+ semi: true,
10
+ arrowParens: "always",
11
+ bracketSameLine: false,
12
+ bracketSpacing: true,
13
+ singleAttributePerLine: true,
14
+ ignorePatterns: [
15
+ "node_modules",
16
+ "dist",
17
+ "out",
18
+ ".turbo",
19
+ ".next",
20
+ "routeTree.gen.ts",
21
+ "openapi.generated.json",
22
+ ],
23
+ };
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{PACKAGE_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "robodev dev --with-fe",
8
+ "deploy": "robodev deploy",
9
+ "rules:gen": "rulesync generate"
10
+ },
11
+ "dependencies": {
12
+ "@robodev-ai/sdk": "^0.14.0"
13
+ },
14
+ "devDependencies": {
15
+ "robodev": "^0.48.0",
16
+ "rulesync": "^8.18.0",
17
+ "typescript": "^5.9.2"
18
+ }
19
+ }
@@ -0,0 +1,99 @@
1
+ import { eq } from "@robodev-ai/sdk";
2
+ import type { ApiHandlerContext, UploadedFile } from "@robodev-ai/sdk";
3
+ import { media } from "../database";
4
+
5
+ export function fail(status: number, message: string) {
6
+ return { status, body: { message } };
7
+ }
8
+
9
+ export function iso(value: Date | string | null | undefined): string | null {
10
+ if (!value) return null;
11
+ return value instanceof Date ? value.toISOString() : value;
12
+ }
13
+
14
+ export function requiredIso(value: Date | string | null | undefined): string {
15
+ return iso(value) ?? new Date().toISOString();
16
+ }
17
+
18
+ export function paginate<T>(items: T[], page?: number, limit?: number) {
19
+ const currentPage = page && page > 0 ? page : 1;
20
+ const currentLimit = limit && limit > 0 ? Math.min(limit, 20) : 20;
21
+ const start = (currentPage - 1) * currentLimit;
22
+ const nextPage = currentPage * currentLimit < items.length ? currentPage + 1 : null;
23
+ return {
24
+ items: items.slice(start, start + currentLimit),
25
+ page: currentPage,
26
+ cursor: String(currentPage),
27
+ nextCursor: nextPage ? String(nextPage) : null,
28
+ limit: currentLimit,
29
+ totalItems: items.length,
30
+ };
31
+ }
32
+
33
+ export async function resolveImage(
34
+ ctx: ApiHandlerContext<unknown, unknown>,
35
+ imageId: string | null | undefined,
36
+ ): Promise<{ id: string; url: string } | null> {
37
+ if (!imageId) return null;
38
+ const [row] = await ctx.db.select().from(media).where(eq(media.id, imageId)).limit(1);
39
+ if (!row?.uploaded) return null;
40
+ try {
41
+ const url = await ctx.storage.getUrl(row.key);
42
+ return { id: row.id, url };
43
+ } catch {
44
+ return { id: row.id, url: `/storage/objects/${encodeURIComponent(row.key)}` };
45
+ }
46
+ }
47
+
48
+ const ALLOWED_IMAGE_TYPES = new Set(["image/jpeg", "image/png", "image/webp"]);
49
+ const MAX_IMAGE_BYTES = 2 * 1024 * 1024;
50
+
51
+ /** Matches dashboard sanitizeFilename / KEY_RE. */
52
+ function sanitizeFilename(name: string): string {
53
+ const base = name.split(/[/\\]/).pop() ?? "";
54
+ return base.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
55
+ }
56
+
57
+ /** Store an uploaded image in Robodev storage and record it in the media table. */
58
+ export async function saveImage(
59
+ ctx: ApiHandlerContext<unknown, unknown>,
60
+ file: UploadedFile,
61
+ resourceName: string,
62
+ ): Promise<{ id: string } | ReturnType<typeof fail>> {
63
+ const mimeType = file.mimetype || "application/octet-stream";
64
+ if (file.data.length > MAX_IMAGE_BYTES) {
65
+ return fail(400, "File is too large");
66
+ }
67
+ if (!ALLOWED_IMAGE_TYPES.has(mimeType)) {
68
+ return fail(400, "Unsupported image type");
69
+ }
70
+
71
+ const fileName = file.filename || "image";
72
+ const safe = sanitizeFilename(fileName) || "image";
73
+ const key = `${resourceName}s/${ctx.user!.id}/${crypto.randomUUID()}-${safe}`;
74
+ const [row] = await ctx.db
75
+ .insert(media)
76
+ .values({
77
+ key,
78
+ userId: ctx.user!.id,
79
+ resourceName,
80
+ fileName,
81
+ fileSize: file.data.length,
82
+ mimeType,
83
+ })
84
+ .returning();
85
+
86
+ try {
87
+ await ctx.storage.upload(row!.key, file.data, {
88
+ public: true,
89
+ contentType: mimeType,
90
+ });
91
+ } catch (error) {
92
+ return fail(503, error instanceof Error ? error.message : "Storage is not configured");
93
+ }
94
+
95
+ await ctx.db.update(media).set({ uploaded: new Date() }).where(eq(media.id, row!.id));
96
+ return { id: row!.id };
97
+ }
98
+
99
+ export { media };
@@ -0,0 +1,7 @@
1
+ /** GET /api/health — liveness check for the deployed project host. */
2
+ import { defineApi, z } from "@robodev-ai/sdk";
3
+
4
+ export const get = defineApi({
5
+ response: z.object({ ok: z.literal(true) }),
6
+ handler: async () => ({ ok: true as const }),
7
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Placeholder schema. Replace the items table or add tables, then publish.
3
+ * Keep the media table if the product needs file uploads (see the
4
+ * media-feature skill).
5
+ */
6
+ import { defineDatabase, integer, pgTable, text, timestamp, uuid } from "@robodev-ai/sdk";
7
+
8
+ export const items = pgTable("items", {
9
+ id: uuid("id").primaryKey().defaultRandom(),
10
+ name: text("name").notNull(),
11
+ });
12
+
13
+ export const media = pgTable("media", {
14
+ id: uuid("id").primaryKey().defaultRandom(),
15
+ key: text("key").notNull(),
16
+ userId: text("userId").notNull(),
17
+ resourceName: text("resourceName").notNull(),
18
+ fileName: text("fileName").notNull(),
19
+ fileSize: integer("fileSize").notNull(),
20
+ mimeType: text("mimeType").notNull(),
21
+ uploaded: timestamp("uploaded", { withTimezone: true }),
22
+ createdAt: timestamp("createdAt", { withTimezone: true }).defaultNow(),
23
+ });
24
+
25
+ export default defineDatabase({
26
+ name: "app",
27
+ tables: { items, media },
28
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "private": true,
3
+ "dependencies": {}
4
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json",
3
+ "targets": {
4
+ "cursor": ["rules", "skills"],
5
+ "claudecode": ["rules", "skills"],
6
+ "codexcli": ["rules", "skills"],
7
+ },
8
+ "outputRoots": ["."],
9
+ // Clean stale outputs when source rules are removed. All sources live under .rulesync/.
10
+ "delete": true,
11
+ "verbose": false,
12
+ "silent": false,
13
+ "global": false,
14
+ "simulateCommands": false,
15
+ "simulateSubagents": false,
16
+ "simulateSkills": false,
17
+ "gitignoreTargetsOnly": true,
18
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "noEmit": true
9
+ },
10
+ "include": ["robodev/**/*.ts"]
11
+ }
@@ -0,0 +1,29 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend calls app APIs through the shared client, not raw fetch."
5
+ globs: ["src/**/*.{ts,tsx}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when frontend code calls the API."
9
+ globs: ["src/**/*.{ts,tsx}"]
10
+ ---
11
+
12
+ # Frontend API Boundary
13
+
14
+ Call app APIs through `client` from `@/lib/client`:
15
+
16
+ ```ts
17
+ import { client } from "@/lib/client";
18
+
19
+ const todos = await client.get<Todo[]>("/api/todos");
20
+ const created = await client.post<Todo>("/api/todos", { title });
21
+ await client.put(`/api/todos/${id}`, { done: true });
22
+ await client.del(`/api/todos/${id}`);
23
+ ```
24
+
25
+ - `client` adds the `Authorization` header and refreshes tokens on 401. Never call `fetch()` on `/api/...` directly and never build your own token handling.
26
+ - Define response types next to the feature (or a shared `src/types.ts`) and pass them as the generic parameter.
27
+ - Do not import `@robodev-ai/sdk`, `robodev/database.ts`, or anything from `robodev/` into `src/` — backend and frontend never share code.
28
+ - The committed orval client under `src/api/generated/` serves the seed `getHealth` call and optional local regeneration (`pnpm run generate:api`); it does not send auth headers, so do not add authenticated endpoints to it.
29
+ - After changing API contracts, update the frontend calls in the same batch so the preview keeps rendering.
@@ -0,0 +1,19 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Frontend page, feature, and component file structure."
5
+ globs: ["src/**/*.{ts,tsx}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when organizing frontend components and pages."
9
+ globs: ["src/**/*.{ts,tsx}"]
10
+ ---
11
+
12
+ # Frontend Component Structure
13
+
14
+ - Pages live in `src/pages/<Name>.tsx` and are wired in `src/App.tsx` with a `<Route>`. Protected pages wrap in `AuthGuard`.
15
+ - Reusable primitives stay in `src/components/ui/`. Feature components live in `src/components/<feature>/` (for example `src/components/todos/TodoRow.tsx`).
16
+ - Every named component gets its own file. Anything mapped from an array (cards, table rows, list items) should be a component.
17
+ - Pages own routing concerns and data loading; feature components own the UI and interactions.
18
+ - Generic formatting/parsing helpers go in `src/lib/` (for example `src/lib/format.ts`), not inside component files.
19
+ - Ship in small vertical slices: land one route end to end (schema → API → page → route) before starting the next, so the preview keeps rendering.
@@ -0,0 +1,38 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Hosted UI kit: app.css tokens and src/components/ui primitives. No Tailwind."
5
+ globs: ["src/**/*.{ts,tsx,css}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when building or styling frontend UI."
9
+ globs: ["src/**/*.{ts,tsx,css}"]
10
+ ---
11
+
12
+ # Hosted UI Kit
13
+
14
+ There is **no Tailwind**. The hosted compile bundles CSS as-is: every class must exist in `src/app.css`. A class that is not defined there silently renders unstyled.
15
+
16
+ ## Components (import from `@/components/ui/<file>`)
17
+
18
+ - `Button` (`variant`: default | destructive | outline | secondary | ghost | link; `size`: default | sm | lg | icon)
19
+ - `Input`, `Textarea`, `Label`
20
+ - `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`
21
+ - `Badge` (`variant`: default | secondary | destructive | outline)
22
+ - `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`
23
+ - `Dialog` (`{ open, onClose, title?, footer? }`, controlled)
24
+ - `Skeleton`, `Spinner`, `EmptyState` (`{ title, description?, action? }`)
25
+
26
+ ## Layout and text classes (already in app.css)
27
+
28
+ - `.page` (centered max-width container), `.page-header` + `.page-title`
29
+ - `.stack` (column, 1rem gap), `.row` (inline, 0.5rem gap)
30
+ - `.field` (label + input + error), `.field-error`, `.muted`
31
+ - `.auth-page` + `.auth-card` for full-screen centered forms
32
+
33
+ ## Rules
34
+
35
+ - Compose screens from these primitives plus the layout classes. Form rows are `<div className="field"><Label/><Input/></div>` inside a `.stack` form.
36
+ - New visual treatment = add a class to `src/app.css` using the `hsl(var(--token))` color variables. Do not inline hex colors or long `style={{...}}` objects.
37
+ - Tokens: `--background/foreground`, `--card`, `--popover`, `--primary`, `--secondary`, `--muted`, `--accent`, `--destructive` (each with `-foreground`), `--border`, `--input`, `--ring`, `--radius`. Dark mode = `.dark` class on `<html>` or `<body>`.
38
+ - Loading uses `Skeleton` or `Spinner`; no-data uses `EmptyState` with an action button. Never leave a bare "no items" paragraph.
@@ -0,0 +1,29 @@
1
+ ---
2
+ root: true
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Hosted Shadcn project overview and source-of-truth guidance."
5
+ globs: ["**/*"]
6
+ cursor:
7
+ alwaysApply: true
8
+ description: "Hosted Shadcn project overview and source-of-truth guidance."
9
+ globs: ["**/*"]
10
+ ---
11
+
12
+ # Hosted Shadcn seed AI rules
13
+
14
+ This project is a hosted React frontend (root `index.html` + `src/**`) plus a Robodev backend under `robodev/` (`robodev/database.ts` + `robodev/api/**/*.ts` using `@robodev-ai/sdk`). Keep this layout. Do not convert this tree to Tiny (`packages/be` + `apps/fe`).
15
+
16
+ The hosted compile is esbuild only — **no Tailwind, no PostCSS**. Every CSS class used in `src/` must be defined in `src/app.css`. There is no Tailwind anywhere in this tree; do not write utility classes like `flex` or `p-4`.
17
+
18
+ Frontend packages on the hosted live app are only: `react`, `react-dom`, `react-router-dom`, `@robodev-ai/client`. Importing anything else in `src/` fails the hosted build.
19
+
20
+ Auth is seeded and reserved on the project host at `/api/user/*`: reuse `src/lib/client.ts`, `src/lib/auth.tsx` (`AuthProvider`, `useAuth`, `AuthGuard`), `src/pages/Login.tsx`, `src/pages/Register.tsx`.
21
+
22
+ Use the more specific rules for:
23
+
24
+ - Robodev `defineDatabase` tables in `robodev/database.ts`.
25
+ - Robodev `defineApi` routes in `robodev/api/`.
26
+ - Reserved `/api/user/*` auth contracts and the seeded auth files.
27
+ - The frontend API boundary (`client` from `@/lib/client`).
28
+ - The hosted UI kit in `src/components/ui` and `src/app.css` tokens.
29
+ - Component and page structure.
@@ -0,0 +1,22 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Robodev defineApi file routes and handler conventions."
5
+ globs: ["robodev/api/**/*.ts"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when editing Robodev API routes."
9
+ globs: ["robodev/api/**/*.ts"]
10
+ ---
11
+
12
+ # Robodev API
13
+
14
+ Project APIs live in `robodev/api/`. The route is the file path: `robodev/api/foo.ts` → `/api/foo`, `robodev/api/foo/[id].ts` → `/api/foo/:id`.
15
+
16
+ - Export `get` / `post` / `put` / `patch` / `delete` as `defineApi(...)` from `@robodev-ai/sdk`.
17
+ - Shared helpers belong in `robodev/api/_lib.ts`. Do not export `defineApi` handlers from `_lib.ts`.
18
+ - Do not add `robodev/api/user/**`. Those paths are reserved for Robodev Auth.
19
+ - Use `auth: "required"` on routes that belong to a signed-in user, and scope rows by `ctx.user.id`.
20
+ - Handler context includes `db`, `user`, `email`, `storage`, `env`, `params`, `query`, `body`. These are injected — never `import { db } from "@robodev-ai/sdk"`.
21
+ - Return JSON for 200, or `{ status, body }` for errors (400/403/404).
22
+ - After a batch of API or schema edits, run `robodev-publish be` so the APIs go live.
@@ -0,0 +1,47 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Reserved Robodev /api/user auth contracts and the seeded auth files."
5
+ globs: ["**/*.ts", "src/**/*.{ts,tsx}"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when touching auth, login, register, or /api/user clients."
9
+ globs:
10
+ [
11
+ "robodev/database.ts",
12
+ "robodev/api/**/*.ts",
13
+ "src/lib/**/*.{ts,tsx}",
14
+ "src/pages/Login.tsx",
15
+ "src/pages/Register.tsx",
16
+ ]
17
+ ---
18
+
19
+ # Robodev Auth
20
+
21
+ End-user auth is reserved on the project host. Do not implement `api/user/**` in this project.
22
+
23
+ **Auth is already seeded — reuse it, never rebuild it:**
24
+
25
+ - `src/lib/client.ts` — same-origin `createClient` that stores tokens, sends `Authorization`, and refreshes on 401.
26
+ - `src/lib/auth.tsx` — `AuthProvider` (mounted in `App.tsx`), `useAuth()`, and `AuthGuard`.
27
+ - `src/pages/Login.tsx` / `src/pages/Register.tsx` — working forms. Restyle them; do not reimplement token handling.
28
+
29
+ Protect a route: `<Route path="/todos" element={<AuthGuard><TodosPage /></AuthGuard>} />`.
30
+
31
+ ## Contracts
32
+
33
+ | Method | Path | Notes |
34
+ | ------- | ----------------------------------------- | ------------------------------------------------------- |
35
+ | POST | `/api/user/auth/login` | `{ email, password }` → `{ accessToken, refreshToken }` |
36
+ | POST | `/api/user/auth/register` | password min 12, 201, token pair |
37
+ | POST | `/api/user/auth/refresh` | `{ refreshToken }` → new pair |
38
+ | GET | `/api/user/auth/magic-link` | query `email`, generic status |
39
+ | GET | `/api/user/auth/magic-link/callback` | query `code` → tokens |
40
+ | POST | `/api/user/auth/forgot-password` | `{ email }` |
41
+ | POST | `/api/user/auth/forgot-password/callback` | `{ code, password }` |
42
+ | GET | `/api/user/auth/google` | start OAuth; `redirect_uri` required |
43
+ | GET/PUT | `/api/user/me` | flat `{ id, name, email }` |
44
+
45
+ Access tokens last 15 minutes, refresh tokens 30 days. `client` handles both — never touch localStorage for tokens yourself.
46
+
47
+ Error `code` is top-level: `invalid-credentials`, `identity-already-exists`, `identity-not-found`, `nonce-invalid`.
@@ -0,0 +1,21 @@
1
+ ---
2
+ root: false
3
+ targets: ["claudecode", "codexcli", "cursor"]
4
+ description: "Robodev database.ts Drizzle schema and deploy sync."
5
+ globs: ["robodev/database.ts"]
6
+ cursor:
7
+ alwaysApply: false
8
+ description: "Apply when editing the database schema."
9
+ globs: ["robodev/database.ts"]
10
+ ---
11
+
12
+ # Robodev Database
13
+
14
+ The schema lives in `robodev/database.ts`. Default-export `defineDatabase` with Drizzle `pgTable` definitions. `robodev-publish be` syncs it; there are no migration files.
15
+
16
+ - Import table helpers from `@robodev-ai/sdk` (`pgTable`, `uuid`, `text`, `timestamp`, `integer`, and query helpers).
17
+ - Do not recreate User or auth tables. End users live in platform auth. Store `userId` as `text` matching `ctx.user.id`.
18
+ - The seeded `items` table is a placeholder — replace it with the product's real tables.
19
+ - Export named tables so API files can import them from `../database`.
20
+ - Prefer scalar columns and foreign keys. Destructive drops need deploy confirmation or `--force`.
21
+ - Schema changes only via this file + publish — never DDL in SQL.
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: robodev-crud-page
3
+ description: Build a list-create-edit-delete page for a database table in the hosted Shadcn layout. Use it whenever a request needs a page that lists records and lets the user add, change, or remove them — a table page, an admin screen, a CRUD feature, a "manage X" page.
4
+ ---
5
+
6
+ # Add a CRUD page (hosted Shadcn layout)
7
+
8
+ Four files per resource, in this order. `todos` is the running example.
9
+
10
+ ## 1. Table — `robodev/database.ts`
11
+
12
+ ```ts
13
+ import { defineDatabase, pgTable, text, uuid, boolean, timestamp } from "@robodev-ai/sdk";
14
+
15
+ export const todos = pgTable("todos", {
16
+ id: uuid("id").primaryKey().defaultRandom(),
17
+ userId: text("user_id").notNull(),
18
+ title: text("title").notNull(),
19
+ done: boolean("done").notNull().default(false),
20
+ createdAt: timestamp("created_at").notNull().defaultNow(),
21
+ });
22
+
23
+ export default defineDatabase({ name: "app", tables: { todos } });
24
+ ```
25
+
26
+ ## 2. Routes — `robodev/api/todos.ts` and `robodev/api/todos/[id].ts`
27
+
28
+ ```ts
29
+ // robodev/api/todos.ts
30
+ import { defineApi, z, eq, desc } from "@robodev-ai/sdk";
31
+ import { todos } from "../database";
32
+
33
+ export const get = defineApi({
34
+ auth: "required",
35
+ response: z.array(z.object({ id: z.string(), title: z.string(), done: z.boolean() })),
36
+ handler: async ({ db, user }) =>
37
+ db.select().from(todos).where(eq(todos.userId, user.id)).orderBy(desc(todos.createdAt)),
38
+ });
39
+
40
+ export const post = defineApi({
41
+ auth: "required",
42
+ body: z.object({ title: z.string().min(1) }),
43
+ response: z.object({ id: z.string(), title: z.string(), done: z.boolean() }),
44
+ handler: async ({ db, user, body }) => {
45
+ const [row] = await db.insert(todos).values({ userId: user.id, title: body.title }).returning();
46
+ return row;
47
+ },
48
+ });
49
+ ```
50
+
51
+ ```ts
52
+ // robodev/api/todos/[id].ts
53
+ import { defineApi, z, and, eq } from "@robodev-ai/sdk";
54
+ import { todos } from "../../database";
55
+
56
+ const owned = (userId: string, id: string) => and(eq(todos.userId, userId), eq(todos.id, id));
57
+
58
+ export const put = defineApi({
59
+ auth: "required",
60
+ body: z.object({ title: z.string().min(1).optional(), done: z.boolean().optional() }),
61
+ response: z.object({ id: z.string(), title: z.string(), done: z.boolean() }),
62
+ handler: async ({ db, user, params, body }) => {
63
+ const [row] = await db.update(todos).set(body).where(owned(user.id, params.id)).returning();
64
+ if (!row) return { status: 404, body: { message: "Not found" } };
65
+ return row;
66
+ },
67
+ });
68
+
69
+ const remove = defineApi({
70
+ auth: "required",
71
+ response: z.object({ ok: z.literal(true) }),
72
+ handler: async ({ db, user, params }) => {
73
+ await db.delete(todos).where(owned(user.id, params.id));
74
+ return { ok: true as const };
75
+ },
76
+ });
77
+ // `delete` is a reserved word: define under another name, export as `delete`.
78
+ export { remove as delete };
79
+ ```
80
+
81
+ Then `robodev-publish be`.
82
+
83
+ ## 3. Page — `src/pages/Todos.tsx`
84
+
85
+ List with `Table`, create/edit in a `Dialog` form (`.field` rows), `EmptyState` when the list is empty, `Skeleton` while loading, all data through `client`:
86
+
87
+ ```tsx
88
+ import { useEffect, useState } from "react";
89
+ import { client } from "@/lib/client";
90
+ import { Button } from "@/components/ui/button";
91
+ import { Dialog } from "@/components/ui/dialog";
92
+ import { EmptyState } from "@/components/ui/empty-state";
93
+ import { Input } from "@/components/ui/input";
94
+ import { Label } from "@/components/ui/label";
95
+ import { Skeleton } from "@/components/ui/skeleton";
96
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
97
+
98
+ type Todo = { id: string; title: string; done: boolean };
99
+
100
+ export function Todos() {
101
+ const [todos, setTodos] = useState<Todo[] | null>(null);
102
+ const [open, setOpen] = useState(false);
103
+ const [title, setTitle] = useState("");
104
+
105
+ async function load() {
106
+ setTodos(await client.get<Todo[]>("/api/todos"));
107
+ }
108
+ useEffect(() => {
109
+ void load();
110
+ }, []);
111
+
112
+ async function create() {
113
+ await client.post("/api/todos", { title });
114
+ setTitle("");
115
+ setOpen(false);
116
+ await load();
117
+ }
118
+
119
+ return (
120
+ <div className="page">
121
+ <div className="page-header">
122
+ <h1 className="page-title">Todos</h1>
123
+ <Button onClick={() => setOpen(true)}>New todo</Button>
124
+ </div>
125
+ {todos === null ? (
126
+ <div className="stack">
127
+ <Skeleton style={{ height: "2.5rem" }} />
128
+ <Skeleton style={{ height: "2.5rem" }} />
129
+ </div>
130
+ ) : todos.length === 0 ? (
131
+ <EmptyState
132
+ title="No todos yet"
133
+ description="Create your first todo to get started."
134
+ action={<Button onClick={() => setOpen(true)}>New todo</Button>}
135
+ />
136
+ ) : (
137
+ <Table>
138
+ <TableHeader>
139
+ <TableRow>
140
+ <TableHead>Title</TableHead>
141
+ <TableHead>Status</TableHead>
142
+ </TableRow>
143
+ </TableHeader>
144
+ <TableBody>
145
+ {todos.map((todo) => (
146
+ <TableRow key={todo.id}>
147
+ <TableCell>{todo.title}</TableCell>
148
+ <TableCell>{todo.done ? "Done" : "Open"}</TableCell>
149
+ </TableRow>
150
+ ))}
151
+ </TableBody>
152
+ </Table>
153
+ )}
154
+ <Dialog
155
+ open={open}
156
+ onClose={() => setOpen(false)}
157
+ title="New todo"
158
+ footer={
159
+ <>
160
+ <Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
161
+ <Button onClick={() => void create()}>Create</Button>
162
+ </>
163
+ }
164
+ >
165
+ <div className="field">
166
+ <Label htmlFor="title">Title</Label>
167
+ <Input id="title" value={title} onChange={(e) => setTitle(e.target.value)} />
168
+ </div>
169
+ </Dialog>
170
+ </div>
171
+ );
172
+ }
173
+ ```
174
+
175
+ Extract `TodoRow` and the dialog form into `src/components/todos/` when they grow.
176
+
177
+ ## 4. Route — `src/App.tsx`
178
+
179
+ ```tsx
180
+ <Route path="/todos" element={<AuthGuard><Todos /></AuthGuard>} />
181
+ ```
@@ -1,8 +1,15 @@
1
1
  # {{NAME}}
2
2
 
3
- Blank `robodev/database.ts` and `GET /api/health` plus a hosted Welcome page with shadcn and an orval client. No sample schema.
3
+ Blank `robodev/database.ts` and `GET /api/health` plus a hosted Welcome page with a shadcn-style UI kit, seeded auth, and an orval client. No sample schema.
4
4
 
5
- The project App URL serves the hosted Welcome page (`/` via React Router in `src/pages/Home.tsx`). Edit `index.html` and `src/`, then `robodev deploy` (or Code + Save). Agents: read `ROBODEV.md`. Unless the product is a public page, add login/register via reserved `/api/user/auth/*` and protect routes.
5
+ The project App URL serves the hosted Welcome page (`/` via React Router in `src/pages/Home.tsx`). Edit `index.html` and `src/`, then `robodev deploy` (or Code + Save). Agents: read `ROBODEV.md` and `.rulesync/rules/project-overview.md`.
6
+
7
+ What is seeded:
8
+
9
+ - **Auth**: `src/lib/client.ts` (`@robodev-ai/client`, same-origin), `src/lib/auth.tsx` (`AuthProvider`, `useAuth`, `AuthGuard`), `src/pages/Login.tsx`, `src/pages/Register.tsx`, plus a `*` NotFound route. Reuse these — do not reimplement token handling.
10
+ - **UI kit**: shadcn-style components in `src/components/ui` (button, input, textarea, label, card, badge, table, dialog, skeleton, spinner, empty-state) styled by design tokens and plain CSS in `src/app.css` (light + `.dark`). The hosted compile does not run Tailwind, so there is no Tailwind in this tree — every class lives in `app.css`.
11
+ - **API path**: call app APIs with `client.get/post/put/patch/del`. The committed orval client under `src/api/generated/` covers the seed `getHealth`; regenerate locally with `pnpm run generate:api` after editing `src/api/openapi.json` if you want typed public endpoints.
12
+ - **Agent rules**: `.rulesync/rules/*` and `.rulesync/skills/robodev-crud-page/SKILL.md`.
6
13
 
7
14
  ```bash
8
15
  robodev create my-app --template shadcn
@@ -15,6 +22,4 @@ pnpm run generate:api # regenerate the orval client from src/api/openapi.json
15
22
 
16
23
  Do not write a `frontend` block in `robodev/.robodev` — that block is for Tiny `apps/fe`. Root `index.html` + `src/` deploy as hosted React.
17
24
 
18
- Hosted compile does not run Tailwind. `src/app.css` is plain CSS so Welcome works on the project host. `tailwind.config.ts` and `postcss.config.js` are for local `shadcn add` / Vite only. The orval client under `src/api/generated/` is committed; regenerate it after API changes.
19
-
20
25
  Docs: https://robodev.povio.dev/docs