create-lego-one 2.0.10 → 2.0.13

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 (242) hide show
  1. package/dist/index.cjs +179 -0
  2. package/dist/index.cjs.map +1 -1
  3. package/package.json +5 -3
  4. package/template/.cursor/rules/rules.mdc +639 -0
  5. package/template/.dockerignore +58 -0
  6. package/template/.env.example +18 -0
  7. package/template/.eslintignore +5 -0
  8. package/template/.eslintrc.js +28 -0
  9. package/template/.prettierignore +6 -0
  10. package/template/.prettierrc +11 -0
  11. package/template/CLAUDE.md +634 -0
  12. package/template/Dockerfile +67 -0
  13. package/template/PROMPT.md +457 -0
  14. package/template/README.md +325 -0
  15. package/template/docker-compose.yml +48 -0
  16. package/template/docker-entrypoint.sh +23 -0
  17. package/template/docs/checkpoints/.template.md +64 -0
  18. package/template/docs/checkpoints/framework/01-infrastructure-setup.md +132 -0
  19. package/template/docs/checkpoints/framework/02-pocketbase-setup.md +155 -0
  20. package/template/docs/checkpoints/framework/03-host-kernel.md +170 -0
  21. package/template/docs/checkpoints/framework/04-auth-system.md +163 -0
  22. package/template/docs/checkpoints/framework/phase-05-multitenancy-rbac.md +223 -0
  23. package/template/docs/checkpoints/framework/phase-06-ui-components.md +260 -0
  24. package/template/docs/checkpoints/framework/phase-07-communication-system.md +276 -0
  25. package/template/docs/checkpoints/framework/phase-08-plugin-system.md +91 -0
  26. package/template/docs/checkpoints/framework/phase-09-dashboard-plugin.md +111 -0
  27. package/template/docs/checkpoints/framework/phase-10-todo-plugin.md +169 -0
  28. package/template/docs/checkpoints/framework/phase-11-testing.md +264 -0
  29. package/template/docs/checkpoints/framework/phase-12-deployment.md +294 -0
  30. package/template/docs/checkpoints/framework/phase-13-documentation.md +312 -0
  31. package/template/docs/framework/plans/00-index.md +164 -0
  32. package/template/docs/framework/plans/01-infrastructure-setup.md +855 -0
  33. package/template/docs/framework/plans/02-pocketbase-setup.md +1374 -0
  34. package/template/docs/framework/plans/03-host-kernel.md +1518 -0
  35. package/template/docs/framework/plans/04-auth-system.md +1466 -0
  36. package/template/docs/framework/plans/05-multitenancy-rbac.md +1527 -0
  37. package/template/docs/framework/plans/06-ui-components.md +1478 -0
  38. package/template/docs/framework/plans/07-communication-system.md +1106 -0
  39. package/template/docs/framework/plans/08-plugin-system.md +1179 -0
  40. package/template/docs/framework/plans/09-dashboard-plugin.md +1137 -0
  41. package/template/docs/framework/plans/10-todo-plugin.md +1343 -0
  42. package/template/docs/framework/plans/11-testing.md +935 -0
  43. package/template/docs/framework/plans/12-deployment.md +896 -0
  44. package/template/docs/framework/prompts/0-boilerplate-modernjs.md +151 -0
  45. package/template/docs/framework/research/00-modernjs-audit.md +488 -0
  46. package/template/docs/framework/research/01-system-blueprint.md +721 -0
  47. package/template/docs/framework/research/02-data-migration-protocol.md +699 -0
  48. package/template/docs/framework/research/03-host-setup.md +714 -0
  49. package/template/docs/framework/research/04-plugin-architecture.md +645 -0
  50. package/template/docs/framework/research/05-slot-injection-pattern.md +671 -0
  51. package/template/docs/framework/research/06-cli-strategy.md +615 -0
  52. package/template/docs/framework/research/07-deployment.md +629 -0
  53. package/template/docs/framework/research/README.md +282 -0
  54. package/template/docs/framework/setup/00-index.md +210 -0
  55. package/template/docs/framework/setup/01-framework-structure.md +308 -0
  56. package/template/docs/framework/setup/02-development-workflow.md +405 -0
  57. package/template/docs/framework/setup/03-environment-setup.md +215 -0
  58. package/template/docs/framework/setup/04-kernel-architecture.md +499 -0
  59. package/template/docs/framework/setup/05-plugin-system.md +620 -0
  60. package/template/docs/framework/setup/06-communication-patterns.md +451 -0
  61. package/template/docs/framework/setup/07-plugin-development.md +582 -0
  62. package/template/docs/framework/setup/08-component-library.md +658 -0
  63. package/template/docs/framework/setup/09-data-integration.md +609 -0
  64. package/template/docs/framework/setup/10-auth-rbac.md +497 -0
  65. package/template/docs/framework/setup/11-hooks-api.md +393 -0
  66. package/template/docs/framework/setup/12-components-api.md +665 -0
  67. package/template/docs/framework/setup/13-deployment-guide.md +566 -0
  68. package/template/docs/framework/setup/README.md +548 -0
  69. package/template/host/e2e/auth.spec.ts +38 -0
  70. package/template/host/e2e/layout.spec.ts +38 -0
  71. package/template/host/modern.config.ts +19 -0
  72. package/template/host/package.json +71 -0
  73. package/template/host/playwright.config.ts +34 -0
  74. package/template/host/postcss.config.mjs +6 -0
  75. package/template/host/src/App.tsx +6 -0
  76. package/template/host/src/bootstrap.tsx +74 -0
  77. package/template/host/src/global.css +59 -0
  78. package/template/host/src/index.ts +2 -0
  79. package/template/host/src/kernel/__tests__/lib-utils.test.ts +32 -0
  80. package/template/host/src/kernel/__tests__/rbac-hooks.test.tsx +114 -0
  81. package/template/host/src/kernel/__tests__/rbac-utils.test.ts +108 -0
  82. package/template/host/src/kernel/auth/ProtectedRoute.tsx +41 -0
  83. package/template/host/src/kernel/auth/components/LoginForm.tsx +97 -0
  84. package/template/host/src/kernel/auth/components/LogoutButton.tsx +79 -0
  85. package/template/host/src/kernel/auth/hooks.ts +174 -0
  86. package/template/host/src/kernel/auth/index.ts +5 -0
  87. package/template/host/src/kernel/auth/schemas.ts +27 -0
  88. package/template/host/src/kernel/auth/service.ts +197 -0
  89. package/template/host/src/kernel/auth/types.ts +36 -0
  90. package/template/host/src/kernel/channels/ChannelBus.ts +181 -0
  91. package/template/host/src/kernel/channels/ChannelProvider.tsx +57 -0
  92. package/template/host/src/kernel/channels/events.ts +27 -0
  93. package/template/host/src/kernel/channels/hooks.ts +168 -0
  94. package/template/host/src/kernel/channels/index.ts +6 -0
  95. package/template/host/src/kernel/channels/integrations/ToastIntegration.tsx +60 -0
  96. package/template/host/src/kernel/channels/plugin-hooks.ts +72 -0
  97. package/template/host/src/kernel/channels/types.ts +112 -0
  98. package/template/host/src/kernel/components/__tests__/Badge.test.tsx +35 -0
  99. package/template/host/src/kernel/components/__tests__/Button.test.tsx +63 -0
  100. package/template/host/src/kernel/components/__tests__/Input.test.tsx +64 -0
  101. package/template/host/src/kernel/components/index.ts +32 -0
  102. package/template/host/src/kernel/components/ui/alert.tsx +58 -0
  103. package/template/host/src/kernel/components/ui/avatar.tsx +47 -0
  104. package/template/host/src/kernel/components/ui/badge.tsx +35 -0
  105. package/template/host/src/kernel/components/ui/button.tsx +50 -0
  106. package/template/host/src/kernel/components/ui/card.tsx +78 -0
  107. package/template/host/src/kernel/components/ui/dialog.tsx +116 -0
  108. package/template/host/src/kernel/components/ui/dropdown-menu.tsx +192 -0
  109. package/template/host/src/kernel/components/ui/index.ts +7 -0
  110. package/template/host/src/kernel/components/ui/input.tsx +24 -0
  111. package/template/host/src/kernel/components/ui/label.tsx +21 -0
  112. package/template/host/src/kernel/components/ui/popover.tsx +28 -0
  113. package/template/host/src/kernel/components/ui/progress.tsx +25 -0
  114. package/template/host/src/kernel/components/ui/scroll-area.tsx +45 -0
  115. package/template/host/src/kernel/components/ui/select.tsx +155 -0
  116. package/template/host/src/kernel/components/ui/separator.tsx +28 -0
  117. package/template/host/src/kernel/components/ui/skeleton.tsx +15 -0
  118. package/template/host/src/kernel/components/ui/switch.tsx +26 -0
  119. package/template/host/src/kernel/components/ui/table.tsx +116 -0
  120. package/template/host/src/kernel/components/ui/tabs.tsx +52 -0
  121. package/template/host/src/kernel/components/ui/toast.tsx +126 -0
  122. package/template/host/src/kernel/components/ui/toaster.tsx +34 -0
  123. package/template/host/src/kernel/components/ui/tooltip.tsx +27 -0
  124. package/template/host/src/kernel/components/ui/use-toast.ts +183 -0
  125. package/template/host/src/kernel/index.ts +48 -0
  126. package/template/host/src/kernel/lib/cn.ts +1 -0
  127. package/template/host/src/kernel/lib/utils.ts +36 -0
  128. package/template/host/src/kernel/plugins/Slot.tsx +41 -0
  129. package/template/host/src/kernel/plugins/SlotProvider.tsx +88 -0
  130. package/template/host/src/kernel/plugins/index.ts +23 -0
  131. package/template/host/src/kernel/plugins/loader.ts +122 -0
  132. package/template/host/src/kernel/plugins/schemas.ts +54 -0
  133. package/template/host/src/kernel/plugins/store.ts +185 -0
  134. package/template/host/src/kernel/plugins/types.ts +103 -0
  135. package/template/host/src/kernel/providers/PocketBaseProvider.tsx +70 -0
  136. package/template/host/src/kernel/providers/QueryProvider.tsx +28 -0
  137. package/template/host/src/kernel/providers/ThemeProvider.tsx +25 -0
  138. package/template/host/src/kernel/providers/index.ts +3 -0
  139. package/template/host/src/kernel/rbac/components/OrganizationSelector.tsx +69 -0
  140. package/template/host/src/kernel/rbac/components/PermissionGate.tsx +43 -0
  141. package/template/host/src/kernel/rbac/hooks.ts +379 -0
  142. package/template/host/src/kernel/rbac/index.ts +6 -0
  143. package/template/host/src/kernel/rbac/service.ts +504 -0
  144. package/template/host/src/kernel/rbac/types.ts +164 -0
  145. package/template/host/src/kernel/rbac/utils.ts +34 -0
  146. package/template/host/src/kernel/shared-state/bridge.ts +31 -0
  147. package/template/host/src/kernel/shared-state/index.ts +3 -0
  148. package/template/host/src/kernel/shared-state/store.ts +62 -0
  149. package/template/host/src/kernel/shared-state/types.ts +60 -0
  150. package/template/host/src/kernel/use-migrations.ts +72 -0
  151. package/template/host/src/layout/MobileMenu.tsx +61 -0
  152. package/template/host/src/layout/Shell.tsx +42 -0
  153. package/template/host/src/layout/Sidebar.tsx +178 -0
  154. package/template/host/src/layout/Topbar.tsx +50 -0
  155. package/template/host/src/layout/index.ts +4 -0
  156. package/template/host/src/lib/pocketbase/client.ts +38 -0
  157. package/template/host/src/lib/pocketbase/collections/audit_logs.ts +87 -0
  158. package/template/host/src/lib/pocketbase/collections/index.ts +19 -0
  159. package/template/host/src/lib/pocketbase/collections/organizations.ts +63 -0
  160. package/template/host/src/lib/pocketbase/collections/permissions.ts +57 -0
  161. package/template/host/src/lib/pocketbase/collections/roles.ts +55 -0
  162. package/template/host/src/lib/pocketbase/collections/todos.ts +74 -0
  163. package/template/host/src/lib/pocketbase/collections/user_roles.ts +57 -0
  164. package/template/host/src/lib/pocketbase/collections/users.ts +43 -0
  165. package/template/host/src/lib/pocketbase/index.ts +5 -0
  166. package/template/host/src/lib/pocketbase/migrations.ts +44 -0
  167. package/template/host/src/lib/pocketbase/seed/permissions.ts +8 -0
  168. package/template/host/src/lib/pocketbase/seed/roles.ts +22 -0
  169. package/template/host/src/lib/pocketbase/seed.ts +113 -0
  170. package/template/host/src/lib/pocketbase/types.ts +102 -0
  171. package/template/host/src/modern.runtime.ts +26 -0
  172. package/template/host/src/plugins.d.ts +9 -0
  173. package/template/host/src/providers/PocketBaseProvider.tsx +30 -0
  174. package/template/host/src/routes/_.tsx +6 -0
  175. package/template/host/src/routes/dashboard._.tsx +41 -0
  176. package/template/host/src/routes/index.tsx +93 -0
  177. package/template/host/src/routes/login.tsx +36 -0
  178. package/template/host/src/saas.config.ts +52 -0
  179. package/template/host/src/test/setup.ts +65 -0
  180. package/template/host/src/test/utils.tsx +69 -0
  181. package/template/host/src/test/vitest-globals.d.ts +19 -0
  182. package/template/host/src/vite-env.d.ts +16 -0
  183. package/template/host/tailwind.config.ts +77 -0
  184. package/template/host/tsconfig.json +19 -0
  185. package/template/host/vitest.config.ts +30 -0
  186. package/template/nginx.conf +72 -0
  187. package/template/package.json +44 -0
  188. package/template/packages/plugins/@lego/plugin-dashboard/modern.config.ts +19 -0
  189. package/template/packages/plugins/@lego/plugin-dashboard/package.json +35 -0
  190. package/template/packages/plugins/@lego/plugin-dashboard/postcss.config.mjs +6 -0
  191. package/template/packages/plugins/@lego/plugin-dashboard/src/App.tsx +27 -0
  192. package/template/packages/plugins/@lego/plugin-dashboard/src/components/ActivityFeed.tsx +63 -0
  193. package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActionSlot.tsx +11 -0
  194. package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActions.tsx +68 -0
  195. package/template/packages/plugins/@lego/plugin-dashboard/src/components/SidebarWidget.tsx +35 -0
  196. package/template/packages/plugins/@lego/plugin-dashboard/src/components/StatCard.tsx +47 -0
  197. package/template/packages/plugins/@lego/plugin-dashboard/src/global.css +24 -0
  198. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useChannelIntegration.ts +43 -0
  199. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useDashboardStats.ts +65 -0
  200. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/usePocketBase.ts +47 -0
  201. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useRecentActivity.ts +55 -0
  202. package/template/packages/plugins/@lego/plugin-dashboard/src/lib/utils.ts +6 -0
  203. package/template/packages/plugins/@lego/plugin-dashboard/src/pages/DashboardPage.tsx +105 -0
  204. package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.config.ts +121 -0
  205. package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.ts +18 -0
  206. package/template/packages/plugins/@lego/plugin-dashboard/src/vite-env.d.ts +32 -0
  207. package/template/packages/plugins/@lego/plugin-dashboard/tailwind.config.ts +35 -0
  208. package/template/packages/plugins/@lego/plugin-dashboard/tsconfig.json +18 -0
  209. package/template/packages/plugins/@lego/plugin-todo/modern.config.ts +18 -0
  210. package/template/packages/plugins/@lego/plugin-todo/package.json +41 -0
  211. package/template/packages/plugins/@lego/plugin-todo/postcss.config.mjs +6 -0
  212. package/template/packages/plugins/@lego/plugin-todo/src/App.tsx +12 -0
  213. package/template/packages/plugins/@lego/plugin-todo/src/components/SidebarWidget.tsx +16 -0
  214. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoDialog.tsx +55 -0
  215. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoFilters.tsx +79 -0
  216. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoForm.tsx +94 -0
  217. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoItem.tsx +121 -0
  218. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoList.tsx +41 -0
  219. package/template/packages/plugins/@lego/plugin-todo/src/components/index.ts +6 -0
  220. package/template/packages/plugins/@lego/plugin-todo/src/global.css +59 -0
  221. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useCreateTodo.ts +62 -0
  222. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useDeleteTodo.ts +46 -0
  223. package/template/packages/plugins/@lego/plugin-todo/src/hooks/usePocketBase.ts +38 -0
  224. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useTodos.ts +64 -0
  225. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useUpdateTodo.ts +35 -0
  226. package/template/packages/plugins/@lego/plugin-todo/src/index.tsx +5 -0
  227. package/template/packages/plugins/@lego/plugin-todo/src/lib/utils.ts +20 -0
  228. package/template/packages/plugins/@lego/plugin-todo/src/pages/TodoPage.tsx +89 -0
  229. package/template/packages/plugins/@lego/plugin-todo/src/plugin.config.ts +104 -0
  230. package/template/packages/plugins/@lego/plugin-todo/src/plugin.ts +13 -0
  231. package/template/packages/plugins/@lego/plugin-todo/src/schemas.ts +37 -0
  232. package/template/packages/plugins/@lego/plugin-todo/src/types.ts +42 -0
  233. package/template/packages/plugins/@lego/plugin-todo/src/vite-env.d.ts +31 -0
  234. package/template/packages/plugins/@lego/plugin-todo/tailwind.config.ts +51 -0
  235. package/template/packages/plugins/@lego/plugin-todo/tsconfig.json +18 -0
  236. package/template/pnpm-workspace.yaml +4 -0
  237. package/template/pocketbase/CHANGELOG.md +911 -0
  238. package/template/pocketbase/LICENSE.md +17 -0
  239. package/template/scripts/create-plugin.js +221 -0
  240. package/template/scripts/deploy.sh +56 -0
  241. package/template/tsconfig.base.json +26 -0
  242. package/template/tsconfig.json +8 -0
@@ -0,0 +1,1343 @@
1
+ # Todo Plugin Implementation Plan
2
+
3
+ > **For AI Implementing This Plan:** This is document 10 of 13. Complete documents 01-09 first.
4
+
5
+ **Goal:** Implement the Todo plugin with full CRUD functionality (Create, Read, Update, Delete), demonstrating a complete data-driven plugin with PocketBase integration.
6
+
7
+ **Architecture:** Todo plugin is a Modern.js app registered as a Garfish sub-app. It manages todo items stored in PocketBase with full multi-tenancy support. Uses TanStack Query for data fetching and mutations.
8
+
9
+ **Tech Stack:** Modern.js, React, TypeScript, TanStack Query, PocketBase, Zod validation, Tailwind CSS
10
+
11
+ ---
12
+
13
+ ## Prerequisites
14
+
15
+ - ✅ Completed `01-infrastructure-setup.md`
16
+ - ✅ Completed `02-pocketbase-setup.md` (todos collection)
17
+ - ✅ Completed `03-host-kernel.md` through `09-dashboard-plugin.md`
18
+
19
+ ---
20
+
21
+ ## Task 1: Create Todo Plugin Structure
22
+
23
+ **Files:**
24
+ - Create: `packages/plugins/@lego/plugin-todo/package.json`
25
+ - Create: `packages/plugins/@lego/plugin-todo/tsconfig.json`
26
+ - Create: `packages/plugins/@lego/plugin-todo/modern.config.ts`
27
+ - Create: `packages/plugins/@lego/plugin-todo/.gitignore`
28
+
29
+ ### Step 1: Create package.json
30
+
31
+ **File:** `packages/plugins/@lego/plugin-todo/package.json`
32
+
33
+ ```json
34
+ {
35
+ "name": "@lego/plugin-todo",
36
+ "version": "1.0.0",
37
+ "private": true,
38
+ "type": "module",
39
+ "scripts": {
40
+ "dev": "modern dev",
41
+ "build": "modern build",
42
+ "start": "modern start",
43
+ "lint": "eslint src --ext .ts,.tsx",
44
+ "typecheck": "tsc --noEmit"
45
+ },
46
+ "dependencies": {
47
+ "@garfish/hooks": "^1.22.0",
48
+ "@garfish/router": "^1.22.0",
49
+ "@garfish/react-scope": "^1.22.0",
50
+ "@hookform/resolvers": "^3.9.0",
51
+ "@modern-js/runtime": "^2.60.0",
52
+ "@modern-js/runtime/garfish": "^2.60.0",
53
+ "@radix-ui/react-dialog": "^1.1.2",
54
+ "@radix-ui/react-label": "^2.1.0",
55
+ "@radix-ui/react-slot": "^1.1.0",
56
+ "@tanstack/react-query": "^5.59.0",
57
+ "class-variance-authority": "^0.7.0",
58
+ "clsx": "^2.1.1",
59
+ "lucide-react": "^0.454.0",
60
+ "pocketbase": "^0.21.5",
61
+ "react": "^18.3.1",
62
+ "react-dom": "^18.3.1",
63
+ "react-hook-form": "^7.53.0",
64
+ "tailwind-merge": "^2.5.4",
65
+ "zod": "^3.23.8"
66
+ },
67
+ "devDependencies": {
68
+ "@modern-js/app-tools": "^2.60.0",
69
+ "@modern-js/plugin-garfish": "^2.60.0",
70
+ "@types/react": "^18.3.12",
71
+ "@types/react-dom": "^18.3.1",
72
+ "autoprefixer": "^10.4.20",
73
+ "postcss": "^8.4.49",
74
+ "tailwindcss": "^3.4.15",
75
+ "typescript": "^5.6.3"
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### Step 2: Create tsconfig.json
81
+
82
+ **File:** `packages/plugins/@lego/plugin-todo/tsconfig.json`
83
+
84
+ ```json
85
+ {
86
+ "extends": "@modern-js/tsconfig/base.json",
87
+ "compilerOptions": {
88
+ "jsx": "react-jsx",
89
+ "strict": true,
90
+ "esModuleInterop": true,
91
+ "skipLibCheck": true,
92
+ "moduleResolution": "bundler",
93
+ "resolveJsonModule": true,
94
+ "isolatedModules": true,
95
+ "paths": {
96
+ "@/*": ["./src/*"]
97
+ }
98
+ },
99
+ "include": ["src"],
100
+ "exclude": ["node_modules", "dist"]
101
+ }
102
+ ```
103
+
104
+ ### Step 3: Create modern.config.ts
105
+
106
+ **File:** `packages/plugins/@lego/plugin-todo/modern.config.ts`
107
+
108
+ ```typescript
109
+ import { appTools, defineConfig } from '@modern-js/app-tools';
110
+ import { garfishPlugin } from '@modern-js/plugin-garfish';
111
+
112
+ export default defineConfig({
113
+ dev: {
114
+ port: 3002,
115
+ },
116
+
117
+ runtime: {
118
+ router: true,
119
+ },
120
+
121
+ deploy: {
122
+ microFrontend: true,
123
+ },
124
+
125
+ plugins: [appTools(), garfishPlugin()],
126
+ });
127
+ ```
128
+
129
+ ### Step 4: Create .gitignore
130
+
131
+ **File:** `packages/plugins/@lego/plugin-todo/.gitignore`
132
+
133
+ ```
134
+ node_modules/
135
+ dist/
136
+ .modern/
137
+ *.local
138
+ .DS_Store
139
+ ```
140
+
141
+ ### Step 5: Install dependencies
142
+
143
+ **Run:** From root directory
144
+
145
+ ```bash
146
+ pnpm install
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Task 2: Create Todo Types and Schemas
152
+
153
+ **Files:**
154
+ - Create: `packages/plugins/@lego/plugin-todo/src/types.ts`
155
+ - Create: `packages/plugins/@lego/plugin-todo/src/schemas.ts`
156
+
157
+ ### Step 1: Create todo types
158
+
159
+ **File:** `packages/plugins/@lego/plugin-todo/src/types.ts`
160
+
161
+ ```typescript
162
+ import type { Record } from 'pocketbase';
163
+
164
+ /**
165
+ * Todo item from PocketBase
166
+ */
167
+ export interface Todo {
168
+ id: string;
169
+ title: string;
170
+ description?: string;
171
+ completed: boolean;
172
+ priority: 'low' | 'medium' | 'high';
173
+ dueDate?: string;
174
+ organizationId: string;
175
+ ownerId: string;
176
+ created: string;
177
+ updated: string;
178
+ }
179
+
180
+ /**
181
+ * Todo form data
182
+ */
183
+ export interface TodoFormData {
184
+ title: string;
185
+ description?: string;
186
+ priority: 'low' | 'medium' | 'high';
187
+ dueDate?: string;
188
+ }
189
+
190
+ /**
191
+ * Todo filter options
192
+ */
193
+ export interface TodoFilters {
194
+ status?: 'all' | 'active' | 'completed';
195
+ priority?: 'all' | 'low' | 'medium' | 'high';
196
+ search?: string;
197
+ }
198
+
199
+ /**
200
+ * Todo list item with expand
201
+ */
202
+ export interface TodoWithOwner extends Todo {
203
+ ownerName?: string;
204
+ ownerEmail?: string;
205
+ }
206
+ ```
207
+
208
+ ### Step 2: Create Zod schemas
209
+
210
+ **File:** `packages/plugins/@lego/plugin-todo/src/schemas.ts`
211
+
212
+ ```typescript
213
+ import { z } from 'zod';
214
+
215
+ /**
216
+ * Todo form validation schema
217
+ */
218
+ export const todoFormSchema = z.object({
219
+ title: z.string()
220
+ .min(1, 'Title is required')
221
+ .max(200, 'Title must be less than 200 characters'),
222
+ description: z.string()
223
+ .max(1000, 'Description must be less than 1000 characters')
224
+ .optional(),
225
+ priority: z.enum(['low', 'medium', 'high'], {
226
+ required_error: 'Priority is required',
227
+ }),
228
+ dueDate: z.string().optional(),
229
+ });
230
+
231
+ export type TodoFormInput = z.infer<typeof todoFormSchema>;
232
+
233
+ /**
234
+ * Todo update schema (all fields optional)
235
+ */
236
+ export const todoUpdateSchema = z.object({
237
+ title: z.string()
238
+ .min(1, 'Title is required')
239
+ .max(200, 'Title must be less than 200 characters')
240
+ .optional(),
241
+ description: z.string()
242
+ .max(1000, 'Description must be less than 1000 characters')
243
+ .optional(),
244
+ completed: z.boolean().optional(),
245
+ priority: z.enum(['low', 'medium', 'high']).optional(),
246
+ dueDate: z.string().optional(),
247
+ });
248
+
249
+ export type TodoUpdateInput = z.infer<typeof todoUpdateSchema>;
250
+ ```
251
+
252
+ ---
253
+
254
+ ## Task 3: Create Plugin Configuration
255
+
256
+ **Files:**
257
+ - Create: `packages/plugins/@lego/plugin-todo/src/plugin.config.ts`
258
+ - Create: `packages/plugins/@lego/plugin-todo/src/plugin.ts`
259
+
260
+ ### Step 1: Create plugin config
261
+
262
+ **File:** `packages/plugins/@lego/plugin-todo/src/plugin.config.ts`
263
+
264
+ ```typescript
265
+ import type { PluginConfig } from '@lego/kernel/plugins';
266
+ import { SidebarWidget } from './components/SidebarWidget';
267
+
268
+ /**
269
+ * Todo plugin configuration
270
+ */
271
+ export const pluginConfig: PluginConfig = {
272
+ manifest: {
273
+ name: '@lego/plugin-todo',
274
+ version: '1.0.0',
275
+ displayName: 'Todo',
276
+ description: 'Task management with full CRUD functionality',
277
+ author: 'Lego-One',
278
+ permissions: ['todos.manage'],
279
+ },
280
+
281
+ enabled: true,
282
+
283
+ slots: [
284
+ {
285
+ slot: 'sidebar:nav',
286
+ component: SidebarWidget,
287
+ order: 60, // After Dashboard
288
+ props: {
289
+ to: '/todos',
290
+ icon: 'CheckSquare',
291
+ label: 'Todos',
292
+ },
293
+ },
294
+ ],
295
+
296
+ routes: [
297
+ {
298
+ path: '/todos',
299
+ component: () => import('./pages/TodoPage').then(m => m.default),
300
+ protected: true,
301
+ permissions: ['todos.read'],
302
+ },
303
+ ],
304
+
305
+ settings: [
306
+ {
307
+ key: 'defaultPriority',
308
+ type: 'select',
309
+ label: 'Default Priority',
310
+ description: 'Default priority for new todos',
311
+ defaultValue: 'medium',
312
+ options: [
313
+ { label: 'Low', value: 'low' },
314
+ { label: 'Medium', value: 'medium' },
315
+ { label: 'High', value: 'high' },
316
+ ],
317
+ },
318
+ {
319
+ key: 'showCompleted',
320
+ type: 'boolean',
321
+ label: 'Show Completed',
322
+ description: 'Show completed todos in the list',
323
+ defaultValue: true,
324
+ },
325
+ ],
326
+ };
327
+ ```
328
+
329
+ ### Step 2: Create plugin entry point
330
+
331
+ **File:** `packages/plugins/@lego/plugin-todo/src/plugin.ts`
332
+
333
+ ```typescript
334
+ /**
335
+ * Plugin entry point
336
+ */
337
+ import { pluginConfig } from './plugin.config';
338
+ import TodoApp from './App';
339
+
340
+ export default {
341
+ config: pluginConfig,
342
+ App: TodoApp,
343
+ };
344
+
345
+ export { pluginConfig };
346
+ export { default as App } from './App';
347
+ ```
348
+
349
+ ---
350
+
351
+ ## Task 4: Create Todo Hooks (PocketBase Integration)
352
+
353
+ **Files:**
354
+ - Create: `packages/plugins/@lego/plugin-todo/src/hooks/usePocketBase.ts`
355
+ - Create: `packages/plugins/@lego/plugin-todo/src/hooks/useTodos.ts`
356
+ - Create: `packages/plugins/@lego/plugin-todo/src/hooks/useCreateTodo.ts`
357
+ - Create: `packages/plugins/@lego/plugin-todo/src/hooks/useUpdateTodo.ts`
358
+ - Create: `packages/plugins/@lego/plugin-todo/src/hooks/useDeleteTodo.ts`
359
+
360
+ ### Step 1: Create PocketBase hook
361
+
362
+ **File:** `packages/plugins/@lego/plugin-todo/src/hooks/usePocketBase.ts`
363
+
364
+ ```typescript
365
+ import { useEffect, useState } from 'react';
366
+
367
+ export function usePocketBase() {
368
+ const [pb, setPb] = useState<any>(null);
369
+
370
+ useEffect(() => {
371
+ const kernelState = (window as any).__LEGO_KERNEL_STATE__;
372
+
373
+ if (!kernelState) {
374
+ console.error('[Todo] Kernel state bridge not found');
375
+ return;
376
+ }
377
+
378
+ const pbUrl = import.meta.env.VITE_POCKETBASE_URL || 'http://127.0.0.1:8090';
379
+ const PocketBase = require('pocketbase').default;
380
+ const client = new PocketBase(pbUrl);
381
+
382
+ // Sync auth token
383
+ const state = kernelState.useGlobalKernelState.getState();
384
+ if (state.token) {
385
+ client.authStore.save(state.token, null);
386
+ }
387
+
388
+ const unsubscribe = kernelState.useGlobalKernelState.subscribe((newState: any) => {
389
+ if (newState.token && newState.token !== client.authStore.token) {
390
+ client.authStore.save(newState.token, null);
391
+ }
392
+ });
393
+
394
+ setPb(client);
395
+
396
+ return () => {
397
+ unsubscribe();
398
+ };
399
+ }, []);
400
+
401
+ return pb;
402
+ }
403
+ ```
404
+
405
+ ### Step 2: Create useTodos hook
406
+
407
+ **File:** `packages/plugins/@lego/plugin-todo/src/hooks/useTodos.ts`
408
+
409
+ ```typescript
410
+ import { useQuery } from '@tanstack/react-query';
411
+ import { usePocketBase } from './usePocketBase';
412
+ import type { TodoWithOwner, TodoFilters } from '../types';
413
+
414
+ export function useTodos(filters: TodoFilters = {}) {
415
+ const pb = usePocketBase();
416
+
417
+ return useQuery({
418
+ queryKey: ['todos', filters],
419
+ queryFn: async (): Promise<TodoWithOwner[]> => {
420
+ if (!pb) {
421
+ throw new Error('PocketBase not initialized');
422
+ }
423
+
424
+ const kernelState = (window as any).__LEGO_KERNEL_STATE__;
425
+ const state = kernelState?.useGlobalKernelState?.getState();
426
+
427
+ if (!state?.organization?.id) {
428
+ return [];
429
+ }
430
+
431
+ const orgId = state.organization.id;
432
+
433
+ // Build filter
434
+ let filterParts: string[] = [`organizationId = "${orgId}"`];
435
+
436
+ if (filters.status === 'active') {
437
+ filterParts.push('completed = false');
438
+ } else if (filters.status === 'completed') {
439
+ filterParts.push('completed = true');
440
+ }
441
+
442
+ if (filters.priority && filters.priority !== 'all') {
443
+ filterParts.push(`priority = "${filters.priority}"`);
444
+ }
445
+
446
+ if (filters.search) {
447
+ filterParts.push(`title ~ "${filters.search}"`);
448
+ }
449
+
450
+ const result = await pb.collection('todos').getList(1, 50, {
451
+ filter: filterParts.join(' && '),
452
+ sort: filters.status === 'completed' ? '-updated,-created' : '-created',
453
+ expand: 'ownerId',
454
+ });
455
+
456
+ return result.items.map((item: any) => ({
457
+ id: item.id,
458
+ title: item.title,
459
+ description: item.description,
460
+ completed: item.completed,
461
+ priority: item.priority,
462
+ dueDate: item.dueDate,
463
+ organizationId: item.organizationId,
464
+ ownerId: item.ownerId,
465
+ created: item.created,
466
+ updated: item.updated,
467
+ ownerName: item.expand?.ownerId?.name || item.expand?.ownerId?.email,
468
+ ownerEmail: item.expand?.ownerId?.email,
469
+ }));
470
+ },
471
+ enabled: !!pb,
472
+ });
473
+ }
474
+ ```
475
+
476
+ ### Step 3: Create useCreateTodo hook
477
+
478
+ **File:** `packages/plugins/@lego/plugin-todo/src/hooks/useCreateTodo.ts`
479
+
480
+ ```typescript
481
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
482
+ import { usePocketBase } from './usePocketBase';
483
+ import type { TodoFormData } from '../types';
484
+ import { useToastChannel } from '@lego/kernel/channels';
485
+
486
+ export function useCreateTodo() {
487
+ const pb = usePocketBase();
488
+ const queryClient = useQueryClient();
489
+ const publishToast = useToastChannel();
490
+
491
+ return useMutation({
492
+ mutationFn: async (data: TodoFormData) => {
493
+ if (!pb) {
494
+ throw new Error('PocketBase not initialized');
495
+ }
496
+
497
+ const kernelState = (window as any).__LEGO_KERNEL_STATE__;
498
+ const state = kernelState?.useGlobalKernelState?.getState();
499
+
500
+ if (!state?.organization?.id) {
501
+ throw new Error('No organization selected');
502
+ }
503
+
504
+ const result = await pb.collection('todos').create({
505
+ title: data.title,
506
+ description: data.description || '',
507
+ completed: false,
508
+ priority: data.priority,
509
+ dueDate: data.dueDate || null,
510
+ organizationId: state.organization.id,
511
+ ownerId: state.user?.id,
512
+ });
513
+
514
+ return result;
515
+ },
516
+ onSuccess: () => {
517
+ queryClient.invalidateQueries({ queryKey: ['todos'] });
518
+ publishToast({
519
+ type: 'success',
520
+ title: 'Todo created',
521
+ description: 'Your todo has been created successfully',
522
+ });
523
+ },
524
+ onError: (error: any) => {
525
+ publishToast({
526
+ type: 'error',
527
+ title: 'Failed to create todo',
528
+ description: error.message || 'An error occurred',
529
+ });
530
+ },
531
+ });
532
+ }
533
+ ```
534
+
535
+ ### Step 4: Create useUpdateTodo hook
536
+
537
+ **File:** `packages/plugins/@lego/plugin-todo/src/hooks/useUpdateTodo.ts`
538
+
539
+ ```typescript
540
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
541
+ import { usePocketBase } from './usePocketBase';
542
+ import { useToastChannel } from '@lego/kernel/channels';
543
+
544
+ export function useUpdateTodo() {
545
+ const pb = usePocketBase();
546
+ const queryClient = useQueryClient();
547
+ const publishToast = useToastChannel();
548
+
549
+ return useMutation({
550
+ mutationFn: async ({ id, data }: { id: string; data: Partial<any> }) => {
551
+ if (!pb) {
552
+ throw new Error('PocketBase not initialized');
553
+ }
554
+
555
+ const result = await pb.collection('todos').update(id, data);
556
+
557
+ return result;
558
+ },
559
+ onSuccess: () => {
560
+ queryClient.invalidateQueries({ queryKey: ['todos'] });
561
+ },
562
+ onError: (error: any) => {
563
+ publishToast({
564
+ type: 'error',
565
+ title: 'Failed to update todo',
566
+ description: error.message || 'An error occurred',
567
+ });
568
+ },
569
+ });
570
+ }
571
+ ```
572
+
573
+ ### Step 5: Create useDeleteTodo hook
574
+
575
+ **File:** `packages/plugins/@lego/plugin-todo/src/hooks/useDeleteTodo.ts`
576
+
577
+ ```typescript
578
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
579
+ import { usePocketBase } from './usePocketBase';
580
+ import { useToastChannel } from '@lego/kernel/channels';
581
+
582
+ export function useDeleteTodo() {
583
+ const pb = usePocketBase();
584
+ const queryClient = useQueryClient();
585
+ const publishToast = useToastChannel();
586
+
587
+ return useMutation({
588
+ mutationFn: async (id: string) => {
589
+ if (!pb) {
590
+ throw new Error('PocketBase not initialized');
591
+ }
592
+
593
+ await pb.collection('todos').delete(id);
594
+
595
+ return id;
596
+ },
597
+ onSuccess: () => {
598
+ queryClient.invalidateQueries({ queryKey: ['todos'] });
599
+ publishToast({
600
+ type: 'success',
601
+ title: 'Todo deleted',
602
+ description: 'Your todo has been deleted',
603
+ });
604
+ },
605
+ onError: (error: any) => {
606
+ publishToast({
607
+ type: 'error',
608
+ title: 'Failed to delete todo',
609
+ description: error.message || 'An error occurred',
610
+ });
611
+ },
612
+ });
613
+ }
614
+ ```
615
+
616
+ ---
617
+
618
+ ## Task 5: Create Todo Components
619
+
620
+ **Files:**
621
+ - Create: `packages/plugins/@lego/plugin-todo/src/components/TodoList.tsx`
622
+ - Create: `packages/plugins/@lego/plugin-todo/src/components/TodoItem.tsx`
623
+ - Create: `packages/plugins/@lego/plugin-todo/src/components/TodoForm.tsx`
624
+ - Create: `packages/plugins/@lego/plugin-todo/src/components/TodoDialog.tsx`
625
+ - Create: `packages/plugins/@lego/plugin-todo/src/components/TodoFilters.tsx`
626
+ - Create: `packages/plugins/@lego/plugin-todo/src/components/SidebarWidget.tsx`
627
+
628
+ ### Step 1: Create todo list component
629
+
630
+ **File:** `packages/plugins/@lego/plugin-todo/src/components/TodoList.tsx`
631
+
632
+ ```typescript
633
+ import { TodoWithOwner } from '../types';
634
+ import { TodoItem } from './TodoItem';
635
+
636
+ interface TodoListProps {
637
+ todos: TodoWithOwner[];
638
+ isLoading?: boolean;
639
+ }
640
+
641
+ export function TodoList({ todos, isLoading }: TodoListProps) {
642
+ if (isLoading) {
643
+ return (
644
+ <div className="space-y-3">
645
+ {[1, 2, 3, 4, 5].map((i) => (
646
+ <div key={i} className="h-20 animate-pulse rounded-lg bg-muted" />
647
+ ))}
648
+ </div>
649
+ );
650
+ }
651
+
652
+ if (todos.length === 0) {
653
+ return (
654
+ <div className="flex flex-col items-center justify-center py-12 text-center">
655
+ <div className="flex h-16 w-16 items-center justify-center rounded-full bg-muted">
656
+ <span className="text-2xl">📝</span>
657
+ </div>
658
+ <h3 className="mt-4 text-lg font-semibold">No todos found</h3>
659
+ <p className="mt-2 text-sm text-muted-foreground">
660
+ Create your first todo to get started
661
+ </p>
662
+ </div>
663
+ );
664
+ }
665
+
666
+ return (
667
+ <div className="space-y-3">
668
+ {todos.map((todo) => (
669
+ <TodoItem key={todo.id} todo={todo} />
670
+ ))}
671
+ </div>
672
+ );
673
+ }
674
+ ```
675
+
676
+ ### Step 2: Create todo item component
677
+
678
+ **File:** `packages/plugins/@lego/plugin-todo/src/components/TodoItem.tsx`
679
+
680
+ ```typescript
681
+ import { useState } from 'react';
682
+ import { CheckSquare, Square, Trash2, Edit2, Calendar } from 'lucide-react';
683
+ import { TodoWithOwner } from '../types';
684
+ import { useUpdateTodo } from '../hooks/useUpdateTodo';
685
+ import { useDeleteTodo } from '../hooks/useDeleteTodo';
686
+ import { cn } from '@lego/kernel/lib/utils';
687
+ import { formatRelativeTime } from '@lego/kernel/lib/utils';
688
+
689
+ interface TodoItemProps {
690
+ todo: TodoWithOwner;
691
+ }
692
+
693
+ const priorityColors = {
694
+ low: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300',
695
+ medium: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300',
696
+ high: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300',
697
+ };
698
+
699
+ export function TodoItem({ todo }: TodoItemProps) {
700
+ const updateTodo = useUpdateTodo();
701
+ const deleteTodo = useDeleteTodo();
702
+ const [isEditing, setIsEditing] = useState(false);
703
+
704
+ const handleToggleComplete = () => {
705
+ updateTodo.mutate({
706
+ id: todo.id,
707
+ data: { completed: !todo.completed },
708
+ });
709
+ };
710
+
711
+ const handleDelete = () => {
712
+ if (confirm('Are you sure you want to delete this todo?')) {
713
+ deleteTodo.mutate(todo.id);
714
+ }
715
+ };
716
+
717
+ return (
718
+ <div
719
+ className={cn(
720
+ 'group rounded-lg border p-4 transition-colors hover:bg-muted/50',
721
+ todo.completed && 'opacity-60'
722
+ )}
723
+ >
724
+ <div className="flex items-start gap-3">
725
+ {/* Checkbox */}
726
+ <button
727
+ onClick={handleToggleComplete}
728
+ disabled={updateTodo.isPending}
729
+ className="mt-0.5 shrink-0"
730
+ >
731
+ {todo.completed ? (
732
+ <CheckSquare className="h-5 w-5 text-primary" />
733
+ ) : (
734
+ <Square className="h-5 w-5 text-muted-foreground" />
735
+ )}
736
+ </button>
737
+
738
+ {/* Content */}
739
+ <div className="flex-1 space-y-1">
740
+ <div className="flex items-start justify-between gap-2">
741
+ <h4
742
+ className={cn(
743
+ 'font-medium',
744
+ todo.completed && 'line-through text-muted-foreground'
745
+ )}
746
+ >
747
+ {todo.title}
748
+ </h4>
749
+ <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100">
750
+ <button
751
+ onClick={() => setIsEditing(true)}
752
+ className="rounded p-1 hover:bg-muted"
753
+ >
754
+ <Edit2 className="h-4 w-4" />
755
+ </button>
756
+ <button
757
+ onClick={handleDelete}
758
+ disabled={deleteTodo.isPending}
759
+ className="rounded p-1 hover:bg-destructive hover:text-destructive-foreground"
760
+ >
761
+ <Trash2 className="h-4 w-4" />
762
+ </button>
763
+ </div>
764
+ </div>
765
+
766
+ {todo.description && (
767
+ <p className="text-sm text-muted-foreground">{todo.description}</p>
768
+ )}
769
+
770
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
771
+ <span className={cn('rounded-full px-2 py-0.5', priorityColors[todo.priority])}>
772
+ {todo.priority}
773
+ </span>
774
+ {todo.dueDate && (
775
+ <span className="flex items-center gap-1">
776
+ <Calendar className="h-3 w-3" />
777
+ {formatRelativeTime(todo.dueDate)}
778
+ </span>
779
+ )}
780
+ {todo.ownerName && (
781
+ <span>Created by {todo.ownerName}</span>
782
+ )}
783
+ </div>
784
+ </div>
785
+ </div>
786
+
787
+ {/* Edit dialog would go here - for now using inline state */}
788
+ {isEditing && (
789
+ <div className="mt-4 pt-4 border-t">
790
+ <p className="text-sm text-muted-foreground">Edit functionality coming soon</p>
791
+ <button
792
+ onClick={() => setIsEditing(false)}
793
+ className="mt-2 text-sm text-primary hover:underline"
794
+ >
795
+ Close
796
+ </button>
797
+ </div>
798
+ )}
799
+ </div>
800
+ );
801
+ }
802
+ ```
803
+
804
+ ### Step 3: Create todo form component
805
+
806
+ **File:** `packages/plugins/@lego/plugin-todo/src/components/TodoForm.tsx`
807
+
808
+ ```typescript
809
+ import { useForm } from 'react-hook-form';
810
+ import { zodResolver } from '@hookform/resolvers/zod';
811
+ import { todoFormSchema, type TodoFormInput } from '../schemas';
812
+ import { Button } from '@lego/kernel/components';
813
+ import { Input } from '@lego/kernel/components';
814
+ import { Label } from '@lego/kernel/components';
815
+ import {
816
+ Select,
817
+ SelectContent,
818
+ SelectItem,
819
+ SelectTrigger,
820
+ SelectValue,
821
+ } from '@lego/kernel/components';
822
+
823
+ interface TodoFormProps {
824
+ onSubmit: (data: TodoFormInput) => void;
825
+ isLoading?: boolean;
826
+ }
827
+
828
+ export function TodoForm({ onSubmit, isLoading }: TodoFormProps) {
829
+ const {
830
+ register,
831
+ handleSubmit,
832
+ setValue,
833
+ formState: { errors },
834
+ } = useForm<TodoFormInput>({
835
+ resolver: zodResolver(todoFormSchema),
836
+ defaultValues: {
837
+ priority: 'medium',
838
+ },
839
+ });
840
+
841
+ return (
842
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
843
+ <div className="space-y-2">
844
+ <Label htmlFor="title">Title *</Label>
845
+ <Input
846
+ id="title"
847
+ placeholder="What needs to be done?"
848
+ {...register('title')}
849
+ />
850
+ {errors.title && (
851
+ <p className="text-sm text-destructive">{errors.title.message}</p>
852
+ )}
853
+ </div>
854
+
855
+ <div className="space-y-2">
856
+ <Label htmlFor="description">Description</Label>
857
+ <Input
858
+ id="description"
859
+ placeholder="Add details (optional)"
860
+ {...register('description')}
861
+ />
862
+ {errors.description && (
863
+ <p className="text-sm text-destructive">{errors.description.message}</p>
864
+ )}
865
+ </div>
866
+
867
+ <div className="space-y-2">
868
+ <Label htmlFor="priority">Priority *</Label>
869
+ <Select
870
+ onValueChange={(value) => setValue('priority', value as any)}
871
+ defaultValue="medium"
872
+ >
873
+ <SelectTrigger>
874
+ <SelectValue placeholder="Select priority" />
875
+ </SelectTrigger>
876
+ <SelectContent>
877
+ <SelectItem value="low">Low</SelectItem>
878
+ <SelectItem value="medium">Medium</SelectItem>
879
+ <SelectItem value="high">High</SelectItem>
880
+ </SelectContent>
881
+ </Select>
882
+ {errors.priority && (
883
+ <p className="text-sm text-destructive">{errors.priority.message}</p>
884
+ )}
885
+ </div>
886
+
887
+ <div className="space-y-2">
888
+ <Label htmlFor="dueDate">Due Date</Label>
889
+ <Input
890
+ id="dueDate"
891
+ type="date"
892
+ {...register('dueDate')}
893
+ />
894
+ </div>
895
+
896
+ <Button type="submit" className="w-full" disabled={isLoading}>
897
+ {isLoading ? 'Creating...' : 'Create Todo'}
898
+ </Button>
899
+ </form>
900
+ );
901
+ }
902
+ ```
903
+
904
+ ### Step 4: Create todo dialog component
905
+
906
+ **File:** `packages/plugins/@lego/plugin-todo/src/components/TodoDialog.tsx`
907
+
908
+ ```typescript
909
+ import { useState } from 'react';
910
+ import { Plus } from 'lucide-react';
911
+ import { Button } from '@lego/kernel/components';
912
+ import {
913
+ Dialog,
914
+ DialogContent,
915
+ DialogDescription,
916
+ DialogHeader,
917
+ DialogTitle,
918
+ DialogTrigger,
919
+ } from '@lego/kernel/components';
920
+ import { TodoForm } from './TodoForm';
921
+ import { useCreateTodo } from '../hooks/useCreateTodo';
922
+ import type { TodoFormInput } from '../schemas';
923
+
924
+ export function TodoDialog() {
925
+ const [open, setOpen] = useState(false);
926
+ const createTodo = useCreateTodo();
927
+
928
+ const handleSubmit = (data: TodoFormInput) => {
929
+ createTodo.mutate(data, {
930
+ onSuccess: () => {
931
+ setOpen(false);
932
+ },
933
+ });
934
+ };
935
+
936
+ return (
937
+ <Dialog open={open} onOpenChange={setOpen}>
938
+ <DialogTrigger asChild>
939
+ <Button>
940
+ <Plus className="mr-2 h-4 w-4" />
941
+ New Todo
942
+ </Button>
943
+ </DialogTrigger>
944
+ <DialogContent>
945
+ <DialogHeader>
946
+ <DialogTitle>Create Todo</DialogTitle>
947
+ <DialogDescription>
948
+ Add a new todo item to your list
949
+ </DialogDescription>
950
+ </DialogHeader>
951
+ <TodoForm onSubmit={handleSubmit} isLoading={createTodo.isPending} />
952
+ </DialogContent>
953
+ </Dialog>
954
+ );
955
+ }
956
+ ```
957
+
958
+ ### Step 5: Create todo filters component
959
+
960
+ **File:** `packages/plugins/@lego/plugin-todo/src/components/TodoFilters.tsx`
961
+
962
+ ```typescript
963
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@lego/kernel/components';
964
+ import { Input } from '@lego/kernel/components';
965
+ import { Label } from '@lego/kernel/components';
966
+ import type { TodoFilters } from '../types';
967
+
968
+ interface TodoFiltersProps {
969
+ filters: TodoFilters;
970
+ onFiltersChange: (filters: TodoFilters) => void;
971
+ }
972
+
973
+ export function TodoFilters({ filters, onFiltersChange }: TodoFiltersProps) {
974
+ return (
975
+ <div className="flex flex-wrap items-center gap-4">
976
+ <div className="flex items-center gap-2">
977
+ <Label htmlFor="status">Status</Label>
978
+ <Select
979
+ value={filters.status || 'all'}
980
+ onValueChange={(value) =>
981
+ onFiltersChange({ ...filters, status: value as any })
982
+ }
983
+ >
984
+ <SelectTrigger id="status" className="w-32">
985
+ <SelectValue />
986
+ </SelectTrigger>
987
+ <SelectContent>
988
+ <SelectItem value="all">All</SelectItem>
989
+ <SelectItem value="active">Active</SelectItem>
990
+ <SelectItem value="completed">Completed</SelectItem>
991
+ </SelectContent>
992
+ </Select>
993
+ </div>
994
+
995
+ <div className="flex items-center gap-2">
996
+ <Label htmlFor="priority">Priority</Label>
997
+ <Select
998
+ value={filters.priority || 'all'}
999
+ onValueChange={(value) =>
1000
+ onFiltersChange({ ...filters, priority: value as any })
1001
+ }
1002
+ >
1003
+ <SelectTrigger id="priority" className="w-32">
1004
+ <SelectValue />
1005
+ </SelectTrigger>
1006
+ <SelectContent>
1007
+ <SelectItem value="all">All</SelectItem>
1008
+ <SelectItem value="low">Low</SelectItem>
1009
+ <SelectItem value="medium">Medium</SelectItem>
1010
+ <SelectItem value="high">High</SelectItem>
1011
+ </SelectContent>
1012
+ </Select>
1013
+ </div>
1014
+
1015
+ <div className="flex items-center gap-2">
1016
+ <Label htmlFor="search">Search</Label>
1017
+ <Input
1018
+ id="search"
1019
+ placeholder="Search todos..."
1020
+ value={filters.search || ''}
1021
+ onChange={(e) =>
1022
+ onFiltersChange({ ...filters, search: e.target.value })
1023
+ }
1024
+ className="w-64"
1025
+ />
1026
+ </div>
1027
+ </div>
1028
+ );
1029
+ }
1030
+ ```
1031
+
1032
+ ### Step 6: Create sidebar widget
1033
+
1034
+ **File:** `packages/plugins/@lego/plugin-todo/src/components/SidebarWidget.tsx`
1035
+
1036
+ ```typescript
1037
+ import { NavLink } from '@modern-js/runtime/router';
1038
+ import { cn } from '@lego/kernel/lib/utils';
1039
+ import { CheckSquare } from 'lucide-react';
1040
+
1041
+ interface SidebarWidgetProps {
1042
+ to: string;
1043
+ icon: string;
1044
+ label: string;
1045
+ }
1046
+
1047
+ export function SidebarWidget({ to, label }: SidebarWidgetProps) {
1048
+ return (
1049
+ <NavLink
1050
+ to={to}
1051
+ className={({ isActive }) =>
1052
+ cn(
1053
+ 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
1054
+ isActive
1055
+ ? 'bg-primary text-primary-foreground'
1056
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'
1057
+ )
1058
+ }
1059
+ >
1060
+ <CheckSquare className="h-5 w-5" />
1061
+ <span>{label}</span>
1062
+ </NavLink>
1063
+ );
1064
+ }
1065
+ ```
1066
+
1067
+ ---
1068
+
1069
+ ## Task 6: Create Todo Page
1070
+
1071
+ **Files:**
1072
+ - Create: `packages/plugins/@lego/plugin-todo/src/pages/TodoPage.tsx`
1073
+ - Create: `packages/plugins/@lego/plugin-todo/src/App.tsx`
1074
+ - Create: `packages/plugins/@lego/plugin-todo/src/global.css`
1075
+
1076
+ ### Step 1: Create todo page
1077
+
1078
+ **File:** `packages/plugins/@lego/plugin-todo/src/pages/TodoPage.tsx`
1079
+
1080
+ ```typescript
1081
+ import { useState } from 'react';
1082
+ import { useTodos } from '../hooks/useTodos';
1083
+ import { TodoList } from '../components/TodoList';
1084
+ import { TodoDialog } from '../components/TodoDialog';
1085
+ import { TodoFilters } from '../components/TodoFilters';
1086
+ import type { TodoFilters as TodoFiltersType } from '../types';
1087
+ import { usePluginReady } from '@lego/kernel/channels';
1088
+
1089
+ export default function TodoPage() {
1090
+ const [filters, setFilters] = useState<TodoFiltersType>({
1091
+ status: 'all',
1092
+ priority: 'all',
1093
+ });
1094
+
1095
+ const { data: todos, isLoading } = useTodos(filters);
1096
+
1097
+ // Notify host that plugin is ready
1098
+ usePluginReady('@lego/plugin-todo', '1.0.0');
1099
+
1100
+ return (
1101
+ <div className="space-y-6">
1102
+ {/* Header */}
1103
+ <div className="flex items-center justify-between">
1104
+ <div>
1105
+ <h1 className="text-3xl font-bold tracking-tight">Todos</h1>
1106
+ <p className="text-muted-foreground">
1107
+ Manage your tasks and stay organized
1108
+ </p>
1109
+ </div>
1110
+ <TodoDialog />
1111
+ </div>
1112
+
1113
+ {/* Filters */}
1114
+ <TodoFilters filters={filters} onFiltersChange={setFilters} />
1115
+
1116
+ {/* Todo List */}
1117
+ <TodoList todos={todos || []} isLoading={isLoading} />
1118
+ </div>
1119
+ );
1120
+ }
1121
+ ```
1122
+
1123
+ ### Step 2: Create app component
1124
+
1125
+ **File:** `packages/plugins/@lego/plugin-todo/src/App.tsx`
1126
+
1127
+ ```typescript
1128
+ import './global.css';
1129
+
1130
+ interface TodoAppProps {
1131
+ basename?: string;
1132
+ }
1133
+
1134
+ export default function TodoApp({ basename }: TodoAppProps) {
1135
+ return (
1136
+ <div className="todo-plugin">
1137
+ {/* Routes handled by host */}
1138
+ </div>
1139
+ );
1140
+ }
1141
+ ```
1142
+
1143
+ ### Step 3: Create global CSS
1144
+
1145
+ **File:** `packages/plugins/@lego/plugin-todo/src/global.css`
1146
+
1147
+ ```css
1148
+ @tailwind base;
1149
+ @tailwind components;
1150
+ @tailwind utilities;
1151
+
1152
+ @layer base {
1153
+ :root {
1154
+ --background: 0 0% 100%;
1155
+ --foreground: 222.2 84% 4.9%;
1156
+ }
1157
+
1158
+ .dark {
1159
+ --background: 222.2 84% 4.9%;
1160
+ --foreground: 210 40% 98%;
1161
+ }
1162
+ }
1163
+
1164
+ @layer base {
1165
+ * {
1166
+ @apply border-border;
1167
+ }
1168
+ body {
1169
+ @apply bg-background text-foreground;
1170
+ }
1171
+ }
1172
+ ```
1173
+
1174
+ ---
1175
+
1176
+ ## Task 7: Create Tailwind Config
1177
+
1178
+ **Files:**
1179
+ - Create: `packages/plugins/@lego/plugin-todo/tailwind.config.ts`
1180
+ - Create: `packages/plugins/@lego/plugin-todo/postcss.config.js`
1181
+
1182
+ ### Step 1: Create Tailwind config
1183
+
1184
+ **File:** `packages/plugins/@lego/plugin-todo/tailwind.config.ts`
1185
+
1186
+ ```typescript
1187
+ import type { Config } from 'tailwindcss';
1188
+
1189
+ const config: Config = {
1190
+ darkMode: ['class'],
1191
+ content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
1192
+ theme: {
1193
+ extend: {
1194
+ colors: {
1195
+ border: 'hsl(var(--border))',
1196
+ background: 'hsl(var(--background))',
1197
+ foreground: 'hsl(var(--foreground))',
1198
+ primary: {
1199
+ DEFAULT: 'hsl(var(--primary))',
1200
+ foreground: 'hsl(var(--primary-foreground))',
1201
+ },
1202
+ muted: {
1203
+ DEFAULT: 'hsl(var(--muted))',
1204
+ foreground: 'hsl(var(--muted-foreground))',
1205
+ },
1206
+ card: {
1207
+ DEFAULT: 'hsl(var(--card))',
1208
+ foreground: 'hsl(var(--card-foreground))',
1209
+ },
1210
+ destructive: {
1211
+ DEFAULT: 'hsl(var(--destructive))',
1212
+ foreground: 'hsl(var(--destructive-foreground))',
1213
+ },
1214
+ },
1215
+ borderRadius: {
1216
+ lg: 'var(--radius)',
1217
+ },
1218
+ },
1219
+ },
1220
+ plugins: [],
1221
+ };
1222
+
1223
+ export default config;
1224
+ ```
1225
+
1226
+ ### Step 2: Create PostCSS config
1227
+
1228
+ **File:** `packages/plugins/@lego/plugin-todo/postcss.config.js`
1229
+
1230
+ ```javascript
1231
+ module.exports = {
1232
+ plugins: {
1233
+ tailwindcss: {},
1234
+ autoprefixer: {},
1235
+ },
1236
+ };
1237
+ ```
1238
+
1239
+ ---
1240
+
1241
+ ## Verification
1242
+
1243
+ ### Step 1: Build the todo plugin
1244
+
1245
+ **Run:**
1246
+
1247
+ ```bash
1248
+ cd packages/plugins/@lego/plugin-todo
1249
+ pnpm run build
1250
+ ```
1251
+
1252
+ Expected: Build completes without errors.
1253
+
1254
+ ### Step 2: Start todo plugin dev server
1255
+
1256
+ **Run:**
1257
+
1258
+ ```bash
1259
+ cd packages/plugins/@lego/plugin-todo
1260
+ pnpm run dev
1261
+ ```
1262
+
1263
+ Expected: Server starts on http://localhost:3002
1264
+
1265
+ ### Step 3: Start host dev server (separate terminal)
1266
+
1267
+ **Run:**
1268
+
1269
+ ```bash
1270
+ cd host
1271
+ pnpm run dev
1272
+ ```
1273
+
1274
+ Expected: Server starts on http://localhost:8080
1275
+
1276
+ ### Step 4: Test todo plugin
1277
+
1278
+ 1. Open http://localhost:8080
1279
+ 2. Login with admin credentials
1280
+ 3. Navigate to /todos
1281
+ 4. Should see:
1282
+ - Todos page with filters
1283
+ - "New Todo" button
1284
+ - Todo list (empty initially)
1285
+ 5. Click "New Todo" and create a todo
1286
+ 6. Verify todo appears in list
1287
+ 7. Test completing a todo (click checkbox)
1288
+ 8. Test deleting a todo
1289
+ 9. Test filters (status, priority, search)
1290
+
1291
+ ---
1292
+
1293
+ ## Summary
1294
+
1295
+ After completing this document, you will have:
1296
+
1297
+ 1. ✅ Complete Todo plugin as Modern.js + Garfish sub-app
1298
+ 2. ✅ Full CRUD operations for todos
1299
+ 3. ✅ PocketBase integration with multi-tenancy
1300
+ 4. ✅ Form validation with Zod
1301
+ 5. ✅ Todo filters (status, priority, search)
1302
+ 6. ✅ Create todo dialog with form
1303
+ 7. ✅ Todo list with items (toggle, delete)
1304
+ 8. ✅ Channel integration (toasts, ready event)
1305
+ 9. ✅ Independent dev server (:3002)
1306
+
1307
+ **Next:** `11-testing.md` - Implement unit, component, and E2E tests with Vitest and Playwright.
1308
+
1309
+ ---
1310
+
1311
+ ## Files Created
1312
+
1313
+ ```
1314
+ packages/plugins/@lego/plugin-todo/
1315
+ ├── package.json
1316
+ ├── tsconfig.json
1317
+ ├── modern.config.ts
1318
+ ├── tailwind.config.ts
1319
+ ├── postcss.config.js
1320
+ ├── .gitignore
1321
+ └── src/
1322
+ ├── global.css
1323
+ ├── plugin.ts
1324
+ ├── plugin.config.ts
1325
+ ├── types.ts
1326
+ ├── schemas.ts
1327
+ ├── App.tsx
1328
+ ├── pages/
1329
+ │ └── TodoPage.tsx
1330
+ ├── components/
1331
+ │ ├── TodoList.tsx
1332
+ │ ├── TodoItem.tsx
1333
+ │ ├── TodoForm.tsx
1334
+ │ ├── TodoDialog.tsx
1335
+ │ ├── TodoFilters.tsx
1336
+ │ └── SidebarWidget.tsx
1337
+ └── hooks/
1338
+ ├── usePocketBase.ts
1339
+ ├── useTodos.ts
1340
+ ├── useCreateTodo.ts
1341
+ ├── useUpdateTodo.ts
1342
+ └── useDeleteTodo.ts
1343
+ ```