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.
- package/bin/build/build.mjs +53 -0
- package/bin/build/build_bin.mjs +17 -0
- package/bin/build/cli/client.mjs +52 -0
- package/bin/build/cli/css.mjs +22 -0
- package/bin/build/cli/index.mjs +40 -0
- package/bin/build/cli/ssr.mjs +21 -0
- package/bin/build/server/aliases.mjs +112 -0
- package/bin/build/server/babel.config.js +24 -0
- package/bin/build/server/banner.mjs +20 -0
- package/bin/build/server/bundle.mjs +111 -0
- package/bin/build/server/fix.mjs +15 -0
- package/bin/build/server/index.mjs +69 -0
- package/bin/build/server/options.mjs +83 -0
- package/bin/create/auth.mjs +23 -0
- package/bin/create/copy.mjs +175 -0
- package/bin/create/docker.mjs +25 -0
- package/bin/create/index.mjs +137 -0
- package/bin/create/pkgjson.mjs +72 -0
- package/bin/create/prepare-template.mjs +158 -0
- package/bin/create/validation.mjs +27 -0
- package/bin/dev/dev.mjs +32 -23
- package/bin/dev/dev_start.mjs +6 -5
- package/bin/index.mjs +94 -52
- package/bin/prod-bin/create-bin.mjs +42 -27
- package/bin/prod-bin/run.mjs +13 -9
- package/bin/{prod-run → run}/pid.mjs +4 -4
- package/bin/run/restart.mjs +13 -0
- package/bin/run/start.mjs +18 -0
- package/bin/run/stop.mjs +20 -0
- package/bin/util.mjs +35 -11
- package/package.json +59 -39
- package/src/config/.env +34 -12
- package/src/config/defaults.mjs +253 -185
- package/src/config/env.mjs +40 -22
- package/src/config/index.mjs +19 -24
- package/src/config/util.mjs +25 -10
- package/src/db-conn.mjs +34 -0
- package/src/engines/cron/emails.mjs +10 -5
- package/src/engines/cron/index.mjs +45 -51
- package/src/engines/cron/init.mjs +16 -17
- package/src/engines/cron/ipsum.mjs +65 -60
- package/src/engines/cron/syscheck.mjs +30 -30
- package/src/engines/emailer/index.mjs +1 -2
- package/src/engines/emailer/queue.mjs +14 -20
- package/src/engines/emailer/transporter.mjs +86 -74
- package/src/engines/geoip/index.mjs +23 -28
- package/src/engines/http/index.mjs +26 -15
- package/src/engines/logger/buildErrorEmailBody.mjs +72 -0
- package/src/engines/logger/index.mjs +114 -122
- package/src/engines/logger/injectStackTrace.mjs +59 -0
- package/src/engines/logger/logger_mail.mjs +47 -61
- package/src/engines/logger/reopenTransportOnHupSignal.mjs +12 -13
- package/src/engines/parser/Parser.mjs +77 -60
- package/src/engines/parser/index.mjs +1 -1
- package/src/engines/schema/diffObjs.mjs +41 -0
- package/src/engines/schema/index.mjs +4 -0
- package/src/engines/schema/input.mjs +54 -0
- package/src/engines/schema/output.mjs +66 -0
- package/src/engines/socket/index.mjs +44 -46
- package/src/index.mjs +15 -10
- package/src/middleware/auth/basic.mjs +41 -40
- package/src/middleware/auth/custom.mjs +10 -13
- package/src/middleware/auth/guest.mjs +27 -27
- package/src/middleware/auth/passport/index.mjs +374 -0
- package/src/middleware/auth/passport/session/index.mjs +43 -0
- package/src/middleware/auth/{credentials → passport}/session/store.mjs +35 -15
- package/src/middleware/auth/passport/session/store_koa_redis.mjs +3 -0
- package/src/middleware/context/cache/index.mjs +78 -33
- package/src/middleware/context/cache/options.mjs +19 -21
- package/src/middleware/context/db.mjs +45 -20
- package/src/middleware/context/index.mjs +12 -12
- package/src/middleware/extra.mjs +4 -5
- package/src/middleware/http/body.mjs +25 -25
- package/src/middleware/http/catcher.mjs +81 -8
- package/src/middleware/http/custom_blacklist.mjs +19 -16
- package/src/middleware/http/headers.mjs +37 -34
- package/src/middleware/http/ratelimit.mjs +16 -23
- package/src/middleware/http/request.mjs +60 -65
- package/src/middleware/routes/catch_js_error.mjs +30 -23
- package/src/middleware/routes/robots.mjs +4 -7
- package/src/middleware/routes/router/crud/attachCrudRoutes.mjs +108 -90
- package/src/middleware/routes/router/crud/getCrudConfig.mjs +31 -55
- package/src/middleware/routes/router/defaults.mjs +6 -19
- package/src/middleware/routes/router/index.mjs +17 -21
- package/src/middleware/routes/router/queries/attachQueriesRoutes.mjs +227 -50
- package/src/middleware/routes/router/queries/getQueriesConfig.mjs +45 -55
- package/src/middleware/routes/router/utils.mjs +41 -26
- package/src/middleware/ssr/context.mjs +5 -7
- package/src/middleware/ssr/html.mjs +66 -43
- package/src/middleware/ssr/loader.mjs +11 -14
- package/src/middleware/ssr/ssr_render.mjs +39 -22
- package/src/middleware/static/index.mjs +33 -14
- package/src/middleware/vite/devserver.mjs +38 -22
- package/src/middleware/vite/watcher.mjs +12 -14
- package/src/server-cron.mjs +13 -8
- package/src/server-dev.mjs +13 -16
- package/src/server.mjs +49 -51
- package/template/.agent/skills/miolo-app-arch/SKILL.md +218 -0
- package/template/.agent/skills/miolo-auth/SKILL.md +450 -0
- package/template/.agent/skills/miolo-cli-router/SKILL.md +394 -0
- package/template/.agent/skills/miolo-database/SKILL.md +358 -0
- package/template/.agent/skills/miolo-react-patterns/SKILL.md +426 -0
- package/template/.agent/skills/miolo-routing/SKILL.md +326 -0
- package/template/.agent/skills/miolo-schemas/SKILL.md +329 -0
- package/template/.agent/skills/miolo-session-context/SKILL.md +397 -0
- package/template/.agent/skills/miolo-ssr/SKILL.md +433 -0
- package/template/.editorconfig +18 -0
- package/template/.env +120 -0
- package/template/biome.json +63 -0
- package/template/components.json +21 -0
- package/template/db/init.sh +89 -0
- package/template/db/sql/00_drop.sql +2 -0
- package/template/db/sql/01_users.sql +31 -0
- package/template/db/sql/02_todos.sql +20 -0
- package/template/docker/Dockerfile +13 -0
- package/template/docker/docker-compose.yaml +79 -0
- package/template/gitignore +42 -0
- package/template/jsconfig.json +18 -0
- package/template/package.json +88 -0
- package/template/postcss.config.js +9 -0
- package/template/src/cli/App.jsx +25 -0
- package/template/src/cli/components/JsonTreeViewer.jsx +128 -0
- package/template/src/cli/components/shadcn-io/spinner/index.jsx +232 -0
- package/template/src/cli/components/stepper.jsx +408 -0
- package/template/src/cli/components/ui/avatar.jsx +36 -0
- package/template/src/cli/components/ui/badge.jsx +31 -0
- package/template/src/cli/components/ui/breadcrumb.jsx +97 -0
- package/template/src/cli/components/ui/card.jsx +73 -0
- package/template/src/cli/components/ui/collapsible.jsx +16 -0
- package/template/src/cli/components/ui/dropdown-menu.jsx +179 -0
- package/template/src/cli/components/ui/field.jsx +217 -0
- package/template/src/cli/components/ui/input.jsx +19 -0
- package/template/src/cli/components/ui/label.jsx +17 -0
- package/template/src/cli/components/ui/pagination.jsx +99 -0
- package/template/src/cli/components/ui/patched/alert.jsx +56 -0
- package/template/src/cli/components/ui/patched/button.jsx +45 -0
- package/template/src/cli/components/ui/patched/dialog.jsx +114 -0
- package/template/src/cli/components/ui/patched/sidebar.jsx +660 -0
- package/template/src/cli/components/ui/select.jsx +141 -0
- package/template/src/cli/components/ui/separator.jsx +21 -0
- package/template/src/cli/components/ui/sheet.jsx +115 -0
- package/template/src/cli/components/ui/skeleton.jsx +13 -0
- package/template/src/cli/components/ui/sonner.jsx +22 -0
- package/template/src/cli/components/ui/switch.jsx +25 -0
- package/template/src/cli/components/ui/table.jsx +88 -0
- package/template/src/cli/components/ui/textarea.jsx +16 -0
- package/template/src/cli/components/ui/tooltip.jsx +45 -0
- package/template/src/cli/config/store_keys.mjs +2 -0
- package/template/src/cli/context/data/DataContext.jsx +5 -0
- package/template/src/cli/context/data/DataProvider.jsx +44 -0
- package/template/src/cli/context/data/useBreads.mjs +15 -0
- package/template/src/cli/context/data/useDataContext.mjs +4 -0
- package/template/src/cli/context/session/SessionContext.mjs +4 -0
- package/template/src/cli/context/session/SessionProvider.jsx +31 -0
- package/template/src/cli/context/session/makePermissioner.mjs +34 -0
- package/template/src/cli/context/session/useSessionContext.mjs +6 -0
- package/template/src/cli/context/theme/ThemeContext.mjs +4 -0
- package/template/src/cli/context/theme/ThemeProvider.jsx +49 -0
- package/template/src/cli/context/theme/useThemeContext.mjs +6 -0
- package/template/src/cli/context/ui/UIContext.jsx +5 -0
- package/template/src/cli/context/ui/UIProvider.jsx +16 -0
- package/template/src/cli/context/ui/useUIContext.mjs +4 -0
- package/template/src/cli/context/util.mjs +17 -0
- package/template/src/cli/entry-cli.jsx +33 -0
- package/template/src/cli/hooks/useIsMobile.mjs +19 -0
- package/template/src/cli/hooks/useStoragedState.mjs +63 -0
- package/template/src/cli/index.html +29 -0
- package/template/src/cli/layout/app-sidebar.jsx +25 -0
- package/template/src/cli/layout/main-layout.jsx +63 -0
- package/template/src/cli/layout/nav-last-todos.jsx +72 -0
- package/template/src/cli/layout/nav-main.jsx +39 -0
- package/template/src/cli/layout/nav-user.jsx +105 -0
- package/template/src/cli/layout/prop-switcher.jsx +93 -0
- package/template/src/cli/lib/utils.mjs +10 -0
- package/template/src/cli/pages/Index.jsx +13 -0
- package/template/src/cli/pages/IndexOffline.jsx +13 -0
- package/template/src/cli/pages/IndexOnline.jsx +18 -0
- package/template/src/cli/pages/dash/Dashboard.jsx +29 -0
- package/template/src/cli/pages/offline/Login.jsx +43 -0
- package/template/src/cli/pages/offline/LoginForm.jsx +115 -0
- package/template/src/cli/pages/security/Security.jsx +39 -0
- package/template/src/cli/pages/security/SecurityForm.jsx +106 -0
- package/template/src/cli/pages/todos/TodoActions.jsx +99 -0
- package/template/src/cli/pages/todos/TodoAdd.jsx +43 -0
- package/template/src/cli/pages/todos/TodoList.jsx +60 -0
- package/template/src/cli/pages/todos/Todos.jsx +23 -0
- package/template/src/cli/pages/todos/context/TodosContext.jsx +5 -0
- package/template/src/cli/pages/todos/context/TodosProvider.jsx +191 -0
- package/template/src/cli/pages/todos/context/useTodosContext.mjs +4 -0
- package/template/src/ns/models/Todo.mjs +29 -0
- package/template/src/ns/models/TodoList.mjs +8 -0
- package/template/src/ns/models/User.mjs +40 -0
- package/template/src/server/bot/check_today.mjs +10 -0
- package/template/src/server/io/cache/base.mjs +21 -0
- package/template/src/server/io/db/filter.mjs +92 -0
- package/template/src/server/io/db/todos/delete.mjs +29 -0
- package/template/src/server/io/db/todos/find.mjs +13 -0
- package/template/src/server/io/db/todos/read.mjs +83 -0
- package/template/src/server/io/db/todos/toggle.mjs +37 -0
- package/template/src/server/io/db/todos/upsave.mjs +32 -0
- package/template/src/server/io/db/triggers/user.mjs +13 -0
- package/template/src/server/io/db/users/auth.mjs +132 -0
- package/template/src/server/io/db/users/pwd.mjs +38 -0
- package/template/src/server/io/db/users/save.mjs +17 -0
- package/template/src/server/miolo/auth/basic.mjs +15 -0
- package/template/src/server/miolo/auth/guest.mjs +3 -0
- package/template/src/server/miolo/auth/passport.mjs +73 -0
- package/template/src/server/miolo/cache.mjs +11 -0
- package/template/src/server/miolo/cron/foo.mjs +7 -0
- package/template/src/server/miolo/cron/index.mjs +28 -0
- package/template/src/server/miolo/cron/invalidate.mjs +21 -0
- package/template/src/server/miolo/db.mjs +36 -0
- package/template/src/server/miolo/http.mjs +14 -0
- package/template/src/server/miolo/index.mjs +43 -0
- package/template/src/server/miolo/routes/crud.mjs +16 -0
- package/template/src/server/miolo/routes/index.mjs +8 -0
- package/template/src/server/miolo/ssr/entry-server.jsx +13 -0
- package/template/src/server/miolo/ssr/loader.mjs +18 -0
- package/template/src/server/routes/index.mjs +66 -0
- package/template/src/server/routes/todos/mod.mjs +52 -0
- package/template/src/server/routes/todos/read.mjs +45 -0
- package/template/src/server/routes/todos/special.mjs +47 -0
- package/template/src/server/routes/users/user.mjs +54 -0
- package/template/src/server/server.mjs +10 -0
- package/template/src/server/utils/crypt.mjs +38 -0
- package/template/src/server/utils/io.mjs +15 -0
- package/template/src/server/utils/pwdfor.mjs +25 -0
- package/template/src/server/utils/schema.mjs +22 -0
- package/template/src/static/img/default/profile.png +0 -0
- package/template/src/static/img/favicon.ico +0 -0
- package/template/src/static/img/miolo_logo.png +0 -0
- package/template/src/static/img/miolo_name.png +0 -0
- package/template/src/static/public/manifest.json +21 -0
- package/template/src/static/public/sw.js +79 -0
- package/template/src/static/style/globals.css +156 -0
- package/template/src/static/style/json-tree.css +54 -0
- package/template/src/static/style/skeleton.css +49 -0
- package/bin/prod-build/build-client.mjs +0 -67
- package/bin/prod-build/build-server.mjs +0 -58
- package/bin/prod-run/restart.mjs +0 -9
- package/bin/prod-run/start.mjs +0 -15
- package/bin/prod-run/stop.mjs +0 -20
- package/src/engines/logger/verify.mjs +0 -22
- package/src/middleware/auth/credentials/index.mjs +0 -151
- package/src/middleware/auth/credentials/session/index.mjs +0 -24
- 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
|