snice 7.3.0 → 7.4.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 (209) hide show
  1. package/.agents/skills/snice/SKILL.md +12 -16
  2. package/README.md +5 -1
  3. package/adapters/react/SniceProvider.js +1 -1
  4. package/adapters/react/SniceRouter.js +1 -1
  5. package/adapters/react/matchRoute.js +1 -1
  6. package/adapters/react/useRequestHandler.js +1 -1
  7. package/bin/analyzer-contracts.js +3 -1
  8. package/bin/project-analyzer.js +219 -6
  9. package/bin/snice.js +77 -13
  10. package/dist/app-context.d.ts +10 -10
  11. package/dist/cdn/accordion/snice-accordion.js +1 -1
  12. package/dist/cdn/action-bar/snice-action-bar.js +1 -1
  13. package/dist/cdn/activity-feed/snice-activity-feed.js +1 -1
  14. package/dist/cdn/alert/snice-alert.js +1 -1
  15. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  16. package/dist/cdn/approval-flow/snice-approval-flow.js +1 -1
  17. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  18. package/dist/cdn/availability/snice-availability.js +1 -1
  19. package/dist/cdn/avatar/snice-avatar.js +1 -1
  20. package/dist/cdn/avatar-group/snice-avatar-group.js +1 -1
  21. package/dist/cdn/badge/snice-badge.js +1 -1
  22. package/dist/cdn/banner/snice-banner.js +1 -1
  23. package/dist/cdn/binpack/snice-binpack.js +1 -1
  24. package/dist/cdn/book/snice-book.js +1 -1
  25. package/dist/cdn/booking/snice-booking.js +1 -1
  26. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  27. package/dist/cdn/button/snice-button.js +1 -1
  28. package/dist/cdn/calendar/snice-calendar.js +1 -1
  29. package/dist/cdn/camera/snice-camera.js +1 -1
  30. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  31. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  32. package/dist/cdn/card/snice-card.js +1 -1
  33. package/dist/cdn/carousel/snice-carousel.js +1 -1
  34. package/dist/cdn/cart/snice-cart.js +1 -1
  35. package/dist/cdn/chart/snice-chart.js +1 -1
  36. package/dist/cdn/chat/snice-chat.js +1 -1
  37. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  38. package/dist/cdn/chip/snice-chip.js +1 -1
  39. package/dist/cdn/code-block/snice-code-block.js +1 -1
  40. package/dist/cdn/color-display/snice-color-display.js +1 -1
  41. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  42. package/dist/cdn/command-palette/snice-command-palette.js +1 -1
  43. package/dist/cdn/comments/snice-comments.js +1 -1
  44. package/dist/cdn/countdown/snice-countdown.js +1 -1
  45. package/dist/cdn/cropper/snice-cropper.js +1 -1
  46. package/dist/cdn/data-card/snice-data-card.js +1 -1
  47. package/dist/cdn/date-picker/snice-date-picker.js +1 -1
  48. package/dist/cdn/date-range-picker/snice-date-range-picker.js +1 -1
  49. package/dist/cdn/date-time-picker/snice-date-time-picker.js +1 -1
  50. package/dist/cdn/diff/snice-diff.js +1 -1
  51. package/dist/cdn/divider/snice-divider.js +1 -1
  52. package/dist/cdn/doc/snice-doc.js +1 -1
  53. package/dist/cdn/draw/snice-draw.js +1 -1
  54. package/dist/cdn/drawer/snice-drawer.js +1 -1
  55. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  56. package/dist/cdn/estimate/snice-estimate.js +1 -1
  57. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  58. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  59. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  60. package/dist/cdn/flow/snice-flow.js +1 -1
  61. package/dist/cdn/form-layout/snice-form-layout.js +1 -1
  62. package/dist/cdn/funnel/snice-funnel.js +1 -1
  63. package/dist/cdn/gantt/snice-gantt.js +1 -1
  64. package/dist/cdn/gauge/snice-gauge.js +1 -1
  65. package/dist/cdn/grid/snice-grid.js +1 -1
  66. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  67. package/dist/cdn/image/snice-image.js +1 -1
  68. package/dist/cdn/input/snice-input.js +1 -1
  69. package/dist/cdn/invoice/snice-invoice.js +1 -1
  70. package/dist/cdn/kanban/snice-kanban.js +1 -1
  71. package/dist/cdn/key-value/snice-key-value.js +1 -1
  72. package/dist/cdn/kpi/snice-kpi.js +1 -1
  73. package/dist/cdn/layout/snice-layout.js +1 -1
  74. package/dist/cdn/leaderboard/snice-leaderboard.js +1 -1
  75. package/dist/cdn/link/snice-link.js +1 -1
  76. package/dist/cdn/link-preview/snice-link-preview.js +1 -1
  77. package/dist/cdn/list/snice-list.js +1 -1
  78. package/dist/cdn/location/snice-location.js +1 -1
  79. package/dist/cdn/login/snice-login.js +1 -1
  80. package/dist/cdn/map/snice-map.js +1 -1
  81. package/dist/cdn/markdown/snice-markdown.js +1 -1
  82. package/dist/cdn/masonry/snice-masonry.js +1 -1
  83. package/dist/cdn/menu/snice-menu.js +1 -1
  84. package/dist/cdn/message-strip/snice-message-strip.js +1 -1
  85. package/dist/cdn/modal/snice-modal.js +1 -1
  86. package/dist/cdn/music-player/snice-music-player.js +1 -1
  87. package/dist/cdn/nav/snice-nav.js +1 -1
  88. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  89. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  90. package/dist/cdn/order-tracker/snice-order-tracker.js +1 -1
  91. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  92. package/dist/cdn/pagination/snice-pagination.js +1 -1
  93. package/dist/cdn/paint/snice-paint.js +1 -1
  94. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  95. package/dist/cdn/permission-matrix/snice-permission-matrix.js +1 -1
  96. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  97. package/dist/cdn/popover/snice-popover.js +1 -1
  98. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  99. package/dist/cdn/product-card/snice-product-card.js +1 -1
  100. package/dist/cdn/progress/snice-progress.js +1 -1
  101. package/dist/cdn/progress-ring/snice-progress-ring.js +1 -1
  102. package/dist/cdn/qr-code/snice-qr-code.js +1 -1
  103. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  104. package/dist/cdn/radio/snice-radio.js +1 -1
  105. package/dist/cdn/range-slider/snice-range-slider.js +1 -1
  106. package/dist/cdn/rating/snice-rating.js +1 -1
  107. package/dist/cdn/receipt/snice-receipt.js +1 -1
  108. package/dist/cdn/recipe/snice-recipe.js +1 -1
  109. package/dist/cdn/runtime/README.md +2 -2
  110. package/dist/cdn/runtime/snice-runtime.esm.js +309 -188
  111. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  112. package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
  113. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  114. package/dist/cdn/runtime/snice-runtime.js +309 -187
  115. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  116. package/dist/cdn/runtime/snice-runtime.min.js +1 -1
  117. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  118. package/dist/cdn/sankey/snice-sankey.js +1 -1
  119. package/dist/cdn/segmented-control/snice-segmented-control.js +1 -1
  120. package/dist/cdn/select/snice-select.js +1 -1
  121. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  122. package/dist/cdn/slider/snice-slider.js +1 -1
  123. package/dist/cdn/sortable/snice-sortable.js +1 -1
  124. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  125. package/dist/cdn/spinner/snice-spinner.js +1 -1
  126. package/dist/cdn/split-button/snice-split-button.js +1 -1
  127. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  128. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  129. package/dist/cdn/stat-group/snice-stat-group.js +1 -1
  130. package/dist/cdn/step-input/snice-step-input.js +1 -1
  131. package/dist/cdn/stepper/snice-stepper.js +1 -1
  132. package/dist/cdn/switch/snice-switch.js +1 -1
  133. package/dist/cdn/table/snice-table.esm.js +1 -1
  134. package/dist/cdn/table/snice-table.js +1 -1
  135. package/dist/cdn/tabs/snice-tabs.js +1 -1
  136. package/dist/cdn/tag/snice-tag.js +1 -1
  137. package/dist/cdn/tag-input/snice-tag-input.js +1 -1
  138. package/dist/cdn/terminal/snice-terminal.js +1 -1
  139. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  140. package/dist/cdn/textarea/snice-textarea.js +1 -1
  141. package/dist/cdn/time-picker/snice-time-picker.js +1 -1
  142. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  143. package/dist/cdn/timeline/snice-timeline.js +1 -1
  144. package/dist/cdn/timer/snice-timer.js +1 -1
  145. package/dist/cdn/toast/snice-toast.js +1 -1
  146. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  147. package/dist/cdn/tree/snice-tree.js +1 -1
  148. package/dist/cdn/treemap/snice-treemap.js +1 -1
  149. package/dist/cdn/user-card/snice-user-card.js +1 -1
  150. package/dist/cdn/video-player/snice-video-player.js +1 -1
  151. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  152. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  153. package/dist/cdn/weather/snice-weather.js +1 -1
  154. package/dist/cdn/work-order/snice-work-order.js +1 -1
  155. package/dist/components/pagination/snice-pagination.types.d.ts +5 -0
  156. package/dist/context-provider.d.ts +11 -1
  157. package/dist/context.d.ts +5 -6
  158. package/dist/index.cjs +306 -184
  159. package/dist/index.cjs.map +1 -1
  160. package/dist/index.d.ts +2 -1
  161. package/dist/index.esm.js +306 -185
  162. package/dist/index.esm.js.map +1 -1
  163. package/dist/index.iife.js +306 -184
  164. package/dist/index.iife.js.map +1 -1
  165. package/dist/react/SniceProvider.js +1 -1
  166. package/dist/react/SniceRouter.js +1 -1
  167. package/dist/react/index.js +1 -1
  168. package/dist/react/matchRoute.js +1 -1
  169. package/dist/react/useRequestHandler.js +1 -1
  170. package/dist/symbols.cjs +1 -1
  171. package/dist/symbols.esm.js +1 -1
  172. package/dist/testing-dom.cjs +48 -0
  173. package/dist/testing-dom.cjs.map +1 -0
  174. package/dist/testing-dom.d.ts +23 -0
  175. package/dist/testing-dom.esm.js +45 -0
  176. package/dist/testing-dom.esm.js.map +1 -0
  177. package/dist/transitions.cjs +1 -1
  178. package/dist/transitions.esm.js +1 -1
  179. package/dist/types/context.d.ts +8 -8
  180. package/dist/utils.d.ts +3 -1
  181. package/docs/ai/api.md +12 -1
  182. package/docs/ai/architecture.md +7 -3
  183. package/docs/ai/bindings.md +1 -1
  184. package/docs/ai/cli.md +22 -0
  185. package/docs/ai/components/badge.md +4 -0
  186. package/docs/ai/components/pagination.md +4 -2
  187. package/docs/ai/components/segmented-control.md +5 -1
  188. package/docs/ai/controllers.md +30 -11
  189. package/docs/ai/decorators.md +2 -2
  190. package/docs/ai/events.md +8 -2
  191. package/docs/ai/fetcher.md +32 -1
  192. package/docs/ai/lifecycle.md +4 -1
  193. package/docs/ai/patterns.md +9 -4
  194. package/docs/ai/request-response.md +20 -6
  195. package/docs/ai/routing.md +8 -1
  196. package/docs/ai/testing.md +71 -0
  197. package/docs/bindings.md +1 -1
  198. package/docs/cli.md +23 -0
  199. package/docs/components/badge.md +4 -0
  200. package/docs/components/pagination.md +8 -5
  201. package/docs/components/segmented-control.md +5 -1
  202. package/docs/controllers.md +30 -11
  203. package/docs/events.md +11 -2
  204. package/docs/fetcher.md +32 -1
  205. package/docs/lifecycle.md +5 -1
  206. package/docs/request-response.md +25 -7
  207. package/docs/routing.md +10 -1
  208. package/docs/testing.md +71 -0
  209. package/package.json +6 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.2.0
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.2.0
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.2.0
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.2.0
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.2.0
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
package/dist/symbols.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.2.0
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.2.0
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -0,0 +1,48 @@
1
+ /*!
2
+ * snice v7.3.0
3
+ * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
+ * (c) 2024
5
+ * Released under the MIT License.
6
+ *
7
+ * GENERATED FILE — DO NOT EDIT. Source: src/. Rebuild: npm run build:core
8
+ */
9
+ 'use strict';
10
+
11
+ /**
12
+ * Standards-shaped DOM compatibility for non-browser test runners.
13
+ *
14
+ * This module only fills missing IDL surface. It never replaces an existing
15
+ * implementation and does not attempt to simulate layout, paint, or browser
16
+ * focus navigation.
17
+ */
18
+ /**
19
+ * Fill proven DOM-runner IDL gaps without replacing native implementations.
20
+ * Safe to call more than once.
21
+ */
22
+ function installDOMTestingCompatibility(scope = globalThis) {
23
+ const installed = [];
24
+ const HTMLElementConstructor = scope.HTMLElement;
25
+ if (!HTMLElementConstructor)
26
+ return { installed };
27
+ const prototype = HTMLElementConstructor.prototype;
28
+ if (!('autofocus' in prototype)) {
29
+ Object.defineProperty(prototype, 'autofocus', {
30
+ configurable: true,
31
+ enumerable: true,
32
+ get() {
33
+ return this.hasAttribute('autofocus');
34
+ },
35
+ set(value) {
36
+ this.toggleAttribute('autofocus', Boolean(value));
37
+ },
38
+ });
39
+ installed.push('HTMLElement.autofocus');
40
+ }
41
+ return { installed };
42
+ }
43
+ /** Result of the side-effect installation performed by `snice/testing/dom`. */
44
+ const domTestingCompatibility = installDOMTestingCompatibility();
45
+
46
+ exports.domTestingCompatibility = domTestingCompatibility;
47
+ exports.installDOMTestingCompatibility = installDOMTestingCompatibility;
48
+ //# sourceMappingURL=testing-dom.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing-dom.cjs","sources":["../src/testing-dom.ts"],"sourcesContent":["/**\n * Standards-shaped DOM compatibility for non-browser test runners.\n *\n * This module only fills missing IDL surface. It never replaces an existing\n * implementation and does not attempt to simulate layout, paint, or browser\n * focus navigation.\n */\n\nexport type DOMTestingCompatibilityFeature = 'HTMLElement.autofocus';\n\nexport interface DOMTestingCompatibilityReport {\n /** Standards features installed because the current DOM omitted them. */\n installed: readonly DOMTestingCompatibilityFeature[];\n}\n\ninterface DOMTestingGlobal {\n HTMLElement?: typeof HTMLElement;\n}\n\n/**\n * Fill proven DOM-runner IDL gaps without replacing native implementations.\n * Safe to call more than once.\n */\nexport function installDOMTestingCompatibility(\n scope: DOMTestingGlobal = globalThis as DOMTestingGlobal\n): DOMTestingCompatibilityReport {\n const installed: DOMTestingCompatibilityFeature[] = [];\n const HTMLElementConstructor = scope.HTMLElement;\n if (!HTMLElementConstructor) return { installed };\n\n const prototype = HTMLElementConstructor.prototype;\n if (!('autofocus' in prototype)) {\n Object.defineProperty(prototype, 'autofocus', {\n configurable: true,\n enumerable: true,\n get(this: HTMLElement): boolean {\n return this.hasAttribute('autofocus');\n },\n set(this: HTMLElement, value: boolean) {\n this.toggleAttribute('autofocus', Boolean(value));\n },\n });\n installed.push('HTMLElement.autofocus');\n }\n\n return { installed };\n}\n\n/** Result of the side-effect installation performed by `snice/testing/dom`. */\nexport const domTestingCompatibility = installDOMTestingCompatibility();\n"],"names":[],"mappings":";;;;;;;;;;AAAA;;;;;;AAMG;AAaH;;;AAGG;AACG,SAAU,8BAA8B,CAC5C,KAAA,GAA0B,UAA8B,EAAA;IAExD,MAAM,SAAS,GAAqC,EAAE;AACtD,IAAA,MAAM,sBAAsB,GAAG,KAAK,CAAC,WAAW;AAChD,IAAA,IAAI,CAAC,sBAAsB;QAAE,OAAO,EAAE,SAAS,EAAE;AAEjD,IAAA,MAAM,SAAS,GAAG,sBAAsB,CAAC,SAAS;AAClD,IAAA,IAAI,EAAE,WAAW,IAAI,SAAS,CAAC,EAAE;AAC/B,QAAA,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE;AAC5C,YAAA,YAAY,EAAE,IAAI;AAClB,YAAA,UAAU,EAAE,IAAI;YAChB,GAAG,GAAA;AACD,gBAAA,OAAO,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC;YACvC,CAAC;AACD,YAAA,GAAG,CAAoB,KAAc,EAAA;gBACnC,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACnD,CAAC;AACF,SAAA,CAAC;AACF,QAAA,SAAS,CAAC,IAAI,CAAC,uBAAuB,CAAC;IACzC;IAEA,OAAO,EAAE,SAAS,EAAE;AACtB;AAEA;AACO,MAAM,uBAAuB,GAAG,8BAA8B;;;;;"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Standards-shaped DOM compatibility for non-browser test runners.
3
+ *
4
+ * This module only fills missing IDL surface. It never replaces an existing
5
+ * implementation and does not attempt to simulate layout, paint, or browser
6
+ * focus navigation.
7
+ */
8
+ export type DOMTestingCompatibilityFeature = 'HTMLElement.autofocus';
9
+ export interface DOMTestingCompatibilityReport {
10
+ /** Standards features installed because the current DOM omitted them. */
11
+ installed: readonly DOMTestingCompatibilityFeature[];
12
+ }
13
+ interface DOMTestingGlobal {
14
+ HTMLElement?: typeof HTMLElement;
15
+ }
16
+ /**
17
+ * Fill proven DOM-runner IDL gaps without replacing native implementations.
18
+ * Safe to call more than once.
19
+ */
20
+ export declare function installDOMTestingCompatibility(scope?: DOMTestingGlobal): DOMTestingCompatibilityReport;
21
+ /** Result of the side-effect installation performed by `snice/testing/dom`. */
22
+ export declare const domTestingCompatibility: DOMTestingCompatibilityReport;
23
+ export {};
@@ -0,0 +1,45 @@
1
+ /*!
2
+ * snice v7.3.0
3
+ * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
+ * (c) 2024
5
+ * Released under the MIT License.
6
+ *
7
+ * GENERATED FILE — DO NOT EDIT. Source: src/. Rebuild: npm run build:core
8
+ */
9
+ /**
10
+ * Standards-shaped DOM compatibility for non-browser test runners.
11
+ *
12
+ * This module only fills missing IDL surface. It never replaces an existing
13
+ * implementation and does not attempt to simulate layout, paint, or browser
14
+ * focus navigation.
15
+ */
16
+ /**
17
+ * Fill proven DOM-runner IDL gaps without replacing native implementations.
18
+ * Safe to call more than once.
19
+ */
20
+ function installDOMTestingCompatibility(scope = globalThis) {
21
+ const installed = [];
22
+ const HTMLElementConstructor = scope.HTMLElement;
23
+ if (!HTMLElementConstructor)
24
+ return { installed };
25
+ const prototype = HTMLElementConstructor.prototype;
26
+ if (!('autofocus' in prototype)) {
27
+ Object.defineProperty(prototype, 'autofocus', {
28
+ configurable: true,
29
+ enumerable: true,
30
+ get() {
31
+ return this.hasAttribute('autofocus');
32
+ },
33
+ set(value) {
34
+ this.toggleAttribute('autofocus', Boolean(value));
35
+ },
36
+ });
37
+ installed.push('HTMLElement.autofocus');
38
+ }
39
+ return { installed };
40
+ }
41
+ /** Result of the side-effect installation performed by `snice/testing/dom`. */
42
+ const domTestingCompatibility = installDOMTestingCompatibility();
43
+
44
+ export { domTestingCompatibility, installDOMTestingCompatibility };
45
+ //# sourceMappingURL=testing-dom.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing-dom.esm.js","sources":["../src/testing-dom.ts"],"sourcesContent":["/**\n * Standards-shaped DOM compatibility for non-browser test runners.\n *\n * This module only fills missing IDL surface. It never replaces an existing\n * implementation and does not attempt to simulate layout, paint, or browser\n * focus navigation.\n */\n\nexport type DOMTestingCompatibilityFeature = 'HTMLElement.autofocus';\n\nexport interface DOMTestingCompatibilityReport {\n /** Standards features installed because the current DOM omitted them. */\n installed: readonly DOMTestingCompatibilityFeature[];\n}\n\ninterface DOMTestingGlobal {\n HTMLElement?: typeof HTMLElement;\n}\n\n/**\n * Fill proven DOM-runner IDL gaps without replacing native implementations.\n * Safe to call more than once.\n */\nexport function installDOMTestingCompatibility(\n scope: DOMTestingGlobal = globalThis as DOMTestingGlobal\n): DOMTestingCompatibilityReport {\n const installed: DOMTestingCompatibilityFeature[] = [];\n const HTMLElementConstructor = scope.HTMLElement;\n if (!HTMLElementConstructor) return { installed };\n\n const prototype = HTMLElementConstructor.prototype;\n if (!('autofocus' in prototype)) {\n Object.defineProperty(prototype, 'autofocus', {\n configurable: true,\n enumerable: true,\n get(this: HTMLElement): boolean {\n return this.hasAttribute('autofocus');\n },\n set(this: HTMLElement, value: boolean) {\n this.toggleAttribute('autofocus', Boolean(value));\n },\n });\n installed.push('HTMLElement.autofocus');\n }\n\n return { installed };\n}\n\n/** Result of the side-effect installation performed by `snice/testing/dom`. */\nexport const domTestingCompatibility = installDOMTestingCompatibility();\n"],"names":[],"mappings":";;;;;;;;AAAA;;;;;;AAMG;AAaH;;;AAGG;AACG,SAAU,8BAA8B,CAC5C,KAAA,GAA0B,UAA8B,EAAA;IAExD,MAAM,SAAS,GAAqC,EAAE;AACtD,IAAA,MAAM,sBAAsB,GAAG,KAAK,CAAC,WAAW;AAChD,IAAA,IAAI,CAAC,sBAAsB;QAAE,OAAO,EAAE,SAAS,EAAE;AAEjD,IAAA,MAAM,SAAS,GAAG,sBAAsB,CAAC,SAAS;AAClD,IAAA,IAAI,EAAE,WAAW,IAAI,SAAS,CAAC,EAAE;AAC/B,QAAA,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE;AAC5C,YAAA,YAAY,EAAE,IAAI;AAClB,YAAA,UAAU,EAAE,IAAI;YAChB,GAAG,GAAA;AACD,gBAAA,OAAO,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC;YACvC,CAAC;AACD,YAAA,GAAG,CAAoB,KAAc,EAAA;gBACnC,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACnD,CAAC;AACF,SAAA,CAAC;AACF,QAAA,SAAS,CAAC,IAAI,CAAC,uBAAuB,CAAC;IACzC;IAEA,OAAO,EAAE,SAAS,EAAE;AACtB;AAEA;AACO,MAAM,uBAAuB,GAAG,8BAA8B;;;;"}
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.2.0
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.2.0
2
+ * snice v7.3.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -9,21 +9,21 @@ declare const REGISTERED_ELEMENTS_SET: unique symbol;
9
9
  * Represents the bundled router state that can notify registered elements of changes
10
10
  */
11
11
  export declare class Context {
12
- [REGISTERED_ELEMENTS]: WeakMap<HTMLElement, Set<string>>;
12
+ [REGISTERED_ELEMENTS]: WeakMap<object, Set<string>>;
13
13
  [IS_UPDATING]: boolean;
14
14
  /**
15
- * Register an element to receive context updates
15
+ * Register an element or controller to receive context updates
16
16
  * @internal Used by @context decorator
17
17
  */
18
- [CONTEXT_REGISTER]: (element: HTMLElement, methodName: string) => void;
18
+ [CONTEXT_REGISTER]: (element: object, methodName: string) => void;
19
19
  /**
20
- * Unregister an element from receiving context updates. With a methodName,
20
+ * Unregister a subscriber from context updates. With a methodName,
21
21
  * removes only that one handler (so a `once` handler tearing itself down
22
22
  * doesn't kill the element's other @context handlers); without one, removes
23
- * the element entirely (used on disconnect).
23
+ * the subscriber entirely (used on disconnect/detach).
24
24
  * @internal Used by @context decorator cleanup
25
25
  */
26
- [CONTEXT_UNREGISTER]: (element: HTMLElement, methodName?: string) => void;
26
+ [CONTEXT_UNREGISTER]: (element: object, methodName?: string) => void;
27
27
  /**
28
28
  * Update the context and notify all registered elements
29
29
  * Prevents infinite loops by tracking update state
@@ -31,10 +31,10 @@ export declare class Context {
31
31
  */
32
32
  [CONTEXT_UPDATE]: (context: AppContext, placards: Placard[], currentRoute: string, routeParams: RouteParams) => void;
33
33
  /**
34
- * Notify a specific element of the current context state
34
+ * Notify a specific subscriber of the current context state
35
35
  * @internal Used by @context decorator
36
36
  */
37
- [CONTEXT_NOTIFY_ELEMENT]: (element: HTMLElement) => void;
37
+ [CONTEXT_NOTIFY_ELEMENT]: (element: object) => void;
38
38
  private [REGISTERED_ELEMENTS_SET];
39
39
  /**
40
40
  * Unique immutable identifier for this context instance
package/dist/utils.d.ts CHANGED
@@ -101,7 +101,9 @@ export declare function invokeImmediateWatchers(instance: any, constructor: any)
101
101
  /**
102
102
  * Create a debounced version of a function
103
103
  */
104
- export declare function createDebounced<T extends (...args: any[]) => any>(fn: T, delay: number): T;
104
+ export declare function createDebounced<T extends (...args: any[]) => any>(fn: T, delay: number): T & {
105
+ cancel(): void;
106
+ };
105
107
  /**
106
108
  * Create a throttled version of a function (leading + trailing edge)
107
109
  */
package/docs/ai/api.md CHANGED
@@ -122,7 +122,7 @@ html`
122
122
  @dispatch(eventName: string, options?: { debounce?, throttle?, dispatchOnUndefined?, scope?, daemon?, ...EventInit })
123
123
  // Fires CustomEvent after method, detail = return value
124
124
  // Supports async methods (dispatches after promise resolves)
125
- // dispatchOnUndefined: false (default) — skips dispatch if method returns undefined
125
+ // dispatchOnUndefined: false — skips when return is undefined; omitted/true still dispatches
126
126
  // scope: 'global' | selector | EventTarget | () => EventTarget | null — redirects dispatch target
127
127
  ```
128
128
 
@@ -178,6 +178,8 @@ html`
178
178
  // Example:
179
179
  // @respond('fetch-user')
180
180
  // async handleFetchUser(payload: { id: string }) {
181
+ // const fetch = getContextFetch(this);
182
+ // if (!fetch) throw new Error('Controller requires context fetch');
181
183
  // const user = await fetch(`/api/users/${payload.id}`).then(r => r.json());
182
184
  // return user;
183
185
  // }
@@ -191,6 +193,14 @@ const context = getContext(elementOrController);
191
193
  // Provide before connect/attach. Call release() during app/test teardown.
192
194
  ```
193
195
 
196
+ Context transport:
197
+ ```typescript
198
+ const release = provideContext(appRoot, appContext, { fetch: appFetch });
199
+ const fetch = getContextFetch(elementOrController);
200
+ // Router supplies its ContextAwareFetcher-bound Context.fetch automatically.
201
+ // AppContext is not mutated with a reserved transport key.
202
+ ```
203
+
194
204
  ## Observers
195
205
 
196
206
  ```typescript
@@ -222,6 +232,7 @@ const context = getContext(elementOrController);
222
232
 
223
233
  @context(options?: { debounce?, throttle?, once? })
224
234
  // Method decorator: receives Context on navigation and ctx.update() calls
235
+ // Supported on pages, descendant elements, and attached controllers.
225
236
  // Called on: initial load, route change, ctx.update()
226
237
  // Example:
227
238
  // @context() handleContext(ctx: Context) {
@@ -103,8 +103,9 @@ decomposition suggestions.
103
103
  2. ControllerPart commit / attributeChangedCallback detects change
104
104
  3. Detach old controller (if any)
105
105
  4. Attach new controller (class refs skip the registry; reference-deduped)
106
- 5. setupEventHandlers for controller
107
- 6. On detach: cleanupEventHandlers
106
+ 5. Register `@context` and catch up with the current Router context
107
+ 6. Set up `@observe`, `@respond`, and `@on` handlers
108
+ 7. On detach: clean up every managed decorator
108
109
  - Class bindings own the element; their decorator name is reflected as a diagnostic-only `controller="name"` marker
109
110
  - Native elements: class bindings attach via ControllerPart; string attrs via MutationObserver
110
111
 
@@ -184,9 +185,12 @@ async *fetchData(): Response<Data> {
184
185
  }
185
186
 
186
187
  // Controller responds (receives payload, returns result directly)
188
+ private ctx!: Context;
189
+ @context() receiveContext(ctx: Context) { this.ctx = ctx; }
190
+
187
191
  @respond('fetch-data')
188
192
  async handleFetch(payload: { id: string }) {
189
- return await fetch(`/api/${payload.id}`).then(r => r.json());
193
+ return await this.ctx.fetch(`/api/${payload.id}`).then(r => r.json());
190
194
  }
191
195
  // Wiring: html`<my-element controller=${MyController}></my-element>` (or controller="my-controller" in raw HTML)
192
196
  ```
@@ -48,7 +48,7 @@ Ordinary attributes/comments support multiple expressions. Property, boolean, ev
48
48
  - Direct JS assignment; preserves type and identity.
49
49
  - null/undefined/false assigned unchanged; `nothing` assigns undefined; `noChange` skips.
50
50
  - Same committed value skips write.
51
- - `live(value)` compares value to the current DOM property instead of last commit; use only in property bindings when controlled native state must be reasserted after DOM/user mutation.
51
+ - `live(value)` compares value to the current DOM property instead of last commit; use only in property bindings when controlled native or custom-element state must be reasserted after the user or element mutates it.
52
52
  - `live()` does not observe DOM mutations or schedule a render. It reasserts only when the owning template renders. A modal open-state change can supply that render when reopening should restore an unchanged seed value.
53
53
 
54
54
  ```typescript
package/docs/ai/cli.md CHANGED
@@ -59,6 +59,28 @@ Non-blocking architecture suggestions:
59
59
 
60
60
  Both `doctor` and `validate` accept `--json`.
61
61
 
62
+ ## Diagnostic codes and `.sniceignore`
63
+
64
+ Every error, warning, and suggestion has a stable `code` in JSON and text
65
+ output. Create `.sniceignore` at the project root to suppress an accepted
66
+ diagnostic. Entries are exact; glob patterns are not used.
67
+
68
+ ```text
69
+ # One exact diagnostic (preferred)
70
+ snice/prefer-dispatch-decorator src/components/legacy-filter.ts:42:5
71
+
72
+ # Every instance in one file
73
+ snice/prefer-dispatch-decorator src/components/legacy-filter.ts
74
+
75
+ # The rule everywhere in the project
76
+ snice/prefer-dispatch-decorator
77
+ ```
78
+
79
+ `code path:line` is also accepted. Paths are project-relative. A code-only
80
+ entry suppresses the rule globally, including its effect on the command's exit
81
+ status; use it only when the project intentionally rejects that rule. The same
82
+ file applies to `check`, `doctor`, and `validate`.
83
+
62
84
  ## init-ai
63
85
 
64
86
  ```bash
@@ -19,6 +19,10 @@ showZero: boolean = false; // attr: show-zero — render 0 instead of h
19
19
  offset: number = 0;
20
20
  ```
21
21
 
22
+ The indicator is hidden when there is no dot, non-empty `content`, positive
23
+ `count`, or `showZero`. Slotted content remains available as the element the
24
+ badge would overlay.
25
+
22
26
  ## Methods
23
27
 
24
28
  - `setBadgeContent(content)` - Set text content
@@ -57,9 +57,11 @@ variant: 'default'|'rounded'|'text' = 'default';
57
57
 
58
58
  ```typescript
59
59
  import 'snice/components/pagination/snice-pagination';
60
+ import type { PaginationChangeDetail } from 'snice/components/pagination/snice-pagination.types';
60
61
 
61
- pagination.addEventListener('pagination-change', (e) => {
62
- console.log('Page:', e.detail.page);
62
+ pagination.addEventListener('pagination-change', (event) => {
63
+ const { detail } = event as CustomEvent<PaginationChangeDetail>;
64
+ console.log('Page:', detail.page);
63
65
  });
64
66
  ```
65
67
 
@@ -5,7 +5,7 @@ Multi-option switcher with sliding indicator. One selected at a time.
5
5
  ## Properties
6
6
 
7
7
  ```typescript
8
- value: string = '';
8
+ value: string = ''; // user selection updates this before value-change dispatches
9
9
  options: SegmentedControlOption[] = []; // JS only: { value, label, icon?, disabled? }
10
10
  size: 'small'|'medium'|'large' = 'medium';
11
11
  disabled: boolean = false;
@@ -15,6 +15,10 @@ disabled: boolean = false;
15
15
 
16
16
  - `value-change` → `{ value: string, previousValue: string, option: SegmentedControlOption, control }`
17
17
 
18
+ `value` is self-mutating: selecting an option assigns it before the event is
19
+ dispatched. An owner that must reassert an unchanged value on a later render
20
+ should use a `.value=${live(value)}` property binding.
21
+
18
22
  ## CSS Parts
19
23
 
20
24
  - `base` - Root container
@@ -75,13 +75,14 @@ el.controller = UserController; // snice elements
75
75
  **Attachment flow:**
76
76
  1. Controller instance created
77
77
  2. `element` property set
78
- 3. Router context passed (if available)
78
+ 3. Router application context passed (if available)
79
79
  4. Element's `ready` promise awaited
80
80
  5. `attach()` called
81
- 6. Observers set up
82
- 7. Channel/response handlers set up
83
- 8. Event handlers set up
84
- 9. `controller-attached` event dispatched
81
+ 6. `@context` handlers registered and caught up with the current Router context
82
+ 7. Observers set up
83
+ 8. Channel/response handlers set up
84
+ 9. Event handlers set up
85
+ 10. `controller-attached` event dispatched
85
86
 
86
87
  Exception to step 4: `await attachController(this, ControllerClass)` inside the
87
88
  host's own `@ready` handler attaches immediately because initial render is
@@ -96,8 +97,9 @@ good architecture; pages should orchestrate directly.
96
97
  3. Observers cleaned up
97
98
  4. Channel/response handlers cleaned up
98
99
  5. Event handlers cleaned up
99
- 6. Controller scope cleaned up
100
- 7. `controller-detached` event dispatched
100
+ 6. `@context` handlers cleaned up
101
+ 7. Controller scope cleaned up
102
+ 8. `controller-detached` event dispatched
101
103
 
102
104
  ## Native Element Controllers
103
105
 
@@ -113,7 +115,7 @@ Controllers provide specific behaviors (fetch/sort/filter) to generic visual com
113
115
 
114
116
  ## Resource Cleanup
115
117
 
116
- Framework auto-cleans `@on`, `@observe`, `@respond` handlers. Clean up your own resources (WebSockets, timers, manual listeners) in `detach`:
118
+ Framework auto-cleans `@on`, `@observe`, `@respond`, and `@context` handlers. Clean up your own resources (WebSockets, timers, manual listeners) in `detach`:
117
119
 
118
120
  ```typescript
119
121
  @controller('resource-controller')
@@ -192,6 +194,8 @@ the element's reactive public API. Dispatch outcome events, never mutate the
192
194
  element's rendered DOM.
193
195
 
194
196
  ```typescript
197
+ import { context, type Context } from 'snice';
198
+
195
199
  interface DataHost<T> extends HTMLElement {
196
200
  loading: boolean;
197
201
  error: string;
@@ -202,17 +206,24 @@ interface DataHost<T> extends HTMLElement {
202
206
  @controller('data-controller')
203
207
  class DataController<T> implements IController<DataHost<T>> {
204
208
  element: DataHost<T> | null = null;
209
+ private ctx?: Context;
205
210
  private abort?: AbortController;
206
211
  private version = 0;
207
212
 
208
- async attach(element: DataHost<T>) {
213
+ attach(element: DataHost<T>) {
209
214
  this.element = element;
210
- await this.reload();
215
+ }
216
+
217
+ @context()
218
+ receiveContext(ctx: Context) {
219
+ this.ctx = ctx;
220
+ void this.reload();
211
221
  }
212
222
 
213
223
  async detach() {
214
224
  this.version++;
215
225
  this.abort?.abort();
226
+ this.ctx = undefined;
216
227
  }
217
228
 
218
229
  async reload() {
@@ -226,7 +237,9 @@ class DataController<T> implements IController<DataHost<T>> {
226
237
  host.empty = false;
227
238
 
228
239
  try {
229
- const response = await fetch('/api/data', { signal: abort.signal });
240
+ const ctx = this.ctx;
241
+ if (!ctx) throw new Error('DataController requires Router context');
242
+ const response = await ctx.fetch('/api/data', { signal: abort.signal });
230
243
  if (!response.ok) throw new Error(`Request failed (${response.status})`);
231
244
  const data = await response.json() as T[];
232
245
  if (version !== this.version || this.element !== host) return;
@@ -251,6 +264,12 @@ class DataController<T> implements IController<DataHost<T>> {
251
264
  }
252
265
  ```
253
266
 
267
+ `@context()` works on controllers as well as elements. It receives the same
268
+ long-lived `Context` instance, including `application`, navigation state, and
269
+ the Router's middleware-aware `fetch`. Managed decorators are activated after
270
+ `attach()`, so start context-dependent work in the `@context()` handler (or in
271
+ an event handled later), not in `attach()`.
272
+
254
273
  Required pieces: abort previous work, increment a version for stale-response
255
274
  guarding, reset loading/error/empty before fetch, check version + host identity
256
275
  before every commit, expose retry as `reload()`, and render all four states in
@@ -66,8 +66,8 @@ Resolver re-runs on reconnect — listeners track DOM moves.
66
66
  ## Communication
67
67
  - `@request(channel, options?)` - Async generator request pattern; `{ daemon: 'name' }` targets an app-context daemon
68
68
  - `@respond(channel, options?)` - Handle requests from `@request`; `{ daemon: 'name' }` installs on that daemon target
69
- - `@context(options?)` - Receive router navigation context updates
70
- - `provideContext(root, context)` / `getContext(participant)` - Explicit raw app-context provisioning and lookup
69
+ - `@context(options?)` - Receive router navigation context updates on pages, descendant elements, and attached controllers
70
+ - `provideContext(root, context, { fetch? })` / `getContext(participant)` / `getContextFetch(participant)` - Explicit app-context and transport provisioning/lookup
71
71
 
72
72
  ## Observers
73
73
  - `@observe(target, selector?, options?)` - Watch intersection, resize, media query, mutation
package/docs/ai/events.md CHANGED
@@ -272,7 +272,7 @@ updateStatus(status: string) {
272
272
 
273
273
  ```typescript
274
274
  interface DispatchOptions extends EventInit {
275
- dispatchOnUndefined?: boolean; // Skip dispatch when return is undefined (default: true)
275
+ dispatchOnUndefined?: boolean; // Undefined return still dispatches unless false (default: true)
276
276
  debounce?: number; // Debounce dispatch by ms
277
277
  throttle?: number; // Throttle dispatch by ms
278
278
  // Where to dispatch the event (see scope below)
@@ -379,7 +379,13 @@ methods themselves default to their own private target. See
379
379
 
380
380
  ## Custom events
381
381
 
382
- Dispatching manually:
382
+ Prefer `@dispatch` for a static custom event emitted from a Snice element or a
383
+ controller's host. It supplies the standard bubbling/composed behavior, uses
384
+ the method return value as `detail`, and keeps event timing options declarative.
385
+
386
+ Manual dispatch remains a valid low-level escape hatch when code needs the
387
+ Event object, a dynamic event name, `dispatchEvent()`'s cancellation boolean,
388
+ or a non-host target:
383
389
 
384
390
  ```typescript
385
391
  notify() {
@@ -30,7 +30,13 @@ const router = Router({ target: '#app', context: { auth: null }, fetcher });
30
30
  router.initialize();
31
31
  ```
32
32
 
33
- ## `ctx.fetch` in pages
33
+ Pages, descendant elements, and attached controllers receive the Router's
34
+ long-lived `Context` through `@context()`; its `ctx.fetch` is the bound,
35
+ middleware-aware function. `getContextFetch(this)` remains the lower-level
36
+ lookup for code using an explicit non-router
37
+ `provideContext(root, appContext, { fetch })` boundary.
38
+
39
+ ## `ctx.fetch` in pages, elements, and controllers
34
40
 
35
41
  Available on any `Context` obtained via `@context()`; middleware applies automatically:
36
42
 
@@ -45,6 +51,31 @@ class UserPage extends HTMLElement {
45
51
  }
46
52
  ```
47
53
 
54
+ Controllers use the same decorator. Managed decorators are activated after
55
+ `attach()`, so start context-dependent work from the handler:
56
+
57
+ ```typescript
58
+ @controller('user-data')
59
+ class UserDataController implements IController<HTMLElement> {
60
+ element: HTMLElement | null = null;
61
+ private ctx?: Context;
62
+
63
+ attach(element: HTMLElement) { this.element = element; }
64
+ detach() { this.ctx = undefined; }
65
+
66
+ @context()
67
+ receiveContext(ctx: Context) {
68
+ this.ctx = ctx;
69
+ void this.load();
70
+ }
71
+
72
+ async load() {
73
+ const response = await this.ctx!.fetch('/api/users');
74
+ // Commit through the host's public API.
75
+ }
76
+ }
77
+ ```
78
+
48
79
  ## Middleware types
49
80
 
50
81
  ### Request middleware — runs before the `fetch()` call
@@ -120,7 +120,10 @@ Options object always comes after the property names — works with multiple wat
120
120
 
121
121
  ## @context()
122
122
 
123
- Receives router context updates. Called whenever the router context changes (navigation, app context update, etc.):
123
+ Receives router context updates on pages, descendant elements, and attached
124
+ controllers. Called whenever the router context changes (navigation, app
125
+ context update, etc.). A controller's managed decorators activate after
126
+ `attach()`, so context-dependent startup belongs in this handler:
124
127
 
125
128
  ```typescript
126
129
  @property({ type: Array }) placards: Placard[] = [];
@@ -170,17 +170,17 @@ import { page } from '../router'; // NOT from 'snice'!
170
170
  @page({ tag: 'user-page', routes: ['/users/:id'], guards: [isAuthenticated] })
171
171
  class UserPage extends HTMLElement {
172
172
  @property() id = '';
173
- private appContext?: AppContext;
173
+ private ctx?: Context;
174
174
 
175
175
  @context()
176
176
  handleContext(ctx: Context) {
177
- this.appContext = ctx.application;
177
+ this.ctx = ctx;
178
178
  this.requestRender();
179
179
  }
180
180
 
181
181
  @ready()
182
182
  async load() {
183
- const user = await fetch(`/api/users/${this.id}`).then(r => r.json());
183
+ const user = await this.ctx!.fetch(`/api/users/${this.id}`).then(r => r.json());
184
184
  // ...
185
185
  }
186
186
  }
@@ -219,9 +219,14 @@ Mock controller for tests, real API controller in production — same element.
219
219
  // Controller responds to requests (receives payload, returns result directly)
220
220
  @controller('api')
221
221
  class API {
222
+ private ctx!: Context;
223
+
224
+ @context()
225
+ receiveContext(ctx: Context) { this.ctx = ctx; }
226
+
222
227
  @respond('fetch-user')
223
228
  async handleFetchUser(payload: { id: string }) {
224
- const user = await fetch(`/api/users/${payload.id}`).then(r => r.json());
229
+ const user = await this.ctx.fetch(`/api/users/${payload.id}`).then(r => r.json());
225
230
  return user; // Direct return, not callback
226
231
  }
227
232
  }