snice 7.0.3 → 7.1.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 (223) 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 +64 -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 +2 -2
  115. package/dist/cdn/runtime/README.md +2 -2
  116. package/dist/cdn/runtime/snice-runtime.esm.js +3825 -3337
  117. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  118. package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
  119. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  120. package/dist/cdn/runtime/snice-runtime.js +3827 -3336
  121. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  122. package/dist/cdn/runtime/snice-runtime.min.js +1 -1
  123. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  124. package/dist/cdn/sankey/snice-sankey.js +2 -2
  125. package/dist/cdn/segmented-control/snice-segmented-control.js +2 -2
  126. package/dist/cdn/select/snice-select.js +2 -2
  127. package/dist/cdn/skeleton/snice-skeleton.js +2 -2
  128. package/dist/cdn/slider/snice-slider.js +2 -2
  129. package/dist/cdn/sortable/snice-sortable.js +2 -2
  130. package/dist/cdn/sparkline/snice-sparkline.js +2 -2
  131. package/dist/cdn/spinner/snice-spinner.js +2 -2
  132. package/dist/cdn/split-button/snice-split-button.js +2 -2
  133. package/dist/cdn/split-pane/snice-split-pane.js +2 -2
  134. package/dist/cdn/spotlight/snice-spotlight.js +2 -2
  135. package/dist/cdn/stat-group/snice-stat-group.js +2 -2
  136. package/dist/cdn/step-input/snice-step-input.js +2 -2
  137. package/dist/cdn/stepper/snice-stepper.js +2 -2
  138. package/dist/cdn/switch/snice-switch.js +2 -2
  139. package/dist/cdn/table/README.md +6 -2
  140. package/dist/cdn/table/snice-table.esm.js +347 -54
  141. package/dist/cdn/table/snice-table.esm.js.map +1 -1
  142. package/dist/cdn/table/snice-table.esm.min.js +105 -44
  143. package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
  144. package/dist/cdn/table/snice-table.js +348 -57
  145. package/dist/cdn/table/snice-table.js.map +1 -1
  146. package/dist/cdn/table/snice-table.min.js +153 -92
  147. package/dist/cdn/table/snice-table.min.js.map +1 -1
  148. package/dist/cdn/tabs/snice-tabs.js +2 -2
  149. package/dist/cdn/tag/snice-tag.js +2 -2
  150. package/dist/cdn/tag-input/snice-tag-input.js +2 -2
  151. package/dist/cdn/terminal/snice-terminal.js +2 -2
  152. package/dist/cdn/testimonial/snice-testimonial.js +2 -2
  153. package/dist/cdn/textarea/snice-textarea.js +2 -2
  154. package/dist/cdn/time-picker/snice-time-picker.js +2 -2
  155. package/dist/cdn/time-range-picker/snice-time-range-picker.js +2 -2
  156. package/dist/cdn/timeline/snice-timeline.js +2 -2
  157. package/dist/cdn/timer/snice-timer.js +2 -2
  158. package/dist/cdn/toast/snice-toast.js +2 -2
  159. package/dist/cdn/tooltip/snice-tooltip.js +2 -2
  160. package/dist/cdn/tree/snice-tree.js +2 -2
  161. package/dist/cdn/treemap/snice-treemap.js +2 -2
  162. package/dist/cdn/user-card/snice-user-card.js +2 -2
  163. package/dist/cdn/video-player/snice-video-player.js +2 -2
  164. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +2 -2
  165. package/dist/cdn/waterfall/snice-waterfall.js +2 -2
  166. package/dist/cdn/weather/snice-weather.js +2 -2
  167. package/dist/cdn/work-order/snice-work-order.js +2 -2
  168. package/dist/context-provider.d.ts +16 -0
  169. package/dist/controller.d.ts +5 -0
  170. package/dist/daemon-target.d.ts +10 -0
  171. package/dist/daemon.d.ts +10 -0
  172. package/dist/index.cjs +3821 -3330
  173. package/dist/index.cjs.map +1 -1
  174. package/dist/index.d.ts +3 -0
  175. package/dist/index.esm.js +3819 -3331
  176. package/dist/index.esm.js.map +1 -1
  177. package/dist/index.iife.js +3821 -3330
  178. package/dist/index.iife.js.map +1 -1
  179. package/dist/keyboard-filter.d.ts +25 -0
  180. package/dist/on.d.ts +1 -1
  181. package/dist/parts.d.ts +2 -19
  182. package/dist/react/SniceProvider.js +2 -2
  183. package/dist/react/SniceRouter.js +2 -2
  184. package/dist/react/index.js +2 -2
  185. package/dist/react/matchRoute.js +2 -2
  186. package/dist/react/useRequestHandler.js +2 -2
  187. package/dist/request-response.d.ts +9 -1
  188. package/dist/symbols.cjs +19 -2
  189. package/dist/symbols.cjs.map +1 -1
  190. package/dist/symbols.d.ts +5 -0
  191. package/dist/symbols.esm.js +15 -3
  192. package/dist/symbols.esm.js.map +1 -1
  193. package/dist/testing.d.ts +1 -1
  194. package/dist/transitions.cjs +2 -2
  195. package/dist/transitions.esm.js +2 -2
  196. package/dist/types/app-context.d.ts +9 -58
  197. package/dist/types/daemon.d.ts +8 -0
  198. package/dist/types/dispatch-options.d.ts +2 -0
  199. package/dist/types/index.d.ts +1 -0
  200. package/dist/types/on-options.d.ts +5 -0
  201. package/dist/types/request-options.d.ts +2 -0
  202. package/dist/types/respond-options.d.ts +2 -0
  203. package/docs/ai/DEVELOPMENT.md +15 -1
  204. package/docs/ai/README.md +5 -1
  205. package/docs/ai/api.md +13 -4
  206. package/docs/ai/architecture.md +51 -2
  207. package/docs/ai/bindings.md +1 -1
  208. package/docs/ai/cli.md +9 -0
  209. package/docs/ai/controllers.md +1 -1
  210. package/docs/ai/daemons.md +80 -0
  211. package/docs/ai/decorators.md +7 -5
  212. package/docs/ai/events.md +6 -0
  213. package/docs/ai/patterns.md +49 -4
  214. package/docs/ai/request-response.md +26 -9
  215. package/docs/ai/routing.md +16 -1
  216. package/docs/bindings.md +1 -1
  217. package/docs/cli.md +7 -0
  218. package/docs/controllers.md +8 -2
  219. package/docs/daemons.md +200 -0
  220. package/docs/events.md +19 -2
  221. package/docs/request-response.md +20 -13
  222. package/docs/routing.md +25 -1
  223. 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
@@ -414,6 +414,9 @@ interface OnOptions {
414
414
 
415
415
  // Where to attach the listener (see scope section below)
416
416
  scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
417
+
418
+ // Named daemon from the nearest provided app context
419
+ daemon?: string;
417
420
  }
418
421
  ```
419
422
 
@@ -461,6 +464,9 @@ re-resolves and re-attaches, so resolver-based scopes track DOM moves correctly.
461
464
  `scope` is compatible with the delegation selector — the listener attaches on the
462
465
  scoped target and still matches the selector when an event fires within it.
463
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
+
464
470
  #### Throttling
465
471
 
466
472
  ```typescript
@@ -609,13 +615,15 @@ interface DispatchOptions extends EventInit {
609
615
  throttle?: number; // Throttle dispatch by ms
610
616
  // Where to dispatch the event (see scope section below)
611
617
  scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
618
+ // Named daemon from the nearest provided app context
619
+ daemon?: string;
612
620
  }
613
621
  ```
614
622
 
615
623
  #### scope — controlling the dispatch target
616
624
 
617
- By default, `@dispatch` calls `this.dispatchEvent(event)` the event originates from
618
- 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
619
627
  event behaves as if it originated there. Use this with `@on({ scope })` to express
620
628
  cross-cutting events without going through bubbling.
621
629
 
@@ -651,6 +659,10 @@ the event is **not dispatched** and a `console.warn` is emitted. The method's re
651
659
  value still flows through `dispatchOnUndefined` / `debounce` / `throttle` semantics
652
660
  before the scope check.
653
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
+
654
666
  ### Debounce/Throttle
655
667
 
656
668
  ```typescript
@@ -788,6 +800,11 @@ rather than silently binding to the wrong node. See
788
800
  For a request that needs an answer rather than a broadcast, use
789
801
  [@request / @respond](./request-response.md) instead.
790
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
+
791
808
  ## Custom Events
792
809
 
793
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.3",
3
+ "version": "7.1.1",
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",