create-lego-one 2.0.12 → 2.0.14

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 (78) hide show
  1. package/dist/index.cjs +150 -15
  2. package/dist/index.cjs.map +1 -1
  3. package/package.json +1 -1
  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/package.json +1 -1
  70. package/template/nginx.conf +72 -0
  71. package/template/package.json +1 -1
  72. package/template/packages/plugins/@lego/plugin-dashboard/package.json +1 -1
  73. package/template/packages/plugins/@lego/plugin-todo/package.json +1 -1
  74. package/template/pocketbase/CHANGELOG.md +911 -0
  75. package/template/pocketbase/LICENSE.md +17 -0
  76. package/template/scripts/create-plugin.js +221 -0
  77. package/template/scripts/deploy.sh +56 -0
  78. package/template/tsconfig.base.json +26 -0
@@ -0,0 +1,1518 @@
1
+ # Host Kernel Implementation Plan
2
+
3
+ > **For AI Implementing This Plan:** This is document 03 of 13. Complete `01-infrastructure-setup.md` and `02-pocketbase-setup.md` first.
4
+
5
+ **Goal:** Initialize the Modern.js host application (the "Kernel") with shared state, layout, routing, Garfish configuration, and base providers.
6
+
7
+ **Architecture:** Host app is the kernel that provides authentication, multitenancy, RBAC, shared services, and layout structure. Plugins are loaded dynamically as sub-apps via Garfish.
8
+
9
+ **Tech Stack:** Modern.js, Rspack, Garfish, React 18, TypeScript, Zustand, TanStack Query v5, Tailwind CSS, PocketBase
10
+
11
+ ---
12
+
13
+ ## Prerequisites
14
+
15
+ - ✅ Completed `01-infrastructure-setup.md`
16
+ - ✅ Completed `02-pocketbase-setup.md`
17
+ - ✅ pnpm workspace configured
18
+ - ✅ PocketBase collections and migrations in place
19
+
20
+ ---
21
+
22
+ ## Task 1: Initialize Modern.js Host App
23
+
24
+ **Files:**
25
+ - Create: `host/package.json`
26
+ - Create: `host/tsconfig.json`
27
+ - Create: `host/modern.config.ts`
28
+ - Create: `host/.gitignore`
29
+
30
+ ### Step 1: Create host package.json
31
+
32
+ **File:** `host/package.json`
33
+
34
+ ```json
35
+ {
36
+ "name": "host",
37
+ "version": "1.0.0",
38
+ "private": true,
39
+ "type": "module",
40
+ "scripts": {
41
+ "dev": "modern dev",
42
+ "build": "modern build",
43
+ "start": "modern start",
44
+ "lint": "eslint src --ext .ts,.tsx",
45
+ "typecheck": "tsc --noEmit"
46
+ },
47
+ "dependencies": {
48
+ "@garfish/hooks": "^1.22.0",
49
+ "@modern-js/runtime": "^2.60.0",
50
+ "@modern-js/runtime/garfish": "^2.60.0",
51
+ "@radix-ui/react-dropdown-menu": "^2.1.2",
52
+ "@radix-ui/react-slot": "^1.1.0",
53
+ "@tanstack/react-query": "^5.59.0",
54
+ "class-variance-authority": "^0.7.0",
55
+ "clsx": "^2.1.1",
56
+ "garfish": "^1.22.0",
57
+ "lucide-react": "^0.454.0",
58
+ "pocketbase": "^0.21.5",
59
+ "react": "^18.3.1",
60
+ "react-dom": "^18.3.1",
61
+ "tailwind-merge": "^2.5.4",
62
+ "tailwindcss-animate": "^1.0.7",
63
+ "zustand": "^5.0.1"
64
+ },
65
+ "devDependencies": {
66
+ "@modern-js/app-tools": "^2.60.0",
67
+ "@modern-js/plugin-garfish": "^2.60.0",
68
+ "@modern-js/tsconfig": "^2.60.0",
69
+ "@types/react": "^18.3.12",
70
+ "@types/react-dom": "^18.3.1",
71
+ "autoprefixer": "^10.4.20",
72
+ "postcss": "^8.4.49",
73
+ "tailwindcss": "^3.4.15",
74
+ "typescript": "^5.6.3"
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### Step 2: Create host tsconfig.json
80
+
81
+ **File:** `host/tsconfig.json`
82
+
83
+ ```json
84
+ {
85
+ "extends": "@modern-js/tsconfig/base.json",
86
+ "compilerOptions": {
87
+ "jsx": "react-jsx",
88
+ "strict": true,
89
+ "esModuleInterop": true,
90
+ "skipLibCheck": true,
91
+ "moduleResolution": "bundler",
92
+ "resolveJsonModule": true,
93
+ "isolatedModules": true,
94
+ "paths": {
95
+ "@/*": ["./src/*"],
96
+ "@lego/kernel/*": ["./src/kernel/*"]
97
+ }
98
+ },
99
+ "include": ["src"],
100
+ "exclude": ["node_modules", "dist"]
101
+ }
102
+ ```
103
+
104
+ ### Step 3: Create host modern.config.ts
105
+
106
+ **File:** `host/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
+ // Enable Rspack for high-performance builds
114
+ tools: {
115
+ rspack: (config) => {
116
+ return config;
117
+ },
118
+ },
119
+
120
+ // Enable file-based routing for the host kernel
121
+ runtime: {
122
+ router: true,
123
+ },
124
+
125
+ // Register Garfish plugin for micro-frontend capabilities
126
+ plugins: [appTools(), garfishPlugin()],
127
+ });
128
+ ```
129
+
130
+ ### Step 4: Create host .gitignore
131
+
132
+ **File:** `host/.gitignore`
133
+
134
+ ```
135
+ node_modules/
136
+ dist/
137
+ .modern/
138
+ *.local
139
+ .DS_Store
140
+ ```
141
+
142
+ ### Step 5: Install host dependencies
143
+
144
+ **Run:** From root directory
145
+
146
+ ```bash
147
+ pnpm install
148
+ ```
149
+
150
+ Expected: All dependencies installed successfully.
151
+
152
+ ---
153
+
154
+ ## Task 2: Configure Tailwind CSS for Modern.js
155
+
156
+ **Files:**
157
+ - Create: `host/tailwind.config.ts`
158
+ - Create: `host/postcss.config.js`
159
+ - Create: `host/src/global.css`
160
+ - Create: `host/src/App.tsx`
161
+
162
+ ### Step 1: Create Tailwind config
163
+
164
+ **File:** `host/tailwind.config.ts`
165
+
166
+ ```typescript
167
+ import type { Config } from 'tailwindcss';
168
+
169
+ const config: Config = {
170
+ darkMode: ['class'],
171
+ content: [
172
+ './src/**/*.{js,ts,jsx,tsx,mdx}',
173
+ '../../packages/plugins/@lego/*/src/**/*.{js,ts,jsx,tsx,mdx}',
174
+ ],
175
+ theme: {
176
+ container: {
177
+ center: true,
178
+ padding: '2rem',
179
+ screens: {
180
+ '2xl': '1400px',
181
+ },
182
+ },
183
+ extend: {
184
+ colors: {
185
+ border: 'hsl(var(--border))',
186
+ input: 'hsl(var(--input))',
187
+ ring: 'hsl(var(--ring))',
188
+ background: 'hsl(var(--background))',
189
+ foreground: 'hsl(var(--foreground))',
190
+ primary: {
191
+ DEFAULT: 'hsl(var(--primary))',
192
+ foreground: 'hsl(var(--primary-foreground))',
193
+ },
194
+ secondary: {
195
+ DEFAULT: 'hsl(var(--secondary))',
196
+ foreground: 'hsl(var(--secondary-foreground))',
197
+ },
198
+ destructive: {
199
+ DEFAULT: 'hsl(var(--destructive))',
200
+ foreground: 'hsl(var(--destructive-foreground))',
201
+ },
202
+ muted: {
203
+ DEFAULT: 'hsl(var(--muted))',
204
+ foreground: 'hsl(var(--muted-foreground))',
205
+ },
206
+ accent: {
207
+ DEFAULT: 'hsl(var(--accent))',
208
+ foreground: 'hsl(var(--accent-foreground))',
209
+ },
210
+ popover: {
211
+ DEFAULT: 'hsl(var(--popover))',
212
+ foreground: 'hsl(var(--popover-foreground))',
213
+ },
214
+ card: {
215
+ DEFAULT: 'hsl(var(--card))',
216
+ foreground: 'hsl(var(--card-foreground))',
217
+ },
218
+ },
219
+ borderRadius: {
220
+ lg: 'var(--radius)',
221
+ md: 'calc(var(--radius) - 2px)',
222
+ sm: 'calc(var(--radius) - 4px)',
223
+ },
224
+ keyframes: {
225
+ 'accordion-down': {
226
+ from: { height: '0' },
227
+ to: { height: 'var(--radix-accordion-content-height)' },
228
+ },
229
+ 'accordion-up': {
230
+ from: { height: 'var(--radix-accordion-content-height)' },
231
+ to: { height: '0' },
232
+ },
233
+ },
234
+ animation: {
235
+ 'accordion-down': 'accordion-down 0.2s ease-out',
236
+ 'accordion-up': 'accordion-up 0.2s ease-out',
237
+ },
238
+ },
239
+ },
240
+ plugins: [require('tailwindcss-animate')],
241
+ };
242
+
243
+ export default config;
244
+ ```
245
+
246
+ ### Step 2: Create PostCSS config
247
+
248
+ **File:** `host/postcss.config.js`
249
+
250
+ ```javascript
251
+ module.exports = {
252
+ plugins: {
253
+ tailwindcss: {},
254
+ autoprefixer: {},
255
+ },
256
+ };
257
+ ```
258
+
259
+ ### Step 3: Create global CSS with CSS variables
260
+
261
+ **File:** `host/src/global.css`
262
+
263
+ ```css
264
+ @tailwind base;
265
+ @tailwind components;
266
+ @tailwind utilities;
267
+
268
+ @layer base {
269
+ :root {
270
+ --background: 0 0% 100%;
271
+ --foreground: 222.2 84% 4.9%;
272
+ --card: 0 0% 100%;
273
+ --card-foreground: 222.2 84% 4.9%;
274
+ --popover: 0 0% 100%;
275
+ --popover-foreground: 222.2 84% 4.9%;
276
+ --primary: 221.2 83.2% 53.3%;
277
+ --primary-foreground: 210 40% 98%;
278
+ --secondary: 210 40% 96.1%;
279
+ --secondary-foreground: 222.2 47.4% 11.2%;
280
+ --muted: 210 40% 96.1%;
281
+ --muted-foreground: 215.4 16.3% 46.9%;
282
+ --accent: 210 40% 96.1%;
283
+ --accent-foreground: 222.2 47.4% 11.2%;
284
+ --destructive: 0 84.2% 60.2%;
285
+ --destructive-foreground: 210 40% 98%;
286
+ --border: 214.3 31.8% 91.4%;
287
+ --input: 214.3 31.8% 91.4%;
288
+ --ring: 221.2 83.2% 53.3%;
289
+ --radius: 0.5rem;
290
+ }
291
+
292
+ .dark {
293
+ --background: 222.2 84% 4.9%;
294
+ --foreground: 210 40% 98%;
295
+ --card: 222.2 84% 4.9%;
296
+ --card-foreground: 210 40% 98%;
297
+ --popover: 222.2 84% 4.9%;
298
+ --popover-foreground: 210 40% 98%;
299
+ --primary: 217.2 91.2% 59.8%;
300
+ --primary-foreground: 222.2 47.4% 11.2%;
301
+ --secondary: 217.2 32.6% 17.5%;
302
+ --secondary-foreground: 210 40% 98%;
303
+ --muted: 217.2 32.6% 17.5%;
304
+ --muted-foreground: 215 20.2% 65.1%;
305
+ --accent: 217.2 32.6% 17.5%;
306
+ --accent-foreground: 210 40% 98%;
307
+ --destructive: 0 62.8% 30.6%;
308
+ --destructive-foreground: 210 40% 98%;
309
+ --border: 217.2 32.6% 17.5%;
310
+ --input: 217.2 32.6% 17.5%;
311
+ --ring: 224.3 76.3% 48%;
312
+ }
313
+ }
314
+
315
+ @layer base {
316
+ * {
317
+ @apply border-border;
318
+ }
319
+ body {
320
+ @apply bg-background text-foreground;
321
+ }
322
+ }
323
+ ```
324
+
325
+ ### Step 4: Create root App component
326
+
327
+ **File:** `host/src/App.tsx`
328
+
329
+ ```typescript
330
+ import { Outlet } from '@modern-js/runtime/router';
331
+ import './global.css';
332
+
333
+ export default function App() {
334
+ return <Outlet />;
335
+ }
336
+ ```
337
+
338
+ ---
339
+
340
+ ## Task 3: Create Shared State Bridge
341
+
342
+ **Files:**
343
+ - Create: `host/src/kernel/shared-state/types.ts`
344
+ - Create: `host/src/kernel/shared-state/store.ts`
345
+ - Create: `host/src/kernel/shared-state/bridge.ts`
346
+ - Create: `host/src/kernel/shared-state/index.ts`
347
+
348
+ ### Step 1: Create shared state types
349
+
350
+ **File:** `host/src/kernel/shared-state/types.ts`
351
+
352
+ ```typescript
353
+ import type { RecordAuth } from 'pocketbase';
354
+
355
+ export interface User {
356
+ id: string;
357
+ email: string;
358
+ name: string;
359
+ avatar?: string;
360
+ }
361
+
362
+ export interface Organization {
363
+ id: string;
364
+ name: string;
365
+ slug: string;
366
+ }
367
+
368
+ export interface AuthState {
369
+ user: User | null;
370
+ token: string | null;
371
+ isAuthenticated: boolean;
372
+ organization: Organization | null;
373
+ }
374
+
375
+ export type Theme = 'light' | 'dark' | 'system';
376
+
377
+ export interface UIState {
378
+ theme: Theme;
379
+ sidebarOpen: boolean;
380
+ mobileMenuOpen: boolean;
381
+ }
382
+
383
+ export interface Toast {
384
+ id: string;
385
+ title: string;
386
+ description?: string;
387
+ variant?: 'default' | 'destructive' | 'success';
388
+ }
389
+
390
+ export interface GlobalKernelState extends AuthState, UIState {
391
+ // Auth actions
392
+ setUser: (user: User | null) => void;
393
+ setToken: (token: string | null) => void;
394
+ setOrganization: (org: Organization | null) => void;
395
+ clearAuth: () => void;
396
+
397
+ // UI actions
398
+ setTheme: (theme: Theme) => void;
399
+ toggleSidebar: () => void;
400
+ setSidebarOpen: (open: boolean) => void;
401
+ toggleMobileMenu: () => void;
402
+ setMobileMenuOpen: (open: boolean) => void;
403
+
404
+ // Toast actions
405
+ toasts: Toast[];
406
+ addToast: (toast: Omit<Toast, 'id'>) => void;
407
+ removeToast: (id: string) => void;
408
+
409
+ // Loading state
410
+ isLoading: boolean;
411
+ setIsLoading: (loading: boolean) => void;
412
+ }
413
+ ```
414
+
415
+ ### Step 2: Create Zustand store
416
+
417
+ **File:** `host/src/kernel/shared-state/store.ts`
418
+
419
+ ```typescript
420
+ import { create } from 'zustand';
421
+ import { devtools, persist } from 'zustand/middleware';
422
+ import type { GlobalKernelState, Theme, User, Organization, Toast } from './types';
423
+
424
+ const generateId = () => Math.random().toString(36).substring(2, 9);
425
+
426
+ export const useGlobalKernelState = create<GlobalKernelState>()(
427
+ devtools(
428
+ persist(
429
+ (set) => ({
430
+ // Initial state
431
+ user: null,
432
+ token: null,
433
+ isAuthenticated: false,
434
+ organization: null,
435
+ theme: 'system',
436
+ sidebarOpen: true,
437
+ mobileMenuOpen: false,
438
+ toasts: [],
439
+ isLoading: false,
440
+
441
+ // Auth actions
442
+ setUser: (user: User | null) => set({ user, isAuthenticated: !!user }),
443
+ setToken: (token: string | null) => set({ token }),
444
+ setOrganization: (org: Organization | null) => set({ organization: org }),
445
+ clearAuth: () => set({
446
+ user: null,
447
+ token: null,
448
+ isAuthenticated: false,
449
+ organization: null,
450
+ }),
451
+
452
+ // UI actions
453
+ setTheme: (theme: Theme) => set({ theme }),
454
+ toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
455
+ setSidebarOpen: (open: boolean) => set({ sidebarOpen: open }),
456
+ toggleMobileMenu: () => set((state) => ({ mobileMenuOpen: !state.mobileMenuOpen })),
457
+ setMobileMenuOpen: (open: boolean) => set({ mobileMenuOpen: open }),
458
+
459
+ // Toast actions
460
+ addToast: (toast: Omit<Toast, 'id'>) => set((state) => ({
461
+ toasts: [...state.toasts, { ...toast, id: generateId() }],
462
+ })),
463
+ removeToast: (id: string) => set((state) => ({
464
+ toasts: state.toasts.filter((t) => t.id !== id),
465
+ })),
466
+
467
+ // Loading actions
468
+ setIsLoading: (isLoading: boolean) => set({ isLoading }),
469
+ }),
470
+ {
471
+ name: 'lego-kernel-state',
472
+ partialize: (state) => ({
473
+ theme: state.theme,
474
+ sidebarOpen: state.sidebarOpen,
475
+ token: state.token,
476
+ }),
477
+ }
478
+ ),
479
+ { name: 'LegoKernelState' }
480
+ )
481
+ );
482
+ ```
483
+
484
+ ### Step 3: Create window bridge for plugins
485
+
486
+ **File:** `host/src/kernel/shared-state/bridge.ts`
487
+
488
+ ```typescript
489
+ import { useGlobalKernelState } from './store';
490
+
491
+ // Declare window type for shared state
492
+ declare global {
493
+ interface Window {
494
+ __LEGO_KERNEL_STATE__?: {
495
+ useGlobalKernelState: typeof useGlobalKernelState;
496
+ };
497
+ }
498
+ }
499
+
500
+ // Register shared state to window for plugin access
501
+ export function registerSharedState() {
502
+ if (typeof window !== 'undefined') {
503
+ window.__LEGO_KERNEL_STATE__ = {
504
+ useGlobalKernelState,
505
+ };
506
+ }
507
+ }
508
+
509
+ // Hook for plugins to access kernel state
510
+ export function useKernelState() {
511
+ return useGlobalKernelState;
512
+ }
513
+ ```
514
+
515
+ ### Step 4: Create barrel export
516
+
517
+ **File:** `host/src/kernel/shared-state/index.ts`
518
+
519
+ ```typescript
520
+ export * from './types';
521
+ export * from './store';
522
+ export * from './bridge';
523
+ ```
524
+
525
+ ---
526
+
527
+ ## Task 4: Create Base Providers
528
+
529
+ **Files:**
530
+ - Create: `host/src/kernel/providers/PocketBaseProvider.tsx`
531
+ - Create: `host/src/kernel/providers/QueryProvider.tsx`
532
+ - Create: `host/src/kernel/providers/ThemeProvider.tsx`
533
+ - Create: `host/src/kernel/providers/index.ts`
534
+
535
+ ### Step 1: Create PocketBase provider
536
+
537
+ **File:** `host/src/kernel/providers/PocketBaseProvider.tsx`
538
+
539
+ ```typescript
540
+ import { createContext, useContext, useEffect, useState } from 'react';
541
+ import PocketBase from 'pocketbase';
542
+ import { useGlobalKernelState } from '../shared-state';
543
+
544
+ const PocketBaseContext = createContext<PocketBase | null>(null);
545
+
546
+ export function PocketBaseProvider({ children }: { children: React.ReactNode }) {
547
+ const [pb, setPb] = useState<PocketBase | null>(null);
548
+ const { token, clearAuth } = useGlobalKernelState();
549
+
550
+ useEffect(() => {
551
+ // Initialize PocketBase client
552
+ const client = new PocketBase(import.meta.env.VITE_POCKETBASE_URL || 'http://127.0.0.1:8090');
553
+
554
+ // Load stored token
555
+ const storedToken = localStorage.getItem('pocketbase_auth');
556
+ if (storedToken) {
557
+ client.authStore.save(storedToken, null);
558
+ }
559
+
560
+ setPb(client);
561
+
562
+ // Set up auth cleanup on token invalidation
563
+ client.authStore.onChange((token, model) => {
564
+ if (!token) {
565
+ clearAuth();
566
+ localStorage.removeItem('pocketbase_auth');
567
+ } else {
568
+ localStorage.setItem('pocketbase_auth', token);
569
+ }
570
+ });
571
+
572
+ return () => {
573
+ // Cleanup on unmount
574
+ };
575
+ }, [clearAuth]);
576
+
577
+ // Update auth store when token changes in state
578
+ useEffect(() => {
579
+ if (pb && token && pb.authStore.token !== token) {
580
+ pb.authStore.save(token, null);
581
+ }
582
+ }, [pb, token]);
583
+
584
+ if (!pb) {
585
+ return null; // or a loading spinner
586
+ }
587
+
588
+ return (
589
+ <PocketBaseContext.Provider value={pb}>
590
+ {children}
591
+ </PocketBaseContext.Provider>
592
+ );
593
+ }
594
+
595
+ export function usePocketBase() {
596
+ const context = useContext(PocketBaseContext);
597
+ if (!context) {
598
+ throw new Error('usePocketBase must be used within PocketBaseProvider');
599
+ }
600
+ return context;
601
+ }
602
+ ```
603
+
604
+ ### Step 2: Create TanStack Query provider
605
+
606
+ **File:** `host/src/kernel/providers/QueryProvider.tsx`
607
+
608
+ ```typescript
609
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
610
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
611
+ import { useState, type ReactNode } from 'react';
612
+
613
+ export function QueryProvider({ children }: { children: ReactNode }) {
614
+ const [queryClient] = useState(
615
+ () =>
616
+ new QueryClient({
617
+ defaultOptions: {
618
+ queries: {
619
+ staleTime: 1000 * 60 * 5, // 5 minutes
620
+ gcTime: 1000 * 60 * 30, // 30 minutes (formerly cacheTime)
621
+ retry: 1,
622
+ refetchOnWindowFocus: false,
623
+ },
624
+ },
625
+ })
626
+ );
627
+
628
+ return (
629
+ <QueryClientProvider client={queryClient}>
630
+ {children}
631
+ {import.meta.env.MODE === 'development' && (
632
+ <ReactQueryDevtools initialIsOpen={false} />
633
+ )}
634
+ </QueryClientProvider>
635
+ );
636
+ }
637
+ ```
638
+
639
+ ### Step 3: Create theme provider
640
+
641
+ **File:** `host/src/kernel/providers/ThemeProvider.tsx`
642
+
643
+ ```typescript
644
+ import { useEffect } from 'react';
645
+ import { useGlobalKernelState, type Theme } from '../shared-state';
646
+
647
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
648
+ const { theme } = useGlobalKernelState();
649
+
650
+ useEffect(() => {
651
+ const root = window.document.documentElement;
652
+ root.classList.remove('light', 'dark');
653
+
654
+ let effectiveTheme: 'light' | 'dark' = 'light';
655
+
656
+ if (theme === 'system') {
657
+ effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
658
+ ? 'dark'
659
+ : 'light';
660
+ } else {
661
+ effectiveTheme = theme;
662
+ }
663
+
664
+ root.classList.add(effectiveTheme);
665
+ }, [theme]);
666
+
667
+ return <>{children}</>;
668
+ }
669
+ ```
670
+
671
+ ### Step 4: Create barrel export
672
+
673
+ **File:** `host/src/kernel/providers/index.ts`
674
+
675
+ ```typescript
676
+ export * from './PocketBaseProvider';
677
+ export * from './QueryProvider';
678
+ export * from './ThemeProvider';
679
+ ```
680
+
681
+ ---
682
+
683
+ ## Task 5: Create UI Utilities
684
+
685
+ **Files:**
686
+ - Create: `host/src/kernel/lib/utils.ts`
687
+ - Create: `host/src/kernel/lib/cn.ts`
688
+
689
+ ### Step 1: Create utility functions
690
+
691
+ **File:** `host/src/kernel/lib/utils.ts`
692
+
693
+ ```typescript
694
+ import { type ClassValue, clsx } from 'clsx';
695
+ import { twMerge } from 'tailwind-merge';
696
+
697
+ export function cn(...inputs: ClassValue[]) {
698
+ return twMerge(clsx(inputs));
699
+ }
700
+
701
+ export function getInitials(name: string): string {
702
+ return name
703
+ .split(' ')
704
+ .map((n) => n[0])
705
+ .join('')
706
+ .toUpperCase()
707
+ .slice(0, 2);
708
+ }
709
+
710
+ export function formatDate(date: string | Date): string {
711
+ return new Intl.DateTimeFormat('en-US', {
712
+ month: 'short',
713
+ day: 'numeric',
714
+ year: 'numeric',
715
+ }).format(new Date(date));
716
+ }
717
+
718
+ export function formatRelativeTime(date: string | Date): string {
719
+ const now = new Date();
720
+ const past = new Date(date);
721
+ const diffInSeconds = Math.floor((now.getTime() - past.getTime()) / 1000);
722
+
723
+ if (diffInSeconds < 60) return 'just now';
724
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
725
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
726
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
727
+
728
+ return formatDate(date);
729
+ }
730
+ ```
731
+
732
+ ### Step 2: Create barrel export
733
+
734
+ **File:** `host/src/kernel/lib/cn.ts`
735
+
736
+ ```typescript
737
+ export { cn } from './utils';
738
+ ```
739
+
740
+ ---
741
+
742
+ ## Task 6: Create Host Layout Structure
743
+
744
+ **Files:**
745
+ - Create: `host/src/layout/Shell.tsx`
746
+ - Create: `host/src/layout/Sidebar.tsx`
747
+ - Create: `host/src/layout/Topbar.tsx`
748
+ - Create: `host/src/layout/MobileMenu.tsx`
749
+ - Create: `host/src/layout/index.ts`
750
+
751
+ ### Step 1: Create shell layout component
752
+
753
+ **File:** `host/src/layout/Shell.tsx`
754
+
755
+ ```typescript
756
+ import { Outlet } from '@modern-js/runtime/router';
757
+ import { Sidebar } from './Sidebar';
758
+ import { Topbar } from './Topbar';
759
+ import { MobileMenu } from './MobileMenu';
760
+ import { useGlobalKernelState } from '../kernel/shared-state';
761
+
762
+ export function Shell() {
763
+ const { sidebarOpen, mobileMenuOpen } = useGlobalKernelState();
764
+
765
+ return (
766
+ <div className="min-h-screen bg-background">
767
+ {/* Mobile menu overlay */}
768
+ {mobileMenuOpen && (
769
+ <div
770
+ className="fixed inset-0 z-40 bg-background/80 backdrop-blur-sm lg:hidden"
771
+ aria-hidden="true"
772
+ />
773
+ )}
774
+
775
+ {/* Sidebar */}
776
+ <Sidebar />
777
+
778
+ {/* Main content area */}
779
+ <div
780
+ className={`transition-all duration-300 lg:pl-64 ${
781
+ sidebarOpen ? 'lg:pl-64' : 'lg:pl-16'
782
+ }`}
783
+ >
784
+ {/* Topbar */}
785
+ <Topbar />
786
+
787
+ {/* Page content */}
788
+ <main className="min-h-[calc(100vh-4rem)] p-6">
789
+ <Outlet />
790
+ </main>
791
+ </div>
792
+
793
+ {/* Mobile menu */}
794
+ <MobileMenu />
795
+ </div>
796
+ );
797
+ }
798
+ ```
799
+
800
+ ### Step 2: Create sidebar component
801
+
802
+ **File:** `host/src/layout/Sidebar.tsx`
803
+
804
+ ```typescript
805
+ import { Link, NavLink } from '@modern-js/runtime/router';
806
+ import { useGlobalKernelState } from '../kernel/shared-state';
807
+ import {
808
+ Home,
809
+ LayoutDashboard,
810
+ Settings,
811
+ ChevronLeft,
812
+ ChevronRight,
813
+ CheckSquare,
814
+ } from 'lucide-react';
815
+ import { cn } from '../kernel/lib/utils';
816
+
817
+ const navItems = [
818
+ { to: '/', icon: Home, label: 'Home' },
819
+ { to: '/dashboard', icon: LayoutDashboard, label: 'Dashboard' },
820
+ { to: '/todos', icon: CheckSquare, label: 'Todos' },
821
+ ];
822
+
823
+ export function Sidebar() {
824
+ const { sidebarOpen, toggleSidebar, mobileMenuOpen } = useGlobalKernelState();
825
+
826
+ return (
827
+ <>
828
+ {/* Mobile sidebar */}
829
+ <aside
830
+ className={cn(
831
+ 'fixed inset-y-0 left-0 z-50 w-64 transform border-r bg-card transition-transform duration-300 lg:hidden',
832
+ mobileMenuOpen ? 'translate-x-0' : '-translate-x-full'
833
+ )}
834
+ >
835
+ <div className="flex h-16 items-center justify-between border-b px-6">
836
+ <Link to="/" className="flex items-center gap-2 font-semibold">
837
+ <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
838
+ L
839
+ </div>
840
+ <span>Lego-One</span>
841
+ </Link>
842
+ </div>
843
+
844
+ <nav className="space-y-1 p-4">
845
+ {navItems.map((item) => (
846
+ <NavLink
847
+ key={item.to}
848
+ to={item.to}
849
+ end={item.to === '/'}
850
+ className={({ isActive }) =>
851
+ cn(
852
+ 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
853
+ isActive
854
+ ? 'bg-primary text-primary-foreground'
855
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'
856
+ )
857
+ }
858
+ >
859
+ <item.icon className="h-5 w-5" />
860
+ {item.label}
861
+ </NavLink>
862
+ ))}
863
+ </nav>
864
+
865
+ <div className="absolute bottom-0 left-0 right-0 border-t p-4">
866
+ <NavLink
867
+ to="/settings"
868
+ className={({ isActive }) =>
869
+ cn(
870
+ 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
871
+ isActive
872
+ ? 'bg-primary text-primary-foreground'
873
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'
874
+ )
875
+ }
876
+ >
877
+ <Settings className="h-5 w-5" />
878
+ Settings
879
+ </NavLink>
880
+ </div>
881
+ </aside>
882
+
883
+ {/* Desktop sidebar */}
884
+ <aside
885
+ className={cn(
886
+ 'fixed inset-y-0 left-0 z-30 hidden border-r bg-card transition-all duration-300 lg:block',
887
+ sidebarOpen ? 'w-64' : 'w-16'
888
+ )}
889
+ >
890
+ <div className="flex h-16 items-center justify-between border-b px-4">
891
+ {sidebarOpen ? (
892
+ <Link to="/" className="flex items-center gap-2 font-semibold">
893
+ <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
894
+ L
895
+ </div>
896
+ <span>Lego-One</span>
897
+ </Link>
898
+ ) : (
899
+ <Link to="/" className="flex items-center justify-center">
900
+ <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
901
+ L
902
+ </div>
903
+ </Link>
904
+ )}
905
+
906
+ <button
907
+ onClick={toggleSidebar}
908
+ className="rounded-lg p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground"
909
+ >
910
+ {sidebarOpen ? (
911
+ <ChevronLeft className="h-5 w-5" />
912
+ ) : (
913
+ <ChevronRight className="h-5 w-5" />
914
+ )}
915
+ </button>
916
+ </div>
917
+
918
+ <nav className="space-y-1 p-2">
919
+ {navItems.map((item) => (
920
+ <NavLink
921
+ key={item.to}
922
+ to={item.to}
923
+ end={item.to === '/'}
924
+ className={({ isActive }) =>
925
+ cn(
926
+ 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
927
+ !sidebarOpen && 'justify-center',
928
+ isActive
929
+ ? 'bg-primary text-primary-foreground'
930
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'
931
+ )
932
+ }
933
+ title={!sidebarOpen ? item.label : undefined}
934
+ >
935
+ <item.icon className="h-5 w-5 flex-shrink-0" />
936
+ {sidebarOpen && <span>{item.label}</span>}
937
+ </NavLink>
938
+ ))}
939
+ </nav>
940
+
941
+ <div className="absolute bottom-0 left-0 right-0 border-t p-2">
942
+ <NavLink
943
+ to="/settings"
944
+ className={({ isActive }) =>
945
+ cn(
946
+ 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
947
+ !sidebarOpen && 'justify-center',
948
+ isActive
949
+ ? 'bg-primary text-primary-foreground'
950
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'
951
+ )
952
+ }
953
+ title={!sidebarOpen ? 'Settings' : undefined}
954
+ >
955
+ <Settings className="h-5 w-5 flex-shrink-0" />
956
+ {sidebarOpen && <span>Settings</span>}
957
+ </NavLink>
958
+ </div>
959
+ </aside>
960
+ </>
961
+ );
962
+ }
963
+ ```
964
+
965
+ ### Step 3: Create topbar component
966
+
967
+ **File:** `host/src/layout/Topbar.tsx`
968
+
969
+ ```typescript
970
+ import { useGlobalKernelState } from '../kernel/shared-state';
971
+ import { Menu, Bell, User } from 'lucide-react';
972
+ import { Link } from '@modern-js/runtime/router';
973
+ import { getInitials } from '../kernel/lib/utils';
974
+
975
+ export function Topbar() {
976
+ const { toggleMobileMenu, user } = useGlobalKernelState();
977
+
978
+ return (
979
+ <header className="sticky top-0 z-20 flex h-16 items-center gap-4 border-b bg-background px-6">
980
+ {/* Mobile menu button */}
981
+ <button
982
+ onClick={toggleMobileMenu}
983
+ className="lg:hidden rounded-lg p-2 text-muted-foreground hover:bg-muted"
984
+ >
985
+ <Menu className="h-5 w-5" />
986
+ </button>
987
+
988
+ {/* Breadcrumb/spacer */}
989
+ <div className="flex-1" />
990
+
991
+ {/* Actions */}
992
+ <div className="flex items-center gap-2">
993
+ <button className="rounded-lg p-2 text-muted-foreground hover:bg-muted">
994
+ <Bell className="h-5 w-5" />
995
+ </button>
996
+
997
+ {user ? (
998
+ <Link
999
+ to="/settings/profile"
1000
+ className="flex h-9 w-9 items-center justify-center rounded-full bg-primary text-primary-foreground text-sm font-medium"
1001
+ >
1002
+ {getInitials(user.name)}
1003
+ </Link>
1004
+ ) : (
1005
+ <div className="flex h-9 w-9 items-center justify-center rounded-full bg-muted text-muted-foreground">
1006
+ <User className="h-5 w-5" />
1007
+ </div>
1008
+ )}
1009
+ </div>
1010
+ </header>
1011
+ );
1012
+ }
1013
+ ```
1014
+
1015
+ ### Step 4: Create mobile menu component
1016
+
1017
+ **File:** `host/src/layout/MobileMenu.tsx`
1018
+
1019
+ ```typescript
1020
+ import { NavLink } from '@modern-js/runtime/router';
1021
+ import { useGlobalKernelState } from '../kernel/shared-state';
1022
+ import {
1023
+ Home,
1024
+ LayoutDashboard,
1025
+ Settings,
1026
+ CheckSquare,
1027
+ X,
1028
+ } from 'lucide-react';
1029
+ import { cn } from '../kernel/lib/utils';
1030
+
1031
+ const navItems = [
1032
+ { to: '/', icon: Home, label: 'Home' },
1033
+ { to: '/dashboard', icon: LayoutDashboard, label: 'Dashboard' },
1034
+ { to: '/todos', icon: CheckSquare, label: 'Todos' },
1035
+ { to: '/settings', icon: Settings, label: 'Settings' },
1036
+ ];
1037
+
1038
+ export function MobileMenu() {
1039
+ const { mobileMenuOpen, setMobileMenuOpen } = useGlobalKernelState();
1040
+
1041
+ if (!mobileMenuOpen) return null;
1042
+
1043
+ return (
1044
+ <div className="fixed inset-0 z-50 lg:hidden">
1045
+ <div className="fixed inset-y-0 right-0 w-64 border-l bg-background p-6 shadow-lg">
1046
+ <div className="flex items-center justify-between mb-8">
1047
+ <h2 className="text-lg font-semibold">Menu</h2>
1048
+ <button
1049
+ onClick={() => setMobileMenuOpen(false)}
1050
+ className="rounded-lg p-2 text-muted-foreground hover:bg-muted"
1051
+ >
1052
+ <X className="h-5 w-5" />
1053
+ </button>
1054
+ </div>
1055
+
1056
+ <nav className="space-y-1">
1057
+ {navItems.map((item) => (
1058
+ <NavLink
1059
+ key={item.to}
1060
+ to={item.to}
1061
+ end={item.to === '/'}
1062
+ onClick={() => setMobileMenuOpen(false)}
1063
+ className={({ isActive }) =>
1064
+ cn(
1065
+ 'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
1066
+ isActive
1067
+ ? 'bg-primary text-primary-foreground'
1068
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'
1069
+ )
1070
+ }
1071
+ >
1072
+ <item.icon className="h-5 w-5" />
1073
+ {item.label}
1074
+ </NavLink>
1075
+ ))}
1076
+ </nav>
1077
+ </div>
1078
+ </div>
1079
+ );
1080
+ }
1081
+ ```
1082
+
1083
+ ### Step 5: Create barrel export
1084
+
1085
+ **File:** `host/src/layout/index.ts`
1086
+
1087
+ ```typescript
1088
+ export * from './Shell';
1089
+ export * from './Sidebar';
1090
+ export * from './Topbar';
1091
+ export * from './MobileMenu';
1092
+ ```
1093
+
1094
+ ---
1095
+
1096
+ ## Task 7: Configure Garfish and Create Plugin Routes
1097
+
1098
+ **Files:**
1099
+ - Create: `host/src/modern.runtime.ts`
1100
+ - Create: `host/src/routes/_.tsx`
1101
+ - Create: `host/src/routes/index.tsx`
1102
+ - Create: `host/src/routes/dashboard._.tsx`
1103
+
1104
+ ### Step 1: Create Modern.js runtime config
1105
+
1106
+ **File:** `host/src/modern.runtime.ts`
1107
+
1108
+ ```typescript
1109
+ import { defineRuntimeConfig } from '@modern-js/runtime';
1110
+
1111
+ // Development vs Production configuration
1112
+ const isDev = import.meta.env.MODE === 'development';
1113
+
1114
+ export default defineRuntimeConfig({
1115
+ masterApp: {
1116
+ apps: [
1117
+ {
1118
+ name: '@lego/plugin-dashboard',
1119
+ // Dev: Separate server │ Prod: Bundled via dynamic import
1120
+ entry: isDev
1121
+ ? 'http://localhost:3001'
1122
+ : () => import('@lego/plugin-dashboard'),
1123
+ activeWhen: '/dashboard',
1124
+ },
1125
+ {
1126
+ name: '@lego/plugin-todo',
1127
+ entry: isDev
1128
+ ? 'http://localhost:3002'
1129
+ : () => import('@lego/plugin-todo'),
1130
+ activeWhen: '/todos',
1131
+ },
1132
+ ],
1133
+ },
1134
+ });
1135
+ ```
1136
+
1137
+ ### Step 2: Create plugin route wrapper
1138
+
1139
+ **File:** `host/src/routes/_.tsx`
1140
+
1141
+ ```typescript
1142
+ import { Outlet } from '@modern-js/runtime/router';
1143
+ import { Shell } from '../layout';
1144
+
1145
+ export default function PluginWrapper() {
1146
+ return (
1147
+ <Shell>
1148
+ <Outlet />
1149
+ </Shell>
1150
+ );
1151
+ }
1152
+ ```
1153
+
1154
+ ### Step 3: Create home/index route
1155
+
1156
+ **File:** `host/src/routes/index.tsx`
1157
+
1158
+ ```typescript
1159
+ import { Link } from '@modern-js/runtime/router';
1160
+ import { ArrowRight, LayoutDashboard, CheckSquare, Shield } from 'lucide-react';
1161
+ import { useGlobalKernelState } from '../kernel/shared-state';
1162
+
1163
+ export default function HomePage() {
1164
+ const { isAuthenticated, user } = useGlobalKernelState();
1165
+
1166
+ return (
1167
+ <div className="mx-auto max-w-4xl">
1168
+ <div className="space-y-6">
1169
+ {/* Hero */}
1170
+ <div className="rounded-xl border bg-card p-8 text-center">
1171
+ <h1 className="text-4xl font-bold tracking-tight">
1172
+ Welcome to Lego-One
1173
+ </h1>
1174
+ <p className="mt-4 text-lg text-muted-foreground">
1175
+ A modular SaaS boilerplate with microkernel architecture
1176
+ </p>
1177
+
1178
+ {isAuthenticated ? (
1179
+ <div className="mt-8">
1180
+ <p className="text-muted-foreground">
1181
+ Welcome back, {user?.name || 'User'}!
1182
+ </p>
1183
+ </div>
1184
+ ) : (
1185
+ <div className="mt-8 flex justify-center gap-4">
1186
+ <Link
1187
+ to="/login"
1188
+ className="inline-flex items-center gap-2 rounded-lg bg-primary px-6 py-3 font-medium text-primary-foreground hover:bg-primary/90"
1189
+ >
1190
+ Sign In
1191
+ <ArrowRight className="h-4 w-4" />
1192
+ </Link>
1193
+ </div>
1194
+ )}
1195
+ </div>
1196
+
1197
+ {/* Features */}
1198
+ <div className="grid gap-6 md:grid-cols-3">
1199
+ <div className="rounded-xl border bg-card p-6">
1200
+ <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
1201
+ <LayoutDashboard className="h-6 w-6" />
1202
+ </div>
1203
+ <h3 className="mt-4 text-lg font-semibold">Dashboard Plugin</h3>
1204
+ <p className="mt-2 text-sm text-muted-foreground">
1205
+ Overview of your account with stats and quick actions
1206
+ </p>
1207
+ <Link
1208
+ to="/dashboard"
1209
+ className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
1210
+ >
1211
+ View Dashboard
1212
+ <ArrowRight className="ml-1 h-4 w-4" />
1213
+ </Link>
1214
+ </div>
1215
+
1216
+ <div className="rounded-xl border bg-card p-6">
1217
+ <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
1218
+ <CheckSquare className="h-6 w-6" />
1219
+ </div>
1220
+ <h3 className="mt-4 text-lg font-semibold">Todo Plugin</h3>
1221
+ <p className="mt-2 text-sm text-muted-foreground">
1222
+ Manage your tasks with this example plugin
1223
+ </p>
1224
+ <Link
1225
+ to="/todos"
1226
+ className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
1227
+ >
1228
+ View Todos
1229
+ <ArrowRight className="ml-1 h-4 w-4" />
1230
+ </Link>
1231
+ </div>
1232
+
1233
+ <div className="rounded-xl border bg-card p-6">
1234
+ <div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 text-primary">
1235
+ <Shield className="h-6 w-6" />
1236
+ </div>
1237
+ <h3 className="mt-4 text-lg font-semibold">Multi-Tenancy</h3>
1238
+ <p className="mt-2 text-sm text-muted-foreground">
1239
+ Built-in support for organizations and RBAC
1240
+ </p>
1241
+ <Link
1242
+ to="/settings"
1243
+ className="mt-4 inline-flex items-center text-sm font-medium text-primary hover:underline"
1244
+ >
1245
+ Manage Settings
1246
+ <ArrowRight className="ml-1 h-4 w-4" />
1247
+ </Link>
1248
+ </div>
1249
+ </div>
1250
+ </div>
1251
+ </div>
1252
+ );
1253
+ }
1254
+ ```
1255
+
1256
+ ### Step 4: Create dashboard plugin route
1257
+
1258
+ **File:** `host/src/routes/dashboard._.tsx`
1259
+
1260
+ ```typescript
1261
+ import { useModuleApps } from '@modern-js/runtime/garfish';
1262
+ import { useGlobalKernelState } from '../kernel/shared-state';
1263
+ import { Skeleton } from '../kernel/components/ui/skeleton';
1264
+
1265
+ export default function DashboardPluginRoute() {
1266
+ const { isAuthenticated, isLoading } = useGlobalKernelState();
1267
+ const moduleApps = useModuleApps();
1268
+
1269
+ const DashboardApp = moduleApps.find((app) => app.name === '@lego/plugin-dashboard');
1270
+
1271
+ if (isLoading) {
1272
+ return (
1273
+ <div className="space-y-6">
1274
+ <Skeleton className="h-8 w-64" />
1275
+ <div className="grid gap-6 md:grid-cols-3">
1276
+ <Skeleton className="h-32" />
1277
+ <Skeleton className="h-32" />
1278
+ <Skeleton className="h-32" />
1279
+ </div>
1280
+ <Skeleton className="h-64" />
1281
+ </div>
1282
+ );
1283
+ }
1284
+
1285
+ if (!isAuthenticated) {
1286
+ return (
1287
+ <div className="text-center">
1288
+ <h1 className="text-2xl font-bold">Authentication Required</h1>
1289
+ <p className="mt-2 text-muted-foreground">
1290
+ Please sign in to access the dashboard.
1291
+ </p>
1292
+ </div>
1293
+ );
1294
+ }
1295
+
1296
+ if (!DashboardApp) {
1297
+ return (
1298
+ <div className="text-center">
1299
+ <h1 className="text-2xl font-bold">Plugin Not Found</h1>
1300
+ <p className="mt-2 text-muted-foreground">
1301
+ The Dashboard plugin could not be loaded.
1302
+ </p>
1303
+ </div>
1304
+ );
1305
+ }
1306
+
1307
+ // The plugin app will be rendered here by Garfish
1308
+ return DashboardApp.App;
1309
+ }
1310
+ ```
1311
+
1312
+ ---
1313
+
1314
+ ## Task 8: Create Bootstrap and Root Entry
1315
+
1316
+ **Files:**
1317
+ - Create: `host/src/bootstrap.tsx`
1318
+ - Create: `host/src/kernel/index.ts`
1319
+ - Modify: `host/src/App.tsx`
1320
+
1321
+ ### Step 1: Create bootstrap file
1322
+
1323
+ **File:** `host/src/bootstrap.tsx`
1324
+
1325
+ ```typescript
1326
+ import { StrictMode } from 'react';
1327
+ import { createRoot } from 'react-dom/client';
1328
+ import { BrowserRouter } from '@modern-js/runtime/router';
1329
+ import App from './App';
1330
+ import { registerSharedState } from './kernel/shared-state';
1331
+ import { PocketBaseProvider } from './kernel/providers';
1332
+ import { QueryProvider } from './kernel/providers';
1333
+ import { ThemeProvider } from './kernel/providers';
1334
+
1335
+ // Register shared state bridge for plugins
1336
+ registerSharedState();
1337
+
1338
+ // Create root
1339
+ const container = document.getElementById('root');
1340
+ if (container) {
1341
+ const root = createRoot(container);
1342
+
1343
+ root.render(
1344
+ <StrictMode>
1345
+ <BrowserRouter>
1346
+ <ThemeProvider>
1347
+ <QueryProvider>
1348
+ <PocketBaseProvider>
1349
+ <App />
1350
+ </PocketBaseProvider>
1351
+ </QueryProvider>
1352
+ </ThemeProvider>
1353
+ </BrowserRouter>
1354
+ </StrictMode>
1355
+ );
1356
+ }
1357
+ ```
1358
+
1359
+ ### Step 2: Create kernel barrel export
1360
+
1361
+ **File:** `host/src/kernel/index.ts`
1362
+
1363
+ ```typescript
1364
+ export * from './shared-state';
1365
+ export * from './providers';
1366
+ export * from './lib/utils';
1367
+ export * from './components';
1368
+ ```
1369
+
1370
+ ### Step 3: Update App.tsx to use shell layout
1371
+
1372
+ **File:** `host/src/App.tsx`
1373
+
1374
+ ```typescript
1375
+ import { Outlet } from '@modern-js/runtime/router';
1376
+ import './global.css';
1377
+
1378
+ export default function App() {
1379
+ return <Outlet />;
1380
+ }
1381
+ ```
1382
+
1383
+ ### Step 4: Create basic UI components needed
1384
+
1385
+ **File:** `host/src/kernel/components/ui/skeleton.tsx`
1386
+
1387
+ ```typescript
1388
+ import { cn } from '../../../lib/utils';
1389
+
1390
+ function Skeleton({
1391
+ className,
1392
+ ...props
1393
+ }: React.HTMLAttributes<HTMLDivElement>) {
1394
+ return (
1395
+ <div
1396
+ className={cn('animate-pulse rounded-md bg-muted', className)}
1397
+ {...props}
1398
+ />
1399
+ );
1400
+ }
1401
+
1402
+ export { Skeleton };
1403
+ ```
1404
+
1405
+ ---
1406
+
1407
+ ## Task 9: Update Root Entry Point
1408
+
1409
+ **Files:**
1410
+ - Create: `host/src/index.ts`
1411
+
1412
+ ### Step 1: Create entry point
1413
+
1414
+ **File:** `host/src/index.ts`
1415
+
1416
+ ```typescript
1417
+ import '@modern-js/runtime/garfish';
1418
+ import './bootstrap';
1419
+ ```
1420
+
1421
+ ---
1422
+
1423
+ ## Verification
1424
+
1425
+ ### Step 1: Build the host
1426
+
1427
+ **Run:**
1428
+
1429
+ ```bash
1430
+ cd host
1431
+ pnpm run build
1432
+ ```
1433
+
1434
+ Expected: Build completes without errors.
1435
+
1436
+ ### Step 2: Start development server
1437
+
1438
+ **Run:**
1439
+
1440
+ ```bash
1441
+ cd host
1442
+ pnpm run dev
1443
+ ```
1444
+
1445
+ Expected: Server starts on http://localhost:8080
1446
+
1447
+ ### Step 3: Verify functionality
1448
+
1449
+ 1. Open http://localhost:8080
1450
+ 2. Should see the home page with "Welcome to Lego-One"
1451
+ 3. Sidebar should be visible with navigation items
1452
+ 4. Click menu items to verify routing works
1453
+ 5. Open browser console and verify `window.__LEGO_KERNEL_STATE__` exists
1454
+
1455
+ ---
1456
+
1457
+ ## Summary
1458
+
1459
+ After completing this document, you will have:
1460
+
1461
+ 1. ✅ Modern.js host app initialized with Garfish plugin
1462
+ 2. ✅ Tailwind CSS configured with Shadcn-compatible theme
1463
+ 3. ✅ Shared state bridge via `window.__LEGO_KERNEL_STATE__`
1464
+ 4. ✅ Zustand store for auth, UI, and toast state
1465
+ 5. ✅ Base providers (PocketBase, TanStack Query, Theme)
1466
+ 6. ✅ Layout structure (Sidebar, Topbar, Shell)
1467
+ 7. ✅ File-based routing with plugin wrappers
1468
+ 8. ✅ Development vs Production plugin loading configuration
1469
+
1470
+ **Next:** `04-auth-system.md` - Implement authentication, login/logout, and session management.
1471
+
1472
+ ---
1473
+
1474
+ ## Files Created
1475
+
1476
+ ```
1477
+ host/
1478
+ ├── package.json
1479
+ ├── tsconfig.json
1480
+ ├── modern.config.ts
1481
+ ├── tailwind.config.ts
1482
+ ├── postcss.config.js
1483
+ ├── .gitignore
1484
+ └── src/
1485
+ ├── global.css
1486
+ ├── index.ts
1487
+ ├── bootstrap.tsx
1488
+ ├── App.tsx
1489
+ ├── modern.runtime.ts
1490
+ ├── kernel/
1491
+ │ ├── index.ts
1492
+ │ ├── shared-state/
1493
+ │ │ ├── types.ts
1494
+ │ │ ├── store.ts
1495
+ │ │ ├── bridge.ts
1496
+ │ │ └── index.ts
1497
+ │ ├── providers/
1498
+ │ │ ├── PocketBaseProvider.tsx
1499
+ │ │ ├── QueryProvider.tsx
1500
+ │ │ ├── ThemeProvider.tsx
1501
+ │ │ └── index.ts
1502
+ │ ├── lib/
1503
+ │ │ ├── utils.ts
1504
+ │ │ └── cn.ts
1505
+ │ └── components/
1506
+ │ └── ui/
1507
+ │ └── skeleton.tsx
1508
+ ├── layout/
1509
+ │ ├── Shell.tsx
1510
+ │ ├── Sidebar.tsx
1511
+ │ ├── Topbar.tsx
1512
+ │ ├── MobileMenu.tsx
1513
+ │ └── index.ts
1514
+ └── routes/
1515
+ ├── _.tsx
1516
+ ├── index.tsx
1517
+ └── dashboard._.tsx
1518
+ ```