create-lego-one 2.0.10 → 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 (242) hide show
  1. package/dist/index.cjs +179 -0
  2. package/dist/index.cjs.map +1 -1
  3. package/package.json +5 -3
  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/e2e/auth.spec.ts +38 -0
  70. package/template/host/e2e/layout.spec.ts +38 -0
  71. package/template/host/modern.config.ts +19 -0
  72. package/template/host/package.json +71 -0
  73. package/template/host/playwright.config.ts +34 -0
  74. package/template/host/postcss.config.mjs +6 -0
  75. package/template/host/src/App.tsx +6 -0
  76. package/template/host/src/bootstrap.tsx +74 -0
  77. package/template/host/src/global.css +59 -0
  78. package/template/host/src/index.ts +2 -0
  79. package/template/host/src/kernel/__tests__/lib-utils.test.ts +32 -0
  80. package/template/host/src/kernel/__tests__/rbac-hooks.test.tsx +114 -0
  81. package/template/host/src/kernel/__tests__/rbac-utils.test.ts +108 -0
  82. package/template/host/src/kernel/auth/ProtectedRoute.tsx +41 -0
  83. package/template/host/src/kernel/auth/components/LoginForm.tsx +97 -0
  84. package/template/host/src/kernel/auth/components/LogoutButton.tsx +79 -0
  85. package/template/host/src/kernel/auth/hooks.ts +174 -0
  86. package/template/host/src/kernel/auth/index.ts +5 -0
  87. package/template/host/src/kernel/auth/schemas.ts +27 -0
  88. package/template/host/src/kernel/auth/service.ts +197 -0
  89. package/template/host/src/kernel/auth/types.ts +36 -0
  90. package/template/host/src/kernel/channels/ChannelBus.ts +181 -0
  91. package/template/host/src/kernel/channels/ChannelProvider.tsx +57 -0
  92. package/template/host/src/kernel/channels/events.ts +27 -0
  93. package/template/host/src/kernel/channels/hooks.ts +168 -0
  94. package/template/host/src/kernel/channels/index.ts +6 -0
  95. package/template/host/src/kernel/channels/integrations/ToastIntegration.tsx +60 -0
  96. package/template/host/src/kernel/channels/plugin-hooks.ts +72 -0
  97. package/template/host/src/kernel/channels/types.ts +112 -0
  98. package/template/host/src/kernel/components/__tests__/Badge.test.tsx +35 -0
  99. package/template/host/src/kernel/components/__tests__/Button.test.tsx +63 -0
  100. package/template/host/src/kernel/components/__tests__/Input.test.tsx +64 -0
  101. package/template/host/src/kernel/components/index.ts +32 -0
  102. package/template/host/src/kernel/components/ui/alert.tsx +58 -0
  103. package/template/host/src/kernel/components/ui/avatar.tsx +47 -0
  104. package/template/host/src/kernel/components/ui/badge.tsx +35 -0
  105. package/template/host/src/kernel/components/ui/button.tsx +50 -0
  106. package/template/host/src/kernel/components/ui/card.tsx +78 -0
  107. package/template/host/src/kernel/components/ui/dialog.tsx +116 -0
  108. package/template/host/src/kernel/components/ui/dropdown-menu.tsx +192 -0
  109. package/template/host/src/kernel/components/ui/index.ts +7 -0
  110. package/template/host/src/kernel/components/ui/input.tsx +24 -0
  111. package/template/host/src/kernel/components/ui/label.tsx +21 -0
  112. package/template/host/src/kernel/components/ui/popover.tsx +28 -0
  113. package/template/host/src/kernel/components/ui/progress.tsx +25 -0
  114. package/template/host/src/kernel/components/ui/scroll-area.tsx +45 -0
  115. package/template/host/src/kernel/components/ui/select.tsx +155 -0
  116. package/template/host/src/kernel/components/ui/separator.tsx +28 -0
  117. package/template/host/src/kernel/components/ui/skeleton.tsx +15 -0
  118. package/template/host/src/kernel/components/ui/switch.tsx +26 -0
  119. package/template/host/src/kernel/components/ui/table.tsx +116 -0
  120. package/template/host/src/kernel/components/ui/tabs.tsx +52 -0
  121. package/template/host/src/kernel/components/ui/toast.tsx +126 -0
  122. package/template/host/src/kernel/components/ui/toaster.tsx +34 -0
  123. package/template/host/src/kernel/components/ui/tooltip.tsx +27 -0
  124. package/template/host/src/kernel/components/ui/use-toast.ts +183 -0
  125. package/template/host/src/kernel/index.ts +48 -0
  126. package/template/host/src/kernel/lib/cn.ts +1 -0
  127. package/template/host/src/kernel/lib/utils.ts +36 -0
  128. package/template/host/src/kernel/plugins/Slot.tsx +41 -0
  129. package/template/host/src/kernel/plugins/SlotProvider.tsx +88 -0
  130. package/template/host/src/kernel/plugins/index.ts +23 -0
  131. package/template/host/src/kernel/plugins/loader.ts +122 -0
  132. package/template/host/src/kernel/plugins/schemas.ts +54 -0
  133. package/template/host/src/kernel/plugins/store.ts +185 -0
  134. package/template/host/src/kernel/plugins/types.ts +103 -0
  135. package/template/host/src/kernel/providers/PocketBaseProvider.tsx +70 -0
  136. package/template/host/src/kernel/providers/QueryProvider.tsx +28 -0
  137. package/template/host/src/kernel/providers/ThemeProvider.tsx +25 -0
  138. package/template/host/src/kernel/providers/index.ts +3 -0
  139. package/template/host/src/kernel/rbac/components/OrganizationSelector.tsx +69 -0
  140. package/template/host/src/kernel/rbac/components/PermissionGate.tsx +43 -0
  141. package/template/host/src/kernel/rbac/hooks.ts +379 -0
  142. package/template/host/src/kernel/rbac/index.ts +6 -0
  143. package/template/host/src/kernel/rbac/service.ts +504 -0
  144. package/template/host/src/kernel/rbac/types.ts +164 -0
  145. package/template/host/src/kernel/rbac/utils.ts +34 -0
  146. package/template/host/src/kernel/shared-state/bridge.ts +31 -0
  147. package/template/host/src/kernel/shared-state/index.ts +3 -0
  148. package/template/host/src/kernel/shared-state/store.ts +62 -0
  149. package/template/host/src/kernel/shared-state/types.ts +60 -0
  150. package/template/host/src/kernel/use-migrations.ts +72 -0
  151. package/template/host/src/layout/MobileMenu.tsx +61 -0
  152. package/template/host/src/layout/Shell.tsx +42 -0
  153. package/template/host/src/layout/Sidebar.tsx +178 -0
  154. package/template/host/src/layout/Topbar.tsx +50 -0
  155. package/template/host/src/layout/index.ts +4 -0
  156. package/template/host/src/lib/pocketbase/client.ts +38 -0
  157. package/template/host/src/lib/pocketbase/collections/audit_logs.ts +87 -0
  158. package/template/host/src/lib/pocketbase/collections/index.ts +19 -0
  159. package/template/host/src/lib/pocketbase/collections/organizations.ts +63 -0
  160. package/template/host/src/lib/pocketbase/collections/permissions.ts +57 -0
  161. package/template/host/src/lib/pocketbase/collections/roles.ts +55 -0
  162. package/template/host/src/lib/pocketbase/collections/todos.ts +74 -0
  163. package/template/host/src/lib/pocketbase/collections/user_roles.ts +57 -0
  164. package/template/host/src/lib/pocketbase/collections/users.ts +43 -0
  165. package/template/host/src/lib/pocketbase/index.ts +5 -0
  166. package/template/host/src/lib/pocketbase/migrations.ts +44 -0
  167. package/template/host/src/lib/pocketbase/seed/permissions.ts +8 -0
  168. package/template/host/src/lib/pocketbase/seed/roles.ts +22 -0
  169. package/template/host/src/lib/pocketbase/seed.ts +113 -0
  170. package/template/host/src/lib/pocketbase/types.ts +102 -0
  171. package/template/host/src/modern.runtime.ts +26 -0
  172. package/template/host/src/plugins.d.ts +9 -0
  173. package/template/host/src/providers/PocketBaseProvider.tsx +30 -0
  174. package/template/host/src/routes/_.tsx +6 -0
  175. package/template/host/src/routes/dashboard._.tsx +41 -0
  176. package/template/host/src/routes/index.tsx +93 -0
  177. package/template/host/src/routes/login.tsx +36 -0
  178. package/template/host/src/saas.config.ts +52 -0
  179. package/template/host/src/test/setup.ts +65 -0
  180. package/template/host/src/test/utils.tsx +69 -0
  181. package/template/host/src/test/vitest-globals.d.ts +19 -0
  182. package/template/host/src/vite-env.d.ts +16 -0
  183. package/template/host/tailwind.config.ts +77 -0
  184. package/template/host/tsconfig.json +19 -0
  185. package/template/host/vitest.config.ts +30 -0
  186. package/template/nginx.conf +72 -0
  187. package/template/package.json +44 -0
  188. package/template/packages/plugins/@lego/plugin-dashboard/modern.config.ts +19 -0
  189. package/template/packages/plugins/@lego/plugin-dashboard/package.json +35 -0
  190. package/template/packages/plugins/@lego/plugin-dashboard/postcss.config.mjs +6 -0
  191. package/template/packages/plugins/@lego/plugin-dashboard/src/App.tsx +27 -0
  192. package/template/packages/plugins/@lego/plugin-dashboard/src/components/ActivityFeed.tsx +63 -0
  193. package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActionSlot.tsx +11 -0
  194. package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActions.tsx +68 -0
  195. package/template/packages/plugins/@lego/plugin-dashboard/src/components/SidebarWidget.tsx +35 -0
  196. package/template/packages/plugins/@lego/plugin-dashboard/src/components/StatCard.tsx +47 -0
  197. package/template/packages/plugins/@lego/plugin-dashboard/src/global.css +24 -0
  198. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useChannelIntegration.ts +43 -0
  199. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useDashboardStats.ts +65 -0
  200. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/usePocketBase.ts +47 -0
  201. package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useRecentActivity.ts +55 -0
  202. package/template/packages/plugins/@lego/plugin-dashboard/src/lib/utils.ts +6 -0
  203. package/template/packages/plugins/@lego/plugin-dashboard/src/pages/DashboardPage.tsx +105 -0
  204. package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.config.ts +121 -0
  205. package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.ts +18 -0
  206. package/template/packages/plugins/@lego/plugin-dashboard/src/vite-env.d.ts +32 -0
  207. package/template/packages/plugins/@lego/plugin-dashboard/tailwind.config.ts +35 -0
  208. package/template/packages/plugins/@lego/plugin-dashboard/tsconfig.json +18 -0
  209. package/template/packages/plugins/@lego/plugin-todo/modern.config.ts +18 -0
  210. package/template/packages/plugins/@lego/plugin-todo/package.json +41 -0
  211. package/template/packages/plugins/@lego/plugin-todo/postcss.config.mjs +6 -0
  212. package/template/packages/plugins/@lego/plugin-todo/src/App.tsx +12 -0
  213. package/template/packages/plugins/@lego/plugin-todo/src/components/SidebarWidget.tsx +16 -0
  214. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoDialog.tsx +55 -0
  215. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoFilters.tsx +79 -0
  216. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoForm.tsx +94 -0
  217. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoItem.tsx +121 -0
  218. package/template/packages/plugins/@lego/plugin-todo/src/components/TodoList.tsx +41 -0
  219. package/template/packages/plugins/@lego/plugin-todo/src/components/index.ts +6 -0
  220. package/template/packages/plugins/@lego/plugin-todo/src/global.css +59 -0
  221. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useCreateTodo.ts +62 -0
  222. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useDeleteTodo.ts +46 -0
  223. package/template/packages/plugins/@lego/plugin-todo/src/hooks/usePocketBase.ts +38 -0
  224. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useTodos.ts +64 -0
  225. package/template/packages/plugins/@lego/plugin-todo/src/hooks/useUpdateTodo.ts +35 -0
  226. package/template/packages/plugins/@lego/plugin-todo/src/index.tsx +5 -0
  227. package/template/packages/plugins/@lego/plugin-todo/src/lib/utils.ts +20 -0
  228. package/template/packages/plugins/@lego/plugin-todo/src/pages/TodoPage.tsx +89 -0
  229. package/template/packages/plugins/@lego/plugin-todo/src/plugin.config.ts +104 -0
  230. package/template/packages/plugins/@lego/plugin-todo/src/plugin.ts +13 -0
  231. package/template/packages/plugins/@lego/plugin-todo/src/schemas.ts +37 -0
  232. package/template/packages/plugins/@lego/plugin-todo/src/types.ts +42 -0
  233. package/template/packages/plugins/@lego/plugin-todo/src/vite-env.d.ts +31 -0
  234. package/template/packages/plugins/@lego/plugin-todo/tailwind.config.ts +51 -0
  235. package/template/packages/plugins/@lego/plugin-todo/tsconfig.json +18 -0
  236. package/template/pnpm-workspace.yaml +4 -0
  237. package/template/pocketbase/CHANGELOG.md +911 -0
  238. package/template/pocketbase/LICENSE.md +17 -0
  239. package/template/scripts/create-plugin.js +221 -0
  240. package/template/scripts/deploy.sh +56 -0
  241. package/template/tsconfig.base.json +26 -0
  242. package/template/tsconfig.json +8 -0
@@ -0,0 +1,57 @@
1
+ import { useEffect } from 'react';
2
+ import { ToastIntegration } from './integrations/ToastIntegration';
3
+ import { useGlobalKernelState } from '../shared-state';
4
+ import { useAuth } from '../auth/hooks';
5
+ import { usePublish } from './hooks';
6
+ import { ChannelName } from './types';
7
+
8
+ /**
9
+ * ChannelProvider - Sets up all channel integrations
10
+ *
11
+ * This component:
12
+ * 1. Renders the ToastIntegration component
13
+ * 2. Publishes auth changes when authentication state changes
14
+ * 3. Publishes organization changes when organization changes
15
+ */
16
+ export function ChannelProvider() {
17
+ const { user, isAuthenticated } = useAuth();
18
+ const { organization } = useGlobalKernelState();
19
+ const publish = usePublish();
20
+
21
+ // Publish auth changes
22
+ useEffect(() => {
23
+ publish({
24
+ channel: ChannelName.AUTH_CHANGE,
25
+ data: {
26
+ isAuthenticated,
27
+ userId: user?.id,
28
+ organizationId: organization?.id,
29
+ id: `auth_${Date.now()}`,
30
+ timestamp: Date.now(),
31
+ source: 'host',
32
+ },
33
+ });
34
+ }, [isAuthenticated, user?.id, organization?.id, publish]);
35
+
36
+ // Publish organization changes
37
+ useEffect(() => {
38
+ if (organization) {
39
+ publish({
40
+ channel: ChannelName.ORGANIZATION_CHANGE,
41
+ data: {
42
+ organizationId: organization.id,
43
+ organizationName: organization.name,
44
+ id: `org_${Date.now()}`,
45
+ timestamp: Date.now(),
46
+ source: 'host',
47
+ },
48
+ });
49
+ }
50
+ }, [organization, publish]);
51
+
52
+ return (
53
+ <>
54
+ <ToastIntegration />
55
+ </>
56
+ );
57
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Event constants for type-safe event publishing
3
+ */
4
+ export const Events = {
5
+ TOAST: {
6
+ SUCCESS: (title: string, description?: string) => ({
7
+ type: 'success' as const,
8
+ title,
9
+ description,
10
+ }),
11
+ ERROR: (title: string, description?: string) => ({
12
+ type: 'error' as const,
13
+ title,
14
+ description,
15
+ }),
16
+ INFO: (title: string, description?: string) => ({
17
+ type: 'info' as const,
18
+ title,
19
+ description,
20
+ }),
21
+ WARNING: (title: string, description?: string) => ({
22
+ type: 'warning' as const,
23
+ title,
24
+ description,
25
+ }),
26
+ },
27
+ } as const;
@@ -0,0 +1,168 @@
1
+ import { useEffect, useCallback, useRef } from 'react';
2
+ import { channelBus } from './ChannelBus';
3
+ import { ChannelName, type ChannelMessage, type ChannelSubscriber } from './types';
4
+
5
+ /**
6
+ * Subscribe to a Garfish channel
7
+ *
8
+ * @param channel - The channel name to subscribe to
9
+ * @param callback - The callback function when messages are received
10
+ * @param deps - Dependencies for re-subscription
11
+ */
12
+ export function useChannel<T extends ChannelMessage>(
13
+ channel: ChannelName,
14
+ callback: ChannelSubscriber<T>,
15
+ deps: React.DependencyList = []
16
+ ) {
17
+ const callbackRef = useRef(callback);
18
+
19
+ // Update callback ref without causing re-subscription
20
+ useEffect(() => {
21
+ callbackRef.current = callback;
22
+ }, [callback]);
23
+
24
+ useEffect(() => {
25
+ const unsubscribe = channelBus.subscribe<T>(channel, (data) => {
26
+ callbackRef.current(data);
27
+ });
28
+
29
+ return () => {
30
+ unsubscribe();
31
+ };
32
+ }, [channel, ...deps]);
33
+ }
34
+
35
+ /**
36
+ * Publish to a Garfish channel
37
+ *
38
+ * @returns A function to publish messages
39
+ */
40
+ export function usePublish() {
41
+ return useCallback(<T extends ChannelMessage>(message: T) => {
42
+ channelBus.publish(message);
43
+ }, []);
44
+ }
45
+
46
+ /**
47
+ * Publish toast notifications
48
+ *
49
+ * @returns A function to publish toast messages
50
+ */
51
+ export function useToastChannel() {
52
+ const publish = usePublish();
53
+
54
+ return useCallback((data: any) => {
55
+ publish({
56
+ channel: ChannelName.TOAST,
57
+ data: {
58
+ ...data,
59
+ id: `toast_${Date.now()}`,
60
+ timestamp: Date.now(),
61
+ source: 'plugin',
62
+ },
63
+ });
64
+ }, [publish]);
65
+ }
66
+
67
+ /**
68
+ * Publish navigation events
69
+ *
70
+ * @returns A function to publish navigation messages
71
+ */
72
+ export function useNavigationChannel() {
73
+ const publish = usePublish();
74
+
75
+ return useCallback((path: string, replace = false) => {
76
+ publish({
77
+ channel: ChannelName.NAVIGATION,
78
+ data: {
79
+ path,
80
+ replace,
81
+ id: `nav_${Date.now()}`,
82
+ timestamp: Date.now(),
83
+ source: 'plugin',
84
+ },
85
+ });
86
+ }, [publish]);
87
+ }
88
+
89
+ /**
90
+ * Publish state updates
91
+ *
92
+ * @returns A function to publish state update messages
93
+ */
94
+ export function useStateUpdateChannel() {
95
+ const publish = usePublish();
96
+
97
+ return useCallback((key: string, value: unknown) => {
98
+ publish({
99
+ channel: ChannelName.STATE_UPDATE,
100
+ data: {
101
+ key,
102
+ value,
103
+ id: `state_${Date.now()}`,
104
+ timestamp: Date.now(),
105
+ source: 'plugin',
106
+ },
107
+ });
108
+ }, [publish]);
109
+ }
110
+
111
+ /**
112
+ * Publish plugin ready event
113
+ *
114
+ * Call this when your plugin has finished initializing
115
+ */
116
+ export function usePluginReady(pluginName: string, version: string) {
117
+ const publish = usePublish();
118
+
119
+ useEffect(() => {
120
+ publish({
121
+ channel: ChannelName.PLUGIN_READY,
122
+ data: {
123
+ pluginName,
124
+ version,
125
+ id: `ready_${Date.now()}`,
126
+ timestamp: Date.now(),
127
+ source: pluginName,
128
+ },
129
+ });
130
+ }, [pluginName, version, publish]);
131
+ }
132
+
133
+ /**
134
+ * Publish plugin error event
135
+ *
136
+ * Call this when your plugin encounters an error
137
+ */
138
+ export function usePublishPluginError() {
139
+ const publish = usePublish();
140
+
141
+ return useCallback((pluginName: string, error: string, stack?: string) => {
142
+ publish({
143
+ channel: ChannelName.PLUGIN_ERROR,
144
+ data: {
145
+ pluginName,
146
+ error,
147
+ stack,
148
+ id: `error_${Date.now()}`,
149
+ timestamp: Date.now(),
150
+ source: pluginName,
151
+ },
152
+ });
153
+ }, [publish]);
154
+ }
155
+
156
+ /**
157
+ * Listen for auth changes
158
+ */
159
+ export function useAuthChannel(callback: (data: any) => void) {
160
+ return useChannel(ChannelName.AUTH_CHANGE, callback as any, []);
161
+ }
162
+
163
+ /**
164
+ * Listen for organization changes
165
+ */
166
+ export function useOrganizationChannel(callback: (data: any) => void) {
167
+ return useChannel(ChannelName.ORGANIZATION_CHANGE, callback as any, []);
168
+ }
@@ -0,0 +1,6 @@
1
+ export * from './types';
2
+ export * from './events';
3
+ export * from './ChannelBus';
4
+ export * from './hooks';
5
+ export * from './ChannelProvider';
6
+ export * from './integrations/ToastIntegration';
@@ -0,0 +1,60 @@
1
+ import { useEffect } from 'react';
2
+ import { toast } from '../../components/ui/use-toast';
3
+ import { channelBus } from '../ChannelBus';
4
+ import { ChannelName, type ChannelMessage } from '../types';
5
+
6
+ /**
7
+ * ToastIntegration - Listens to toast channel events and displays them
8
+ *
9
+ * This component subscribes to the TOAST channel and shows toasts
10
+ * when plugins publish events to it. This demonstrates plugin → host communication.
11
+ */
12
+ export function ToastIntegration() {
13
+ useEffect(() => {
14
+ // Subscribe to toast channel
15
+ const unsubscribe = channelBus.subscribe(
16
+ ChannelName.TOAST,
17
+ (message: ChannelMessage) => {
18
+ if (message.channel === ChannelName.TOAST) {
19
+ const { type, title, description } = message.data as any;
20
+
21
+ // Map toast type to variant
22
+ const variant = type === 'error' || type === 'warning' ? 'destructive' : 'default';
23
+
24
+ // Show toast
25
+ toast({
26
+ variant,
27
+ title,
28
+ description,
29
+ });
30
+
31
+ console.log(`[ToastIntegration] Received toast:`, { type, title, description });
32
+ }
33
+ }
34
+ );
35
+
36
+ return () => {
37
+ unsubscribe();
38
+ };
39
+ }, []);
40
+
41
+ // This component doesn't render anything
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * Hook to publish toast events (for plugins to use)
47
+ */
48
+ export function usePublishToast() {
49
+ return (data: any) => {
50
+ channelBus.publish({
51
+ channel: ChannelName.TOAST,
52
+ data: {
53
+ ...data,
54
+ id: `toast_${Date.now()}`,
55
+ timestamp: Date.now(),
56
+ source: 'plugin',
57
+ },
58
+ });
59
+ };
60
+ }
@@ -0,0 +1,72 @@
1
+ import { useCallback } from 'react';
2
+ import { useNavigate } from '@modern-js/runtime/router';
3
+ import { channelBus } from './ChannelBus';
4
+ import { ChannelName, type ToastEventData } from './types';
5
+
6
+ /**
7
+ * Helper hook for plugins to access kernel channels
8
+ *
9
+ * This hook can be used from any plugin via the window bridge:
10
+ * window.__LEGO_KERNEL_STATE__?.usePluginChannels?.()
11
+ */
12
+ export function usePluginChannels() {
13
+ const navigate = useNavigate();
14
+
15
+ // Show toast notification
16
+ const toast = useCallback((data: Omit<ToastEventData, 'id' | 'timestamp' | 'source'>) => {
17
+ channelBus.publish({
18
+ channel: ChannelName.TOAST,
19
+ data: {
20
+ ...data,
21
+ id: `toast_${Date.now()}`,
22
+ timestamp: Date.now(),
23
+ source: 'plugin',
24
+ },
25
+ });
26
+ }, []);
27
+
28
+ // Navigate to a path
29
+ const navigateTo = useCallback((path: string, replace = false) => {
30
+ // Use direct navigation instead of publishing to channel
31
+ // This avoids circular dependencies and works more reliably
32
+ navigate(path, { replace });
33
+ }, [navigate]);
34
+
35
+ // Subscribe to auth changes
36
+ const onAuthChange = useCallback((callback: (data: any) => void) => {
37
+ return channelBus.subscribe(ChannelName.AUTH_CHANGE, callback as any);
38
+ }, []);
39
+
40
+ // Subscribe to organization changes
41
+ const onOrganizationChange = useCallback((callback: (data: any) => void) => {
42
+ return channelBus.subscribe(ChannelName.ORGANIZATION_CHANGE, callback as any);
43
+ }, []);
44
+
45
+ return {
46
+ toast,
47
+ navigateTo,
48
+ onAuthChange,
49
+ onOrganizationChange,
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Register plugin channels helper to window bridge
55
+ */
56
+ export function registerPluginChannels() {
57
+ if (typeof window !== 'undefined') {
58
+ (window as any).__LEGO_PLUGIN_CHANNELS__ = {
59
+ toast: (data: Omit<ToastEventData, 'id' | 'timestamp' | 'source'>) => {
60
+ channelBus.publish({
61
+ channel: ChannelName.TOAST,
62
+ data: {
63
+ ...data,
64
+ id: `toast_${Date.now()}`,
65
+ timestamp: Date.now(),
66
+ source: 'plugin',
67
+ },
68
+ });
69
+ },
70
+ };
71
+ }
72
+ }
@@ -0,0 +1,112 @@
1
+ import type { Garfish } from 'garfish';
2
+
3
+ /**
4
+ * Channel names for different communication topics
5
+ */
6
+ export enum ChannelName {
7
+ TOAST = 'lego:toast',
8
+ NAVIGATION = 'lego:navigation',
9
+ STATE_UPDATE = 'lego:state:update',
10
+ PLUGIN_READY = 'lego:plugin:ready',
11
+ PLUGIN_ERROR = 'lego:plugin:error',
12
+ AUTH_CHANGE = 'lego:auth:change',
13
+ ORGANIZATION_CHANGE = 'lego:organization:change',
14
+ }
15
+
16
+ /**
17
+ * Base event interface
18
+ */
19
+ export interface ChannelEvent {
20
+ id: string;
21
+ timestamp: number;
22
+ source?: string; // Plugin name or 'host'
23
+ }
24
+
25
+ /**
26
+ * Toast event data
27
+ */
28
+ export interface ToastEventData extends ChannelEvent {
29
+ type: 'success' | 'error' | 'info' | 'warning';
30
+ title: string;
31
+ description?: string;
32
+ duration?: number;
33
+ }
34
+
35
+ /**
36
+ * Navigation event data
37
+ */
38
+ export interface NavigationEventData extends ChannelEvent {
39
+ path: string;
40
+ replace?: boolean;
41
+ }
42
+
43
+ /**
44
+ * State update event data
45
+ */
46
+ export interface StateUpdateEventData extends ChannelEvent {
47
+ key: string;
48
+ value: unknown;
49
+ }
50
+
51
+ /**
52
+ * Plugin ready event data
53
+ */
54
+ export interface PluginReadyEventData extends ChannelEvent {
55
+ pluginName: string;
56
+ version: string;
57
+ }
58
+
59
+ /**
60
+ * Plugin error event data
61
+ */
62
+ export interface PluginErrorEventData extends ChannelEvent {
63
+ pluginName: string;
64
+ error: string;
65
+ stack?: string;
66
+ }
67
+
68
+ /**
69
+ * Auth change event data
70
+ */
71
+ export interface AuthChangeEventData extends ChannelEvent {
72
+ isAuthenticated: boolean;
73
+ userId?: string;
74
+ organizationId?: string;
75
+ }
76
+
77
+ /**
78
+ * Organization change event data
79
+ */
80
+ export interface OrganizationChangeEventData extends ChannelEvent {
81
+ organizationId: string;
82
+ organizationName: string;
83
+ }
84
+
85
+ /**
86
+ * Channel message payload
87
+ */
88
+ export type ChannelMessage =
89
+ | { channel: ChannelName.TOAST; data: ToastEventData }
90
+ | { channel: ChannelName.NAVIGATION; data: NavigationEventData }
91
+ | { channel: ChannelName.STATE_UPDATE; data: StateUpdateEventData }
92
+ | { channel: ChannelName.PLUGIN_READY; data: PluginReadyEventData }
93
+ | { channel: ChannelName.PLUGIN_ERROR; data: PluginErrorEventData }
94
+ | { channel: ChannelName.AUTH_CHANGE; data: AuthChangeEventData }
95
+ | { channel: ChannelName.ORGANIZATION_CHANGE; data: OrganizationChangeEventData };
96
+
97
+ /**
98
+ * Channel subscriber callback
99
+ */
100
+ export type ChannelSubscriber<T = ChannelMessage> = (data: T) => void | Promise<void>;
101
+
102
+ /**
103
+ * Channel API
104
+ */
105
+ export interface ChannelAPI {
106
+ publish: <T extends ChannelMessage>(message: T) => void;
107
+ subscribe: <T extends ChannelMessage>(
108
+ channel: ChannelName,
109
+ callback: ChannelSubscriber<T>
110
+ ) => () => void; // Returns unsubscribe function
111
+ unsubscribe: (channel: ChannelName, callback: ChannelSubscriber) => void;
112
+ }
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Badge } from '../ui/badge';
4
+
5
+ describe('Badge Component', () => {
6
+ it('should render children', () => {
7
+ render(<Badge>New</Badge>);
8
+ expect(screen.getByText('New')).toBeInTheDocument();
9
+ });
10
+
11
+ it('should apply default variant classes', () => {
12
+ render(<Badge>Default</Badge>);
13
+ expect(screen.getByText('Default')).toHaveClass('bg-primary');
14
+ });
15
+
16
+ it('should apply secondary variant classes', () => {
17
+ render(<Badge variant="secondary">Secondary</Badge>);
18
+ expect(screen.getByText('Secondary')).toHaveClass('bg-secondary');
19
+ });
20
+
21
+ it('should apply destructive variant classes', () => {
22
+ render(<Badge variant="destructive">Error</Badge>);
23
+ expect(screen.getByText('Error')).toHaveClass('bg-destructive');
24
+ });
25
+
26
+ it('should apply outline variant classes', () => {
27
+ render(<Badge variant="outline">Outline</Badge>);
28
+ expect(screen.getByText('Outline')).toHaveClass('border');
29
+ });
30
+
31
+ it('should apply custom className', () => {
32
+ render(<Badge className="custom-class">Custom</Badge>);
33
+ expect(screen.getByText('Custom')).toHaveClass('custom-class');
34
+ });
35
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { Button } from '../ui/button';
5
+
6
+ describe('Button Component', () => {
7
+ it('should render children', () => {
8
+ render(<Button>Click me</Button>);
9
+ expect(screen.getByText('Click me')).toBeInTheDocument();
10
+ });
11
+
12
+ it('should call onClick when clicked', async () => {
13
+ const handleClick = vi.fn();
14
+ const user = userEvent.setup();
15
+
16
+ render(<Button onClick={handleClick}>Click me</Button>);
17
+
18
+ await user.click(screen.getByText('Click me'));
19
+ expect(handleClick).toHaveBeenCalledTimes(1);
20
+ });
21
+
22
+ it('should not call onClick when disabled', async () => {
23
+ const handleClick = vi.fn();
24
+ const user = userEvent.setup();
25
+
26
+ render(
27
+ <Button onClick={handleClick} disabled>
28
+ Click me
29
+ </Button>
30
+ );
31
+
32
+ await user.click(screen.getByText('Click me'));
33
+ expect(handleClick).not.toHaveBeenCalled();
34
+ });
35
+
36
+ it('should apply variant classes correctly', () => {
37
+ const { rerender } = render(<Button variant="default">Default</Button>);
38
+ expect(screen.getByText('Default')).toHaveClass('bg-primary');
39
+
40
+ rerender(<Button variant="destructive">Destructive</Button>);
41
+ expect(screen.getByText('Destructive')).toHaveClass('bg-destructive');
42
+
43
+ rerender(<Button variant="outline">Outline</Button>);
44
+ expect(screen.getByText('Outline')).toHaveClass('border-input');
45
+
46
+ rerender(<Button variant="ghost">Ghost</Button>);
47
+ expect(screen.getByText('Ghost')).toHaveClass('hover:bg-accent');
48
+ });
49
+
50
+ it('should apply size classes correctly', () => {
51
+ const { rerender } = render(<Button size="default">Default</Button>);
52
+ expect(screen.getByText('Default')).toHaveClass('h-10');
53
+
54
+ rerender(<Button size="sm">Small</Button>);
55
+ expect(screen.getByText('Small')).toHaveClass('h-9');
56
+
57
+ rerender(<Button size="lg">Large</Button>);
58
+ expect(screen.getByText('Large')).toHaveClass('h-11');
59
+
60
+ rerender(<Button size="icon">Icon</Button>);
61
+ expect(screen.getByText('Icon')).toHaveClass('h-10', 'w-10');
62
+ });
63
+ });
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { Input } from '../ui/input';
5
+
6
+ describe('Input Component', () => {
7
+ it('should render input element', () => {
8
+ render(<Input />);
9
+ expect(screen.getByRole('textbox')).toBeInTheDocument();
10
+ });
11
+
12
+ it('should accept and update text input', async () => {
13
+ const user = userEvent.setup();
14
+ render(<Input placeholder="Enter text" />);
15
+
16
+ const input = screen.getByPlaceholderText('Enter text');
17
+ await user.type(input, 'Hello World');
18
+
19
+ expect(input).toHaveValue('Hello World');
20
+ });
21
+
22
+ it('should call onChange when value changes', async () => {
23
+ const handleChange = vi.fn();
24
+ const user = userEvent.setup();
25
+
26
+ render(<Input onChange={handleChange} />);
27
+
28
+ await user.type(screen.getByRole('textbox'), 'a');
29
+
30
+ expect(handleChange).toHaveBeenCalled();
31
+ });
32
+
33
+ it('should be disabled when disabled prop is true', async () => {
34
+ const user = userEvent.setup();
35
+ render(<Input disabled />);
36
+
37
+ const input = screen.getByRole('textbox');
38
+ expect(input).toBeDisabled();
39
+
40
+ await user.type(input, 'test');
41
+ expect(input).toHaveValue('');
42
+ });
43
+
44
+ it('should apply custom className', () => {
45
+ render(<Input className="custom-class" />);
46
+ expect(screen.getByRole('textbox')).toHaveClass('custom-class');
47
+ });
48
+
49
+ it('should accept all standard input props', () => {
50
+ render(
51
+ <Input
52
+ type="email"
53
+ name="email"
54
+ placeholder="email@example.com"
55
+ required
56
+ />
57
+ );
58
+
59
+ const input = screen.getByPlaceholderText('email@example.com');
60
+ expect(input).toHaveAttribute('type', 'email');
61
+ expect(input).toHaveAttribute('name', 'email');
62
+ expect(input).toBeRequired();
63
+ });
64
+ });
@@ -0,0 +1,32 @@
1
+ export * from './ui/avatar';
2
+ export * from './ui/badge';
3
+ export * from './ui/button';
4
+ export * from './ui/card';
5
+ export * from './ui/input';
6
+ export * from './ui/label';
7
+ export * from './ui/alert';
8
+ export * from './ui/dialog';
9
+ export * from './ui/dropdown-menu';
10
+ export * from './ui/select';
11
+ export * from './ui/switch';
12
+ export * from './ui/tabs';
13
+ export * from './ui/separator';
14
+ export * from './ui/popover';
15
+ export * from './ui/scroll-area';
16
+ export * from './ui/progress';
17
+ export * from './ui/tooltip';
18
+ // Exclude Toast to avoid naming conflict with shared-state Toast type
19
+ export {
20
+ ToastProvider,
21
+ ToastViewport,
22
+ ToastAction,
23
+ ToastClose,
24
+ ToastTitle,
25
+ ToastDescription,
26
+ type ToastProps,
27
+ type ToastActionElement,
28
+ } from './ui/toast';
29
+ export * from './ui/toaster';
30
+ export * from './ui/use-toast';
31
+ export * from './ui/table';
32
+ export * from './ui/skeleton';