create-lego-one 2.0.12 → 2.0.13

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 +34 -0
  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,1106 @@
1
+ # Communication System Implementation Plan
2
+
3
+ > **For AI Implementing This Plan:** This is document 07 of 13. Complete documents 01-06 first.
4
+
5
+ **Goal:** Implement Garfish channel bus for inter-plugin communication, custom toast notification system using channels as demonstration, and state synchronization between host and plugins.
6
+
7
+ **Architecture:** Use Garfish's built-in channel system for pub/sub messaging. Plugins can publish events and host can subscribe (and vice versa). Toast system demonstrates plugin→host communication pattern.
8
+
9
+ **Tech Stack:** Garfish channels, React hooks, Zustand state bridge, TypeScript
10
+
11
+ ---
12
+
13
+ ## Prerequisites
14
+
15
+ - ✅ Completed `01-infrastructure-setup.md`
16
+ - ✅ Completed `02-pocketbase-setup.md`
17
+ - ✅ Completed `03-host-kernel.md` (shared state bridge)
18
+ - ✅ Completed `04-auth-system.md`
19
+ - ✅ Completed `05-multitenancy-rbac.md`
20
+ - ✅ Completed `06-ui-components.md` (toast components)
21
+
22
+ ---
23
+
24
+ ## Task 1: Create Garfish Channel Types and Interfaces
25
+
26
+ **Files:**
27
+ - Create: `host/src/kernel/channels/types.ts`
28
+ - Create: `host/src/kernel/channels/events.ts`
29
+
30
+ ### Step 1: Create channel types
31
+
32
+ **File:** `host/src/kernel/channels/types.ts`
33
+
34
+ ```typescript
35
+ import type { Garfish } from 'garfish';
36
+
37
+ /**
38
+ * Channel names for different communication topics
39
+ */
40
+ export enum ChannelName {
41
+ TOAST = 'lego:toast',
42
+ NAVIGATION = 'lego:navigation',
43
+ STATE_UPDATE = 'lego:state:update',
44
+ PLUGIN_READY = 'lego:plugin:ready',
45
+ PLUGIN_ERROR = 'lego:plugin:error',
46
+ AUTH_CHANGE = 'lego:auth:change',
47
+ ORGANIZATION_CHANGE = 'lego:organization:change',
48
+ }
49
+
50
+ /**
51
+ * Base event interface
52
+ */
53
+ export interface ChannelEvent {
54
+ id: string;
55
+ timestamp: number;
56
+ source?: string; // Plugin name or 'host'
57
+ }
58
+
59
+ /**
60
+ * Toast event data
61
+ */
62
+ export interface ToastEventData extends ChannelEvent {
63
+ type: 'success' | 'error' | 'info' | 'warning';
64
+ title: string;
65
+ description?: string;
66
+ duration?: number;
67
+ }
68
+
69
+ /**
70
+ * Navigation event data
71
+ */
72
+ export interface NavigationEventData extends ChannelEvent {
73
+ path: string;
74
+ replace?: boolean;
75
+ }
76
+
77
+ /**
78
+ * State update event data
79
+ */
80
+ export interface StateUpdateEventData extends ChannelEvent {
81
+ key: string;
82
+ value: unknown;
83
+ }
84
+
85
+ /**
86
+ * Plugin ready event data
87
+ */
88
+ export interface PluginReadyEventData extends ChannelEvent {
89
+ pluginName: string;
90
+ version: string;
91
+ }
92
+
93
+ /**
94
+ * Plugin error event data
95
+ */
96
+ export interface PluginErrorEventData extends ChannelEvent {
97
+ pluginName: string;
98
+ error: string;
99
+ stack?: string;
100
+ }
101
+
102
+ /**
103
+ * Auth change event data
104
+ */
105
+ export interface AuthChangeEventData extends ChannelEvent {
106
+ isAuthenticated: boolean;
107
+ userId?: string;
108
+ organizationId?: string;
109
+ }
110
+
111
+ /**
112
+ * Organization change event data
113
+ */
114
+ export interface OrganizationChangeEventData extends ChannelEvent {
115
+ organizationId: string;
116
+ organizationName: string;
117
+ }
118
+
119
+ /**
120
+ * Channel message payload
121
+ */
122
+ export type ChannelMessage =
123
+ | { channel: ChannelName.TOAST; data: ToastEventData }
124
+ | { channel: ChannelName.NAVIGATION; data: NavigationEventData }
125
+ | { channel: ChannelName.STATE_UPDATE; data: StateUpdateEventData }
126
+ | { channel: ChannelName.PLUGIN_READY; data: PluginReadyEventData }
127
+ | { channel: ChannelName.PLUGIN_ERROR; data: PluginErrorEventData }
128
+ | { channel: ChannelName.AUTH_CHANGE; data: AuthChangeEventData }
129
+ | { channel: ChannelName.ORGANIZATION_CHANGE; data: OrganizationChangeEventData };
130
+
131
+ /**
132
+ * Channel subscriber callback
133
+ */
134
+ export type ChannelSubscriber<T = ChannelMessage> = (data: T) => void | Promise<void>;
135
+
136
+ /**
137
+ * Channel API
138
+ */
139
+ export interface ChannelAPI {
140
+ publish: <T extends ChannelMessage>(message: T) => void;
141
+ subscribe: <T extends ChannelMessage>(
142
+ channel: ChannelName,
143
+ callback: ChannelSubscriber<T>
144
+ ) => () => void; // Returns unsubscribe function
145
+ unsubscribe: (channel: ChannelName, callback: ChannelSubscriber) => void;
146
+ }
147
+ ```
148
+
149
+ ### Step 2: Create event constants
150
+
151
+ **File:** `host/src/kernel/channels/events.ts`
152
+
153
+ ```typescript
154
+ /**
155
+ * Event constants for type-safe event publishing
156
+ */
157
+ export const Events = {
158
+ TOAST: {
159
+ SUCCESS: (title: string, description?: string) => ({
160
+ type: 'success' as const,
161
+ title,
162
+ description,
163
+ }),
164
+ ERROR: (title: string, description?: string) => ({
165
+ type: 'error' as const,
166
+ title,
167
+ description,
168
+ }),
169
+ INFO: (title: string, description?: string) => ({
170
+ type: 'info' as const,
171
+ title,
172
+ description,
173
+ }),
174
+ WARNING: (title: string, description?: string) => ({
175
+ type: 'warning' as const,
176
+ title,
177
+ description,
178
+ }),
179
+ },
180
+ } as const;
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Task 2: Create Channel Bus Service
186
+
187
+ **Files:**
188
+ - Create: `host/src/kernel/channels/ChannelBus.ts`
189
+
190
+ ### Step 1: Create channel bus singleton
191
+
192
+ **File:** `host/src/kernel/channels/ChannelBus.ts`
193
+
194
+ ```typescript
195
+ import Garfish from 'garfish';
196
+ import type {
197
+ ChannelName,
198
+ ChannelMessage,
199
+ ChannelSubscriber,
200
+ ChannelAPI,
201
+ } from './types';
202
+
203
+ /**
204
+ * ChannelBus - Singleton service for inter-plugin communication using Garfish channels
205
+ */
206
+ class ChannelBus implements ChannelAPI {
207
+ private garfishInstance: typeof Garfish | null = null;
208
+ private subscribers: Map<ChannelName, Set<ChannelSubscriber>> = new Map();
209
+ private initialized = false;
210
+
211
+ /**
212
+ * Initialize the channel bus with Garfish instance
213
+ */
214
+ initialize(garfish: typeof Garfish): void {
215
+ if (this.initialized) {
216
+ console.warn('[ChannelBus] Already initialized');
217
+ return;
218
+ }
219
+
220
+ this.garfishInstance = garfish;
221
+ this.initialized = true;
222
+
223
+ console.log('[ChannelBus] Initialized');
224
+ }
225
+
226
+ /**
227
+ * Publish a message to a channel
228
+ */
229
+ publish<T extends ChannelMessage>(message: T): void {
230
+ if (!this.initialized || !this.garfishInstance) {
231
+ console.warn('[ChannelBus] Not initialized, cannot publish message');
232
+ return;
233
+ }
234
+
235
+ const { channel, data } = message;
236
+
237
+ // Add metadata
238
+ const enrichedData = {
239
+ ...data,
240
+ id: data.id || this.generateId(),
241
+ timestamp: data.timestamp || Date.now(),
242
+ source: data.source || 'host',
243
+ };
244
+
245
+ // Publish via Garfish channel
246
+ try {
247
+ this.garfishInstance.channel.emit(channel, enrichedData);
248
+ } catch (error) {
249
+ console.error('[ChannelBus] Failed to publish message:', error);
250
+ }
251
+
252
+ // Also notify local subscribers (in same app context)
253
+ const channelSubscribers = this.subscribers.get(channel);
254
+ if (channelSubscribers) {
255
+ channelSubscribers.forEach((callback) => {
256
+ try {
257
+ callback({ channel, data: enrichedData } as T);
258
+ } catch (error) {
259
+ console.error('[ChannelBus] Subscriber callback error:', error);
260
+ }
261
+ });
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Subscribe to a channel
267
+ * Returns unsubscribe function
268
+ */
269
+ subscribe<T extends ChannelMessage>(
270
+ channel: ChannelName,
271
+ callback: ChannelSubscriber<T>
272
+ ): () => void {
273
+ if (!this.initialized || !this.garfishInstance) {
274
+ console.warn('[ChannelBus] Not initialized, subscription may not work');
275
+ }
276
+
277
+ // Add to local subscribers
278
+ if (!this.subscribers.has(channel)) {
279
+ this.subscribers.set(channel, new Set());
280
+ }
281
+ this.subscribers.get(channel)!.add(callback as ChannelSubscriber);
282
+
283
+ // Also subscribe to Garfish channel for cross-app communication
284
+ if (this.garfishInstance) {
285
+ try {
286
+ this.garfishInstance.channel.on(channel, callback as any);
287
+ } catch (error) {
288
+ console.error('[ChannelBus] Failed to subscribe to Garfish channel:', error);
289
+ }
290
+ }
291
+
292
+ console.log(`[ChannelBus] Subscribed to channel: ${channel}`);
293
+
294
+ // Return unsubscribe function
295
+ return () => this.unsubscribe(channel, callback as ChannelSubscriber);
296
+ }
297
+
298
+ /**
299
+ * Unsubscribe from a channel
300
+ */
301
+ unsubscribe(channel: ChannelName, callback: ChannelSubscriber): void {
302
+ // Remove from local subscribers
303
+ const channelSubscribers = this.subscribers.get(channel);
304
+ if (channelSubscribers) {
305
+ channelSubscribers.delete(callback);
306
+ }
307
+
308
+ // Also unsubscribe from Garfish channel
309
+ if (this.garfishInstance) {
310
+ try {
311
+ this.garfishInstance.channel.off(channel, callback as any);
312
+ } catch (error) {
313
+ console.error('[ChannelBus] Failed to unsubscribe from Garfish channel:', error);
314
+ }
315
+ }
316
+
317
+ console.log(`[ChannelBus] Unsubscribed from channel: ${channel}`);
318
+ }
319
+
320
+ /**
321
+ * Unsubscribe all from a channel
322
+ */
323
+ unsubscribeAll(channel: ChannelName): void {
324
+ const channelSubscribers = this.subscribers.get(channel);
325
+ if (channelSubscribers) {
326
+ channelSubscribers.forEach((callback) => {
327
+ if (this.garfishInstance) {
328
+ this.garfishInstance.channel.off(channel, callback as any);
329
+ }
330
+ });
331
+ channelSubscribers.clear();
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Clear all subscribers
337
+ */
338
+ clear(): void {
339
+ this.subscribers.forEach((_, channel) => {
340
+ this.unsubscribeAll(channel);
341
+ });
342
+ this.subscribers.clear();
343
+ }
344
+
345
+ /**
346
+ * Generate unique ID for events
347
+ */
348
+ private generateId(): string {
349
+ return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
350
+ }
351
+
352
+ /**
353
+ * Get list of active channels
354
+ */
355
+ getActiveChannels(): ChannelName[] {
356
+ return Array.from(this.subscribers.keys());
357
+ }
358
+
359
+ /**
360
+ * Get subscriber count for a channel
361
+ */
362
+ getSubscriberCount(channel: ChannelName): number {
363
+ return this.subscribers.get(channel)?.size || 0;
364
+ }
365
+ }
366
+
367
+ // Export singleton instance
368
+ export const channelBus = new ChannelBus();
369
+
370
+ /**
371
+ * Initialize channel bus (call from host bootstrap)
372
+ */
373
+ export function initializeChannelBus(garfish: typeof Garfish): void {
374
+ channelBus.initialize(garfish);
375
+ }
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Task 3: Create Toast Channel Integration
381
+
382
+ **Files:**
383
+ - Create: `host/src/kernel/channels/integrations/ToastIntegration.tsx`
384
+
385
+ ### Step 1: Create toast integration component
386
+
387
+ **File:** `host/src/kernel/channels/integrations/ToastIntegration.tsx`
388
+
389
+ ```typescript
390
+ import { useEffect } from 'react';
391
+ import { toast } from '../../components/ui/use-toast';
392
+ import { channelBus } from '../ChannelBus';
393
+ import { ChannelName, type ToastEventData } from '../types';
394
+
395
+ /**
396
+ * ToastIntegration - Listens to toast channel events and displays them
397
+ *
398
+ * This component subscribes to the TOAST channel and shows toasts
399
+ * when plugins publish events to it. This demonstrates plugin → host communication.
400
+ */
401
+ export function ToastIntegration() {
402
+ useEffect(() => {
403
+ // Subscribe to toast channel
404
+ const unsubscribe = channelBus.subscribe<ToastEventData>(
405
+ ChannelName.TOAST,
406
+ ({ data }) => {
407
+ const { type, title, description, duration = 5000 } = data;
408
+
409
+ // Map toast type to variant
410
+ const variant = type === 'error' || type === 'warning' ? 'destructive' : 'default';
411
+
412
+ // Show toast
413
+ toast({
414
+ variant,
415
+ title,
416
+ description,
417
+ // Note: duration would need custom implementation
418
+ // The default toast doesn't support duration prop
419
+ });
420
+
421
+ console.log(`[ToastIntegration] Received toast:`, { type, title, description });
422
+ }
423
+ );
424
+
425
+ return () => {
426
+ unsubscribe();
427
+ };
428
+ }, []);
429
+
430
+ // This component doesn't render anything
431
+ return null;
432
+ }
433
+
434
+ /**
435
+ * Hook to publish toast events (for plugins to use)
436
+ */
437
+ export function usePublishToast() {
438
+ return (data: Omit<ToastEventData, 'id' | 'timestamp' | 'source'>) => {
439
+ channelBus.publish({
440
+ channel: ChannelName.TOAST,
441
+ data: {
442
+ ...data,
443
+ id: `toast_${Date.now()}`,
444
+ timestamp: Date.now(),
445
+ source: 'plugin',
446
+ },
447
+ });
448
+ };
449
+ }
450
+ ```
451
+
452
+ ---
453
+
454
+ ## Task 4: Create Channel Hooks for Plugins
455
+
456
+ **Files:**
457
+ - Create: `host/src/kernel/channels/hooks.ts`
458
+
459
+ ### Step 1: Create channel hooks
460
+
461
+ **File:** `host/src/kernel/channels/hooks.ts`
462
+
463
+ ```typescript
464
+ import { useEffect, useCallback, useRef } from 'react';
465
+ import { channelBus } from './ChannelBus';
466
+ import { ChannelName, type ChannelMessage, type ChannelSubscriber } from './types';
467
+
468
+ /**
469
+ * Subscribe to a Garfish channel
470
+ *
471
+ * @param channel - The channel name to subscribe to
472
+ * @param callback - The callback function when messages are received
473
+ * @param deps - Dependencies for re-subscription
474
+ */
475
+ export function useChannel<T extends ChannelMessage>(
476
+ channel: ChannelName,
477
+ callback: ChannelSubscriber<T>,
478
+ deps: React.DependencyList = []
479
+ ) {
480
+ const callbackRef = useRef(callback);
481
+
482
+ // Update callback ref without causing re-subscription
483
+ useEffect(() => {
484
+ callbackRef.current = callback;
485
+ }, [callback]);
486
+
487
+ useEffect(() => {
488
+ const unsubscribe = channelBus.subscribe<T>(channel, (data) => {
489
+ callbackRef.current(data);
490
+ });
491
+
492
+ return () => {
493
+ unsubscribe();
494
+ };
495
+ }, [channel, ...deps]);
496
+ }
497
+
498
+ /**
499
+ * Publish to a Garfish channel
500
+ *
501
+ * @returns A function to publish messages
502
+ */
503
+ export function usePublish() {
504
+ return useCallback(<T extends ChannelMessage>(message: T) => {
505
+ channelBus.publish(message);
506
+ }, []);
507
+ }
508
+
509
+ /**
510
+ * Publish toast notifications
511
+ *
512
+ * @returns A function to publish toast messages
513
+ */
514
+ export function useToastChannel() {
515
+ const publish = usePublish();
516
+
517
+ return useCallback((data: Omit<ChannelMessage & { channel: ChannelName.TOAST }, 'channel'>['data']) => {
518
+ publish({
519
+ channel: ChannelName.TOAST,
520
+ data: {
521
+ ...data,
522
+ id: `toast_${Date.now()}`,
523
+ timestamp: Date.now(),
524
+ source: 'plugin',
525
+ },
526
+ });
527
+ }, [publish]);
528
+ }
529
+
530
+ /**
531
+ * Publish navigation events
532
+ *
533
+ * @returns A function to publish navigation messages
534
+ */
535
+ export function useNavigationChannel() {
536
+ const publish = usePublish();
537
+
538
+ return useCallback((path: string, replace = false) => {
539
+ publish({
540
+ channel: ChannelName.NAVIGATION,
541
+ data: {
542
+ path,
543
+ replace,
544
+ id: `nav_${Date.now()}`,
545
+ timestamp: Date.now(),
546
+ source: 'plugin',
547
+ },
548
+ });
549
+ }, [publish]);
550
+ }
551
+
552
+ /**
553
+ * Publish state updates
554
+ *
555
+ * @returns A function to publish state update messages
556
+ */
557
+ export function useStateUpdateChannel() {
558
+ const publish = usePublish();
559
+
560
+ return useCallback((key: string, value: unknown) => {
561
+ publish({
562
+ channel: ChannelName.STATE_UPDATE,
563
+ data: {
564
+ key,
565
+ value,
566
+ id: `state_${Date.now()}`,
567
+ timestamp: Date.now(),
568
+ source: 'plugin',
569
+ },
570
+ });
571
+ }, [publish]);
572
+ }
573
+
574
+ /**
575
+ * Publish plugin ready event
576
+ *
577
+ * Call this when your plugin has finished initializing
578
+ */
579
+ export function usePluginReady(pluginName: string, version: string) {
580
+ const publish = usePublish();
581
+
582
+ useEffect(() => {
583
+ publish({
584
+ channel: ChannelName.PLUGIN_READY,
585
+ data: {
586
+ pluginName,
587
+ version,
588
+ id: `ready_${Date.now()}`,
589
+ timestamp: Date.now(),
590
+ source: pluginName,
591
+ },
592
+ });
593
+ }, [pluginName, version, publish]);
594
+ }
595
+
596
+ /**
597
+ * Publish plugin error event
598
+ *
599
+ * Call this when your plugin encounters an error
600
+ */
601
+ export function usePublishPluginError() {
602
+ const publish = usePublish();
603
+
604
+ return useCallback((pluginName: string, error: string, stack?: string) => {
605
+ publish({
606
+ channel: ChannelName.PLUGIN_ERROR,
607
+ data: {
608
+ pluginName,
609
+ error,
610
+ stack,
611
+ id: `error_${Date.now()}`,
612
+ timestamp: Date.now(),
613
+ source: pluginName,
614
+ },
615
+ });
616
+ }, [publish]);
617
+ }
618
+
619
+ /**
620
+ * Listen for auth changes
621
+ */
622
+ export function useAuthChannel(callback: (data: ChannelMessage & { channel: ChannelName.AUTH_CHANGE })['data']) {
623
+ return useChannel(ChannelName.AUTH_CHANGE, callback, []);
624
+ }
625
+
626
+ /**
627
+ * Listen for organization changes
628
+ */
629
+ export function useOrganizationChannel(callback: (data: ChannelMessage & { channel: ChannelName.ORGANIZATION_CHANGE })['data']) {
630
+ return useChannel(ChannelName.ORGANIZATION_CHANGE, callback, []);
631
+ }
632
+ ```
633
+
634
+ ---
635
+
636
+ ## Task 5: Create Channel Provider and Integration
637
+
638
+ **Files:**
639
+ - Create: `host/src/kernel/channels/ChannelProvider.tsx`
640
+ - Create: `host/src/kernel/channels/index.ts`
641
+
642
+ ### Step 1: Create channel provider component
643
+
644
+ **File:** `host/src/kernel/channels/ChannelProvider.tsx`
645
+
646
+ ```typescript
647
+ import { useEffect } from 'react';
648
+ import { ToastIntegration } from './integrations/ToastIntegration';
649
+ import { useGlobalKernelState } from '../shared-state';
650
+ import { useAuth } from '../auth/hooks';
651
+ import { usePublish } from './hooks';
652
+ import { ChannelName } from './types';
653
+
654
+ /**
655
+ * ChannelProvider - Sets up all channel integrations
656
+ *
657
+ * This component:
658
+ * 1. Renders the ToastIntegration component
659
+ * 2. Publishes auth changes when authentication state changes
660
+ * 3. Publishes organization changes when organization changes
661
+ */
662
+ export function ChannelProvider() {
663
+ const { user, isAuthenticated } = useAuth();
664
+ const { organization } = useGlobalKernelState();
665
+ const publish = usePublish();
666
+
667
+ // Publish auth changes
668
+ useEffect(() => {
669
+ publish({
670
+ channel: ChannelName.AUTH_CHANGE,
671
+ data: {
672
+ isAuthenticated,
673
+ userId: user?.id,
674
+ organizationId: organization?.id,
675
+ id: `auth_${Date.now()}`,
676
+ timestamp: Date.now(),
677
+ source: 'host',
678
+ },
679
+ });
680
+ }, [isAuthenticated, user?.id, organization?.id, publish]);
681
+
682
+ // Publish organization changes
683
+ useEffect(() => {
684
+ if (organization) {
685
+ publish({
686
+ channel: ChannelName.ORGANIZATION_CHANGE,
687
+ data: {
688
+ organizationId: organization.id,
689
+ organizationName: organization.name,
690
+ id: `org_${Date.now()}`,
691
+ timestamp: Date.now(),
692
+ source: 'host',
693
+ },
694
+ });
695
+ }
696
+ }, [organization, publish]);
697
+
698
+ return (
699
+ <>
700
+ <ToastIntegration />
701
+ </>
702
+ );
703
+ }
704
+ ```
705
+
706
+ ### Step 2: Create channels barrel export
707
+
708
+ **File:** `host/src/kernel/channels/index.ts`
709
+
710
+ ```typescript
711
+ export * from './types';
712
+ export * from './events';
713
+ export * from './ChannelBus';
714
+ export * from './hooks';
715
+ export * from './ChannelProvider';
716
+ export * from './integrations/ToastIntegration';
717
+ ```
718
+
719
+ ---
720
+
721
+ ## Task 6: Update Bootstrap to Initialize Channel Bus
722
+
723
+ **Files:**
724
+ - Modify: `host/src/bootstrap.tsx`
725
+ - Modify: `host/src/kernel/index.ts`
726
+
727
+ ### Step 1: Update bootstrap to initialize channels
728
+
729
+ **File:** `host/src/bootstrap.tsx`
730
+
731
+ ```typescript
732
+ import { StrictMode } from 'react';
733
+ import { createRoot } from 'react-dom/client';
734
+ import { BrowserRouter } from '@modern-js/runtime/router';
735
+ import App from './App';
736
+ import { registerSharedState } from './kernel/shared-state';
737
+ import { PocketBaseProvider } from './kernel/providers';
738
+ import { QueryProvider } from './kernel/providers';
739
+ import { ThemeProvider } from './kernel/providers';
740
+ import { Toaster } from './kernel/components/ui/toaster';
741
+ import { ChannelProvider, initializeChannelBus } from './kernel/channels';
742
+ import Garfish from 'garfish';
743
+
744
+ // Register shared state bridge for plugins
745
+ registerSharedState();
746
+
747
+ // Initialize Garfish channel bus
748
+ initializeChannelBus(Garfish);
749
+
750
+ // Create root
751
+ const container = document.getElementById('root');
752
+ if (container) {
753
+ const root = createRoot(container);
754
+
755
+ root.render(
756
+ <StrictMode>
757
+ <BrowserRouter>
758
+ <ThemeProvider>
759
+ <QueryProvider>
760
+ <PocketBaseProvider>
761
+ <ChannelProvider />
762
+ <App />
763
+ <Toaster />
764
+ </PocketBaseProvider>
765
+ </QueryProvider>
766
+ </ThemeProvider>
767
+ </BrowserRouter>
768
+ </StrictMode>
769
+ );
770
+ }
771
+ ```
772
+
773
+ ### Step 2: Update kernel exports
774
+
775
+ **File:** `host/src/kernel/index.ts`
776
+
777
+ ```typescript
778
+ export * from './shared-state';
779
+ export * from './providers';
780
+ export * from './lib/utils';
781
+ export * from './components';
782
+ export * from './channels';
783
+ export * from './auth';
784
+ export * from './rbac';
785
+ ```
786
+
787
+ ---
788
+
789
+ ## Task 7: Create Example Plugin Channel Usage
790
+
791
+ **Files:**
792
+ - Create: `packages/plugins/@lego/plugin-todo/src/channels.example.ts`
793
+
794
+ ### Step 1: Create example for plugin developers
795
+
796
+ **File:** `packages/plugins/@lego/plugin-todo/src/channels.example.ts`
797
+
798
+ ```typescript
799
+ /**
800
+ * Example: How plugins can use the channel system
801
+ *
802
+ * This file demonstrates how a plugin can:
803
+ * 1. Subscribe to host events (auth, organization changes)
804
+ * 2. Publish events to the host (toast notifications, navigation)
805
+ * 3. Communicate with other plugins
806
+ */
807
+
808
+ import { useEffect } from 'react';
809
+ import {
810
+ useToastChannel,
811
+ useAuthChannel,
812
+ useOrganizationChannel,
813
+ usePluginReady,
814
+ usePublishPluginError,
815
+ } from '@lego/kernel/channels';
816
+
817
+ // Example 1: Show toast notification from plugin
818
+ export function useNotifySuccess() {
819
+ const publishToast = useToastChannel();
820
+
821
+ return (title: string, description?: string) => {
822
+ publishToast({
823
+ type: 'success',
824
+ title,
825
+ description,
826
+ });
827
+ };
828
+ }
829
+
830
+ // Example 2: Listen for auth changes
831
+ export function useAuthListener() {
832
+ useAuthChannel((data) => {
833
+ console.log('[Plugin] Auth changed:', data);
834
+
835
+ if (data.isAuthenticated) {
836
+ console.log('[Plugin] User logged in:', data.userId);
837
+ // Refresh plugin data when user logs in
838
+ } else {
839
+ console.log('[Plugin] User logged out');
840
+ // Clear plugin data when user logs out
841
+ }
842
+ });
843
+ }
844
+
845
+ // Example 3: Listen for organization changes
846
+ export function useOrganizationListener() {
847
+ useOrganizationChannel((data) => {
848
+ console.log('[Plugin] Organization changed:', data);
849
+ // Refresh plugin data for new organization
850
+ });
851
+ }
852
+
853
+ // Example 4: Notify host when plugin is ready
854
+ export function usePluginReadyNotification() {
855
+ usePluginReady('@lego/plugin-todo', '1.0.0');
856
+ }
857
+
858
+ // Example 5: Report errors to host
859
+ export function useErrorReporting() {
860
+ const publishError = usePublishPluginError();
861
+
862
+ return (error: Error) => {
863
+ publishError(
864
+ '@lego/plugin-todo',
865
+ error.message,
866
+ error.stack
867
+ );
868
+ };
869
+ }
870
+
871
+ // Example 6: Complete integration hook
872
+ export function useChannelIntegration() {
873
+ const notifySuccess = useNotifySuccess();
874
+ const reportError = useErrorReporting();
875
+
876
+ // Notify when plugin initializes
877
+ usePluginReadyNotification();
878
+
879
+ // Listen to auth changes
880
+ useAuthListener();
881
+
882
+ // Listen to organization changes
883
+ useOrganizationListener();
884
+
885
+ return {
886
+ notifySuccess,
887
+ reportError,
888
+ };
889
+ }
890
+ ```
891
+
892
+ ---
893
+
894
+ ## Task 8: Create Plugin Channel Hook Helper
895
+
896
+ **Files:**
897
+ - Create: `host/src/kernel/channels/plugin-hooks.ts`
898
+
899
+ ### Step 1: Create plugin-specific hooks
900
+
901
+ **File:** `host/src/kernel/channels/plugin-hooks.ts`
902
+
903
+ ```typescript
904
+ import { useCallback, useEffect } from 'react';
905
+ import { useNavigate } from '@modern-js/runtime/router';
906
+ import { channelBus } from './ChannelBus';
907
+ import { ChannelName, type ToastEventData } from './types';
908
+
909
+ /**
910
+ * Helper hook for plugins to access kernel channels
911
+ *
912
+ * This hook can be used from any plugin via the window bridge:
913
+ * window.__LEGO_KERNEL_STATE__?.usePluginChannels?.()
914
+ */
915
+ export function usePluginChannels() {
916
+ const navigate = useNavigate();
917
+
918
+ // Show toast notification
919
+ const toast = useCallback((data: Omit<ToastEventData, 'id' | 'timestamp' | 'source'>) => {
920
+ channelBus.publish({
921
+ channel: ChannelName.TOAST,
922
+ data: {
923
+ ...data,
924
+ id: `toast_${Date.now()}`,
925
+ timestamp: Date.now(),
926
+ source: 'plugin',
927
+ },
928
+ });
929
+ }, []);
930
+
931
+ // Navigate to a path
932
+ const navigateTo = useCallback((path: string, replace = false) => {
933
+ // Use direct navigation instead of publishing to channel
934
+ // This avoids circular dependencies and works more reliably
935
+ navigate(path, { replace });
936
+ }, [navigate]);
937
+
938
+ // Subscribe to auth changes
939
+ const onAuthChange = useCallback((callback: (data: ToastEventData) => void) => {
940
+ return channelBus.subscribe(ChannelName.AUTH_CHANGE, callback);
941
+ }, []);
942
+
943
+ // Subscribe to organization changes
944
+ const onOrganizationChange = useCallback((callback: (data: any) => void) => {
945
+ return channelBus.subscribe(ChannelName.ORGANIZATION_CHANGE, callback);
946
+ }, []);
947
+
948
+ return {
949
+ toast,
950
+ navigateTo,
951
+ onAuthChange,
952
+ onOrganizationChange,
953
+ };
954
+ }
955
+
956
+ /**
957
+ * Register plugin channels helper to window bridge
958
+ */
959
+ export function registerPluginChannels() {
960
+ if (typeof window !== 'undefined') {
961
+ (window as any).__LEGO_PLUGIN_CHANNELS__ = {
962
+ toast: (data: Omit<ToastEventData, 'id' | 'timestamp' | 'source'>) => {
963
+ channelBus.publish({
964
+ channel: ChannelName.TOAST,
965
+ data: {
966
+ ...data,
967
+ id: `toast_${Date.now()}`,
968
+ timestamp: Date.now(),
969
+ source: 'plugin',
970
+ },
971
+ });
972
+ },
973
+ };
974
+ }
975
+ }
976
+ ```
977
+
978
+ ### Step 2: Update bridge to include channels
979
+
980
+ **File:** `host/src/kernel/shared-state/bridge.ts`
981
+
982
+ ```typescript
983
+ import { useGlobalKernelState } from './store';
984
+ import { registerPluginChannels } from '../channels/plugin-hooks';
985
+
986
+ // Declare window type for shared state
987
+ declare global {
988
+ interface Window {
989
+ __LEGO_KERNEL_STATE__?: {
990
+ useGlobalKernelState: typeof useGlobalKernelState;
991
+ };
992
+ __LEGO_PLUGIN_CHANNELS__?: {
993
+ toast: (data: any) => void;
994
+ };
995
+ }
996
+ }
997
+
998
+ // Register shared state to window for plugin access
999
+ export function registerSharedState() {
1000
+ if (typeof window !== 'undefined') {
1001
+ window.__LEGO_KERNEL_STATE__ = {
1002
+ useGlobalKernelState,
1003
+ };
1004
+
1005
+ // Also register plugin channels helper
1006
+ registerPluginChannels();
1007
+ }
1008
+ }
1009
+
1010
+ // Hook for plugins to access kernel state
1011
+ export function useKernelState() {
1012
+ return useGlobalKernelState;
1013
+ }
1014
+ ```
1015
+
1016
+ ---
1017
+
1018
+ ## Verification
1019
+
1020
+ ### Step 1: Build the host
1021
+
1022
+ **Run:**
1023
+
1024
+ ```bash
1025
+ cd host
1026
+ pnpm run build
1027
+ ```
1028
+
1029
+ Expected: Build completes without errors.
1030
+
1031
+ ### Step 2: Start development server
1032
+
1033
+ **Run:**
1034
+
1035
+ ```bash
1036
+ cd host
1037
+ pnpm run dev
1038
+ ```
1039
+
1040
+ Expected: Server starts on http://localhost:8080
1041
+
1042
+ ### Step 3: Test channel system
1043
+
1044
+ 1. Open browser console on host app
1045
+ 2. Access channel bus: `window.__LEGO_PLUGIN_CHANNELS__`
1046
+ 3. Test toast from console:
1047
+
1048
+ ```javascript
1049
+ window.__LEGO_PLUGIN_CHANNELS__.toast({
1050
+ type: 'success',
1051
+ title: 'Test from console',
1052
+ description: 'This toast was triggered from the browser console!'
1053
+ });
1054
+ ```
1055
+
1056
+ 4. Should see a toast notification appear
1057
+
1058
+ ### Step 4: Test auth change events
1059
+
1060
+ 1. Login to the app
1061
+ 2. Open browser console
1062
+ 3. Subscribe to auth channel and verify events are published
1063
+
1064
+ ---
1065
+
1066
+ ## Summary
1067
+
1068
+ After completing this document, you will have:
1069
+
1070
+ 1. ✅ Garfish channel bus singleton service
1071
+ 2. ✅ Type-safe channel system with enum-defined channel names
1072
+ 3. ✅ Toast integration demonstrating plugin→host communication
1073
+ 4. ✅ Hooks for subscribing to and publishing channel events
1074
+ 5. ✅ Channel provider that automatically publishes auth/org changes
1075
+ 6. ✅ Plugin-friendly channel helpers via window bridge
1076
+ 7. ✅ Example code showing how plugins use channels
1077
+
1078
+ **Next:** `08-plugin-system.md` - Implement complete plugin architecture with slot injection, plugin config, and dynamic loading.
1079
+
1080
+ ---
1081
+
1082
+ ## Files Created
1083
+
1084
+ ```
1085
+ host/
1086
+ └── src/
1087
+ └── kernel/
1088
+ └── channels/
1089
+ ├── types.ts
1090
+ ├── events.ts
1091
+ ├── ChannelBus.ts
1092
+ ├── hooks.ts
1093
+ ├── plugin-hooks.ts
1094
+ ├── ChannelProvider.tsx
1095
+ ├── integrations/
1096
+ │ └── ToastIntegration.tsx
1097
+ └── index.ts
1098
+
1099
+ bootstrap.tsx (modified)
1100
+ kernel/shared-state/bridge.ts (modified)
1101
+ kernel/index.ts (modified)
1102
+
1103
+ packages/plugins/@lego/plugin-todo/
1104
+ └── src/
1105
+ └── channels.example.ts (example file)
1106
+ ```