snice 7.0.2 → 7.1.0

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 (304) hide show
  1. package/.agents/skills/snice/SKILL.md +4 -0
  2. package/.agents/skills/snice/references/core-kitchen-sink.ts +39 -1
  3. package/README.md +59 -14
  4. package/adapters/react/SniceProvider.js +2 -2
  5. package/adapters/react/SniceRouter.js +2 -2
  6. package/adapters/react/matchRoute.js +2 -2
  7. package/adapters/react/useRequestHandler.js +2 -2
  8. package/bin/analyzer-contracts.js +6 -1
  9. package/bin/postinstall.js +59 -0
  10. package/bin/project-analyzer.js +466 -5
  11. package/bin/snice.js +3 -0
  12. package/dist/app-context.d.ts +11 -0
  13. package/dist/cdn/accordion/snice-accordion.js +2 -2
  14. package/dist/cdn/action-bar/snice-action-bar.js +2 -2
  15. package/dist/cdn/activity-feed/snice-activity-feed.js +2 -2
  16. package/dist/cdn/alert/snice-alert.js +2 -2
  17. package/dist/cdn/app-tiles/snice-app-tiles.js +2 -2
  18. package/dist/cdn/approval-flow/snice-approval-flow.js +2 -2
  19. package/dist/cdn/audio-recorder/snice-audio-recorder.js +2 -2
  20. package/dist/cdn/availability/snice-availability.js +2 -2
  21. package/dist/cdn/avatar/snice-avatar.js +2 -2
  22. package/dist/cdn/avatar-group/snice-avatar-group.js +2 -2
  23. package/dist/cdn/badge/snice-badge.js +2 -2
  24. package/dist/cdn/banner/snice-banner.js +2 -2
  25. package/dist/cdn/binpack/snice-binpack.js +2 -2
  26. package/dist/cdn/book/snice-book.js +2 -2
  27. package/dist/cdn/booking/snice-booking.js +2 -2
  28. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +2 -2
  29. package/dist/cdn/button/snice-button.js +2 -2
  30. package/dist/cdn/calendar/snice-calendar.js +2 -2
  31. package/dist/cdn/camera/snice-camera.js +2 -2
  32. package/dist/cdn/camera-annotate/snice-camera-annotate.js +2 -2
  33. package/dist/cdn/candlestick/snice-candlestick.js +2 -2
  34. package/dist/cdn/card/snice-card.js +2 -2
  35. package/dist/cdn/carousel/snice-carousel.js +2 -2
  36. package/dist/cdn/cart/snice-cart.js +2 -2
  37. package/dist/cdn/chart/snice-chart.js +2 -2
  38. package/dist/cdn/chat/snice-chat.js +2 -2
  39. package/dist/cdn/checkbox/snice-checkbox.js +2 -2
  40. package/dist/cdn/chip/snice-chip.js +2 -2
  41. package/dist/cdn/code-block/snice-code-block.js +2 -2
  42. package/dist/cdn/color-display/snice-color-display.js +2 -2
  43. package/dist/cdn/color-picker/snice-color-picker.js +2 -2
  44. package/dist/cdn/command-palette/snice-command-palette.js +2 -2
  45. package/dist/cdn/comments/snice-comments.js +2 -2
  46. package/dist/cdn/countdown/snice-countdown.js +2 -2
  47. package/dist/cdn/cropper/snice-cropper.js +2 -2
  48. package/dist/cdn/data-card/snice-data-card.js +2 -2
  49. package/dist/cdn/date-picker/snice-date-picker.js +2 -2
  50. package/dist/cdn/date-range-picker/snice-date-range-picker.js +2 -2
  51. package/dist/cdn/date-time-picker/snice-date-time-picker.js +2 -2
  52. package/dist/cdn/diff/snice-diff.js +2 -2
  53. package/dist/cdn/divider/snice-divider.js +2 -2
  54. package/dist/cdn/doc/snice-doc.js +2 -2
  55. package/dist/cdn/draw/snice-draw.js +2 -2
  56. package/dist/cdn/drawer/snice-drawer.js +2 -2
  57. package/dist/cdn/empty-state/snice-empty-state.js +2 -2
  58. package/dist/cdn/estimate/snice-estimate.js +2 -2
  59. package/dist/cdn/file-gallery/snice-file-gallery.js +2 -2
  60. package/dist/cdn/file-upload/snice-file-upload.js +2 -2
  61. package/dist/cdn/flip-card/snice-flip-card.js +2 -2
  62. package/dist/cdn/flow/snice-flow.js +2 -2
  63. package/dist/cdn/form-layout/snice-form-layout.js +2 -2
  64. package/dist/cdn/funnel/snice-funnel.js +2 -2
  65. package/dist/cdn/gantt/snice-gantt.js +2 -2
  66. package/dist/cdn/gauge/snice-gauge.js +2 -2
  67. package/dist/cdn/grid/snice-grid.js +2 -2
  68. package/dist/cdn/heatmap/snice-heatmap.js +2 -2
  69. package/dist/cdn/image/snice-image.js +2 -2
  70. package/dist/cdn/input/snice-input.js +2 -2
  71. package/dist/cdn/invoice/snice-invoice.js +2 -2
  72. package/dist/cdn/kanban/snice-kanban.js +2 -2
  73. package/dist/cdn/key-value/snice-key-value.js +2 -2
  74. package/dist/cdn/kpi/snice-kpi.js +2 -2
  75. package/dist/cdn/layout/README.md +17 -2
  76. package/dist/cdn/layout/snice-layout.js +1656 -11
  77. package/dist/cdn/layout/snice-layout.js.map +1 -1
  78. package/dist/cdn/layout/snice-layout.min.js +379 -2
  79. package/dist/cdn/layout/snice-layout.min.js.map +1 -1
  80. package/dist/cdn/leaderboard/snice-leaderboard.js +2 -2
  81. package/dist/cdn/link/snice-link.js +2 -2
  82. package/dist/cdn/link-preview/snice-link-preview.js +2 -2
  83. package/dist/cdn/list/snice-list.js +2 -2
  84. package/dist/cdn/location/snice-location.js +2 -2
  85. package/dist/cdn/login/snice-login.js +2 -2
  86. package/dist/cdn/map/snice-map.js +2 -2
  87. package/dist/cdn/markdown/snice-markdown.js +2 -2
  88. package/dist/cdn/masonry/snice-masonry.js +2 -2
  89. package/dist/cdn/menu/snice-menu.js +2 -2
  90. package/dist/cdn/message-strip/snice-message-strip.js +2 -2
  91. package/dist/cdn/modal/snice-modal.js +2 -2
  92. package/dist/cdn/music-player/snice-music-player.js +2 -2
  93. package/dist/cdn/nav/snice-nav.js +2 -2
  94. package/dist/cdn/network-graph/snice-network-graph.js +2 -2
  95. package/dist/cdn/notification-center/snice-notification-center.js +2 -2
  96. package/dist/cdn/order-tracker/snice-order-tracker.js +2 -2
  97. package/dist/cdn/org-chart/snice-org-chart.js +2 -2
  98. package/dist/cdn/pagination/snice-pagination.js +2 -2
  99. package/dist/cdn/paint/snice-paint.js +2 -2
  100. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +2 -2
  101. package/dist/cdn/permission-matrix/snice-permission-matrix.js +2 -2
  102. package/dist/cdn/podcast-player/snice-podcast-player.js +2 -2
  103. package/dist/cdn/popover/snice-popover.js +2 -2
  104. package/dist/cdn/pricing-table/snice-pricing-table.js +2 -2
  105. package/dist/cdn/product-card/snice-product-card.js +2 -2
  106. package/dist/cdn/progress/snice-progress.js +2 -2
  107. package/dist/cdn/progress-ring/snice-progress-ring.js +2 -2
  108. package/dist/cdn/qr-code/snice-qr-code.js +2 -2
  109. package/dist/cdn/qr-reader/snice-qr-reader.js +2 -2
  110. package/dist/cdn/radio/snice-radio.js +2 -2
  111. package/dist/cdn/range-slider/snice-range-slider.js +2 -2
  112. package/dist/cdn/rating/snice-rating.js +2 -2
  113. package/dist/cdn/receipt/snice-receipt.js +2 -2
  114. package/dist/cdn/recipe/snice-recipe.js +3 -3
  115. package/dist/cdn/recipe/snice-recipe.js.map +1 -1
  116. package/dist/cdn/recipe/snice-recipe.min.js +1 -1
  117. package/dist/cdn/recipe/snice-recipe.min.js.map +1 -1
  118. package/dist/cdn/runtime/README.md +2 -2
  119. package/dist/cdn/runtime/snice-runtime.esm.js +3725 -3336
  120. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  121. package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
  122. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  123. package/dist/cdn/runtime/snice-runtime.js +3727 -3335
  124. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  125. package/dist/cdn/runtime/snice-runtime.min.js +1 -1
  126. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  127. package/dist/cdn/sankey/snice-sankey.js +2 -2
  128. package/dist/cdn/segmented-control/snice-segmented-control.js +2 -2
  129. package/dist/cdn/select/snice-select.js +2 -2
  130. package/dist/cdn/skeleton/snice-skeleton.js +2 -2
  131. package/dist/cdn/slider/snice-slider.js +2 -2
  132. package/dist/cdn/sortable/snice-sortable.js +2 -2
  133. package/dist/cdn/sparkline/snice-sparkline.js +2 -2
  134. package/dist/cdn/spinner/snice-spinner.js +2 -2
  135. package/dist/cdn/split-button/snice-split-button.js +2 -2
  136. package/dist/cdn/split-pane/snice-split-pane.js +2 -2
  137. package/dist/cdn/spotlight/snice-spotlight.js +2 -2
  138. package/dist/cdn/stat-group/snice-stat-group.js +2 -2
  139. package/dist/cdn/step-input/snice-step-input.js +2 -2
  140. package/dist/cdn/stepper/snice-stepper.js +2 -2
  141. package/dist/cdn/switch/snice-switch.js +2 -2
  142. package/dist/cdn/table/README.md +6 -2
  143. package/dist/cdn/table/snice-table.esm.js +366 -56
  144. package/dist/cdn/table/snice-table.esm.js.map +1 -1
  145. package/dist/cdn/table/snice-table.esm.min.js +107 -46
  146. package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
  147. package/dist/cdn/table/snice-table.js +367 -59
  148. package/dist/cdn/table/snice-table.js.map +1 -1
  149. package/dist/cdn/table/snice-table.min.js +155 -94
  150. package/dist/cdn/table/snice-table.min.js.map +1 -1
  151. package/dist/cdn/tabs/snice-tabs.js +2 -2
  152. package/dist/cdn/tag/snice-tag.js +2 -2
  153. package/dist/cdn/tag-input/snice-tag-input.js +2 -2
  154. package/dist/cdn/terminal/snice-terminal.js +3 -2
  155. package/dist/cdn/terminal/snice-terminal.js.map +1 -1
  156. package/dist/cdn/terminal/snice-terminal.min.js +1 -0
  157. package/dist/cdn/terminal/snice-terminal.min.js.map +1 -1
  158. package/dist/cdn/testimonial/snice-testimonial.js +2 -2
  159. package/dist/cdn/textarea/snice-textarea.js +2 -2
  160. package/dist/cdn/time-picker/snice-time-picker.js +2 -2
  161. package/dist/cdn/time-range-picker/snice-time-range-picker.js +2 -2
  162. package/dist/cdn/timeline/snice-timeline.js +2 -2
  163. package/dist/cdn/timer/snice-timer.js +2 -2
  164. package/dist/cdn/toast/snice-toast.js +2 -2
  165. package/dist/cdn/tooltip/snice-tooltip.js +2 -2
  166. package/dist/cdn/tree/snice-tree.js +2 -2
  167. package/dist/cdn/treemap/snice-treemap.js +2 -2
  168. package/dist/cdn/user-card/snice-user-card.js +2 -2
  169. package/dist/cdn/video-player/README.md +1 -1
  170. package/dist/cdn/video-player/snice-video-player.js +3 -2
  171. package/dist/cdn/video-player/snice-video-player.js.map +1 -1
  172. package/dist/cdn/video-player/snice-video-player.min.js +1 -0
  173. package/dist/cdn/video-player/snice-video-player.min.js.map +1 -1
  174. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +2 -2
  175. package/dist/cdn/waterfall/snice-waterfall.js +2 -2
  176. package/dist/cdn/weather/snice-weather.js +2 -2
  177. package/dist/cdn/work-order/snice-work-order.js +2 -2
  178. package/dist/components/recipe/snice-recipe.js +1 -1
  179. package/dist/components/recipe/snice-recipe.js.map +1 -1
  180. package/dist/components/table/snice-table.d.ts +1 -0
  181. package/dist/components/table/snice-table.js +18 -2
  182. package/dist/components/table/snice-table.js.map +1 -1
  183. package/dist/components/table/table-tree-data.js +1 -0
  184. package/dist/components/table/table-tree-data.js.map +1 -1
  185. package/dist/components/terminal/snice-terminal.js +1 -0
  186. package/dist/components/terminal/snice-terminal.js.map +1 -1
  187. package/dist/components/video-player/snice-video-player.js +1 -0
  188. package/dist/components/video-player/snice-video-player.js.map +1 -1
  189. package/dist/context-provider.d.ts +16 -0
  190. package/dist/daemon-target.d.ts +10 -0
  191. package/dist/daemon.d.ts +10 -0
  192. package/dist/index.cjs +3721 -3329
  193. package/dist/index.cjs.map +1 -1
  194. package/dist/index.d.ts +3 -0
  195. package/dist/index.esm.js +3719 -3330
  196. package/dist/index.esm.js.map +1 -1
  197. package/dist/index.iife.js +3721 -3329
  198. package/dist/index.iife.js.map +1 -1
  199. package/dist/keyboard-filter.d.ts +25 -0
  200. package/dist/on.d.ts +1 -1
  201. package/dist/parts.d.ts +0 -17
  202. package/dist/react/SniceProvider.js +2 -2
  203. package/dist/react/SniceRouter.js +2 -2
  204. package/dist/react/index.js +2 -2
  205. package/dist/react/matchRoute.js +2 -2
  206. package/dist/react/useRequestHandler.js +2 -2
  207. package/dist/request-response.d.ts +9 -1
  208. package/dist/symbols.cjs +11 -2
  209. package/dist/symbols.cjs.map +1 -1
  210. package/dist/symbols.d.ts +3 -0
  211. package/dist/symbols.esm.js +9 -3
  212. package/dist/symbols.esm.js.map +1 -1
  213. package/dist/testing.d.ts +1 -1
  214. package/dist/transitions.cjs +2 -2
  215. package/dist/transitions.esm.js +2 -2
  216. package/dist/types/app-context.d.ts +9 -58
  217. package/dist/types/daemon.d.ts +8 -0
  218. package/dist/types/dispatch-options.d.ts +2 -0
  219. package/dist/types/index.d.ts +1 -0
  220. package/dist/types/on-options.d.ts +5 -0
  221. package/dist/types/request-options.d.ts +2 -0
  222. package/dist/types/respond-options.d.ts +2 -0
  223. package/docs/ai/DEVELOPMENT.md +15 -1
  224. package/docs/ai/README.md +5 -1
  225. package/docs/ai/api.md +13 -4
  226. package/docs/ai/architecture.md +50 -1
  227. package/docs/ai/cli.md +9 -0
  228. package/docs/ai/components/avatar.md +8 -8
  229. package/docs/ai/components/breadcrumbs.md +2 -1
  230. package/docs/ai/components/button.md +2 -7
  231. package/docs/ai/components/calendar.md +1 -1
  232. package/docs/ai/components/camera.md +1 -1
  233. package/docs/ai/components/chart.md +3 -1
  234. package/docs/ai/components/code-block.md +2 -2
  235. package/docs/ai/components/date-picker.md +5 -19
  236. package/docs/ai/components/date-range-picker.md +4 -12
  237. package/docs/ai/components/draw.md +5 -5
  238. package/docs/ai/components/drawer.md +4 -4
  239. package/docs/ai/components/form-layout.md +2 -0
  240. package/docs/ai/components/icons.md +7 -16
  241. package/docs/ai/components/key-value.md +3 -22
  242. package/docs/ai/components/layout.md +8 -12
  243. package/docs/ai/components/leaderboard.md +2 -2
  244. package/docs/ai/components/link-preview.md +4 -3
  245. package/docs/ai/components/login.md +11 -0
  246. package/docs/ai/components/markdown.md +7 -0
  247. package/docs/ai/components/masonry.md +6 -0
  248. package/docs/ai/components/menu.md +1 -1
  249. package/docs/ai/components/modal.md +7 -0
  250. package/docs/ai/components/notification-center.md +5 -4
  251. package/docs/ai/components/radio.md +7 -0
  252. package/docs/ai/components/rating.md +3 -3
  253. package/docs/ai/components/select.md +4 -13
  254. package/docs/ai/components/spotlight.md +2 -0
  255. package/docs/ai/components/table.md +1 -4
  256. package/docs/ai/components/tag-input.md +6 -0
  257. package/docs/ai/components/terminal.md +6 -0
  258. package/docs/ai/components/tooltip.md +10 -16
  259. package/docs/ai/components/tree.md +2 -2
  260. package/docs/ai/components/video-player.md +5 -0
  261. package/docs/ai/components/waterfall.md +7 -0
  262. package/docs/ai/daemons.md +80 -0
  263. package/docs/ai/decorators.md +6 -4
  264. package/docs/ai/events.md +35 -0
  265. package/docs/ai/patterns.md +45 -1
  266. package/docs/ai/request-response.md +26 -9
  267. package/docs/ai/routing.md +16 -1
  268. package/docs/cli.md +7 -0
  269. package/docs/components/breadcrumbs.md +2 -1
  270. package/docs/components/calendar.md +1 -1
  271. package/docs/components/camera.md +1 -1
  272. package/docs/components/chart.md +1 -1
  273. package/docs/components/code-block.md +13 -4
  274. package/docs/components/color-picker.md +6 -5
  275. package/docs/components/date-time-picker.md +14 -0
  276. package/docs/components/diff.md +2 -1
  277. package/docs/components/draw.md +5 -5
  278. package/docs/components/drawer.md +4 -4
  279. package/docs/components/input.md +1 -0
  280. package/docs/components/layout.md +20 -22
  281. package/docs/components/leaderboard.md +10 -3
  282. package/docs/components/link-preview.md +11 -9
  283. package/docs/components/login.md +4 -0
  284. package/docs/components/menu.md +1 -1
  285. package/docs/components/modal.md +1 -0
  286. package/docs/components/nav.md +1 -0
  287. package/docs/components/network-graph.md +1 -0
  288. package/docs/components/notification-center.md +2 -1
  289. package/docs/components/product-card.md +1 -0
  290. package/docs/components/qr-reader.md +1 -0
  291. package/docs/components/rating.md +4 -4
  292. package/docs/components/select.md +1 -0
  293. package/docs/components/spotlight.md +2 -0
  294. package/docs/components/tabs.md +1 -0
  295. package/docs/components/terminal.md +3 -0
  296. package/docs/components/textarea.md +1 -1
  297. package/docs/components/time-picker.md +14 -0
  298. package/docs/components/tooltip.md +2 -0
  299. package/docs/components/tree.md +2 -2
  300. package/docs/daemons.md +200 -0
  301. package/docs/events.md +92 -2
  302. package/docs/request-response.md +20 -13
  303. package/docs/routing.md +25 -1
  304. package/package.json +6 -4
@@ -0,0 +1,200 @@
1
+ <!-- AI: For the AI-optimized version of this doc, see docs/ai/daemons.md -->
2
+ # Daemons
3
+
4
+ Daemons are ordinary, explicitly constructed objects with state and an
5
+ application-owned lifecycle. The `@daemon` decorator gives each instance a
6
+ private communication target so elements, controllers, and daemons can use
7
+ Snice's two communication models:
8
+
9
+ - `@request` / `@respond` for one request and one asynchronous response.
10
+ - `@dispatch` / `@on` for ephemeral notifications with zero or more listeners.
11
+
12
+ `@daemon` does not construct, cache, globally register, start, or stop
13
+ anything.
14
+
15
+ ## Define and provide a daemon
16
+
17
+ ```typescript
18
+ import { daemon, dispatch, on, request, respond, provideContext } from 'snice';
19
+ import type { Response } from 'snice';
20
+
21
+ @daemon
22
+ class SessionDaemon {
23
+ session: Session | null = null;
24
+
25
+ @respond('get-session')
26
+ getSession() {
27
+ return this.session;
28
+ }
29
+
30
+ @on('set-session')
31
+ setSession(event: CustomEvent<Session>) {
32
+ this.session = event.detail;
33
+ this.sessionChanged();
34
+ }
35
+
36
+ @dispatch('session-changed')
37
+ sessionChanged() {
38
+ return this.session;
39
+ }
40
+ }
41
+
42
+ const session = new SessionDaemon();
43
+ const appContext = {
44
+ daemons: { session }
45
+ };
46
+
47
+ const release = provideContext(document.querySelector('#app')!, appContext);
48
+ ```
49
+
50
+ The context key is the daemon's address. The class decorator takes no name,
51
+ avoiding two sources of truth.
52
+
53
+ Application contexts expose only this daemon surface to Snice:
54
+
55
+ ```typescript
56
+ type DaemonMap = Readonly<Record<string, object>>;
57
+
58
+ interface AppContext {
59
+ readonly daemons?: DaemonMap;
60
+ [key: string]: unknown;
61
+ }
62
+ ```
63
+
64
+ They may still contain application-specific state such as `user`, `theme`, or
65
+ configuration. Extend `AppContext` with those fields to give application code
66
+ their concrete types.
67
+
68
+ ## Router integration
69
+
70
+ `Router` provides its `context` beneath its target before rendering a page.
71
+ No separate `provideContext()` call is needed:
72
+
73
+ ```typescript
74
+ const session = new SessionDaemon();
75
+
76
+ const router = Router({
77
+ target: '#app',
78
+ type: 'hash',
79
+ context: {
80
+ user: null,
81
+ daemons: { session }
82
+ }
83
+ });
84
+ ```
85
+
86
+ `provideContext(root, context)` is the same public mechanism used internally
87
+ by Router. Use it for applications without Router and for isolated tests. It
88
+ returns an idempotent cleanup function.
89
+
90
+ `getContext(elementOrController)` returns the raw application context visible
91
+ to that participant. It is separate from the method-form `@context()`, which
92
+ receives Router navigation updates.
93
+
94
+ ## Communicate from elements and controllers
95
+
96
+ Consumers use the context address and never import the daemon implementation:
97
+
98
+ ```typescript
99
+ @element('session-view')
100
+ class SessionView extends HTMLElement {
101
+ @request<Session | null>('get-session', { daemon: 'session' })
102
+ async *loadSession(): Response<Session | null> {
103
+ return yield {};
104
+ }
105
+
106
+ @dispatch('set-session', { daemon: 'session' })
107
+ setSession(session: Session) {
108
+ return session;
109
+ }
110
+
111
+ @on('session-changed', { daemon: 'session' })
112
+ sessionChanged(event: CustomEvent<Session | null>) {
113
+ this.renderSession(event.detail);
114
+ }
115
+ }
116
+ ```
117
+
118
+ Controllers resolve the context through their attached host element, so the
119
+ same syntax works in controller methods.
120
+
121
+ The reverse direction is also supported. A daemon's `@request` dispatches on
122
+ its own communication target, while an element or controller may install a
123
+ responder there:
124
+
125
+ ```typescript
126
+ @daemon
127
+ class SessionDaemon {
128
+ @request<boolean>('confirm-logout')
129
+ async *confirmLogout(): Response<boolean> {
130
+ return yield {};
131
+ }
132
+ }
133
+
134
+ class SessionView extends HTMLElement {
135
+ @respond('confirm-logout', { daemon: 'session' })
136
+ confirmLogout() {
137
+ return window.confirm('Log out?');
138
+ }
139
+ }
140
+ ```
141
+
142
+ As with DOM-scoped request channels, only one responder should own a daemon
143
+ request channel.
144
+
145
+ ## Resolution and lifecycle
146
+
147
+ Resolution has one path:
148
+
149
+ ```text
150
+ element/controller
151
+ -> nearest explicitly provided application context
152
+ -> context.daemons[name]
153
+ -> that instance's private communication target
154
+ ```
155
+
156
+ There is no global fallback, implicit construction, registry scan, or delayed
157
+ registration.
158
+
159
+ - Construct every daemon explicitly with `new`.
160
+ - Provide the context before connecting elements or attaching controllers.
161
+ `@on` and `@respond` install listeners at those lifecycle boundaries.
162
+ - `@request` and `@dispatch` resolve their daemon when invoked.
163
+ - Disconnecting an element or detaching a controller removes its daemon
164
+ listeners automatically.
165
+ - Releasing the context deactivates its daemon communication. A later provision
166
+ starts with a fresh event target.
167
+ - The same daemon class may have any number of independently provided
168
+ instances.
169
+ - The same name may resolve to different instances under different context
170
+ roots.
171
+
172
+ Missing contexts, missing daemon names, undecorated values, and inactive
173
+ daemon instances produce explicit errors or setup warnings. `daemon` and DOM
174
+ `scope` options are mutually exclusive. Selector delegation is not available
175
+ on a daemon target because it is not a DOM tree.
176
+
177
+ ## Testing
178
+
179
+ Each test owns its instance, context root, and cleanup:
180
+
181
+ ```typescript
182
+ const root = document.createElement('div');
183
+ document.body.appendChild(root);
184
+
185
+ const session = new SessionDaemon();
186
+ const release = provideContext(root, {
187
+ daemons: { session }
188
+ });
189
+
190
+ const view = document.createElement('session-view');
191
+ root.appendChild(view);
192
+ await view.ready;
193
+
194
+ expect(await view.loadSession()).toBeNull();
195
+
196
+ root.remove();
197
+ release();
198
+ ```
199
+
200
+ No shared singleton state or framework reset API is involved.
package/docs/events.md CHANGED
@@ -7,6 +7,7 @@ Event handling in Snice provides two powerful approaches: **template event synta
7
7
  - [Template Event Syntax (Preferred for Elements)](#template-event-syntax-preferred-for-elements)
8
8
  - [@on Decorator](#on-decorator)
9
9
  - [@dispatch Decorator](#dispatch-decorator)
10
+ - [Event Bus](#event-bus)
10
11
  - [Custom Events](#custom-events)
11
12
  - [Event Delegation](#event-delegation)
12
13
  - [Keyboard Shortcuts](#keyboard-shortcuts)
@@ -413,6 +414,9 @@ interface OnOptions {
413
414
 
414
415
  // Where to attach the listener (see scope section below)
415
416
  scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
417
+
418
+ // Named daemon from the nearest provided app context
419
+ daemon?: string;
416
420
  }
417
421
  ```
418
422
 
@@ -460,6 +464,9 @@ re-resolves and re-attaches, so resolver-based scopes track DOM moves correctly.
460
464
  `scope` is compatible with the delegation selector — the listener attaches on the
461
465
  scoped target and still matches the selector when an event fires within it.
462
466
 
467
+ `daemon` is a separate, non-DOM target. It cannot be combined with `scope` and does
468
+ not support selector delegation. See [Daemons](./daemons.md).
469
+
463
470
  #### Throttling
464
471
 
465
472
  ```typescript
@@ -608,13 +615,15 @@ interface DispatchOptions extends EventInit {
608
615
  throttle?: number; // Throttle dispatch by ms
609
616
  // Where to dispatch the event (see scope section below)
610
617
  scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
618
+ // Named daemon from the nearest provided app context
619
+ daemon?: string;
611
620
  }
612
621
  ```
613
622
 
614
623
  #### scope — controlling the dispatch target
615
624
 
616
- By default, `@dispatch` calls `this.dispatchEvent(event)` the event originates from
617
- the host element. The `scope` option redirects the dispatch to another target so the
625
+ By default, `@dispatch` originates from the element or a controller's host; on a
626
+ daemon it uses that instance's private target. The `scope` option redirects a DOM dispatch so the
618
627
  event behaves as if it originated there. Use this with `@on({ scope })` to express
619
628
  cross-cutting events without going through bubbling.
620
629
 
@@ -650,6 +659,10 @@ the event is **not dispatched** and a `console.warn` is emitted. The method's re
650
659
  value still flows through `dispatchOnUndefined` / `debounce` / `throttle` semantics
651
660
  before the scope check.
652
661
 
662
+ Use `{ daemon: 'session' }` to dispatch on an explicitly provided daemon's private
663
+ communication target. `daemon` and `scope` are mutually exclusive. See
664
+ [Daemons](./daemons.md).
665
+
653
666
  ### Debounce/Throttle
654
667
 
655
668
  ```typescript
@@ -715,6 +728,83 @@ class ColorPicker extends HTMLElement {
715
728
  }
716
729
  ```
717
730
 
731
+ ## Event Bus
732
+
733
+ Snice has no bus object and no singleton. A bus is `@dispatch` publishing upward and
734
+ `@on` subscribing at a chosen ancestor -- the scope is a DOM node, so it is created and
735
+ torn down with the DOM.
736
+
737
+ There are two ways to publish, and the choice decides who can hear it.
738
+
739
+ **Bubble up from the host.** `@dispatch` is `bubbles: true, composed: true` by default, so
740
+ the event crosses shadow boundaries and passes every ancestor on its way to `document`.
741
+ Use this when the event is about *this* element and ancestors may care.
742
+
743
+ ```typescript
744
+ @element('product-tile')
745
+ class ProductTile extends HTMLElement {
746
+ @dispatch('bus:cart-added')
747
+ addToCart(sku: string) {
748
+ return { sku, qty: 1 };
749
+ }
750
+ }
751
+ ```
752
+
753
+ **Dispatch directly on a scope.** `@dispatch` takes the same `scope` option as `@on`, which
754
+ fires the event *on* that target instead of bubbling from the host. Use this when the
755
+ subscriber is not an ancestor -- a sibling subtree, or a host that may be detached.
756
+
757
+ ```typescript
758
+ @dispatch('bus:cart-added', { scope: 'global' }) // dispatched on document
759
+ add(id: string) { return { id }; }
760
+ ```
761
+
762
+ Subscribe at the scope you want to share.
763
+
764
+ ```typescript
765
+ // App-wide: listens on document
766
+ @on('bus:cart-added', { scope: 'global' })
767
+ onCartAdded(e: CustomEvent) { /* ... */ }
768
+
769
+ // Feature-scoped: listens on the nearest <cart-shell> ancestor, so a second
770
+ // <cart-shell> elsewhere on the page keeps its own traffic
771
+ @on('bus:cart-added', { scope: 'cart-shell' })
772
+ onCartAdded(e: CustomEvent) { /* ... */ }
773
+ ```
774
+
775
+ Choosing a scope:
776
+
777
+ | Reach | `scope` | Use when |
778
+ |---|---|---|
779
+ | Whole document | `'global'` | Genuinely app-wide: auth expiry, theme change, save shortcut |
780
+ | A feature subtree | selector string | The event belongs to one shell and must not leak to a sibling instance |
781
+ | A specific node | `EventTarget` / resolver | You already hold the node, or the target moves and must re-resolve |
782
+
783
+ Prefer the narrowest scope that works. `'global'` means every instance on the page hears
784
+ every message, which is what makes singleton buses hard to reason about.
785
+
786
+ Match the two halves. A bubbling publish only reaches subscribers that sit on the host's
787
+ ancestor chain; if the subscriber lives in a sibling subtree, scope the dispatch too so
788
+ both meet on the same node. See
789
+ [scope on @dispatch](#scope-controlling-the-dispatch-target).
790
+
791
+ Name events so the routing is visible at the call site -- the `bus:` prefix above is a
792
+ convention, not a framework feature. Any string works.
793
+
794
+ Teardown is automatic: the listener is removed on disconnect from whichever target it
795
+ resolved to, and re-resolved on reconnect, so subscribers follow the host when it moves.
796
+ If a selector scope matches no ancestor the listener is skipped with a `console.warn`
797
+ rather than silently binding to the wrong node. See
798
+ [scope](#scope-controlling-the-listener-target) for the full resolution table.
799
+
800
+ For a request that needs an answer rather than a broadcast, use
801
+ [@request / @respond](./request-response.md) instead.
802
+
803
+ For app-owned state with an explicit lifecycle, provide an `@daemon` instance and
804
+ use `{ daemon: 'name' }` on both publishers and subscribers. This keeps consumers
805
+ decoupled from the implementation class without introducing a global singleton.
806
+ See [Daemons](./daemons.md).
807
+
718
808
  ## Custom Events
719
809
 
720
810
  ### Dispatching Manually
@@ -1,7 +1,8 @@
1
- <!-- AI: For the AI-optimized version of this doc, see docs/ai/patterns.md -->
1
+ <!-- AI: For the AI-optimized version of this doc, see docs/ai/request-response.md -->
2
2
  # Request/Response API Documentation
3
3
 
4
- Request/Response provides request/response communication between elements and controllers using async generators.
4
+ Request/Response provides request/response communication between elements,
5
+ controllers, and explicitly provided daemons using async generators.
5
6
 
6
7
  ## Table of Contents
7
8
  - [Why Request/Response?](#why-request-response)
@@ -45,6 +46,7 @@ function request(requestName: string, options?: RequestOptions): MethodDecorator
45
46
  function respond(requestName: string, options?: RespondOptions): MethodDecorator
46
47
 
47
48
  interface RequestOptions extends EventInit {
49
+ daemon?: string; // Named daemon from nearest provided app context
48
50
  timeout?: number; // Response timeout in ms (default: 120000ms = 2 minutes)
49
51
  discoveryTimeout?: number; // Handler discovery timeout in ms (default: 50ms)
50
52
  debounce?: number; // Debounce requests by specified ms
@@ -54,15 +56,21 @@ interface RequestOptions extends EventInit {
54
56
  }
55
57
 
56
58
  interface RespondOptions {
59
+ daemon?: string; // Install responder on named daemon target
57
60
  debounce?: number; // Debounce responses by specified ms
58
61
  throttle?: number; // Throttle responses by specified ms
59
62
  }
60
63
 
61
- // Recommended type helper for request generator return types:
62
- type RequestResult<T> = AsyncGenerator<any, T, any> | Promise<T>;
63
- // Define this in your project — it satisfies both the generator and the caller
64
+ // Public return type for methods decorated with @request:
65
+ type Response<T = any> = T | any;
64
66
  ```
65
67
 
68
+ TypeScript cannot model a method decorator changing an async generator into a
69
+ promise-returning method. `Response<T>` is the pragmatic annotation for that
70
+ boundary: it keeps strict projects usable while documenting the response value
71
+ for readers and tooling. At runtime, calling the decorated method returns a
72
+ promise for `T`.
73
+
66
74
  #### Response Debounce/Throttle
67
75
 
68
76
  Response handlers can be debounced or throttled:
@@ -92,8 +100,7 @@ Elements use async generators to make requests. The element stays visual — it
92
100
 
93
101
  ```typescript
94
102
  import { element, request, property, render, html } from 'snice';
95
-
96
- type RequestResult<T> = AsyncGenerator<any, T, any> | Promise<T>;
103
+ import type { Response } from 'snice';
97
104
 
98
105
  @element('product-card')
99
106
  class ProductCard extends HTMLElement {
@@ -102,7 +109,7 @@ class ProductCard extends HTMLElement {
102
109
  @property() price = '';
103
110
 
104
111
  @request('fetch-product')
105
- async *loadProduct(): RequestResult<void> {
112
+ async *loadProduct(): Response<void> {
106
113
  const product = await (yield { id: this.productId });
107
114
  this.name = product.name;
108
115
  this.price = product.price;
@@ -164,7 +171,7 @@ The timeout system has **two separate timeouts**:
164
171
  discoveryTimeout: 50, // 50ms to find handler
165
172
  timeout: 30000 // 30s for actual processing
166
173
  })
167
- async *compute(): RequestResult<any> {
174
+ async *compute(): Response<any> {
168
175
  return await (yield data);
169
176
  }
170
177
  ```
@@ -174,13 +181,13 @@ async *compute(): RequestResult<any> {
174
181
  ```typescript
175
182
  // Debounce: wait for typing to stop before searching
176
183
  @request('search', { debounce: 300 })
177
- async *search(): RequestResult<any[]> {
184
+ async *search(): Response<any[]> {
178
185
  return await (yield { query: this.searchTerm });
179
186
  }
180
187
 
181
188
  // Throttle: limit analytics to 1 per second
182
189
  @request('track', { throttle: 1000 })
183
- async *trackEvent(): RequestResult<void> {
190
+ async *trackEvent(): Response<void> {
184
191
  await (yield { event: 'scroll', position: window.scrollY });
185
192
  }
186
193
  ```
@@ -196,7 +203,7 @@ class SafeLoader extends HTMLElement {
196
203
  @property() data: any = null;
197
204
 
198
205
  @request('load-data', { timeout: 5000 })
199
- async *loadData(): RequestResult<void> {
206
+ async *loadData(): Response<void> {
200
207
  try {
201
208
  this.data = await (yield { id: this.dataId });
202
209
  this.error = '';
@@ -290,7 +297,7 @@ class LiveTicker extends HTMLElement {
290
297
  @property() symbol = 'BTC';
291
298
 
292
299
  @request('subscribe-ticker')
293
- async *subscribe(): RequestResult<void> {
300
+ async *subscribe(): Response<void> {
294
301
  await (yield { symbol: this.symbol });
295
302
  }
296
303
 
package/docs/routing.md CHANGED
@@ -35,7 +35,7 @@ interface RouterOptions {
35
35
  document?: Document; // Override document object (for testing)
36
36
  transition?: Transition; // Global transition config
37
37
  layout?: string; // Default layout for all pages
38
- context?: any; // Router context object (shared state)
38
+ context?: any; // App context (shared state and optional daemons)
39
39
  fetcher?: Fetcher; // Optional fetch middleware (see docs/fetcher.md)
40
40
  }
41
41
  ```
@@ -67,6 +67,27 @@ const { page, initialize } = Router({
67
67
  });
68
68
  ```
69
69
 
70
+ Router provides this application context beneath its target before it connects a
71
+ page. That includes explicitly constructed daemon instances:
72
+
73
+ ```typescript
74
+ const session = new SessionDaemon();
75
+
76
+ Router({
77
+ target: '#app',
78
+ type: 'hash',
79
+ context: {
80
+ user: null,
81
+ daemons: { session }
82
+ }
83
+ });
84
+ ```
85
+
86
+ This uses the same `provideContext(root, context)` mechanism available to apps
87
+ without Router. Descendant elements and attached controllers address the instance
88
+ as `{ daemon: 'session' }`; they do not import `SessionDaemon`. See
89
+ [Daemons](./daemons.md).
90
+
70
91
  ## Page Components
71
92
 
72
93
  ### Basic Page
@@ -806,6 +827,9 @@ navigate(path: string): Promise<void>
806
827
 
807
828
  Navigates to the specified path. Uses hash (#) or pushstate depending on router type.
808
829
 
830
+ Do not combine `@page` with `@element` on the same class. `@page` already
831
+ registers the custom element and applies Snice element behavior.
832
+
809
833
  ### initialize()
810
834
 
811
835
  ```typescript
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "snice",
3
- "version": "7.0.2",
3
+ "version": "7.1.0",
4
4
  "type": "module",
5
- "description": "A decorator-driven web component library with differential rendering, routing, controllers, and 130+ ready-made UI components. Use as much or as little as you want. Zero dependencies, works anywhere.",
5
+ "description": "A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.esm.js",
8
8
  "browser": "dist/index.iife.js",
@@ -112,11 +112,11 @@
112
112
  "build:distribution": "rm -rf dist && npm run generate:metadata && rollup -c && npm run build:types && node tooling/build/embed-sourcemap-sources.mjs dist",
113
113
  "build:types": "tsc --project packages/components/tsconfig.json --emitDeclarationOnly && cp packages/components/custom-elements.d.ts dist/components/custom-elements.d.ts",
114
114
  "prebuild:cdn": "test -f dist/index.esm.js || npm run build:distribution",
115
- "build:cdn": "rollup -c rollup.config.cdn.js && cp dist/cdn/*/snice-*.min.js website/public/components/ && node tooling/website/update-cdn-sizes.js",
115
+ "build:cdn": "rollup -c rollup.config.cdn.js && node tooling/website/sync-component-assets.js && node tooling/website/update-cdn-sizes.js",
116
116
  "prebuild:react": "test -f dist/index.d.ts || npm run build:distribution",
117
117
  "build:react": "npm run generate:react-adapters && tsc --project adapters/react/tsconfig.json && node tooling/build/embed-sourcemap-sources.mjs adapters/react",
118
118
  "build:testing": "rollup -c rollup.config.test.js && node tooling/build/embed-sourcemap-sources.mjs dist",
119
- "build:website": "node tooling/website/build-website.js && node tooling/website/build-showcases.js",
119
+ "build:website": "node tooling/website/sync-component-assets.js && node tooling/website/build-website.js && node tooling/website/build-showcases.js",
120
120
  "build:website:full": "npm run build:cdn && npm run build:website",
121
121
  "build:storybook": "storybook build",
122
122
  "generate:metadata": "node tooling/generators/generate-component-metadata.js && node tooling/generators/generate-analyzer-contracts.js",
@@ -142,6 +142,8 @@
142
142
  "test:react": "npm run build:react && npm run test:react:prepared",
143
143
  "test:react:prepared": "vitest run tests/react-adapters",
144
144
  "test:website": "vitest run tests/website.test.ts",
145
+ "gauntlet": "node tooling/testing/run-gauntlet.js",
146
+ "postinstall": "node ./bin/postinstall.js",
145
147
  "test:browser": "npm run test:browser:framework && npm run test:browser:website",
146
148
  "test:browser:framework": "npm run build:distribution && npm run test:browser:framework:prepared",
147
149
  "test:browser:framework:prepared": "node tooling/testing/run-live-tests.js --workers=4",