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,671 @@
1
+ # Slot Injection Pattern: UI Extension System
2
+
3
+ **Project:** Lego-One (Modern.js SaaS OS)
4
+ **Document:** 05 - Slot Injection Pattern
5
+ **Status:** Research Phase
6
+
7
+ ## Executive Summary
8
+
9
+ This document explains the **Slot Injection Pattern** - how plugins can inject UI components into the Host's layout (Sidebar, Topbar) without modifying the Host's source code. This enables plugins to extend the host UI in a controlled, declarative way.
10
+
11
+ ---
12
+
13
+ ## 1. The Slot Injection Problem
14
+
15
+ ### 1.1 Use Case Example
16
+
17
+ **Scenario:** The "Stripe Plugin" needs to:
18
+ 1. Add a "Billing" link to the Host's Sidebar navigation
19
+ 2. Show a "Credit Balance" badge in the Host's Topbar
20
+ 3. Display a subscription warning banner when credits are low
21
+
22
+ **Challenge:** The plugin should be able to do this **without**:
23
+ - Directly importing/modifying Host components
24
+ - Creating tight coupling between plugin and host
25
+ - Breaking when the Host layout changes
26
+
27
+ ### 1.2 Solution: Slot Registry Pattern
28
+
29
+ ```
30
+ ┌─────────────────────────────────────────────────────────────────────────┐
31
+ │ Slot Injection Flow │
32
+ ├─────────────────────────────────────────────────────────────────────────┤
33
+ │ │
34
+ │ 1. PLUGIN INITIALIZATION │
35
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
36
+ │ │ Plugin reads its plugin.config.ts │ │
37
+ │ │ - Defines which slots to inject │ │
38
+ │ │ - Provides component for each slot │ │
39
+ │ │ - Specifies order and visibility rules │ │
40
+ │ └─────────────────────────────────────────────────────────────────┘ │
41
+ │ │ │
42
+ │ ▼ │
43
+ │ 2. SLOT REGISTRATION │
44
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
45
+ │ │ Plugin calls window.__LEGO_SLOT_REGISTRY__.register() │ │
46
+ │ │ - Registers component with global registry │ │
47
+ │ │ - Stores slot name, component, order, visibility │ │
48
+ │ └─────────────────────────────────────────────────────────────────┘ │
49
+ │ │ │
50
+ │ ▼ │
51
+ │ 3. HOST RENDERING │
52
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
53
+ │ │ Host's Sidebar/Topbar components query the registry │ │
54
+ │ │ - Get all items for a specific slot │ │
55
+ │ │ - Sort by order │ │
56
+ │ │ - Filter by visibility │ │
57
+ │ │ - Render each component │ │
58
+ │ └─────────────────────────────────────────────────────────────────┘ │
59
+ │ │ │
60
+ │ ▼ │
61
+ │ 4. RESULT │
62
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
63
+ │ │ Plugin UI appears in host layout seamlessly │ │
64
+ │ │ - No host code modified │ │
65
+ │ │ - Multiple plugins can inject to same slot │ │
66
+ │ │ - Plugins control their own UI │ │
67
+ │ └─────────────────────────────────────────────────────────────────┘ │
68
+ │ │
69
+ └─────────────────────────────────────────────────────────────────────────┘
70
+ ```
71
+
72
+ ---
73
+
74
+ ## 2. Available Slots
75
+
76
+ ### 2.1 Standard Slot Locations
77
+
78
+ | Slot Name | Location | Description |
79
+ |-----------|----------|-------------|
80
+ | `sidebar:nav` | Sidebar navigation list | Navigation links |
81
+ | `sidebar:header` | Sidebar top section | Branding, logo |
82
+ | `sidebar:footer` | Sidebar bottom section | Version info, help |
83
+ | `topbar:actions` | Topbar right side | Action buttons |
84
+ | `topbar:menu` | Topbar dropdown menu | Menu items |
85
+ | `topbar:search` | Topbar search area | Search components |
86
+ | `dashboard:widgets` | Dashboard grid | Dashboard widgets |
87
+ | `layout:banner` | Above main content | Notification banners |
88
+
89
+ ### 2.2 Slot Item Interface
90
+
91
+ ```typescript
92
+ interface SlotItem {
93
+ // Unique identifier for this slot item
94
+ id: string;
95
+
96
+ // Plugin name (for debugging/unregistration)
97
+ pluginName: string;
98
+
99
+ // React component to render
100
+ component: React.ComponentType;
101
+
102
+ // Sort order (lower = higher priority)
103
+ order: number;
104
+
105
+ // Visibility check function
106
+ isVisible: () => boolean;
107
+
108
+ // Cleanup function (called on unregister)
109
+ onUnmount?: () => void;
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 3. Implementation
116
+
117
+ ### 3.1 Host: Slot Registry
118
+
119
+ **File:** `host/src/kernel/slot-registry.ts`
120
+
121
+ ```typescript
122
+ import { reactive } from '@modern-js/runtime';
123
+
124
+ export type SlotName =
125
+ | 'sidebar:nav'
126
+ | 'sidebar:header'
127
+ | 'sidebar:footer'
128
+ | 'topbar:actions'
129
+ | 'topbar:menu'
130
+ | 'topbar:search'
131
+ | 'dashboard:widgets'
132
+ | 'layout:banner';
133
+
134
+ export interface SlotItem {
135
+ id: string;
136
+ pluginName: string;
137
+ component: React.ComponentType;
138
+ order: number;
139
+ isVisible: () => boolean;
140
+ onUnmount?: () => void;
141
+ }
142
+
143
+ interface SlotRegistry {
144
+ slots: Record<SlotName, SlotItem[]>;
145
+ register: (slot: SlotName, item: Omit<SlotItem, 'id'>) => string;
146
+ unregister: (slot: SlotName, id: string) => void;
147
+ getItems: (slot: SlotName) => SlotItem[];
148
+ clearPlugin: (pluginName: string) => void;
149
+ }
150
+
151
+ // Global singleton for slot registration
152
+ export const slotRegistry: SlotRegistry = reactive({
153
+ slots: {
154
+ 'sidebar:nav': [],
155
+ 'sidebar:header': [],
156
+ 'sidebar:footer': [],
157
+ 'topbar:actions': [],
158
+ 'topbar:menu': [],
159
+ 'topbar:search': [],
160
+ 'dashboard:widgets': [],
161
+ 'layout:banner': [],
162
+ },
163
+
164
+ register(slot: SlotName, item: Omit<SlotItem, 'id'>) {
165
+ const id = `${slot}-${item.pluginName}-${Date.now()}`;
166
+ const slotItem: SlotItem = { ...item, id };
167
+
168
+ if (!this.slots[slot]) {
169
+ this.slots[slot] = [];
170
+ }
171
+
172
+ this.slots[slot].push(slotItem);
173
+
174
+ // Sort by order (lower first)
175
+ this.slots[slot].sort((a, b) => a.order - b.order);
176
+
177
+ console.log(`[SlotRegistry] Registered "${id}" to "${slot}"`);
178
+ return id;
179
+ },
180
+
181
+ unregister(slot: SlotName, id: string) {
182
+ const items = this.slots[slot];
183
+ if (!items) return;
184
+
185
+ const item = items.find((i) => i.id === id);
186
+ if (item?.onUnmount) {
187
+ item.onUnmount();
188
+ }
189
+
190
+ this.slots[slot] = items.filter((i) => i.id !== id);
191
+ console.log(`[SlotRegistry] Unregistered "${id}" from "${slot}"`);
192
+ },
193
+
194
+ getItems(slot: SlotName) {
195
+ const items = this.slots[slot] || [];
196
+ // Filter by visibility
197
+ return items.filter((item) => item.isVisible());
198
+ },
199
+
200
+ clearPlugin(pluginName: string) {
201
+ for (const slot of Object.keys(this.slots) as SlotName[]) {
202
+ const items = this.slots[slot];
203
+ for (const item of items) {
204
+ if (item.pluginName === pluginName) {
205
+ this.unregister(slot, item.id);
206
+ }
207
+ }
208
+ }
209
+ },
210
+ });
211
+
212
+ // Register to window for plugin access
213
+ export function initSlotRegistry() {
214
+ (window as any).__LEGO_SLOT_REGISTRY__ = slotRegistry;
215
+ }
216
+ ```
217
+
218
+ ### 3.2 Host: Initialize Registry
219
+
220
+ **File:** `host/src/bootstrap.tsx`
221
+
222
+ ```typescript
223
+ import { initSlotRegistry } from './kernel/slot-registry';
224
+ import { registerSharedState } from './kernel/shared-state-bridge';
225
+
226
+ // Initialize systems before app starts
227
+ initSlotRegistry();
228
+ registerSharedState();
229
+ ```
230
+
231
+ ### 3.3 Host: Sidebar Component
232
+
233
+ **File:** `host/src/layout/Sidebar.tsx`
234
+
235
+ ```typescript
236
+ import { Link, useLocation } from '@modern-js/runtime/router';
237
+ import { cn } from '@/lib/utils';
238
+ import { slotRegistry } from '@/kernel/slot-registry';
239
+ import { ScrollArea } from '@/components/ui/scroll-area';
240
+ import { Separator } from '@/components/ui/separator';
241
+
242
+ export function Sidebar() {
243
+ const location = useLocation();
244
+
245
+ // Get registered navigation items
246
+ const navItems = slotRegistry.getItems('sidebar:nav');
247
+ const headerItems = slotRegistry.getItems('sidebar:header');
248
+ const footerItems = slotRegistry.getItems('sidebar:footer');
249
+
250
+ return (
251
+ <div className="flex flex-col h-full border-r bg-muted/40 w-64">
252
+ {/* Header Slot */}
253
+ {headerItems.length > 0 && (
254
+ <div className="p-4 space-y-2">
255
+ {headerItems.map((item) => (
256
+ <item.component key={item.id} />
257
+ ))}
258
+ </div>
259
+ )}
260
+
261
+ <Separator />
262
+
263
+ {/* Navigation */}
264
+ <ScrollArea className="flex-1 px-3 py-2">
265
+ <div className="space-y-1">
266
+ {/* Default nav items */}
267
+ <NavLink to="/dashboard" icon="Layout">
268
+ Dashboard
269
+ </NavLink>
270
+ <NavLink to="/apps" icon="Grid3x3">
271
+ Apps
272
+ </NavLink>
273
+ <NavLink to="/settings" icon="Settings">
274
+ Settings
275
+ </NavLink>
276
+
277
+ {/* Plugin-injected nav items */}
278
+ {navItems.map((item) => (
279
+ <item.component key={item.id} />
280
+ ))}
281
+ </div>
282
+ </ScrollArea>
283
+
284
+ <Separator />
285
+
286
+ {/* Footer Slot */}
287
+ {footerItems.length > 0 && (
288
+ <div className="p-4 space-y-2">
289
+ {footerItems.map((item) => (
290
+ <item.component key={item.id} />
291
+ ))}
292
+ </div>
293
+ )}
294
+ </div>
295
+ );
296
+ }
297
+
298
+ function NavLink({
299
+ to,
300
+ icon,
301
+ children,
302
+ }: {
303
+ to: string;
304
+ icon: string;
305
+ children: React.ReactNode;
306
+ }) {
307
+ const location = useLocation();
308
+ const isActive = location.pathname === to || location.pathname.startsWith(to + '/');
309
+
310
+ return (
311
+ <Link
312
+ to={to}
313
+ className={cn(
314
+ 'flex items-center gap-3 px-3 py-2 text-sm rounded-md transition-colors',
315
+ isActive
316
+ ? 'bg-primary text-primary-foreground'
317
+ : 'hover:bg-accent hover:text-accent-foreground'
318
+ )}
319
+ >
320
+ {/* Icon would be rendered here */}
321
+ <span>{icon}</span>
322
+ <span>{children}</span>
323
+ </Link>
324
+ );
325
+ }
326
+ ```
327
+
328
+ ### 3.4 Host: Topbar Component
329
+
330
+ **File:** `host/src/layout/Topbar.tsx`
331
+
332
+ ```typescript
333
+ import { slotRegistry } from '@/kernel/slot-registry';
334
+ import { Button } from '@/components/ui/button';
335
+ import {
336
+ DropdownMenu,
337
+ DropdownMenuContent,
338
+ DropdownMenuItem,
339
+ DropdownMenuTrigger,
340
+ } from '@/components/ui/dropdown-menu';
341
+ import { MoreVertical } from 'lucide-react';
342
+
343
+ export function Topbar() {
344
+ // Get registered topbar items
345
+ const actionItems = slotRegistry.getItems('topbar:actions');
346
+ const menuItems = slotRegistry.getItems('topbar:menu');
347
+
348
+ return (
349
+ <header className="h-14 border-b flex items-center justify-between px-6">
350
+ <div className="flex items-center gap-4">
351
+ {/* Breadcrumb or page title would go here */}
352
+ <h1 className="font-semibold">Lego-One</h1>
353
+ </div>
354
+
355
+ <div className="flex items-center gap-2">
356
+ {/* Plugin-injected action buttons */}
357
+ {actionItems.map((item) => (
358
+ <item.component key={item.id} />
359
+ ))}
360
+
361
+ {/* User menu */}
362
+ <DropdownMenu>
363
+ <DropdownMenuTrigger asChild>
364
+ <Button variant="ghost" size="icon">
365
+ <MoreVertical className="h-4 w-4" />
366
+ </Button>
367
+ </DropdownMenuTrigger>
368
+ <DropdownMenuContent align="end">
369
+ <DropdownMenuItem>Profile</DropdownMenuItem>
370
+ <DropdownMenuItem>Settings</DropdownMenuItem>
371
+
372
+ {/* Plugin-injected menu items */}
373
+ {menuItems.map((item) => (
374
+ <item.component key={item.id} />
375
+ ))}
376
+
377
+ <DropdownMenuItem>Logout</DropdownMenuItem>
378
+ </DropdownMenuContent>
379
+ </DropdownMenu>
380
+ </div>
381
+ </header>
382
+ );
383
+ }
384
+ ```
385
+
386
+ ---
387
+
388
+ ## 4. Plugin: Slot Injection
389
+
390
+ ### 4.1 Plugin Config: Define Slots
391
+
392
+ **File:** `packages/plugins/@lego/plugin-stripe/plugin.config.ts`
393
+
394
+ ```typescript
395
+ import { definePluginConfig } from '@lego/kernel/plugin-config';
396
+
397
+ export default definePluginConfig({
398
+ name: '@lego/plugin-stripe',
399
+ version: '1.0.0',
400
+ displayName: 'Stripe Integration',
401
+ description: 'Payment processing via Stripe',
402
+
403
+ // Define slot injections
404
+ slots: {
405
+ 'sidebar:nav': {
406
+ component: './src/components/slots/SidebarBillingLink',
407
+ order: 50,
408
+ isVisible: () => {
409
+ const state = (window as any).__LEGO_KERNEL_STATE__;
410
+ const user = state?.useGlobalKernelState.getState().currentUser;
411
+ return user?.role === 'admin' || user?.role === 'billing';
412
+ },
413
+ },
414
+
415
+ 'topbar:actions': {
416
+ component: './src/components/slots/CreditBalanceBadge',
417
+ order: 10,
418
+ isVisible: () => true, // Always visible
419
+ },
420
+
421
+ 'topbar:menu': {
422
+ component: './src/components/slots/SubscriptionMenuItem',
423
+ order: 100,
424
+ },
425
+ },
426
+ });
427
+ ```
428
+
429
+ ### 4.2 Plugin: Sidebar Link Component
430
+
431
+ **File:** `packages/plugins/@lego/plugin-stripe/src/components/slots/SidebarBillingLink.tsx`
432
+
433
+ ```typescript
434
+ import { Link } from '@modern-js/runtime/router';
435
+ import { CreditCard } from 'lucide-react';
436
+ import { cn } from '@/lib/utils';
437
+
438
+ export function SidebarBillingLink() {
439
+ return (
440
+ <Link
441
+ to="/billing"
442
+ className={cn(
443
+ 'flex items-center gap-3 px-3 py-2 text-sm rounded-md',
444
+ 'transition-colors hover:bg-accent hover:text-accent-foreground'
445
+ )}
446
+ >
447
+ <CreditCard className="h-4 w-4" />
448
+ <span>Billing</span>
449
+ </Link>
450
+ );
451
+ }
452
+
453
+ export default SidebarBillingLink;
454
+ ```
455
+
456
+ ### 4.3 Plugin: Credit Badge Component
457
+
458
+ **File:** `packages/plugins/@lego/plugin-stripe/src/components/slots/CreditBalanceBadge.tsx`
459
+
460
+ ```typescript
461
+ import { useQuery } from '@tanstack/react-query';
462
+ import { Badge } from '@/components/ui/badge';
463
+ import { CreditCard } from 'lucide-react';
464
+
465
+ export function CreditBalanceBadge() {
466
+ const { data: balance } = useQuery({
467
+ queryKey: ['stripe', 'balance'],
468
+ queryFn: async () => {
469
+ const res = await fetch('/api/stripe/balance');
470
+ return res.json();
471
+ },
472
+ refetchInterval: 60000, // Refresh every minute
473
+ });
474
+
475
+ const credits = balance?.credits || 0;
476
+ const isLow = credits < 10;
477
+
478
+ return (
479
+ <Badge
480
+ variant={isLow ? 'destructive' : 'secondary'}
481
+ className="flex items-center gap-1"
482
+ >
483
+ <CreditCard className="h-3 w-3" />
484
+ <span>{credits} credits</span>
485
+ </Badge>
486
+ );
487
+ }
488
+
489
+ export default CreditBalanceBadge;
490
+ ```
491
+
492
+ ### 4.4 Plugin: Menu Item Component
493
+
494
+ **File:** `packages/plugins/@lego/plugin-stripe/src/components/slots/SubscriptionMenuItem.tsx`
495
+
496
+ ```typescript
497
+ import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
498
+ import { useNavigate } from '@modern-js/runtime/router';
499
+
500
+ export function SubscriptionMenuItem() {
501
+ const navigate = useNavigate();
502
+
503
+ return (
504
+ <DropdownMenuItem onClick={() => navigate('/subscription')}>
505
+ Manage Subscription
506
+ </DropdownMenuItem>
507
+ );
508
+ }
509
+
510
+ export default SubscriptionMenuItem;
511
+ ```
512
+
513
+ ### 4.5 Plugin: Register Slots on Load
514
+
515
+ **File:** `packages/plugins/@lego/plugin-stripe/src/App.tsx`
516
+
517
+ ```typescript
518
+ import { useEffect } from 'react';
519
+ import { BrowserRouter, Routes, Route } from '@modern-js/runtime/router';
520
+ import pluginConfig from '../../plugin.config';
521
+ import { registerPluginSlots } from './lib/slots';
522
+
523
+ export default function PluginApp({ basename }: { basename: string }) {
524
+ useEffect(() => {
525
+ // Register all slots defined in plugin.config.ts
526
+ registerPluginSlots(pluginConfig.slots, '@lego/plugin-stripe');
527
+ }, []);
528
+
529
+ return (
530
+ <BrowserRouter basename={basename}>
531
+ <Routes>
532
+ {/* Plugin routes */}
533
+ </Routes>
534
+ </BrowserRouter>
535
+ );
536
+ }
537
+ ```
538
+
539
+ ### 4.6 Plugin: Slot Registration Helper
540
+
541
+ **File:** `packages/plugins/@lego/plugin-stripe/src/lib/slots.ts`
542
+
543
+ ```typescript
544
+ interface SlotConfig {
545
+ component: string;
546
+ order: number;
547
+ isVisible?: () => boolean;
548
+ }
549
+
550
+ interface SlotsConfig {
551
+ [slotName: string]: SlotConfig;
552
+ }
553
+
554
+ export async function registerPluginSlots(
555
+ slots: SlotsConfig,
556
+ pluginName: string
557
+ ) {
558
+ const registry = (window as any).__LEGO_SLOT_REGISTRY__;
559
+
560
+ if (!registry) {
561
+ console.warn('[Slots] Registry not found. Host not initialized?');
562
+ return;
563
+ }
564
+
565
+ for (const [slotName, slotConfig] of Object.entries(slots)) {
566
+ try {
567
+ const module = await import(/* @vite-ignore */ slotConfig.component);
568
+ const Component = module.default;
569
+
570
+ registry.register(slotName, {
571
+ pluginName,
572
+ component: Component,
573
+ order: slotConfig.order,
574
+ isVisible: slotConfig.isVisible || (() => true),
575
+ onUnmount: () => {
576
+ console.log(`[Slots] Unmounted ${slotName} for ${pluginName}`);
577
+ },
578
+ });
579
+ } catch (error) {
580
+ console.error(`[Slots] Failed to register "${slotName}":`, error);
581
+ }
582
+ }
583
+ }
584
+ ```
585
+
586
+ ---
587
+
588
+ ## 5. Advanced Patterns
589
+
590
+ ### 5.1 Dynamic Slot Items
591
+
592
+ Slots can be registered/unregistered dynamically:
593
+
594
+ ```typescript
595
+ // Show a notification banner only when needed
596
+ function showLowCreditsBanner() {
597
+ const registry = (window as any).__LEGO_SLOT_REGISTRY__;
598
+
599
+ const id = registry.register('layout:banner', {
600
+ pluginName: '@lego/plugin-stripe',
601
+ component: LowCreditsBanner,
602
+ order: 1,
603
+ isVisible: () => true,
604
+ });
605
+
606
+ // Unregister after user dismisses
607
+ return () => registry.unregister('layout:banner', id);
608
+ }
609
+ ```
610
+
611
+ ### 5.2 Slot Data Passing
612
+
613
+ Pass data from host to slot components via context:
614
+
615
+ ```typescript
616
+ // Host provides context
617
+ <SlotDataProvider value={{ currentUser, theme }}>
618
+ <Sidebar />
619
+ </SlotDataProvider>
620
+
621
+ // Slot component consumes context
622
+ function SidebarBillingLink() {
623
+ const { currentUser } = useSlotDataContext();
624
+ // ...
625
+ }
626
+ ```
627
+
628
+ ### 5.3 Conditional Rendering
629
+
630
+ Use visibility rules for complex conditions:
631
+
632
+ ```typescript
633
+ isVisible: () => {
634
+ const state = window.__LEGO_KERNEL_STATE__;
635
+ const user = state?.useGlobalKernelState.getState().currentUser;
636
+
637
+ // Only show for admins with active subscription
638
+ return (
639
+ user?.role === 'admin' &&
640
+ user?.subscription?.status === 'active'
641
+ );
642
+ }
643
+ ```
644
+
645
+ ---
646
+
647
+ ## 6. Slot Best Practices
648
+
649
+ | Practice | Description |
650
+ |----------|-------------|
651
+ | **Use Semantic Names** | `sidebar:nav` not `sidebar:left` |
652
+ | **Order Predictably** | Use increments of 10 for easy reordering |
653
+ | **Cleanup Resources** | Use `onUnmount` for subscriptions |
654
+ | **Handle Missing Registry** | Gracefully degrade if host not ready |
655
+ | **Type Safety** | Export slot component types |
656
+ | **Avoid Side Effects** | Slot components should be pure |
657
+
658
+ ---
659
+
660
+ ## 7. Next Steps
661
+
662
+ 1. **`06-cli-strategy.md`**: CLI tool for project scaffolding
663
+ 2. **`07-deployment.md`**: Deploy host and plugins to production
664
+
665
+ ---
666
+
667
+ ## References
668
+
669
+ - [Radix UI Primitives](https://www.radix-ui.com/)
670
+ - [TanStack Query](https://tanstack.com/query/latest)
671
+ - [Modern.js Runtime](https://modernjs.dev/guides/topic-detail/runtime/)