mvc-kit 2.12.5 → 2.13.1

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 (382) hide show
  1. package/BEST_PRACTICES.md +1390 -0
  2. package/agent-config/bin/postinstall.mjs +4 -3
  3. package/agent-config/bin/setup.mjs +5 -1
  4. package/agent-config/claude-code/agents/mvc-kit-architect.md +16 -8
  5. package/agent-config/claude-code/skills/guide/SKILL.md +29 -7
  6. package/agent-config/claude-code/skills/guide/patterns.md +12 -0
  7. package/agent-config/claude-code/skills/guide/recipes.md +510 -0
  8. package/agent-config/claude-code/skills/guide/testing.md +297 -0
  9. package/agent-config/claude-code/skills/review/SKILL.md +3 -13
  10. package/agent-config/claude-code/skills/review/checklist.md +30 -5
  11. package/agent-config/claude-code/skills/scaffold/SKILL.md +4 -13
  12. package/agent-config/lib/install-claude.mjs +90 -25
  13. package/dist/Channel.cjs +276 -300
  14. package/dist/Channel.cjs.map +1 -1
  15. package/dist/Channel.js +275 -299
  16. package/dist/Channel.js.map +1 -1
  17. package/dist/Collection.cjs +424 -504
  18. package/dist/Collection.cjs.map +1 -1
  19. package/dist/Collection.js +423 -503
  20. package/dist/Collection.js.map +1 -1
  21. package/dist/Controller.cjs +70 -67
  22. package/dist/Controller.cjs.map +1 -1
  23. package/dist/Controller.js +69 -66
  24. package/dist/Controller.js.map +1 -1
  25. package/dist/EventBus.cjs +77 -88
  26. package/dist/EventBus.cjs.map +1 -1
  27. package/dist/EventBus.js +76 -87
  28. package/dist/EventBus.js.map +1 -1
  29. package/dist/Feed.cjs +81 -77
  30. package/dist/Feed.cjs.map +1 -1
  31. package/dist/Feed.js +80 -76
  32. package/dist/Feed.js.map +1 -1
  33. package/dist/Model.cjs +181 -207
  34. package/dist/Model.cjs.map +1 -1
  35. package/dist/Model.js +179 -205
  36. package/dist/Model.js.map +1 -1
  37. package/dist/Pagination.cjs +75 -73
  38. package/dist/Pagination.cjs.map +1 -1
  39. package/dist/Pagination.js +74 -72
  40. package/dist/Pagination.js.map +1 -1
  41. package/dist/Pending.cjs +255 -287
  42. package/dist/Pending.cjs.map +1 -1
  43. package/dist/Pending.js +253 -285
  44. package/dist/Pending.js.map +1 -1
  45. package/dist/PersistentCollection.cjs +242 -285
  46. package/dist/PersistentCollection.cjs.map +1 -1
  47. package/dist/PersistentCollection.js +241 -284
  48. package/dist/PersistentCollection.js.map +1 -1
  49. package/dist/Resource.cjs +166 -174
  50. package/dist/Resource.cjs.map +1 -1
  51. package/dist/Resource.js +164 -172
  52. package/dist/Resource.js.map +1 -1
  53. package/dist/Selection.cjs +84 -94
  54. package/dist/Selection.cjs.map +1 -1
  55. package/dist/Selection.js +83 -93
  56. package/dist/Selection.js.map +1 -1
  57. package/dist/Service.cjs +54 -55
  58. package/dist/Service.cjs.map +1 -1
  59. package/dist/Service.js +53 -54
  60. package/dist/Service.js.map +1 -1
  61. package/dist/Sorting.cjs +102 -101
  62. package/dist/Sorting.cjs.map +1 -1
  63. package/dist/Sorting.js +102 -101
  64. package/dist/Sorting.js.map +1 -1
  65. package/dist/Trackable.cjs +112 -80
  66. package/dist/Trackable.cjs.map +1 -1
  67. package/dist/Trackable.js +111 -79
  68. package/dist/Trackable.js.map +1 -1
  69. package/dist/ViewModel.cjs +528 -576
  70. package/dist/ViewModel.cjs.map +1 -1
  71. package/dist/ViewModel.js +525 -573
  72. package/dist/ViewModel.js.map +1 -1
  73. package/dist/bindPublicMethods.cjs +43 -24
  74. package/dist/bindPublicMethods.cjs.map +1 -1
  75. package/dist/bindPublicMethods.js +43 -24
  76. package/dist/bindPublicMethods.js.map +1 -1
  77. package/dist/errors.cjs +67 -68
  78. package/dist/errors.cjs.map +1 -1
  79. package/dist/errors.js +68 -71
  80. package/dist/errors.js.map +1 -1
  81. package/dist/mvc-kit.cjs +44 -46
  82. package/dist/mvc-kit.js +5 -32
  83. package/dist/produceDraft.cjs +105 -95
  84. package/dist/produceDraft.cjs.map +1 -1
  85. package/dist/produceDraft.js +106 -97
  86. package/dist/produceDraft.js.map +1 -1
  87. package/dist/react/components/CardList.cjs +30 -40
  88. package/dist/react/components/CardList.cjs.map +1 -1
  89. package/dist/react/components/CardList.js +31 -41
  90. package/dist/react/components/CardList.js.map +1 -1
  91. package/dist/react/components/DataTable.cjs +146 -169
  92. package/dist/react/components/DataTable.cjs.map +1 -1
  93. package/dist/react/components/DataTable.js +147 -170
  94. package/dist/react/components/DataTable.js.map +1 -1
  95. package/dist/react/components/InfiniteScroll.cjs +51 -42
  96. package/dist/react/components/InfiniteScroll.cjs.map +1 -1
  97. package/dist/react/components/InfiniteScroll.js +52 -43
  98. package/dist/react/components/InfiniteScroll.js.map +1 -1
  99. package/dist/react/components/types.cjs +10 -6
  100. package/dist/react/components/types.cjs.map +1 -1
  101. package/dist/react/components/types.js +11 -9
  102. package/dist/react/components/types.js.map +1 -1
  103. package/dist/react/guards.cjs +10 -6
  104. package/dist/react/guards.cjs.map +1 -1
  105. package/dist/react/guards.js +11 -9
  106. package/dist/react/guards.js.map +1 -1
  107. package/dist/react/provider.cjs +23 -20
  108. package/dist/react/provider.cjs.map +1 -1
  109. package/dist/react/provider.js +23 -21
  110. package/dist/react/provider.js.map +1 -1
  111. package/dist/react/use-event-bus.cjs +24 -20
  112. package/dist/react/use-event-bus.cjs.map +1 -1
  113. package/dist/react/use-event-bus.js +24 -21
  114. package/dist/react/use-event-bus.js.map +1 -1
  115. package/dist/react/use-instance.cjs +43 -36
  116. package/dist/react/use-instance.cjs.map +1 -1
  117. package/dist/react/use-instance.js +43 -36
  118. package/dist/react/use-instance.js.map +1 -1
  119. package/dist/react/use-local.cjs +48 -64
  120. package/dist/react/use-local.cjs.map +1 -1
  121. package/dist/react/use-local.js +47 -63
  122. package/dist/react/use-local.js.map +1 -1
  123. package/dist/react/use-model.cjs +84 -98
  124. package/dist/react/use-model.cjs.map +1 -1
  125. package/dist/react/use-model.js +84 -100
  126. package/dist/react/use-model.js.map +1 -1
  127. package/dist/react/use-singleton.cjs +19 -23
  128. package/dist/react/use-singleton.cjs.map +1 -1
  129. package/dist/react/use-singleton.js +16 -20
  130. package/dist/react/use-singleton.js.map +1 -1
  131. package/dist/react/use-subscribe-only.cjs +28 -22
  132. package/dist/react/use-subscribe-only.cjs.map +1 -1
  133. package/dist/react/use-subscribe-only.js +28 -22
  134. package/dist/react/use-subscribe-only.js.map +1 -1
  135. package/dist/react/use-teardown.cjs +20 -19
  136. package/dist/react/use-teardown.cjs.map +1 -1
  137. package/dist/react/use-teardown.js +20 -19
  138. package/dist/react/use-teardown.js.map +1 -1
  139. package/dist/react-native/NativeCollection.cjs +98 -78
  140. package/dist/react-native/NativeCollection.cjs.map +1 -1
  141. package/dist/react-native/NativeCollection.js +97 -77
  142. package/dist/react-native/NativeCollection.js.map +1 -1
  143. package/dist/react-native.cjs +2 -4
  144. package/dist/react-native.js +1 -4
  145. package/dist/react.cjs +24 -26
  146. package/dist/react.js +1 -17
  147. package/dist/singleton.cjs +28 -22
  148. package/dist/singleton.cjs.map +1 -1
  149. package/dist/singleton.js +29 -26
  150. package/dist/singleton.js.map +1 -1
  151. package/dist/walkPrototypeChain.cjs +20 -12
  152. package/dist/walkPrototypeChain.cjs.map +1 -1
  153. package/dist/walkPrototypeChain.js +21 -13
  154. package/dist/walkPrototypeChain.js.map +1 -1
  155. package/dist/web/IndexedDBCollection.cjs +53 -36
  156. package/dist/web/IndexedDBCollection.cjs.map +1 -1
  157. package/dist/web/IndexedDBCollection.js +52 -35
  158. package/dist/web/IndexedDBCollection.js.map +1 -1
  159. package/dist/web/WebStorageCollection.cjs +82 -84
  160. package/dist/web/WebStorageCollection.cjs.map +1 -1
  161. package/dist/web/WebStorageCollection.js +81 -83
  162. package/dist/web/WebStorageCollection.js.map +1 -1
  163. package/dist/web/idb.cjs +107 -99
  164. package/dist/web/idb.cjs.map +1 -1
  165. package/dist/web/idb.js +108 -105
  166. package/dist/web/idb.js.map +1 -1
  167. package/dist/web.cjs +4 -6
  168. package/dist/web.js +1 -5
  169. package/dist/wrapAsyncMethods.cjs +141 -168
  170. package/dist/wrapAsyncMethods.cjs.map +1 -1
  171. package/dist/wrapAsyncMethods.js +141 -168
  172. package/dist/wrapAsyncMethods.js.map +1 -1
  173. package/examples/primitive/channel.ts +109 -0
  174. package/examples/primitive/collection.ts +118 -0
  175. package/examples/primitive/controller.ts +118 -0
  176. package/examples/primitive/counter.ts +108 -0
  177. package/examples/primitive/env.d.ts +1 -0
  178. package/examples/primitive/eventbus.ts +77 -0
  179. package/examples/primitive/feed.ts +162 -0
  180. package/examples/primitive/model.ts +82 -0
  181. package/examples/primitive/pagination.ts +91 -0
  182. package/examples/primitive/pending.ts +189 -0
  183. package/examples/primitive/persistent-collection.ts +116 -0
  184. package/examples/primitive/resource.ts +114 -0
  185. package/examples/primitive/selection.ts +96 -0
  186. package/examples/primitive/sorting.ts +112 -0
  187. package/examples/primitive/timer.ts +58 -0
  188. package/examples/primitive/trackable.ts +225 -0
  189. package/examples/primitive/tsconfig.json +20 -0
  190. package/examples/primitive/viewmodel-service.ts +161 -0
  191. package/examples/react/AuthExample/index.html +12 -0
  192. package/examples/react/AuthExample/src/App.tsx +29 -0
  193. package/examples/react/AuthExample/src/components/AdminPage.tsx +51 -0
  194. package/examples/react/AuthExample/src/components/AppHeader.tsx +32 -0
  195. package/examples/react/AuthExample/src/components/AuthGuard.tsx +50 -0
  196. package/examples/react/AuthExample/src/components/AuthScreen.tsx +181 -0
  197. package/examples/react/AuthExample/src/components/DashboardPage.tsx +41 -0
  198. package/examples/react/AuthExample/src/components/ProfilePage.tsx +44 -0
  199. package/examples/react/AuthExample/src/components/Toast.tsx +41 -0
  200. package/examples/react/AuthExample/src/env.d.ts +10 -0
  201. package/examples/react/AuthExample/src/events/AppEventBus.ts +7 -0
  202. package/examples/react/AuthExample/src/main.tsx +10 -0
  203. package/examples/react/AuthExample/src/mock/api.ts +78 -0
  204. package/examples/react/AuthExample/src/models/LoginFormModel.ts +19 -0
  205. package/examples/react/AuthExample/src/models/RegisterFormModel.ts +25 -0
  206. package/examples/react/AuthExample/src/services/AuthService.ts +21 -0
  207. package/examples/react/AuthExample/src/styles.css +445 -0
  208. package/examples/react/AuthExample/src/types/auth.ts +12 -0
  209. package/examples/react/AuthExample/src/viewmodels/AuthViewModel.ts +111 -0
  210. package/examples/react/AuthExample/tsconfig.json +22 -0
  211. package/examples/react/AuthExample/vite.config.ts +18 -0
  212. package/examples/react/ComplexApp/index.html +12 -0
  213. package/examples/react/ComplexApp/src/App.tsx +17 -0
  214. package/examples/react/ComplexApp/src/channels/ActivityChannel.ts +24 -0
  215. package/examples/react/ComplexApp/src/channels/DashboardChannel.ts +26 -0
  216. package/examples/react/ComplexApp/src/channels/ErrorsChannel.ts +5 -0
  217. package/examples/react/ComplexApp/src/channels/LatencyChannel.ts +5 -0
  218. package/examples/react/ComplexApp/src/channels/OrdersChannel.ts +5 -0
  219. package/examples/react/ComplexApp/src/channels/RevenueChannel.ts +5 -0
  220. package/examples/react/ComplexApp/src/channels/TrafficChannel.ts +5 -0
  221. package/examples/react/ComplexApp/src/channels/UsersMetricChannel.ts +5 -0
  222. package/examples/react/ComplexApp/src/collections/DashboardCollection.ts +6 -0
  223. package/examples/react/ComplexApp/src/collections/ErrorsCollection.ts +3 -0
  224. package/examples/react/ComplexApp/src/collections/LatencyCollection.ts +3 -0
  225. package/examples/react/ComplexApp/src/collections/OrdersCollection.ts +3 -0
  226. package/examples/react/ComplexApp/src/collections/RevenueCollection.ts +3 -0
  227. package/examples/react/ComplexApp/src/collections/TrafficCollection.ts +3 -0
  228. package/examples/react/ComplexApp/src/collections/UsersMetricCollection.ts +3 -0
  229. package/examples/react/ComplexApp/src/components/activity/ActivityFeed.tsx +31 -0
  230. package/examples/react/ComplexApp/src/components/activity/ActivityItemRow.tsx +35 -0
  231. package/examples/react/ComplexApp/src/components/dashboard/DashboardCard.tsx +37 -0
  232. package/examples/react/ComplexApp/src/components/dashboard/DashboardPage.tsx +34 -0
  233. package/examples/react/ComplexApp/src/components/layout/Navbar.tsx +32 -0
  234. package/examples/react/ComplexApp/src/components/layout/SocialFeedPanel.tsx +57 -0
  235. package/examples/react/ComplexApp/src/components/shared/Spinner.tsx +3 -0
  236. package/examples/react/ComplexApp/src/components/shared/StatusIndicator.tsx +13 -0
  237. package/examples/react/ComplexApp/src/components/shared/Toast.tsx +40 -0
  238. package/examples/react/ComplexApp/src/env.d.ts +10 -0
  239. package/examples/react/ComplexApp/src/events/AppEventBus.ts +7 -0
  240. package/examples/react/ComplexApp/src/main.tsx +10 -0
  241. package/examples/react/ComplexApp/src/mock-remote/MockWebSocket.ts +38 -0
  242. package/examples/react/ComplexApp/src/mock-remote/activity-api.ts +48 -0
  243. package/examples/react/ComplexApp/src/mock-remote/dashboard-generators.ts +45 -0
  244. package/examples/react/ComplexApp/src/mock-remote/delay.ts +18 -0
  245. package/examples/react/ComplexApp/src/mock-remote/social-api.ts +55 -0
  246. package/examples/react/ComplexApp/src/resources/ActivityResource.ts +12 -0
  247. package/examples/react/ComplexApp/src/resources/SocialFeedResource.ts +17 -0
  248. package/examples/react/ComplexApp/src/styles.css +463 -0
  249. package/examples/react/ComplexApp/src/types/activity.ts +8 -0
  250. package/examples/react/ComplexApp/src/types/dashboard.ts +5 -0
  251. package/examples/react/ComplexApp/src/types/social.ts +8 -0
  252. package/examples/react/ComplexApp/src/types/users.ts +6 -0
  253. package/examples/react/ComplexApp/src/viewmodels/ActivityFeedViewModel.ts +68 -0
  254. package/examples/react/ComplexApp/src/viewmodels/AppStateViewModel.ts +26 -0
  255. package/examples/react/ComplexApp/src/viewmodels/DashboardCardViewModel.ts +69 -0
  256. package/examples/react/ComplexApp/src/viewmodels/ErrorsCardViewModel.ts +9 -0
  257. package/examples/react/ComplexApp/src/viewmodels/LatencyCardViewModel.ts +9 -0
  258. package/examples/react/ComplexApp/src/viewmodels/OrdersCardViewModel.ts +9 -0
  259. package/examples/react/ComplexApp/src/viewmodels/RevenueCardViewModel.ts +9 -0
  260. package/examples/react/ComplexApp/src/viewmodels/SocialFeedViewModel.ts +39 -0
  261. package/examples/react/ComplexApp/src/viewmodels/TrafficCardViewModel.ts +9 -0
  262. package/examples/react/ComplexApp/src/viewmodels/UsersMetricCardViewModel.ts +9 -0
  263. package/examples/react/ComplexApp/tsconfig.json +22 -0
  264. package/examples/react/ComplexApp/vite.config.ts +18 -0
  265. package/examples/react/FullApp/index.html +12 -0
  266. package/examples/react/FullApp/src/App.tsx +28 -0
  267. package/examples/react/FullApp/src/collections/ConversationsCollection.ts +4 -0
  268. package/examples/react/FullApp/src/collections/LocationsCollection.ts +4 -0
  269. package/examples/react/FullApp/src/components/auth/LoginPage.tsx +80 -0
  270. package/examples/react/FullApp/src/components/dashboard/DashboardPage.tsx +29 -0
  271. package/examples/react/FullApp/src/components/dashboard/RecentActivityCard.tsx +35 -0
  272. package/examples/react/FullApp/src/components/dashboard/StatsCard.tsx +19 -0
  273. package/examples/react/FullApp/src/components/layout/AppShell.tsx +31 -0
  274. package/examples/react/FullApp/src/components/layout/Header.tsx +25 -0
  275. package/examples/react/FullApp/src/components/layout/Sidebar.tsx +29 -0
  276. package/examples/react/FullApp/src/components/locations/LocationFilters.tsx +60 -0
  277. package/examples/react/FullApp/src/components/locations/LocationForm.tsx +112 -0
  278. package/examples/react/FullApp/src/components/locations/LocationProfilePage.tsx +81 -0
  279. package/examples/react/FullApp/src/components/locations/LocationsPage.tsx +127 -0
  280. package/examples/react/FullApp/src/components/messaging/ConversationList.tsx +59 -0
  281. package/examples/react/FullApp/src/components/messaging/MessageBubble.tsx +22 -0
  282. package/examples/react/FullApp/src/components/messaging/MessageThread.tsx +100 -0
  283. package/examples/react/FullApp/src/components/messaging/MessagingPage.tsx +52 -0
  284. package/examples/react/FullApp/src/components/shared/ErrorBanner.tsx +3 -0
  285. package/examples/react/FullApp/src/components/shared/Spinner.tsx +7 -0
  286. package/examples/react/FullApp/src/components/shared/Toast.tsx +41 -0
  287. package/examples/react/FullApp/src/components/users/UserFilters.tsx +59 -0
  288. package/examples/react/FullApp/src/components/users/UsersPage.tsx +80 -0
  289. package/examples/react/FullApp/src/components/users/UsersTable.tsx +52 -0
  290. package/examples/react/FullApp/src/env.d.ts +10 -0
  291. package/examples/react/FullApp/src/events/AppEventBus.ts +7 -0
  292. package/examples/react/FullApp/src/main.tsx +10 -0
  293. package/examples/react/FullApp/src/mock/delay.ts +21 -0
  294. package/examples/react/FullApp/src/mock/locations.ts +76 -0
  295. package/examples/react/FullApp/src/mock/messages.ts +237 -0
  296. package/examples/react/FullApp/src/mock/users.ts +84 -0
  297. package/examples/react/FullApp/src/models/LocationFormModel.ts +31 -0
  298. package/examples/react/FullApp/src/models/LoginFormModel.ts +19 -0
  299. package/examples/react/FullApp/src/resources/UsersResource.ts +12 -0
  300. package/examples/react/FullApp/src/services/AuthService.ts +18 -0
  301. package/examples/react/FullApp/src/services/LocationService.ts +23 -0
  302. package/examples/react/FullApp/src/services/MessageService.ts +65 -0
  303. package/examples/react/FullApp/src/services/UserService.ts +23 -0
  304. package/examples/react/FullApp/src/styles.css +767 -0
  305. package/examples/react/FullApp/src/types/conversation.ts +7 -0
  306. package/examples/react/FullApp/src/types/location.ts +12 -0
  307. package/examples/react/FullApp/src/types/message.ts +7 -0
  308. package/examples/react/FullApp/src/types/user.ts +10 -0
  309. package/examples/react/FullApp/src/viewmodels/AuthViewModel.ts +51 -0
  310. package/examples/react/FullApp/src/viewmodels/ConversationsViewModel.ts +89 -0
  311. package/examples/react/FullApp/src/viewmodels/DashboardViewModel.ts +56 -0
  312. package/examples/react/FullApp/src/viewmodels/LocationProfileViewModel.ts +81 -0
  313. package/examples/react/FullApp/src/viewmodels/LocationsViewModel.ts +113 -0
  314. package/examples/react/FullApp/src/viewmodels/MessageThreadViewModel.ts +83 -0
  315. package/examples/react/FullApp/src/viewmodels/UsersViewModel.ts +88 -0
  316. package/examples/react/FullApp/tsconfig.json +22 -0
  317. package/examples/react/FullApp/vite.config.ts +18 -0
  318. package/examples/react/WorkerApp/index.html +12 -0
  319. package/examples/react/WorkerApp/src/App.tsx +24 -0
  320. package/examples/react/WorkerApp/src/channels/MessagingChannel.ts +46 -0
  321. package/examples/react/WorkerApp/src/channels/WorkerStatusChannel.ts +35 -0
  322. package/examples/react/WorkerApp/src/components/auth/LoginPage.tsx +60 -0
  323. package/examples/react/WorkerApp/src/components/layout/AppShell.tsx +31 -0
  324. package/examples/react/WorkerApp/src/components/layout/Header.tsx +23 -0
  325. package/examples/react/WorkerApp/src/components/layout/Sidebar.tsx +28 -0
  326. package/examples/react/WorkerApp/src/components/messaging/ComposeBar.tsx +33 -0
  327. package/examples/react/WorkerApp/src/components/messaging/ConversationList.tsx +59 -0
  328. package/examples/react/WorkerApp/src/components/messaging/MessageBubble.tsx +45 -0
  329. package/examples/react/WorkerApp/src/components/messaging/MessageThread.tsx +93 -0
  330. package/examples/react/WorkerApp/src/components/messaging/MessagingPage.tsx +53 -0
  331. package/examples/react/WorkerApp/src/components/shared/ErrorBanner.tsx +3 -0
  332. package/examples/react/WorkerApp/src/components/shared/PendingBanner.tsx +37 -0
  333. package/examples/react/WorkerApp/src/components/shared/Spinner.tsx +7 -0
  334. package/examples/react/WorkerApp/src/components/shared/Toast.tsx +41 -0
  335. package/examples/react/WorkerApp/src/components/shift/ShiftPage.tsx +98 -0
  336. package/examples/react/WorkerApp/src/components/shift/ShiftTimer.tsx +24 -0
  337. package/examples/react/WorkerApp/src/components/shift/SiteSelector.tsx +27 -0
  338. package/examples/react/WorkerApp/src/components/sites/SiteFilters.tsx +61 -0
  339. package/examples/react/WorkerApp/src/components/sites/SitesPage.tsx +102 -0
  340. package/examples/react/WorkerApp/src/env.d.ts +10 -0
  341. package/examples/react/WorkerApp/src/events/AppEventBus.ts +7 -0
  342. package/examples/react/WorkerApp/src/main.tsx +10 -0
  343. package/examples/react/WorkerApp/src/mock/MockWebSocket.ts +38 -0
  344. package/examples/react/WorkerApp/src/mock/delay.ts +31 -0
  345. package/examples/react/WorkerApp/src/mock/messages.ts +120 -0
  346. package/examples/react/WorkerApp/src/mock/shifts.ts +57 -0
  347. package/examples/react/WorkerApp/src/mock/sites.ts +14 -0
  348. package/examples/react/WorkerApp/src/mock/workers.ts +12 -0
  349. package/examples/react/WorkerApp/src/models/ComposeMessageModel.ts +17 -0
  350. package/examples/react/WorkerApp/src/resources/ConversationsResource.ts +10 -0
  351. package/examples/react/WorkerApp/src/resources/MessagesResource.ts +32 -0
  352. package/examples/react/WorkerApp/src/resources/ShiftResource.ts +73 -0
  353. package/examples/react/WorkerApp/src/resources/SitesResource.ts +11 -0
  354. package/examples/react/WorkerApp/src/resources/WorkersResource.ts +11 -0
  355. package/examples/react/WorkerApp/src/styles.css +756 -0
  356. package/examples/react/WorkerApp/src/types/conversation.ts +7 -0
  357. package/examples/react/WorkerApp/src/types/message.ts +7 -0
  358. package/examples/react/WorkerApp/src/types/shift.ts +13 -0
  359. package/examples/react/WorkerApp/src/types/site.ts +8 -0
  360. package/examples/react/WorkerApp/src/types/worker.ts +8 -0
  361. package/examples/react/WorkerApp/src/viewmodels/AuthViewModel.ts +41 -0
  362. package/examples/react/WorkerApp/src/viewmodels/ConversationsViewModel.ts +83 -0
  363. package/examples/react/WorkerApp/src/viewmodels/MessageThreadViewModel.ts +113 -0
  364. package/examples/react/WorkerApp/src/viewmodels/ShiftViewModel.ts +147 -0
  365. package/examples/react/WorkerApp/src/viewmodels/SitesViewModel.ts +82 -0
  366. package/examples/react/WorkerApp/tsconfig.json +22 -0
  367. package/examples/react/WorkerApp/vite.config.ts +18 -0
  368. package/package.json +11 -9
  369. package/src/Pending.test.ts +1 -2
  370. package/src/Sorting.test.ts +1 -1
  371. package/src/produceDraft.test.ts +3 -3
  372. package/src/react/components/CardList.test.tsx +1 -1
  373. package/src/react/components/DataTable.test.tsx +1 -1
  374. package/src/react/components/InfiniteScroll.test.tsx +5 -5
  375. package/dist/mvc-kit.cjs.map +0 -1
  376. package/dist/mvc-kit.js.map +0 -1
  377. package/dist/react-native.cjs.map +0 -1
  378. package/dist/react-native.js.map +0 -1
  379. package/dist/react.cjs.map +0 -1
  380. package/dist/react.js.map +0 -1
  381. package/dist/web.cjs.map +0 -1
  382. package/dist/web.js.map +0 -1
@@ -0,0 +1,7 @@
1
+ export interface ConversationState {
2
+ id: string;
3
+ participantIds: string[];
4
+ lastMessage: string;
5
+ lastMessageAt: string;
6
+ unreadCount: number;
7
+ }
@@ -0,0 +1,12 @@
1
+ export interface LocationState {
2
+ id: string;
3
+ name: string;
4
+ type: 'office' | 'warehouse' | 'retail';
5
+ status: 'active' | 'inactive' | 'maintenance';
6
+ city: string;
7
+ state: string;
8
+ address: string;
9
+ capacity: number;
10
+ managerId: string;
11
+ createdAt: string;
12
+ }
@@ -0,0 +1,7 @@
1
+ export interface MessageState {
2
+ id: string;
3
+ conversationId: string;
4
+ senderId: string;
5
+ text: string;
6
+ sentAt: string;
7
+ }
@@ -0,0 +1,10 @@
1
+ export interface UserState {
2
+ id: string;
3
+ firstName: string;
4
+ lastName: string;
5
+ email: string;
6
+ role: 'admin' | 'manager' | 'member';
7
+ status: 'active' | 'inactive';
8
+ avatarUrl: string;
9
+ createdAt: string;
10
+ }
@@ -0,0 +1,51 @@
1
+ import { ViewModel, singleton, isAbortError, classifyError } from 'mvc-kit';
2
+ import type { UserState } from '../types/user';
3
+ import { AuthService } from '../services/AuthService';
4
+ import { AppEventBus } from '../events/AppEventBus';
5
+
6
+ interface AuthState {
7
+ user: UserState | null;
8
+ isAuthenticated: boolean;
9
+ }
10
+
11
+ interface AuthEvents {
12
+ loginFailed: { message: string };
13
+ }
14
+
15
+ export class AuthViewModel extends ViewModel<AuthState, AuthEvents> {
16
+ static DEFAULT_STATE: AuthState = { user: null, isAuthenticated: false };
17
+
18
+ // --- Private fields ---
19
+ private authService = singleton(AuthService);
20
+ private bus = singleton(AppEventBus);
21
+
22
+ // --- Computed getters ---
23
+ get displayName(): string {
24
+ const { user } = this.state;
25
+ return user ? `${user.firstName} ${user.lastName}` : '';
26
+ }
27
+
28
+ get initials(): string {
29
+ const { user } = this.state;
30
+ return user ? `${user.firstName[0]}${user.lastName[0]}` : '';
31
+ }
32
+
33
+ // --- Actions ---
34
+ async login(email: string, password: string) {
35
+ try {
36
+ const user = await this.authService.login(email, password, this.disposeSignal);
37
+ this.set({ user, isAuthenticated: true });
38
+ this.bus.emit('toast:show', { message: `Welcome, ${user.firstName}!`, severity: 'success' });
39
+ } catch (e) {
40
+ if (!isAbortError(e)) {
41
+ this.emit('loginFailed', { message: classifyError(e).message });
42
+ }
43
+ throw e;
44
+ }
45
+ }
46
+
47
+ logout() {
48
+ this.set({ user: null, isAuthenticated: false });
49
+ this.bus.emit('toast:show', { message: 'Logged out', severity: 'info' });
50
+ }
51
+ }
@@ -0,0 +1,89 @@
1
+ import { ViewModel, singleton } from 'mvc-kit';
2
+ import type { ConversationState } from '../types/conversation';
3
+ import { MessageService } from '../services/MessageService';
4
+ import { ConversationsCollection } from '../collections/ConversationsCollection';
5
+ import { UsersResource } from '../resources/UsersResource';
6
+ import { AuthViewModel } from './AuthViewModel';
7
+
8
+ export interface ConversationDisplay extends ConversationState {
9
+ displayName: string;
10
+ }
11
+
12
+ interface ConversationsState {
13
+ search: string;
14
+ selectedId: string | null;
15
+ currentUserId: string;
16
+ }
17
+
18
+ export class ConversationsViewModel extends ViewModel<ConversationsState> {
19
+ // --- Private fields ---
20
+ private service = singleton(MessageService);
21
+ private collection = singleton(ConversationsCollection);
22
+ private users = singleton(UsersResource);
23
+
24
+ // --- Computed getters ---
25
+ get items(): ConversationState[] {
26
+ return this.collection.items as ConversationState[];
27
+ }
28
+
29
+ get filtered(): ConversationDisplay[] {
30
+ const { search, currentUserId } = this.state;
31
+ let result = this.items;
32
+
33
+ if (search) {
34
+ const q = search.toLowerCase();
35
+ result = result.filter(conv =>
36
+ conv.participantIds.some(id => {
37
+ const user = this.users.get(id);
38
+ return user
39
+ ? `${user.firstName} ${user.lastName}`.toLowerCase().includes(q)
40
+ : false;
41
+ }),
42
+ );
43
+ }
44
+
45
+ return result.map(conv => ({
46
+ ...conv,
47
+ displayName: conv.participantIds
48
+ .filter(id => id !== currentUserId)
49
+ .map(id => {
50
+ const user = this.users.get(id);
51
+ return user ? `${user.firstName} ${user.lastName}` : 'Unknown';
52
+ })
53
+ .join(', '),
54
+ }));
55
+ }
56
+
57
+ get selected(): ConversationDisplay | undefined {
58
+ return this.filtered.find(c => c.id === this.state.selectedId);
59
+ }
60
+
61
+ get totalUnread(): number {
62
+ return this.items.reduce((sum, c) => sum + c.unreadCount, 0);
63
+ }
64
+
65
+ // --- Lifecycle ---
66
+ protected onInit() {
67
+ const auth = singleton(AuthViewModel);
68
+ const currentUserId = auth.state.user?.id ?? '';
69
+ this.set({ currentUserId });
70
+
71
+ if (this.collection.length === 0) this.load();
72
+ }
73
+
74
+ // --- Actions ---
75
+ async load() {
76
+ const conversations = await this.service.getConversations(
77
+ this.state.currentUserId,
78
+ this.disposeSignal,
79
+ );
80
+ this.collection.reset(conversations);
81
+ }
82
+
83
+ selectConversation(id: string) {
84
+ this.set({ selectedId: id });
85
+ }
86
+
87
+ // --- Setters ---
88
+ setSearch(search: string) { this.set({ search }); }
89
+ }
@@ -0,0 +1,56 @@
1
+ import { ViewModel, singleton } from 'mvc-kit';
2
+ import type { UserState } from '../types/user';
3
+ import type { LocationState } from '../types/location';
4
+ import { LocationService } from '../services/LocationService';
5
+ import { UsersResource } from '../resources/UsersResource';
6
+ import { LocationsCollection } from '../collections/LocationsCollection';
7
+
8
+ export class DashboardViewModel extends ViewModel {
9
+ // --- Private fields ---
10
+ private users = singleton(UsersResource);
11
+ private locationService = singleton(LocationService);
12
+ private locationsCollection = singleton(LocationsCollection);
13
+
14
+ // --- Computed getters ---
15
+ get totalUsers(): number {
16
+ return this.users.length;
17
+ }
18
+
19
+ get activeUsers(): number {
20
+ return (this.users.items as UserState[]).filter(u => u.status === 'active').length;
21
+ }
22
+
23
+ get totalLocations(): number {
24
+ return this.locationsCollection.length;
25
+ }
26
+
27
+ get activeLocations(): number {
28
+ return (this.locationsCollection.items as LocationState[]).filter(l => l.status === 'active').length;
29
+ }
30
+
31
+ get usersByRole(): Record<string, number> {
32
+ const counts: Record<string, number> = {};
33
+ for (const u of this.users.items as UserState[]) {
34
+ counts[u.role] = (counts[u.role] ?? 0) + 1;
35
+ }
36
+ return counts;
37
+ }
38
+
39
+ get recentUsers(): UserState[] {
40
+ return [...(this.users.items as UserState[])]
41
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
42
+ .slice(0, 5);
43
+ }
44
+
45
+ // --- Lifecycle ---
46
+ protected onInit() {
47
+ if (this.users.length === 0) this.users.loadAll();
48
+ if (this.locationsCollection.length === 0) this.loadLocations();
49
+ }
50
+
51
+ // --- Actions ---
52
+ async loadLocations() {
53
+ const locations = await this.locationService.getAll(this.disposeSignal);
54
+ this.locationsCollection.reset(locations);
55
+ }
56
+ }
@@ -0,0 +1,81 @@
1
+ import { ViewModel, singleton, isAbortError } from 'mvc-kit';
2
+ import type { LocationState } from '../types/location';
3
+ import { LocationService } from '../services/LocationService';
4
+ import { LocationsCollection } from '../collections/LocationsCollection';
5
+ import { UsersResource } from '../resources/UsersResource';
6
+ import { AppEventBus } from '../events/AppEventBus';
7
+ import { LocationFormModel } from '../models/LocationFormModel';
8
+
9
+ interface ProfileState {
10
+ location: LocationState | null;
11
+ locationId: string;
12
+ }
13
+
14
+ interface ProfileEvents {
15
+ saved: { id: string };
16
+ }
17
+
18
+ export class LocationProfileViewModel extends ViewModel<ProfileState, ProfileEvents> {
19
+ // --- Private fields ---
20
+ private service = singleton(LocationService);
21
+ private collection = singleton(LocationsCollection);
22
+ private users = singleton(UsersResource);
23
+ private bus = singleton(AppEventBus);
24
+ public model: LocationFormModel | null = null;
25
+
26
+ // --- Computed getters ---
27
+ get managerName(): string {
28
+ const { location } = this.state;
29
+ if (!location) return '';
30
+ const manager = this.users.get(location.managerId);
31
+ return manager ? `${manager.firstName} ${manager.lastName}` : 'Unknown';
32
+ }
33
+
34
+ // --- Lifecycle ---
35
+ protected onInit() {
36
+ this.load();
37
+ }
38
+
39
+ protected onDispose() {
40
+ this.model?.dispose();
41
+ }
42
+
43
+ // --- Actions ---
44
+ async load() {
45
+ const location = await this.service.getById(
46
+ this.state.locationId,
47
+ this.disposeSignal,
48
+ );
49
+ this.model = new LocationFormModel({
50
+ name: location.name,
51
+ type: location.type,
52
+ city: location.city,
53
+ state: location.state,
54
+ address: location.address,
55
+ capacity: location.capacity,
56
+ });
57
+ this.set({ location });
58
+ }
59
+
60
+ async save() {
61
+ if (!this.model || !this.model.valid) return;
62
+
63
+ try {
64
+ const updated = await this.service.update(
65
+ this.state.locationId,
66
+ this.model.state,
67
+ this.disposeSignal,
68
+ );
69
+ this.collection.update(this.state.locationId, updated);
70
+ this.model.commit();
71
+ this.set({ location: updated });
72
+ this.emit('saved', { id: updated.id });
73
+ this.bus.emit('toast:show', { message: 'Location saved', severity: 'success' });
74
+ } catch (e) {
75
+ if (!isAbortError(e)) {
76
+ this.bus.emit('toast:show', { message: 'Failed to save location', severity: 'error' });
77
+ }
78
+ throw e;
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,113 @@
1
+ import { ViewModel, singleton, Sorting, Pagination, Selection } from 'mvc-kit';
2
+ import type { LocationState } from '../types/location';
3
+ import { LocationService } from '../services/LocationService';
4
+ import { LocationsCollection } from '../collections/LocationsCollection';
5
+ import { AppEventBus } from '../events/AppEventBus';
6
+
7
+ interface LocationsState {
8
+ search: string;
9
+ typeFilter: 'all' | LocationState['type'];
10
+ statusFilter: 'all' | LocationState['status'];
11
+ }
12
+
13
+ export class LocationsViewModel extends ViewModel<LocationsState> {
14
+ // --- Private fields ---
15
+ private service = singleton(LocationService);
16
+ collection = singleton(LocationsCollection);
17
+ private bus = singleton(AppEventBus);
18
+
19
+ readonly sorting = new Sorting<LocationState>({ sorts: [{ key: 'name', direction: 'asc' }] });
20
+ readonly pagination = new Pagination({ pageSize: 10 });
21
+ readonly selection = new Selection<string>();
22
+
23
+ // --- Computed getters ---
24
+ get items(): LocationState[] {
25
+ return this.collection.items as LocationState[];
26
+ }
27
+
28
+ get filtered(): LocationState[] {
29
+ const { search, typeFilter, statusFilter } = this.state;
30
+ let result = this.items;
31
+
32
+ if (search) {
33
+ const q = search.toLowerCase();
34
+ result = result.filter(loc =>
35
+ loc.name.toLowerCase().includes(q) ||
36
+ loc.city.toLowerCase().includes(q),
37
+ );
38
+ }
39
+ if (typeFilter !== 'all') {
40
+ result = result.filter(loc => loc.type === typeFilter);
41
+ }
42
+ if (statusFilter !== 'all') {
43
+ result = result.filter(loc => loc.status === statusFilter);
44
+ }
45
+ return result;
46
+ }
47
+
48
+ get sorted(): LocationState[] {
49
+ return this.sorting.apply(this.filtered);
50
+ }
51
+
52
+ get paged(): LocationState[] {
53
+ return this.pagination.apply(this.sorted);
54
+ }
55
+
56
+ get total(): number {
57
+ return this.items.length;
58
+ }
59
+
60
+ get filteredCount(): number {
61
+ return this.filtered.length;
62
+ }
63
+
64
+ get hasResults(): boolean {
65
+ return this.filtered.length > 0;
66
+ }
67
+
68
+ get isEmpty(): boolean {
69
+ return this.total > 0 && !this.hasResults;
70
+ }
71
+
72
+ get selectedItems(): LocationState[] {
73
+ return this.selection.selectedFrom(this.filtered, loc => loc.id);
74
+ }
75
+
76
+ // --- Lifecycle ---
77
+ protected onInit() {
78
+ if (this.collection.length === 0) this.load();
79
+ }
80
+
81
+ // --- Actions ---
82
+ async load() {
83
+ const data = await this.service.getAll(this.disposeSignal);
84
+ this.collection.reset(data);
85
+ }
86
+
87
+ async refresh() {
88
+ await this.load();
89
+ this.bus.emit('toast:show', { message: 'Locations refreshed', severity: 'info' });
90
+ }
91
+
92
+ async bulkToggleStatus() {
93
+ const items = this.selectedItems;
94
+ if (items.length === 0) return;
95
+
96
+ for (const loc of items) {
97
+ const newStatus = loc.status === 'active' ? 'inactive' : 'active';
98
+ const updated = await this.service.update(loc.id, { status: newStatus }, this.disposeSignal);
99
+ this.collection.update(loc.id, updated);
100
+ }
101
+
102
+ this.selection.clear();
103
+ this.bus.emit('toast:show', {
104
+ message: `Toggled status for ${items.length} location(s)`,
105
+ severity: 'success',
106
+ });
107
+ }
108
+
109
+ // --- Setters ---
110
+ setSearch(search: string) { this.set({ search }); this.pagination.reset(); }
111
+ setTypeFilter(typeFilter: LocationsState['typeFilter']) { this.set({ typeFilter }); this.pagination.reset(); }
112
+ setStatusFilter(statusFilter: LocationsState['statusFilter']) { this.set({ statusFilter }); this.pagination.reset(); }
113
+ }
@@ -0,0 +1,83 @@
1
+ import { ViewModel, singleton, Feed } from 'mvc-kit';
2
+ import type { MessageState } from '../types/message';
3
+ import { MessageService } from '../services/MessageService';
4
+ import { AuthViewModel } from './AuthViewModel';
5
+
6
+ interface ThreadState {
7
+ draft: string;
8
+ }
9
+
10
+ interface ThreadEvents {
11
+ messageSent: { conversationId: string };
12
+ }
13
+
14
+ export class MessageThreadViewModel extends ViewModel<ThreadState, ThreadEvents> {
15
+ // --- Private fields ---
16
+ private service = singleton(MessageService);
17
+ private _loadController: AbortController | null = null;
18
+ private _conversationId: string = '';
19
+
20
+ readonly feed = new Feed<MessageState>();
21
+
22
+ // --- Computed getters ---
23
+ get sortedMessages(): MessageState[] {
24
+ return [...this.feed.items].sort(
25
+ (a, b) => new Date(a.sentAt).getTime() - new Date(b.sentAt).getTime(),
26
+ );
27
+ }
28
+
29
+ get canSend(): boolean {
30
+ return this.state.draft.trim().length > 0;
31
+ }
32
+
33
+ // --- Actions ---
34
+
35
+ /** Load messages for a conversation. Cancels any in-flight load via AbortSignal.any(). */
36
+ async loadConversation(conversationId: string) {
37
+ // Cancel previous load (per-call cancellation)
38
+ this._loadController?.abort();
39
+ this._loadController = new AbortController();
40
+ this._conversationId = conversationId;
41
+
42
+ this.feed.reset();
43
+
44
+ const page = await this.service.getMessages(
45
+ conversationId,
46
+ AbortSignal.any([this.disposeSignal, this._loadController.signal]),
47
+ );
48
+ this.feed.appendPage(page);
49
+ }
50
+
51
+ async loadOlderMessages() {
52
+ if (!this.feed.hasMore || !this._conversationId) return;
53
+
54
+ const page = await this.service.getMessages(
55
+ this._conversationId,
56
+ this.disposeSignal,
57
+ { cursor: this.feed.cursor },
58
+ );
59
+ this.feed.appendPage(page);
60
+ }
61
+
62
+ async sendMessage(conversationId: string) {
63
+ const text = this.state.draft.trim();
64
+ if (!text) return;
65
+
66
+ const auth = singleton(AuthViewModel);
67
+ const senderId = auth.state.user?.id ?? '';
68
+
69
+ const message = await this.service.sendMessage(
70
+ conversationId,
71
+ senderId,
72
+ text,
73
+ this.disposeSignal,
74
+ );
75
+
76
+ this.feed.push(message);
77
+ this.set({ draft: '' });
78
+ this.emit('messageSent', { conversationId });
79
+ }
80
+
81
+ // --- Setters ---
82
+ setDraft(draft: string) { this.set({ draft }); }
83
+ }
@@ -0,0 +1,88 @@
1
+ import { ViewModel, singleton, Sorting, Pagination } from 'mvc-kit';
2
+ import type { UserState } from '../types/user';
3
+ import { UserService } from '../services/UserService';
4
+ import { UsersResource } from '../resources/UsersResource';
5
+
6
+ interface UsersState {
7
+ search: string;
8
+ roleFilter: 'all' | UserState['role'];
9
+ statusFilter: 'all' | UserState['status'];
10
+ }
11
+
12
+ export class UsersViewModel extends ViewModel<UsersState> {
13
+ // --- Private fields ---
14
+ private service = singleton(UserService);
15
+ private users = singleton(UsersResource);
16
+
17
+ readonly sorting = new Sorting<UserState>({ sorts: [{ key: 'firstName', direction: 'asc' }] });
18
+ readonly pagination = new Pagination({ pageSize: 25 });
19
+
20
+ // --- Computed getters ---
21
+ get items(): UserState[] {
22
+ return this.users.items as UserState[];
23
+ }
24
+
25
+ get filtered(): UserState[] {
26
+ const { search, roleFilter, statusFilter } = this.state;
27
+ let result = this.items;
28
+
29
+ if (search) {
30
+ const q = search.toLowerCase();
31
+ result = result.filter(u =>
32
+ u.firstName.toLowerCase().includes(q) ||
33
+ u.lastName.toLowerCase().includes(q) ||
34
+ u.email.toLowerCase().includes(q),
35
+ );
36
+ }
37
+ if (roleFilter !== 'all') {
38
+ result = result.filter(u => u.role === roleFilter);
39
+ }
40
+ if (statusFilter !== 'all') {
41
+ result = result.filter(u => u.status === statusFilter);
42
+ }
43
+ return result;
44
+ }
45
+
46
+ get sorted(): UserState[] {
47
+ return this.sorting.apply(this.filtered);
48
+ }
49
+
50
+ get paged(): UserState[] {
51
+ return this.pagination.apply(this.sorted);
52
+ }
53
+
54
+ get total(): number {
55
+ return this.items.length;
56
+ }
57
+
58
+ get filteredCount(): number {
59
+ return this.filtered.length;
60
+ }
61
+
62
+ get hasResults(): boolean {
63
+ return this.filtered.length > 0;
64
+ }
65
+
66
+ get isEmpty(): boolean {
67
+ return this.total > 0 && !this.hasResults;
68
+ }
69
+
70
+ // --- Lifecycle ---
71
+ protected onInit() {
72
+ if (this.users.length === 0) this.users.loadAll();
73
+ }
74
+
75
+ // --- Actions ---
76
+ async toggleStatus(id: string) {
77
+ const user = this.users.get(id);
78
+ if (!user) return;
79
+ const newStatus = user.status === 'active' ? 'inactive' : 'active';
80
+ const updated = await this.service.update(id, { status: newStatus }, this.disposeSignal);
81
+ this.users.update(id, updated);
82
+ }
83
+
84
+ // --- Setters ---
85
+ setSearch(search: string) { this.set({ search }); this.pagination.reset(); }
86
+ setRoleFilter(roleFilter: UsersState['roleFilter']) { this.set({ roleFilter }); this.pagination.reset(); }
87
+ setStatusFilter(statusFilter: UsersState['statusFilter']) { this.set({ statusFilter }); this.pagination.reset(); }
88
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "skipLibCheck": true,
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "erasableSyntaxOnly": false,
13
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
14
+ "types": [],
15
+ "baseUrl": ".",
16
+ "paths": {
17
+ "mvc-kit": ["../../../src/index.ts"],
18
+ "mvc-kit/react": ["../../../src/react/index.ts"]
19
+ }
20
+ },
21
+ "include": ["src"]
22
+ }
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'vite';
2
+ import { resolve } from 'node:path';
3
+
4
+ export default defineConfig({
5
+ root: import.meta.dirname,
6
+ define: {
7
+ __MVC_KIT_DEV__: true,
8
+ },
9
+ resolve: {
10
+ alias: {
11
+ 'mvc-kit/react': resolve(import.meta.dirname, '../../../src/react/index.ts'),
12
+ 'mvc-kit': resolve(import.meta.dirname, '../../../src/index.ts'),
13
+ },
14
+ },
15
+ server: {
16
+ port: 3000,
17
+ },
18
+ });
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>mvc-kit Worker App</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,24 @@
1
+ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
2
+ import { AppShell } from './components/layout/AppShell';
3
+ import { LoginPage } from './components/auth/LoginPage';
4
+ import { ShiftPage } from './components/shift/ShiftPage';
5
+ import { SitesPage } from './components/sites/SitesPage';
6
+ import { MessagingPage } from './components/messaging/MessagingPage';
7
+ import { Toast } from './components/shared/Toast';
8
+
9
+ export function App() {
10
+ return (
11
+ <BrowserRouter>
12
+ <Routes>
13
+ <Route path="/login" element={<LoginPage />} />
14
+ <Route element={<AppShell />}>
15
+ <Route path="/shift" element={<ShiftPage />} />
16
+ <Route path="/sites" element={<SitesPage />} />
17
+ <Route path="/messaging" element={<MessagingPage />} />
18
+ </Route>
19
+ <Route path="*" element={<Navigate to="/shift" replace />} />
20
+ </Routes>
21
+ <Toast />
22
+ </BrowserRouter>
23
+ );
24
+ }