miolo 3.0.0-beta.21 → 3.0.0-beta.210

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 (246) hide show
  1. package/bin/build/build.mjs +53 -0
  2. package/bin/build/build_bin.mjs +17 -0
  3. package/bin/build/cli/client.mjs +52 -0
  4. package/bin/build/cli/css.mjs +22 -0
  5. package/bin/build/cli/index.mjs +40 -0
  6. package/bin/build/cli/ssr.mjs +21 -0
  7. package/bin/build/server/aliases.mjs +112 -0
  8. package/bin/build/server/babel.config.js +24 -0
  9. package/bin/build/server/banner.mjs +20 -0
  10. package/bin/build/server/bundle.mjs +111 -0
  11. package/bin/build/server/fix.mjs +15 -0
  12. package/bin/build/server/index.mjs +69 -0
  13. package/bin/build/server/options.mjs +83 -0
  14. package/bin/create/auth.mjs +23 -0
  15. package/bin/create/copy.mjs +175 -0
  16. package/bin/create/docker.mjs +25 -0
  17. package/bin/create/index.mjs +137 -0
  18. package/bin/create/pkgjson.mjs +72 -0
  19. package/bin/create/prepare-template.mjs +158 -0
  20. package/bin/create/validation.mjs +27 -0
  21. package/bin/dev/dev.mjs +32 -23
  22. package/bin/dev/dev_start.mjs +6 -5
  23. package/bin/index.mjs +94 -52
  24. package/bin/prod-bin/create-bin.mjs +42 -27
  25. package/bin/prod-bin/run.mjs +13 -9
  26. package/bin/{prod-run → run}/pid.mjs +4 -4
  27. package/bin/run/restart.mjs +13 -0
  28. package/bin/run/start.mjs +18 -0
  29. package/bin/run/stop.mjs +20 -0
  30. package/bin/util.mjs +35 -11
  31. package/package.json +59 -39
  32. package/src/config/.env +34 -12
  33. package/src/config/defaults.mjs +253 -185
  34. package/src/config/env.mjs +40 -22
  35. package/src/config/index.mjs +19 -24
  36. package/src/config/util.mjs +25 -10
  37. package/src/db-conn.mjs +34 -0
  38. package/src/engines/cron/emails.mjs +10 -5
  39. package/src/engines/cron/index.mjs +45 -51
  40. package/src/engines/cron/init.mjs +16 -17
  41. package/src/engines/cron/ipsum.mjs +65 -60
  42. package/src/engines/cron/syscheck.mjs +30 -30
  43. package/src/engines/emailer/index.mjs +1 -2
  44. package/src/engines/emailer/queue.mjs +14 -20
  45. package/src/engines/emailer/transporter.mjs +86 -74
  46. package/src/engines/geoip/index.mjs +23 -28
  47. package/src/engines/http/index.mjs +26 -15
  48. package/src/engines/logger/buildErrorEmailBody.mjs +72 -0
  49. package/src/engines/logger/index.mjs +114 -122
  50. package/src/engines/logger/injectStackTrace.mjs +59 -0
  51. package/src/engines/logger/logger_mail.mjs +47 -61
  52. package/src/engines/logger/reopenTransportOnHupSignal.mjs +12 -13
  53. package/src/engines/parser/Parser.mjs +77 -60
  54. package/src/engines/parser/index.mjs +1 -1
  55. package/src/engines/schema/diffObjs.mjs +41 -0
  56. package/src/engines/schema/index.mjs +4 -0
  57. package/src/engines/schema/input.mjs +54 -0
  58. package/src/engines/schema/output.mjs +66 -0
  59. package/src/engines/socket/index.mjs +44 -46
  60. package/src/index.mjs +15 -10
  61. package/src/middleware/auth/basic.mjs +41 -40
  62. package/src/middleware/auth/custom.mjs +10 -13
  63. package/src/middleware/auth/guest.mjs +27 -27
  64. package/src/middleware/auth/passport/index.mjs +374 -0
  65. package/src/middleware/auth/passport/session/index.mjs +43 -0
  66. package/src/middleware/auth/{credentials → passport}/session/store.mjs +35 -15
  67. package/src/middleware/auth/passport/session/store_koa_redis.mjs +3 -0
  68. package/src/middleware/context/cache/index.mjs +78 -33
  69. package/src/middleware/context/cache/options.mjs +19 -21
  70. package/src/middleware/context/db.mjs +45 -20
  71. package/src/middleware/context/index.mjs +12 -12
  72. package/src/middleware/extra.mjs +4 -5
  73. package/src/middleware/http/body.mjs +25 -25
  74. package/src/middleware/http/catcher.mjs +81 -8
  75. package/src/middleware/http/custom_blacklist.mjs +19 -16
  76. package/src/middleware/http/headers.mjs +37 -34
  77. package/src/middleware/http/ratelimit.mjs +16 -23
  78. package/src/middleware/http/request.mjs +60 -65
  79. package/src/middleware/routes/catch_js_error.mjs +30 -23
  80. package/src/middleware/routes/robots.mjs +4 -7
  81. package/src/middleware/routes/router/crud/attachCrudRoutes.mjs +108 -90
  82. package/src/middleware/routes/router/crud/getCrudConfig.mjs +31 -55
  83. package/src/middleware/routes/router/defaults.mjs +6 -19
  84. package/src/middleware/routes/router/index.mjs +17 -21
  85. package/src/middleware/routes/router/queries/attachQueriesRoutes.mjs +227 -50
  86. package/src/middleware/routes/router/queries/getQueriesConfig.mjs +45 -55
  87. package/src/middleware/routes/router/utils.mjs +41 -26
  88. package/src/middleware/ssr/context.mjs +5 -7
  89. package/src/middleware/ssr/html.mjs +66 -43
  90. package/src/middleware/ssr/loader.mjs +11 -14
  91. package/src/middleware/ssr/ssr_render.mjs +39 -22
  92. package/src/middleware/static/index.mjs +33 -14
  93. package/src/middleware/vite/devserver.mjs +38 -22
  94. package/src/middleware/vite/watcher.mjs +12 -14
  95. package/src/server-cron.mjs +13 -8
  96. package/src/server-dev.mjs +13 -16
  97. package/src/server.mjs +49 -51
  98. package/template/.agent/skills/miolo-app-arch/SKILL.md +218 -0
  99. package/template/.agent/skills/miolo-auth/SKILL.md +450 -0
  100. package/template/.agent/skills/miolo-cli-router/SKILL.md +394 -0
  101. package/template/.agent/skills/miolo-database/SKILL.md +358 -0
  102. package/template/.agent/skills/miolo-react-patterns/SKILL.md +426 -0
  103. package/template/.agent/skills/miolo-routing/SKILL.md +326 -0
  104. package/template/.agent/skills/miolo-schemas/SKILL.md +329 -0
  105. package/template/.agent/skills/miolo-session-context/SKILL.md +397 -0
  106. package/template/.agent/skills/miolo-ssr/SKILL.md +433 -0
  107. package/template/.editorconfig +18 -0
  108. package/template/.env +120 -0
  109. package/template/biome.json +63 -0
  110. package/template/components.json +21 -0
  111. package/template/db/init.sh +89 -0
  112. package/template/db/sql/00_drop.sql +2 -0
  113. package/template/db/sql/01_users.sql +31 -0
  114. package/template/db/sql/02_todos.sql +20 -0
  115. package/template/docker/Dockerfile +13 -0
  116. package/template/docker/docker-compose.yaml +79 -0
  117. package/template/gitignore +42 -0
  118. package/template/jsconfig.json +18 -0
  119. package/template/package.json +88 -0
  120. package/template/postcss.config.js +9 -0
  121. package/template/src/cli/App.jsx +25 -0
  122. package/template/src/cli/components/JsonTreeViewer.jsx +128 -0
  123. package/template/src/cli/components/shadcn-io/spinner/index.jsx +232 -0
  124. package/template/src/cli/components/stepper.jsx +408 -0
  125. package/template/src/cli/components/ui/avatar.jsx +36 -0
  126. package/template/src/cli/components/ui/badge.jsx +31 -0
  127. package/template/src/cli/components/ui/breadcrumb.jsx +97 -0
  128. package/template/src/cli/components/ui/card.jsx +73 -0
  129. package/template/src/cli/components/ui/collapsible.jsx +16 -0
  130. package/template/src/cli/components/ui/dropdown-menu.jsx +179 -0
  131. package/template/src/cli/components/ui/field.jsx +217 -0
  132. package/template/src/cli/components/ui/input.jsx +19 -0
  133. package/template/src/cli/components/ui/label.jsx +17 -0
  134. package/template/src/cli/components/ui/pagination.jsx +99 -0
  135. package/template/src/cli/components/ui/patched/alert.jsx +56 -0
  136. package/template/src/cli/components/ui/patched/button.jsx +45 -0
  137. package/template/src/cli/components/ui/patched/dialog.jsx +114 -0
  138. package/template/src/cli/components/ui/patched/sidebar.jsx +660 -0
  139. package/template/src/cli/components/ui/select.jsx +141 -0
  140. package/template/src/cli/components/ui/separator.jsx +21 -0
  141. package/template/src/cli/components/ui/sheet.jsx +115 -0
  142. package/template/src/cli/components/ui/skeleton.jsx +13 -0
  143. package/template/src/cli/components/ui/sonner.jsx +22 -0
  144. package/template/src/cli/components/ui/switch.jsx +25 -0
  145. package/template/src/cli/components/ui/table.jsx +88 -0
  146. package/template/src/cli/components/ui/textarea.jsx +16 -0
  147. package/template/src/cli/components/ui/tooltip.jsx +45 -0
  148. package/template/src/cli/config/store_keys.mjs +2 -0
  149. package/template/src/cli/context/data/DataContext.jsx +5 -0
  150. package/template/src/cli/context/data/DataProvider.jsx +44 -0
  151. package/template/src/cli/context/data/useBreads.mjs +15 -0
  152. package/template/src/cli/context/data/useDataContext.mjs +4 -0
  153. package/template/src/cli/context/session/SessionContext.mjs +4 -0
  154. package/template/src/cli/context/session/SessionProvider.jsx +31 -0
  155. package/template/src/cli/context/session/makePermissioner.mjs +34 -0
  156. package/template/src/cli/context/session/useSessionContext.mjs +6 -0
  157. package/template/src/cli/context/theme/ThemeContext.mjs +4 -0
  158. package/template/src/cli/context/theme/ThemeProvider.jsx +49 -0
  159. package/template/src/cli/context/theme/useThemeContext.mjs +6 -0
  160. package/template/src/cli/context/ui/UIContext.jsx +5 -0
  161. package/template/src/cli/context/ui/UIProvider.jsx +16 -0
  162. package/template/src/cli/context/ui/useUIContext.mjs +4 -0
  163. package/template/src/cli/context/util.mjs +17 -0
  164. package/template/src/cli/entry-cli.jsx +33 -0
  165. package/template/src/cli/hooks/useIsMobile.mjs +19 -0
  166. package/template/src/cli/hooks/useStoragedState.mjs +63 -0
  167. package/template/src/cli/index.html +29 -0
  168. package/template/src/cli/layout/app-sidebar.jsx +25 -0
  169. package/template/src/cli/layout/main-layout.jsx +63 -0
  170. package/template/src/cli/layout/nav-last-todos.jsx +72 -0
  171. package/template/src/cli/layout/nav-main.jsx +39 -0
  172. package/template/src/cli/layout/nav-user.jsx +105 -0
  173. package/template/src/cli/layout/prop-switcher.jsx +93 -0
  174. package/template/src/cli/lib/utils.mjs +10 -0
  175. package/template/src/cli/pages/Index.jsx +13 -0
  176. package/template/src/cli/pages/IndexOffline.jsx +13 -0
  177. package/template/src/cli/pages/IndexOnline.jsx +18 -0
  178. package/template/src/cli/pages/dash/Dashboard.jsx +29 -0
  179. package/template/src/cli/pages/offline/Login.jsx +43 -0
  180. package/template/src/cli/pages/offline/LoginForm.jsx +115 -0
  181. package/template/src/cli/pages/security/Security.jsx +39 -0
  182. package/template/src/cli/pages/security/SecurityForm.jsx +106 -0
  183. package/template/src/cli/pages/todos/TodoActions.jsx +99 -0
  184. package/template/src/cli/pages/todos/TodoAdd.jsx +43 -0
  185. package/template/src/cli/pages/todos/TodoList.jsx +60 -0
  186. package/template/src/cli/pages/todos/Todos.jsx +23 -0
  187. package/template/src/cli/pages/todos/context/TodosContext.jsx +5 -0
  188. package/template/src/cli/pages/todos/context/TodosProvider.jsx +191 -0
  189. package/template/src/cli/pages/todos/context/useTodosContext.mjs +4 -0
  190. package/template/src/ns/models/Todo.mjs +29 -0
  191. package/template/src/ns/models/TodoList.mjs +8 -0
  192. package/template/src/ns/models/User.mjs +40 -0
  193. package/template/src/server/bot/check_today.mjs +10 -0
  194. package/template/src/server/io/cache/base.mjs +21 -0
  195. package/template/src/server/io/db/filter.mjs +92 -0
  196. package/template/src/server/io/db/todos/delete.mjs +29 -0
  197. package/template/src/server/io/db/todos/find.mjs +13 -0
  198. package/template/src/server/io/db/todos/read.mjs +83 -0
  199. package/template/src/server/io/db/todos/toggle.mjs +37 -0
  200. package/template/src/server/io/db/todos/upsave.mjs +32 -0
  201. package/template/src/server/io/db/triggers/user.mjs +13 -0
  202. package/template/src/server/io/db/users/auth.mjs +132 -0
  203. package/template/src/server/io/db/users/pwd.mjs +38 -0
  204. package/template/src/server/io/db/users/save.mjs +17 -0
  205. package/template/src/server/miolo/auth/basic.mjs +15 -0
  206. package/template/src/server/miolo/auth/guest.mjs +3 -0
  207. package/template/src/server/miolo/auth/passport.mjs +73 -0
  208. package/template/src/server/miolo/cache.mjs +11 -0
  209. package/template/src/server/miolo/cron/foo.mjs +7 -0
  210. package/template/src/server/miolo/cron/index.mjs +28 -0
  211. package/template/src/server/miolo/cron/invalidate.mjs +21 -0
  212. package/template/src/server/miolo/db.mjs +36 -0
  213. package/template/src/server/miolo/http.mjs +14 -0
  214. package/template/src/server/miolo/index.mjs +43 -0
  215. package/template/src/server/miolo/routes/crud.mjs +16 -0
  216. package/template/src/server/miolo/routes/index.mjs +8 -0
  217. package/template/src/server/miolo/ssr/entry-server.jsx +13 -0
  218. package/template/src/server/miolo/ssr/loader.mjs +18 -0
  219. package/template/src/server/routes/index.mjs +66 -0
  220. package/template/src/server/routes/todos/mod.mjs +52 -0
  221. package/template/src/server/routes/todos/read.mjs +45 -0
  222. package/template/src/server/routes/todos/special.mjs +47 -0
  223. package/template/src/server/routes/users/user.mjs +54 -0
  224. package/template/src/server/server.mjs +10 -0
  225. package/template/src/server/utils/crypt.mjs +38 -0
  226. package/template/src/server/utils/io.mjs +15 -0
  227. package/template/src/server/utils/pwdfor.mjs +25 -0
  228. package/template/src/server/utils/schema.mjs +22 -0
  229. package/template/src/static/img/default/profile.png +0 -0
  230. package/template/src/static/img/favicon.ico +0 -0
  231. package/template/src/static/img/miolo_logo.png +0 -0
  232. package/template/src/static/img/miolo_name.png +0 -0
  233. package/template/src/static/public/manifest.json +21 -0
  234. package/template/src/static/public/sw.js +79 -0
  235. package/template/src/static/style/globals.css +156 -0
  236. package/template/src/static/style/json-tree.css +54 -0
  237. package/template/src/static/style/skeleton.css +49 -0
  238. package/bin/prod-build/build-client.mjs +0 -67
  239. package/bin/prod-build/build-server.mjs +0 -58
  240. package/bin/prod-run/restart.mjs +0 -9
  241. package/bin/prod-run/start.mjs +0 -15
  242. package/bin/prod-run/stop.mjs +0 -20
  243. package/src/engines/logger/verify.mjs +0 -22
  244. package/src/middleware/auth/credentials/index.mjs +0 -151
  245. package/src/middleware/auth/credentials/session/index.mjs +0 -24
  246. package/src/middleware/auth/credentials/session/store_koa_redis.mjs +0 -3
@@ -0,0 +1,426 @@
1
+ ---
2
+ name: miolo-react-patterns
3
+ description: React patterns and conventions for miolo client applications. Use when creating React components, contexts, hooks, pages, or organizing client-side code in miolo apps. For data loading with SSR, see miolo-ssr skill.
4
+ ---
5
+
6
+ # Miolo React Patterns
7
+
8
+ React patterns and conventions for miolo client-side applications (src/cli/).
9
+
10
+ ## Component Organization
11
+
12
+ Components are organized in `src/cli/` with fixed subdirectory structure:
13
+
14
+ ```
15
+ src/cli/
16
+ ├── components/ # Reusable UI components
17
+ │ ├── ui/ # shadcn/ui components
18
+ │ └── [custom]/ # Custom components
19
+ ├── context/ # React contexts
20
+ │ ├── data/ # Data context
21
+ │ ├── session/ # Session/auth context
22
+ │ ├── theme/ # Theme context
23
+ │ └── ui/ # UI state context
24
+ ├── hooks/ # Custom React hooks
25
+ ├── layout/ # Layout components
26
+ ├── lib/ # Client utilities
27
+ └── pages/ # Page components
28
+ ├── dash/ # Dashboard pages
29
+ ├── offline/ # Unauthenticated pages
30
+ └── [feature]/ # Feature-specific pages
31
+ ```
32
+
33
+ ## Context Pattern
34
+
35
+ Every context follows a three-file pattern:
36
+
37
+ ```
38
+ context/feature/
39
+ ├── FeatureContext.jsx # Context definition
40
+ ├── FeatureProvider.jsx # Provider component
41
+ └── useFeatureContext.mjs # Hook for consuming
42
+ ```
43
+
44
+ ### Context Definition
45
+
46
+ **File:** `context/session/SessionContext.mjs`
47
+
48
+ ```javascript
49
+ import { createContext } from 'react'
50
+
51
+ const SessionContext = createContext()
52
+
53
+ export default SessionContext
54
+ ```
55
+
56
+ ### Provider Component
57
+
58
+ **File:** `context/session/SessionProvider.jsx`
59
+
60
+ ```javascript
61
+ import { useState, useEffect } from 'react'
62
+ import SessionContext from './SessionContext.mjs'
63
+
64
+ export default function SessionProvider({ children }) {
65
+ const [user, setUser] = useState(null)
66
+ const [loading, setLoading] = useState(true)
67
+
68
+ useEffect(() => {
69
+ // Fetch current user
70
+ fetch('/api/user/current')
71
+ .then(res => res.json())
72
+ .then(data => {
73
+ setUser(data.user)
74
+ setLoading(false)
75
+ })
76
+ }, [])
77
+
78
+ const login = async (credentials) => {
79
+ const res = await fetch('/api/user/login', {
80
+ method: 'POST',
81
+ headers: { 'Content-Type': 'application/json' },
82
+ body: JSON.stringify(credentials)
83
+ })
84
+ const data = await res.json()
85
+ if (data.ok) {
86
+ setUser(data.user)
87
+ }
88
+ return data
89
+ }
90
+
91
+ const logout = async () => {
92
+ await fetch('/api/user/logout', { method: 'POST' })
93
+ setUser(null)
94
+ }
95
+
96
+ const value = {
97
+ user,
98
+ loading,
99
+ isAuthenticated: !!user,
100
+ login,
101
+ logout
102
+ }
103
+
104
+ return (
105
+ <SessionContext.Provider value={value}>
106
+ {children}
107
+ </SessionContext.Provider>
108
+ )
109
+ }
110
+ ```
111
+
112
+ ### Consumer Hook
113
+
114
+ **File:** `context/session/useSessionContext.mjs`
115
+
116
+ ```javascript
117
+ import { useContext } from 'react'
118
+ import SessionContext from './SessionContext.mjs'
119
+
120
+ export default function useSessionContext() {
121
+ const context = useContext(SessionContext)
122
+
123
+ if (!context) {
124
+ throw new Error('useSessionContext must be used within SessionProvider')
125
+ }
126
+
127
+ return context
128
+ }
129
+ ```
130
+
131
+ ### Usage in Components
132
+
133
+ ```javascript
134
+ import useSessionContext from '#cli/context/session/useSessionContext.mjs'
135
+
136
+ export default function Profile() {
137
+ const { user, loading, logout } = useSessionContext()
138
+
139
+ if (loading) return <div>Loading...</div>
140
+ if (!user) return <div>Not logged in</div>
141
+
142
+ return (
143
+ <div>
144
+ <h1>Welcome {user.name}</h1>
145
+ <button onClick={logout}>Logout</button>
146
+ </div>
147
+ )
148
+ }
149
+ ```
150
+
151
+ ## Custom Hooks Pattern
152
+
153
+ Custom hooks in `src/cli/hooks/`:
154
+
155
+ ```javascript
156
+ // hooks/useStoragedState.mjs
157
+ import { useState, useEffect } from 'react'
158
+
159
+ export default function useStoragedState(key, defaultValue) {
160
+ const [value, setValue] = useState(() => {
161
+ const stored = localStorage.getItem(key)
162
+ return stored ? JSON.parse(stored) : defaultValue
163
+ })
164
+
165
+ useEffect(() => {
166
+ localStorage.setItem(key, JSON.stringify(value))
167
+ }, [key, value])
168
+
169
+ return [value, setValue]
170
+ }
171
+ ```
172
+
173
+ **Usage:**
174
+ ```javascript
175
+ import useStoragedState from '#cli/hooks/useStoragedState.mjs'
176
+
177
+ function MyComponent() {
178
+ const [settings, setSettings] = useStoragedState('user-settings', {})
179
+ // ...
180
+ }
181
+ ```
182
+
183
+ ## Page Components
184
+
185
+ Pages in `src/cli/pages/` organized by feature:
186
+
187
+ ```javascript
188
+ // pages/todos/Todos.jsx
189
+ import { useState } from 'react'
190
+ import useTodosContext from './context/useTodosContext.mjs'
191
+ import TodoList from './TodoList.jsx'
192
+ import TodoAdd from './TodoAdd.jsx'
193
+
194
+ export default function Todos() {
195
+ const { todos, loading } = useTodosContext()
196
+
197
+ if (loading) return <div>Loading todos...</div>
198
+
199
+ return (
200
+ <div>
201
+ <h1>My Todos</h1>
202
+ <TodoAdd />
203
+ <TodoList todos={todos} />
204
+ </div>
205
+ )
206
+ }
207
+ ```
208
+
209
+ ## Component Best Practices
210
+
211
+ ### Import Aliases
212
+
213
+ Always use import aliases:
214
+
215
+ ```javascript
216
+ // ✅ CORRECT
217
+ import Component from '#cli/components/Component.jsx'
218
+ import useHook from '#cli/hooks/useHook.mjs'
219
+ import { fn } from '#cli/lib/utils.mjs'
220
+
221
+ // ❌ WRONG
222
+ import Component from '../../components/Component.jsx'
223
+ ```
224
+
225
+ ### Component File Extensions
226
+
227
+ - `.jsx` for files with JSX
228
+ - `.mjs` for pure JavaScript (hooks, utilities, context definitions)
229
+
230
+ ### Prop Destructuring
231
+
232
+ ```javascript
233
+ // ✅ CORRECT - Destructure in function signature
234
+ export default function TodoItem({ todo, onToggle, onDelete }) {
235
+ return (
236
+ <div>
237
+ <span>{todo.description}</span>
238
+ <button onClick={() => onToggle(todo.id)}>Toggle</button>
239
+ <button onClick={() => onDelete(todo.id)}>Delete</button>
240
+ </div>
241
+ )
242
+ }
243
+
244
+ // ❌ WRONG - Don't access props.x
245
+ export default function TodoItem(props) {
246
+ return <div>{props.todo.description}</div>
247
+ }
248
+ ```
249
+
250
+ ### Loading and Error States
251
+
252
+ Always handle loading and error states:
253
+
254
+ ```javascript
255
+ export default function DataComponent() {
256
+ const [data, setData] = useState(null)
257
+ const [loading, setLoading] = useState(true)
258
+ const [error, setError] = useState(null)
259
+
260
+ useEffect(() => {
261
+ fetch('/api/data')
262
+ .then(res => res.json())
263
+ .then(json => {
264
+ setData(json.data)
265
+ setLoading(false)
266
+ })
267
+ .catch(err => {
268
+ setError(err.message)
269
+ setLoading(false)
270
+ })
271
+ }, [])
272
+
273
+ if (loading) return <div>Loading...</div>
274
+ if (error) return <div>Error: {error}</div>
275
+ if (!data) return <div>No data</div>
276
+
277
+ return <div>{/* Render data */}</div>
278
+ }
279
+ ```
280
+
281
+ ## Layout Components
282
+
283
+ Layout components in `src/cli/layout/`:
284
+
285
+ ```javascript
286
+ // layout/main-layout.jsx
287
+ import AppSidebar from './app-sidebar.jsx'
288
+ import { SidebarProvider } from '#cli/components/ui/sidebar.jsx'
289
+
290
+ export default function MainLayout({ children }) {
291
+ return (
292
+ <SidebarProvider>
293
+ <div className="flex min-h-screen">
294
+ <AppSidebar />
295
+ <main className="flex-1 p-6">
296
+ {children}
297
+ </main>
298
+ </div>
299
+ </SidebarProvider>
300
+ )
301
+ }
302
+ ```
303
+
304
+ ## Data Fetching Pattern
305
+
306
+ Use contexts for shared data:
307
+
308
+ ```javascript
309
+ // context/data/DataProvider.jsx
310
+ import { useState, useEffect } from 'react'
311
+ import DataContext from './DataContext.jsx'
312
+
313
+ export default function DataProvider({ children }) {
314
+ const [todos, setTodos] = useState([])
315
+ const [loading, setLoading] = useState(true)
316
+
317
+ const fetchTodos = async () => {
318
+ setLoading(true)
319
+ const res = await fetch('/api/todo/list')
320
+ const data = await res.json()
321
+ setTodos(data.data)
322
+ setLoading(false)
323
+ }
324
+
325
+ const addTodo = async (todo) => {
326
+ const res = await fetch('/api/todo/upsave', {
327
+ method: 'POST',
328
+ headers: { 'Content-Type': 'application/json' },
329
+ body: JSON.stringify(todo)
330
+ })
331
+ const data = await res.json()
332
+ if (data.ok) {
333
+ await fetchTodos() // Refresh list
334
+ }
335
+ return data
336
+ }
337
+
338
+ useEffect(() => {
339
+ fetchTodos()
340
+ }, [])
341
+
342
+ const value = {
343
+ todos,
344
+ loading,
345
+ fetchTodos,
346
+ addTodo
347
+ }
348
+
349
+ return (
350
+ <DataContext.Provider value={value}>
351
+ {children}
352
+ </DataContext.Provider>
353
+ )
354
+ }
355
+ ```
356
+
357
+ ## Styling with Tailwind
358
+
359
+ Use Tailwind utility classes:
360
+
361
+ ```javascript
362
+ export default function Card({ title, children }) {
363
+ return (
364
+ <div className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow">
365
+ <h2 className="text-xl font-bold mb-4">{title}</h2>
366
+ <div className="text-gray-700">
367
+ {children}
368
+ </div>
369
+ </div>
370
+ )
371
+ }
372
+ ```
373
+
374
+ ## Form Handling
375
+
376
+ ```javascript
377
+ export default function TodoForm({ onSubmit }) {
378
+ const [description, setDescription] = useState('')
379
+
380
+ const handleSubmit = async (e) => {
381
+ e.preventDefault()
382
+
383
+ if (!description.trim()) return
384
+
385
+ await onSubmit({ description })
386
+ setDescription('') // Clear form
387
+ }
388
+
389
+ return (
390
+ <form onSubmit={handleSubmit} className="flex gap-2">
391
+ <input
392
+ type="text"
393
+ value={description}
394
+ onChange={(e) => setDescription(e.target.value)}
395
+ placeholder="Add todo..."
396
+ className="flex-1 px-4 py-2 border rounded"
397
+ />
398
+ <button type="submit" className="px-6 py-2 bg-blue-500 text-white rounded">
399
+ Add
400
+ </button>
401
+ </form>
402
+ )
403
+ }
404
+ ```
405
+
406
+ ## Best Practices
407
+
408
+ 1. **Use import aliases** - Always use `#cli/` prefix
409
+ 2. **Follow the context pattern** - Three files per context
410
+ 3. **Organize by feature** - Pages and components grouped by domain
411
+ 4. **Handle loading states** - Always show loading/error UI
412
+ 5. **Destructure props** - Don't use `props.x`
413
+ 6. **Keep components small** - Single responsibility
414
+ 7. **Use Tailwind** - Utility-first CSS
415
+ 8. **Contexts for shared state** - Avoid prop drilling
416
+ 9. **Custom hooks for reuse** - Extract reusable logic
417
+ 10. **File extensions matter** - `.jsx` vs `.mjs`
418
+
419
+ ## Examples from miolo-sample
420
+
421
+ See actual implementations:
422
+ - `src/cli/context/session/` - Session context pattern
423
+ - `src/cli/context/data/` - Data fetching context
424
+ - `src/cli/hooks/useStoragedState.mjs` - Custom hook
425
+ - `src/cli/pages/todos/` - Feature page organization
426
+ - `src/cli/layout/main-layout.jsx` - Layout component