@mdxui/zero 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ import type { Preview } from '@storybook/react'
2
+
3
+ /**
4
+ * Zero (Email client) preview configuration
5
+ *
6
+ * Zero is an email client interface that needs:
7
+ * - Fullscreen layout for complete email UI
8
+ * - Support for both light and dark themes
9
+ * - Email-specific context (threads, messages, folders)
10
+ */
11
+ const preview: Preview = {
12
+ parameters: {
13
+ layout: 'fullscreen',
14
+ backgrounds: {
15
+ default: 'light',
16
+ },
17
+ },
18
+ }
19
+
20
+ export default preview
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > @mdxui/zero@5.0.2 typecheck /Users/chrisrisner/Workspace/dot-do/ui/packages/zero
4
+ > tsc --noEmit
5
+
@@ -0,0 +1,415 @@
1
+ # @mdxui/zero Architecture Plan
2
+
3
+ > This document outlines the target architecture for @mdxui/zero, based on analysis of [Mail-0/Zero](https://github.com/Mail-0/Zero) and our own patterns in `@mdxui/cockpit` (developer dashboard) and `@mdxui/widgets` (onboarding wizard).
4
+
5
+ ## Current State
6
+
7
+ The package currently has UI components only - no routing, state management, or data layer:
8
+
9
+ ```
10
+ zero/
11
+ ├── components/ # Shared UI components
12
+ ├── compose/ # EmailComposer (UI only)
13
+ ├── dashboard/ # MailShell, MailSidebar (UI only)
14
+ ├── landing/ # Landing page components
15
+ ├── mail/ # MailList, ThreadDisplay, MessageView (UI only)
16
+ └── pages/ # MailZeroPage (dumb wrapper with local state)
17
+ ```
18
+
19
+ **What's missing:**
20
+ - Router integration (react-router)
21
+ - Context/state management
22
+ - Data layer / API integration
23
+ - Proper page components that work with routes
24
+ - Hooks for data fetching and actions
25
+ - Standalone components for embedding
26
+
27
+ ## Target Architecture
28
+
29
+ Based on Mail-0/Zero and cockpit/developer patterns:
30
+
31
+ ```
32
+ zero/
33
+ ├── context/
34
+ │ ├── zero-context.tsx # ZeroProvider, useZero, useMailState
35
+ │ └── compose-context.tsx # ComposeProvider, useCompose
36
+ ├── data-layer/
37
+ │ ├── api.ts # MailAPI interface
38
+ │ ├── types.ts # API request/response types
39
+ │ └── mock-api.ts # Mock implementation for demos
40
+ ├── hooks/
41
+ │ ├── use-threads.ts # Thread fetching, pagination, search
42
+ │ ├── use-thread.ts # Single thread by ID
43
+ │ ├── use-folders.ts # Folder list with counts
44
+ │ ├── use-labels.ts # Labels CRUD
45
+ │ ├── use-compose.ts # Compose state, draft management
46
+ │ ├── use-mail-actions.ts # archive, delete, star, move, label
47
+ │ ├── use-mail-navigation.ts # Keyboard nav, folder switching
48
+ │ ├── use-optimistic.ts # Optimistic updates
49
+ │ └── use-undo.ts # Undo actions
50
+ ├── pages/
51
+ │ ├── mail-page.tsx # /mail - redirects to inbox
52
+ │ ├── folder-page.tsx # /mail/:folder - inbox, sent, drafts, etc.
53
+ │ ├── thread-page.tsx # /mail/:folder/:threadId
54
+ │ ├── compose-page.tsx # /mail/compose
55
+ │ └── settings-page.tsx # /settings/*
56
+ ├── router.tsx # ZeroRouter + ZeroRoutes
57
+ ├── standalone/
58
+ │ ├── index.ts
59
+ │ ├── standalone-provider.tsx # Minimal provider for embedding
60
+ │ ├── standalone-inbox.tsx # Embeddable inbox
61
+ │ ├── standalone-compose.tsx # Embeddable composer
62
+ │ └── standalone-thread.tsx # Embeddable thread view
63
+ ├── types/
64
+ │ ├── config.ts # ZeroConfig, ZeroRoutes
65
+ │ └── api.ts # API types
66
+ ├── mail/ # (existing) UI components
67
+ ├── compose/ # (existing) UI components
68
+ ├── dashboard/ # (existing) UI components
69
+ └── index.ts # Main exports
70
+ ```
71
+
72
+ ## Route Structure
73
+
74
+ Based on Mail-0/Zero's `routes.ts`, using React Router's `Outlet` for nested layouts:
75
+
76
+ ```typescript
77
+ // Primary mail routes
78
+ /mail → Redirects to /mail/inbox
79
+ /mail/:folder → FolderPage (inbox, sent, drafts, spam, trash, starred, snoozed)
80
+ /mail/:folder/:threadId → ThreadPage (thread detail view)
81
+ /mail/compose → ComposePage (new email)
82
+ /mail/compose?reply=:id → ComposePage (reply mode)
83
+ /mail/compose?forward=:id → ComposePage (forward mode)
84
+
85
+ // Settings routes
86
+ /settings → SettingsPage (redirects to /settings/general)
87
+ /settings/general → General settings
88
+ /settings/labels → Label management
89
+ /settings/appearance → Theme settings
90
+ /settings/notifications → Notification preferences
91
+ /settings/connections → Email account connections
92
+ ```
93
+
94
+ ### Router Implementation
95
+
96
+ Using `Outlet` for nested layouts (similar to cockpit/developer):
97
+
98
+ ```tsx
99
+ // router.tsx
100
+ import { Outlet, Route, Routes, Navigate } from 'react-router-dom'
101
+
102
+ function MailLayout() {
103
+ return (
104
+ <div className="flex h-screen">
105
+ <MailSidebar />
106
+ <div className="flex-1">
107
+ <Outlet /> {/* Child routes render here */}
108
+ </div>
109
+ </div>
110
+ )
111
+ }
112
+
113
+ export function ZeroRoutes() {
114
+ return (
115
+ <Routes>
116
+ <Route element={<MailLayout />}>
117
+ <Route index element={<Navigate to="inbox" replace />} />
118
+ <Route path=":folder" element={<FolderPage />} />
119
+ <Route path=":folder/:threadId" element={<ThreadPage />} />
120
+ <Route path="compose" element={<ComposePage />} />
121
+ </Route>
122
+ <Route path="settings" element={<SettingsLayout />}>
123
+ <Route index element={<Navigate to="general" replace />} />
124
+ <Route path="general" element={<SettingsGeneralPage />} />
125
+ <Route path="labels" element={<SettingsLabelsPage />} />
126
+ <Route path="appearance" element={<SettingsAppearancePage />} />
127
+ </Route>
128
+ </Routes>
129
+ )
130
+ }
131
+ ```
132
+
133
+ This mirrors the pattern in `packages/cockpit/src/developer/router.tsx`.
134
+
135
+ ## Hooks Reference
136
+
137
+ Based on Mail-0/Zero's hooks directory:
138
+
139
+ ### Data Hooks
140
+ | Hook | Purpose |
141
+ |------|---------|
142
+ | `useThreads(folder, options)` | Fetch threads for a folder with pagination |
143
+ | `useThread(threadId)` | Fetch single thread with messages |
144
+ | `useFolders()` | Fetch folders with unread counts |
145
+ | `useLabels()` | Fetch/create/update/delete labels |
146
+ | `useDrafts()` | Draft management |
147
+ | `useAttachments()` | Attachment upload/download |
148
+ | `useStats()` | Email statistics |
149
+
150
+ ### Action Hooks
151
+ | Hook | Purpose |
152
+ |------|---------|
153
+ | `useMailActions()` | archive, delete, star, spam, move, label |
154
+ | `useOptimisticActions()` | Wrap actions with optimistic updates |
155
+ | `useUndoSend()` | Undo recently sent emails |
156
+ | `useCompose()` | Compose state, send, save draft |
157
+
158
+ ### Navigation Hooks
159
+ | Hook | Purpose |
160
+ |------|---------|
161
+ | `useMailNavigation()` | Keyboard shortcuts, folder navigation |
162
+ | `useHotKey(key, callback)` | Register keyboard shortcuts |
163
+
164
+ ### UI Hooks
165
+ | Hook | Purpose |
166
+ |------|---------|
167
+ | `useMediaQuery(query)` | Responsive breakpoints |
168
+ | `useMobile()` | Mobile detection |
169
+ | `useAnimations()` | Animation preferences |
170
+ | `useCopyToClipboard()` | Copy text utility |
171
+
172
+ ## Context Structure
173
+
174
+ ### ZeroProvider
175
+
176
+ With react-router, navigation state comes from URL params - context only holds non-URL state:
177
+
178
+ ```typescript
179
+ interface ZeroContextValue {
180
+ // API client
181
+ api: MailAPI
182
+
183
+ // User & auth
184
+ user: User | null
185
+
186
+ // Settings
187
+ settings: MailSettings
188
+ updateSettings: (updates: Partial<MailSettings>) => void
189
+
190
+ // Multi-select state (not in URL)
191
+ selectedThreadIds: string[]
192
+ selectThreads: (ids: string[]) => void
193
+ clearSelection: () => void
194
+
195
+ // Compose modal state
196
+ composeOpen: boolean
197
+ composeMode: 'new' | 'reply' | 'reply-all' | 'forward' | null
198
+ composeContext: { threadId?: string; messageId?: string } | null
199
+ openCompose: (mode: ComposeMode, context?: ComposeContext) => void
200
+ closeCompose: () => void
201
+ }
202
+
203
+ // Navigation comes from react-router, not context
204
+ // Use useParams() for folder/threadId
205
+ // Use useNavigate() for navigation
206
+ ```
207
+
208
+ ### Usage
209
+
210
+ Similar to DeveloperDashboard, the user gets a single component that handles everything internally:
211
+
212
+ ```tsx
213
+ // Simple usage - ZeroMail handles provider, router, routes internally
214
+ <ZeroMail
215
+ api={mailApi}
216
+ user={user}
217
+ basePath="/mail"
218
+ />
219
+
220
+ // Or mount in existing router
221
+ <Route path="/mail/*" element={
222
+ <ZeroMail api={mailApi} user={user} />
223
+ } />
224
+
225
+ // With configuration
226
+ <ZeroMail
227
+ api={mailApi}
228
+ user={user}
229
+ basePath="/mail"
230
+ config={{
231
+ routes: {
232
+ inbox: true,
233
+ sent: true,
234
+ drafts: true,
235
+ settings: true,
236
+ },
237
+ features: {
238
+ compose: true,
239
+ search: true,
240
+ labels: true,
241
+ },
242
+ }}
243
+ />
244
+ ```
245
+
246
+ Internally, ZeroMail composes:
247
+ ```tsx
248
+ function ZeroMail({ api, user, basePath, config }: ZeroMailProps) {
249
+ return (
250
+ <ZeroProvider api={api} user={user} config={config}>
251
+ <ZeroRouter basePath={basePath}>
252
+ <ZeroRoutes />
253
+ </ZeroRouter>
254
+ </ZeroProvider>
255
+ )
256
+ }
257
+ ```
258
+
259
+ ### URL-Derived State (via react-router)
260
+
261
+ ```typescript
262
+ // In FolderPage.tsx
263
+ function FolderPage() {
264
+ const { folder } = useParams<{ folder: string }>()
265
+ const { data: threads } = useThreads(folder)
266
+ // ...
267
+ }
268
+
269
+ // In ThreadPage.tsx
270
+ function ThreadPage() {
271
+ const { folder, threadId } = useParams<{ folder: string; threadId: string }>()
272
+ const { data: thread } = useThread(threadId)
273
+ const navigate = useNavigate()
274
+
275
+ const handleClose = () => navigate(`/mail/${folder}`)
276
+ const handleNext = (nextId: string) => navigate(`/mail/${folder}/${nextId}`)
277
+ // ...
278
+ }
279
+ ```
280
+
281
+ ### ComposeProvider
282
+
283
+ ```typescript
284
+ interface ComposeContextValue {
285
+ // Draft state
286
+ draft: DraftState
287
+ updateDraft: (updates: Partial<DraftState>) => void
288
+
289
+ // Mode
290
+ mode: 'compose' | 'reply' | 'reply-all' | 'forward'
291
+ replyTo: Message | null
292
+
293
+ // Actions
294
+ send: () => Promise<void>
295
+ saveDraft: () => Promise<void>
296
+ discard: () => void
297
+
298
+ // Attachments
299
+ attachments: Attachment[]
300
+ addAttachment: (file: File) => Promise<void>
301
+ removeAttachment: (id: string) => void
302
+
303
+ // State
304
+ isSending: boolean
305
+ isSaving: boolean
306
+ }
307
+ ```
308
+
309
+ ## API Interface
310
+
311
+ ```typescript
312
+ interface MailAPI {
313
+ // Threads
314
+ getThreads(folder: string, options?: ThreadsOptions): Promise<ThreadsResponse>
315
+ getThread(threadId: string): Promise<Thread>
316
+
317
+ // Messages
318
+ getMessage(messageId: string): Promise<Message>
319
+ sendMessage(draft: DraftState): Promise<Message>
320
+
321
+ // Actions
322
+ archiveThreads(threadIds: string[]): Promise<void>
323
+ deleteThreads(threadIds: string[]): Promise<void>
324
+ starThreads(threadIds: string[], starred: boolean): Promise<void>
325
+ markAsSpam(threadIds: string[]): Promise<void>
326
+ moveThreads(threadIds: string[], folderId: string): Promise<void>
327
+ labelThreads(threadIds: string[], labelIds: string[], action: 'add' | 'remove'): Promise<void>
328
+ snoozeThreads(threadIds: string[], until: string): Promise<void>
329
+
330
+ // Folders
331
+ getFolders(): Promise<Folder[]>
332
+
333
+ // Labels
334
+ getLabels(): Promise<Label[]>
335
+ createLabel(label: CreateLabelInput): Promise<Label>
336
+ updateLabel(id: string, label: UpdateLabelInput): Promise<Label>
337
+ deleteLabel(id: string): Promise<void>
338
+
339
+ // Drafts
340
+ saveDraft(draft: DraftState): Promise<Draft>
341
+ getDrafts(): Promise<Draft[]>
342
+ deleteDraft(draftId: string): Promise<void>
343
+
344
+ // Attachments
345
+ uploadAttachment(file: File): Promise<Attachment>
346
+
347
+ // Search
348
+ searchThreads(query: string, options?: SearchOptions): Promise<ThreadsResponse>
349
+ }
350
+ ```
351
+
352
+ ## Implementation Order
353
+
354
+ ### Phase 1: Core Infrastructure
355
+ 1. [ ] `types/config.ts` - Configuration types
356
+ 2. [ ] `types/api.ts` - API types
357
+ 3. [ ] `data-layer/api.ts` - API interface
358
+ 4. [ ] `data-layer/mock-api.ts` - Mock implementation
359
+ 5. [ ] `context/zero-context.tsx` - Main provider
360
+
361
+ ### Phase 2: Hooks
362
+ 1. [ ] `hooks/use-threads.ts`
363
+ 2. [ ] `hooks/use-thread.ts`
364
+ 3. [ ] `hooks/use-folders.ts`
365
+ 4. [ ] `hooks/use-mail-actions.ts`
366
+ 5. [ ] `hooks/use-compose.ts`
367
+ 6. [ ] `hooks/use-mail-navigation.ts`
368
+
369
+ ### Phase 3: Router & Pages
370
+ 1. [ ] `router.tsx` - Router setup
371
+ 2. [ ] `pages/folder-page.tsx`
372
+ 3. [ ] `pages/thread-page.tsx`
373
+ 4. [ ] `pages/compose-page.tsx`
374
+
375
+ ### Phase 4: Standalone Components
376
+ 1. [ ] `standalone/standalone-provider.tsx`
377
+ 2. [ ] `standalone/standalone-inbox.tsx`
378
+ 3. [ ] `standalone/standalone-compose.tsx`
379
+
380
+ ### Phase 5: Polish
381
+ 1. [ ] Keyboard shortcuts
382
+ 2. [ ] Optimistic updates
383
+ 3. [ ] Undo functionality
384
+ 4. [ ] Settings pages
385
+
386
+ ## Dependencies to Add
387
+
388
+ ```json
389
+ {
390
+ "dependencies": {
391
+ "react-router-dom": "^7.0.0",
392
+ "jotai": "^2.12.0",
393
+ "@tanstack/react-query": "^5.74.0"
394
+ }
395
+ }
396
+ ```
397
+
398
+ ## References
399
+
400
+ - [Mail-0/Zero GitHub](https://github.com/Mail-0/Zero)
401
+ - [Zero routes.ts](https://github.com/Mail-0/Zero/blob/staging/apps/mail/app/routes.ts)
402
+ - [Zero hooks](https://github.com/Mail-0/Zero/tree/staging/apps/mail/hooks)
403
+ - Internal: `packages/cockpit/src/developer/` - Similar architecture
404
+ - Internal: `packages/widgets/src/onboarding/` - Wizard pattern
405
+
406
+ ## Lessons Learned
407
+
408
+ Building UI components first without considering the app architecture leads to:
409
+ 1. Components that don't integrate well with routing
410
+ 2. Props that don't match real data flow patterns
411
+ 3. Missing context for state that needs to be shared
412
+ 4. No clear integration path for real APIs
413
+ 5. Significant refactoring needed later
414
+
415
+ **For future packages:** Start with the provider/context/router architecture first, then build components that consume that infrastructure.
package/CHANGELOG.md ADDED
@@ -0,0 +1,80 @@
1
+ # @mdxui/zero
2
+
3
+ ## 6.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - @mdxui/primitives@6.0.0
8
+ - mdxui@6.0.0
9
+
10
+ ## 5.0.2
11
+
12
+ ### Patch Changes
13
+
14
+ - @mdxui/primitives@5.0.2
15
+ - mdxui@5.0.2
16
+
17
+ ## 5.0.1
18
+
19
+ ### Patch Changes
20
+
21
+ - @mdxui/primitives@5.0.1
22
+ - mdxui@5.0.1
23
+
24
+ ## 5.0.0
25
+
26
+ ### Patch Changes
27
+
28
+ - @mdxui/primitives@5.0.0
29
+ - mdxui@5.0.0
30
+
31
+ ## 4.0.0
32
+
33
+ ### Patch Changes
34
+
35
+ - @mdxui/primitives@4.0.0
36
+ - mdxui@4.0.0
37
+
38
+ ## 3.0.1
39
+
40
+ ### Patch Changes
41
+
42
+ - @mdxui/primitives@3.0.1
43
+ - mdxui@3.0.1
44
+
45
+ ## 3.0.0
46
+
47
+ ### Patch Changes
48
+
49
+ - @mdxui/primitives@3.0.0
50
+ - mdxui@3.0.0
51
+
52
+ ## 2.1.1
53
+
54
+ ### Patch Changes
55
+
56
+ - Updated dependencies
57
+ - mdxui@2.1.1
58
+ - @mdxui/primitives@2.1.1
59
+
60
+ ## 2.0.0
61
+
62
+ ### Patch Changes
63
+
64
+ - Updated dependencies [8101194]
65
+ - mdxui@2.0.0
66
+
67
+ ## 1.0.0
68
+
69
+ ### Patch Changes
70
+
71
+ - Updated dependencies [defc863]
72
+ - mdxui@1.0.0
73
+
74
+ ## 0.4.1
75
+
76
+ ### Patch Changes
77
+
78
+ - Updated dependencies [4d0d1a0]
79
+ - mdxui@0.4.1
80
+ - @mdxui/primitives@0.4.1