snice 7.7.0 → 7.8.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 (216) hide show
  1. package/adapters/react/SniceProvider.js +1 -1
  2. package/adapters/react/SniceRouter.js +1 -1
  3. package/adapters/react/calendar.d.ts +1 -0
  4. package/adapters/react/calendar.d.ts.map +1 -1
  5. package/adapters/react/calendar.js +1 -1
  6. package/adapters/react/calendar.js.map +1 -1
  7. package/adapters/react/calendar.tsx +2 -1
  8. package/adapters/react/matchRoute.js +1 -1
  9. package/adapters/react/useRequestHandler.js +1 -1
  10. package/bin/analyzer-contracts.js +8 -2
  11. package/custom-elements.json +5 -1
  12. package/dist/cdn/accordion/snice-accordion.js +1 -1
  13. package/dist/cdn/action-bar/snice-action-bar.js +1 -1
  14. package/dist/cdn/activity-feed/snice-activity-feed.js +1 -1
  15. package/dist/cdn/alert/snice-alert.js +1 -1
  16. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  17. package/dist/cdn/approval-flow/snice-approval-flow.js +1 -1
  18. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  19. package/dist/cdn/availability/snice-availability.js +1 -1
  20. package/dist/cdn/avatar/snice-avatar.js +1 -1
  21. package/dist/cdn/avatar-group/snice-avatar-group.js +1 -1
  22. package/dist/cdn/badge/snice-badge.js +1 -1
  23. package/dist/cdn/banner/snice-banner.js +1 -1
  24. package/dist/cdn/binpack/snice-binpack.js +1 -1
  25. package/dist/cdn/book/snice-book.js +1 -1
  26. package/dist/cdn/booking/snice-booking.js +1 -1
  27. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  28. package/dist/cdn/button/snice-button.js +1 -1
  29. package/dist/cdn/calendar/README.md +2 -2
  30. package/dist/cdn/calendar/snice-calendar.js +26 -2
  31. package/dist/cdn/calendar/snice-calendar.js.map +1 -1
  32. package/dist/cdn/calendar/snice-calendar.min.js +2 -2
  33. package/dist/cdn/calendar/snice-calendar.min.js.map +1 -1
  34. package/dist/cdn/camera/snice-camera.js +1 -1
  35. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  36. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  37. package/dist/cdn/card/snice-card.js +1 -1
  38. package/dist/cdn/carousel/snice-carousel.js +1 -1
  39. package/dist/cdn/cart/snice-cart.js +1 -1
  40. package/dist/cdn/chart/snice-chart.js +1 -1
  41. package/dist/cdn/chat/snice-chat.js +1 -1
  42. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  43. package/dist/cdn/chip/snice-chip.js +1 -1
  44. package/dist/cdn/code-block/snice-code-block.js +1 -1
  45. package/dist/cdn/color-display/snice-color-display.js +1 -1
  46. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  47. package/dist/cdn/command-palette/snice-command-palette.js +1 -1
  48. package/dist/cdn/comments/snice-comments.js +1 -1
  49. package/dist/cdn/countdown/snice-countdown.js +1 -1
  50. package/dist/cdn/cropper/snice-cropper.js +1 -1
  51. package/dist/cdn/data-card/snice-data-card.js +1 -1
  52. package/dist/cdn/date-picker/snice-date-picker.js +1 -1
  53. package/dist/cdn/date-range-picker/snice-date-range-picker.js +1 -1
  54. package/dist/cdn/date-time-picker/snice-date-time-picker.js +1 -1
  55. package/dist/cdn/diff/snice-diff.js +1 -1
  56. package/dist/cdn/divider/snice-divider.js +1 -1
  57. package/dist/cdn/doc/snice-doc.js +1 -1
  58. package/dist/cdn/draw/snice-draw.js +1 -1
  59. package/dist/cdn/drawer/snice-drawer.js +1 -1
  60. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  61. package/dist/cdn/estimate/snice-estimate.js +1 -1
  62. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  63. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  64. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  65. package/dist/cdn/flow/snice-flow.js +1 -1
  66. package/dist/cdn/form-layout/snice-form-layout.js +1 -1
  67. package/dist/cdn/funnel/snice-funnel.js +1 -1
  68. package/dist/cdn/gantt/snice-gantt.js +1 -1
  69. package/dist/cdn/gauge/snice-gauge.js +1 -1
  70. package/dist/cdn/grid/snice-grid.js +1 -1
  71. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  72. package/dist/cdn/image/snice-image.js +1 -1
  73. package/dist/cdn/input/snice-input.js +1 -1
  74. package/dist/cdn/invoice/snice-invoice.js +1 -1
  75. package/dist/cdn/kanban/snice-kanban.js +1 -1
  76. package/dist/cdn/key-value/snice-key-value.js +1 -1
  77. package/dist/cdn/kpi/snice-kpi.js +1 -1
  78. package/dist/cdn/layout/snice-layout.js +1 -1
  79. package/dist/cdn/leaderboard/snice-leaderboard.js +1 -1
  80. package/dist/cdn/link/snice-link.js +1 -1
  81. package/dist/cdn/link-preview/snice-link-preview.js +1 -1
  82. package/dist/cdn/list/snice-list.js +1 -1
  83. package/dist/cdn/location/snice-location.js +1 -1
  84. package/dist/cdn/login/snice-login.js +1 -1
  85. package/dist/cdn/map/snice-map.js +1 -1
  86. package/dist/cdn/markdown/snice-markdown.js +1 -1
  87. package/dist/cdn/masonry/snice-masonry.js +1 -1
  88. package/dist/cdn/menu/snice-menu.js +1 -1
  89. package/dist/cdn/message-strip/snice-message-strip.js +1 -1
  90. package/dist/cdn/modal/snice-modal.js +1 -1
  91. package/dist/cdn/music-player/snice-music-player.js +1 -1
  92. package/dist/cdn/nav/snice-nav.js +1 -1
  93. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  94. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  95. package/dist/cdn/order-tracker/snice-order-tracker.js +1 -1
  96. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  97. package/dist/cdn/pagination/snice-pagination.js +1 -1
  98. package/dist/cdn/paint/snice-paint.js +1 -1
  99. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  100. package/dist/cdn/permission-matrix/snice-permission-matrix.js +1 -1
  101. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  102. package/dist/cdn/popover/snice-popover.js +1 -1
  103. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  104. package/dist/cdn/product-card/snice-product-card.js +1 -1
  105. package/dist/cdn/progress/snice-progress.js +1 -1
  106. package/dist/cdn/progress-ring/snice-progress-ring.js +1 -1
  107. package/dist/cdn/qr-code/snice-qr-code.js +1 -1
  108. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  109. package/dist/cdn/radio/snice-radio.js +1 -1
  110. package/dist/cdn/range-slider/snice-range-slider.js +1 -1
  111. package/dist/cdn/rating/snice-rating.js +1 -1
  112. package/dist/cdn/receipt/snice-receipt.js +1 -1
  113. package/dist/cdn/recipe/snice-recipe.js +1 -1
  114. package/dist/cdn/runtime/README.md +2 -2
  115. package/dist/cdn/runtime/snice-runtime.esm.js +130 -31
  116. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  117. package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
  118. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  119. package/dist/cdn/runtime/snice-runtime.js +130 -31
  120. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  121. package/dist/cdn/runtime/snice-runtime.min.js +1 -1
  122. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  123. package/dist/cdn/sankey/snice-sankey.js +1 -1
  124. package/dist/cdn/segmented-control/snice-segmented-control.js +1 -1
  125. package/dist/cdn/select/snice-select.js +1 -1
  126. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  127. package/dist/cdn/slider/snice-slider.js +1 -1
  128. package/dist/cdn/sortable/snice-sortable.js +1 -1
  129. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  130. package/dist/cdn/spinner/snice-spinner.js +1 -1
  131. package/dist/cdn/split-button/snice-split-button.js +1 -1
  132. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  133. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  134. package/dist/cdn/stat-group/snice-stat-group.js +1 -1
  135. package/dist/cdn/step-input/snice-step-input.js +1 -1
  136. package/dist/cdn/stepper/snice-stepper.js +1 -1
  137. package/dist/cdn/switch/snice-switch.js +1 -1
  138. package/dist/cdn/table/README.md +2 -2
  139. package/dist/cdn/table/snice-table.esm.js +157 -33
  140. package/dist/cdn/table/snice-table.esm.js.map +1 -1
  141. package/dist/cdn/table/snice-table.esm.min.js +4 -4
  142. package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
  143. package/dist/cdn/table/snice-table.js +157 -33
  144. package/dist/cdn/table/snice-table.js.map +1 -1
  145. package/dist/cdn/table/snice-table.min.js +33 -33
  146. package/dist/cdn/table/snice-table.min.js.map +1 -1
  147. package/dist/cdn/tabs/snice-tabs.js +1 -1
  148. package/dist/cdn/tag/snice-tag.js +1 -1
  149. package/dist/cdn/tag-input/README.md +2 -2
  150. package/dist/cdn/tag-input/snice-tag-input.js +10 -16
  151. package/dist/cdn/tag-input/snice-tag-input.js.map +1 -1
  152. package/dist/cdn/tag-input/snice-tag-input.min.js +2 -2
  153. package/dist/cdn/tag-input/snice-tag-input.min.js.map +1 -1
  154. package/dist/cdn/terminal/snice-terminal.js +1 -1
  155. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  156. package/dist/cdn/textarea/snice-textarea.js +1 -1
  157. package/dist/cdn/time-picker/snice-time-picker.js +1 -1
  158. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  159. package/dist/cdn/timeline/snice-timeline.js +1 -1
  160. package/dist/cdn/timer/snice-timer.js +1 -1
  161. package/dist/cdn/toast/snice-toast.js +1 -1
  162. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  163. package/dist/cdn/tree/snice-tree.js +1 -1
  164. package/dist/cdn/treemap/snice-treemap.js +1 -1
  165. package/dist/cdn/user-card/snice-user-card.js +1 -1
  166. package/dist/cdn/video-player/snice-video-player.js +1 -1
  167. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  168. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  169. package/dist/cdn/weather/snice-weather.js +1 -1
  170. package/dist/cdn/work-order/snice-work-order.js +1 -1
  171. package/dist/components/calendar/snice-calendar.d.ts +2 -0
  172. package/dist/components/calendar/snice-calendar.js +25 -1
  173. package/dist/components/calendar/snice-calendar.js.map +1 -1
  174. package/dist/components/table/snice-row.d.ts +6 -0
  175. package/dist/components/table/snice-row.js +11 -3
  176. package/dist/components/table/snice-row.js.map +1 -1
  177. package/dist/components/table/snice-table.d.ts +31 -3
  178. package/dist/components/table/snice-table.js +89 -16
  179. package/dist/components/table/snice-table.js.map +1 -1
  180. package/dist/components/table/table-cell-presentation.d.ts +11 -0
  181. package/dist/components/table/table-cell-presentation.js +25 -8
  182. package/dist/components/table/table-cell-presentation.js.map +1 -1
  183. package/dist/components/table/table-export.d.ts +6 -0
  184. package/dist/components/table/table-export.js +11 -2
  185. package/dist/components/table/table-export.js.map +1 -1
  186. package/dist/components/table/table-master-detail.d.ts +11 -2
  187. package/dist/components/table/table-master-detail.js +20 -3
  188. package/dist/components/table/table-master-detail.js.map +1 -1
  189. package/dist/components/tag-input/snice-tag-input.d.ts +2 -2
  190. package/dist/components/tag-input/snice-tag-input.js +9 -15
  191. package/dist/components/tag-input/snice-tag-input.js.map +1 -1
  192. package/dist/index.cjs +127 -28
  193. package/dist/index.cjs.map +1 -1
  194. package/dist/index.esm.js +127 -28
  195. package/dist/index.esm.js.map +1 -1
  196. package/dist/index.iife.js +127 -28
  197. package/dist/index.iife.js.map +1 -1
  198. package/dist/react/SniceProvider.js +1 -1
  199. package/dist/react/SniceRouter.js +1 -1
  200. package/dist/react/index.js +1 -1
  201. package/dist/react/matchRoute.js +1 -1
  202. package/dist/react/useRequestHandler.js +1 -1
  203. package/dist/request-response.d.ts +24 -1
  204. package/dist/symbols.cjs +1 -1
  205. package/dist/symbols.esm.js +1 -1
  206. package/dist/testing-dom.cjs +1 -1
  207. package/dist/testing-dom.esm.js +1 -1
  208. package/dist/transitions.cjs +1 -1
  209. package/dist/transitions.esm.js +1 -1
  210. package/docs/ai/components/calendar.md +8 -0
  211. package/docs/ai/components/tag-input.md +4 -0
  212. package/docs/ai/controllers.md +22 -6
  213. package/docs/components/calendar.md +24 -0
  214. package/docs/components/tag-input.md +6 -1
  215. package/docs/controllers.md +57 -6
  216. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.6.0
2
+ * snice v7.7.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.
@@ -1560,17 +1560,33 @@ var Snice = (function (exports) {
1560
1560
  });
1561
1561
  };
1562
1562
  }
1563
- // Helper to setup response handlers for elements and controllers
1564
- function setupResponseHandlers(instance, element) {
1563
+ // Which handler descriptors an instance has already registered, so a second
1564
+ // setup pass only picks up what the first one deferred.
1565
+ const registeredHandlers = new WeakMap();
1566
+ /**
1567
+ * Helper to setup response handlers for elements and controllers.
1568
+ *
1569
+ * Safe to call more than once for the same instance: a handler already
1570
+ * registered is skipped rather than double-bound.
1571
+ */
1572
+ function setupResponseHandlers(instance, element, options) {
1565
1573
  const handlers = instance.constructor[CHANNEL_HANDLERS];
1566
1574
  if (!handlers)
1567
1575
  return;
1576
+ const claim = options?.claim;
1577
+ let registered = registeredHandlers.get(instance);
1578
+ if (!registered) {
1579
+ registered = new Set();
1580
+ registeredHandlers.set(instance, registered);
1581
+ }
1568
1582
  // Store cleanup functions
1569
1583
  // Initialize cleanup object if needed
1570
1584
  if (!instance[CLEANUP]) {
1571
1585
  instance[CLEANUP] = { events: [], channels: [] };
1572
1586
  }
1573
1587
  for (const handler of handlers) {
1588
+ if (registered.has(handler))
1589
+ continue;
1574
1590
  const boundMethod = handler.method.bind(instance);
1575
1591
  const eventName = `@request/${handler.channelName}`;
1576
1592
  // Create timing variables for debounce/throttle per handler
@@ -1654,7 +1670,10 @@ var Snice = (function (exports) {
1654
1670
  // Clear the discovery timeout immediately - we found a handler
1655
1671
  discovery.resolve();
1656
1672
  // Call the timed responder method and handle the result
1657
- Promise.resolve(timedMethod(payload))
1673
+ const answer = claim
1674
+ ? Promise.resolve(claim()).then(() => timedMethod(payload))
1675
+ : Promise.resolve(timedMethod(payload));
1676
+ answer
1658
1677
  .then(result => {
1659
1678
  data.resolve(result);
1660
1679
  })
@@ -1669,10 +1688,17 @@ var Snice = (function (exports) {
1669
1688
  responseTarget = requireDaemonTarget(instance, handler.options.daemon);
1670
1689
  }
1671
1690
  catch (error) {
1691
+ // The app context reaches a controller through its host element, so it
1692
+ // can be unreachable while the host is still disconnected. Leave the
1693
+ // handler for the later pass instead of dropping it permanently.
1694
+ if (options?.deferUnresolvableDaemons)
1695
+ continue;
1672
1696
  console.warn(`[snice/@respond] ${error.message} Responder "${handler.channelName}" skipped.`);
1697
+ registered.add(handler);
1673
1698
  continue;
1674
1699
  }
1675
1700
  }
1701
+ registered.add(handler);
1676
1702
  responseTarget.addEventListener(eventName, responseHandler);
1677
1703
  instance[CLEANUP].channels.push(() => {
1678
1704
  responseTarget.removeEventListener(eventName, responseHandler);
@@ -1681,6 +1707,7 @@ var Snice = (function (exports) {
1681
1707
  }
1682
1708
  // Helper to cleanup response handlers
1683
1709
  function cleanupResponseHandlers(instance) {
1710
+ registeredHandlers.delete(instance);
1684
1711
  if (instance[CLEANUP]?.channels) {
1685
1712
  for (const cleanup of instance[CLEANUP].channels) {
1686
1713
  cleanup();
@@ -1806,6 +1833,8 @@ var Snice = (function (exports) {
1806
1833
  * Use in elements or controllers to listen to DOM events or custom events
1807
1834
  */
1808
1835
  const ON_HANDLERS = getSymbol('on-handlers');
1836
+ // Per-decoration-site counter — see the registration identity note in `on()`.
1837
+ let registrationCount = 0;
1809
1838
  /**
1810
1839
  * @on decorator for listening to events
1811
1840
  *
@@ -1867,18 +1896,42 @@ var Snice = (function (exports) {
1867
1896
  if (!selector && opts.target) {
1868
1897
  selector = opts.target;
1869
1898
  }
1899
+ const eventNames = Array.isArray(eventName) ? eventName : [eventName];
1900
+ // Registration identity — derived from EVERY decorator argument, so no
1901
+ // option can be forgotten here and silently collapse two distinct stacked
1902
+ // @on decorators into one (that is how { debounce: 300 } stacked on a plain
1903
+ // @on for the same event used to lose one of its two registrations).
1904
+ // Options are serialized generically (sorted, so literal key order doesn't
1905
+ // matter); values that can't be compared by value — an EventTarget or a
1906
+ // resolver function passed as `scope` — fall back to this decoration site's
1907
+ // own id, which is stable across instances but unique per @on application.
1908
+ const siteId = `site:${++registrationCount}`;
1909
+ let optionSignature = '';
1910
+ let hasOpaqueOption = false;
1911
+ for (const key of Object.keys(opts).sort()) {
1912
+ const value = opts[key];
1913
+ if (value === undefined)
1914
+ continue;
1915
+ if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
1916
+ hasOpaqueOption = true;
1917
+ continue;
1918
+ }
1919
+ optionSignature += `${key}=${String(value)};`;
1920
+ }
1921
+ const argumentKey = `${eventNames.join(',')}::${selector ?? ''}::${optionSignature}${hasOpaqueOption ? `::${siteId}` : ''}`;
1870
1922
  return function (originalMethod, context) {
1871
1923
  const methodName = context.name;
1924
+ // Same decoration, same method name → same registration. A subclass that
1925
+ // re-declares an identically decorated method must NOT add a second
1926
+ // listener on top of the one its parent's initializer already registered.
1927
+ const registrationKey = `${methodName}::${argumentKey}`;
1872
1928
  context.addInitializer(function () {
1873
1929
  const constructor = this.constructor;
1874
- // Dedup by registration identity (method + events + selector), NOT by
1875
- // method reference alone stacked @on decorators share one method and
1876
- // must each register. Use hasOwnProperty so subclasses get their OWN
1877
- // Set instead of inheriting otherwise parent and child share state
1878
- // and child registrations pollute parent (and vice versa) via the
1879
- // prototype chain.
1880
- const eventNames = Array.isArray(eventName) ? eventName : [eventName];
1881
- const registrationKey = `${methodName}::${eventNames.join(',')}::${selector ?? ''}::daemon:${opts.daemon ?? ''}::light:${opts.light ?? ''}::shadow:${opts.shadow ?? ''}`;
1930
+ // Dedup by registration identity, NOT by method reference stacked @on
1931
+ // decorators share one method and must each register. Use hasOwnProperty
1932
+ // so subclasses get their OWN Set instead of inheriting — otherwise
1933
+ // parent and child share state and child registrations pollute parent
1934
+ // (and vice versa) via the prototype chain.
1882
1935
  if (!Object.prototype.hasOwnProperty.call(constructor, ON_METHODS)) {
1883
1936
  constructor[ON_METHODS] = new Set();
1884
1937
  }
@@ -2893,6 +2946,35 @@ var Snice = (function (exports) {
2893
2946
  // MutationObservers are guarded from treating it as a string attachment.
2894
2947
  restoreDirectControllerAttribute(element);
2895
2948
  }
2949
+ // @respond handlers are installed before the readiness gate below, exactly
2950
+ // as elements install theirs in connectedCallback. A child's mount-time
2951
+ // @request can be the very thing keeping the host from becoming ready, so
2952
+ // the responder has to be reachable while `ready` is still pending. Such a
2953
+ // request also releases the gate — it proves the host subtree is live and
2954
+ // that something is blocked on this attachment — and is answered once
2955
+ // attach() and the remaining setup have run.
2956
+ //
2957
+ // A `@respond({ daemon })` handler resolves its target through the host's app
2958
+ // context, which is unreachable while the host is still disconnected (attach
2959
+ // before appendChild). Those are deferred here and registered by the second
2960
+ // pass past the gate, where they behaved correctly before this ordering.
2961
+ let requestClaimed = false;
2962
+ let releaseReadyGate;
2963
+ let completeAttach;
2964
+ let failAttach;
2965
+ const attached = new Promise((resolve, reject) => {
2966
+ completeAttach = resolve;
2967
+ failAttach = reject;
2968
+ });
2969
+ attached.catch(() => { }); // No requester when nothing was claimed
2970
+ setupResponseHandlers(controllerInstance, element, {
2971
+ deferUnresolvableDaemons: true,
2972
+ claim: () => {
2973
+ requestClaimed = true;
2974
+ releaseReadyGate?.();
2975
+ return attached;
2976
+ },
2977
+ });
2896
2978
  // Normally a controller waits for its host's full initialization. The sole
2897
2979
  // exception is attachController(host, ...) from that host's own @ready
2898
2980
  // handler: awaiting ready there would wait on the current handler itself.
@@ -2905,6 +2987,11 @@ var Snice = (function (exports) {
2905
2987
  try {
2906
2988
  await Promise.race([
2907
2989
  element.ready,
2990
+ new Promise(resolve => {
2991
+ if (requestClaimed)
2992
+ return resolve();
2993
+ releaseReadyGate = resolve;
2994
+ }),
2908
2995
  new Promise((_, reject) => {
2909
2996
  const onAbort = () => {
2910
2997
  // Tagged so fire-and-forget call sites can tell this designed
@@ -2923,8 +3010,10 @@ var Snice = (function (exports) {
2923
3010
  }
2924
3011
  catch (error) {
2925
3012
  // Attach never got past `ready` (aborted or deadline). Roll back the
2926
- // stored references so a later attach does not short-circuit against an
2927
- // instance whose attach() never ran.
3013
+ // stored references and the early responders so a later attach does not
3014
+ // short-circuit against an instance whose attach() never ran.
3015
+ cleanupResponseHandlers(controllerInstance);
3016
+ failAttach(error);
2928
3017
  if (element[CONTROLLER_KEY] === controllerInstance) {
2929
3018
  if (typeof controller !== 'string') {
2930
3019
  writeControllerAttribute(element, null);
@@ -2946,20 +3035,30 @@ var Snice = (function (exports) {
2946
3035
  }
2947
3036
  // Past `ready` — mark the controller so detach knows attach was actually run.
2948
3037
  controllerInstance[CONTROLLER_ATTACHED] = true;
2949
- // Run attach in the controller's scope
2950
- await scope.runOperation(async () => {
2951
- await controllerInstance.attach(element);
2952
- });
2953
- // @context has the same managed lifecycle on controllers as @on,
2954
- // @observe and @respond. Catch up controllers whose async attach completed
2955
- // after the Router announced the current navigation.
2956
- await setupContextHandler(controllerInstance, 'microtask');
2957
- // Setup @observe observers for controller
2958
- setupObservers(controllerInstance, element);
2959
- // Setup @channel handlers for controller
2960
- setupResponseHandlers(controllerInstance, element);
2961
- // Setup @on event handlers for controller
2962
- setupEventHandlers(controllerInstance, element);
3038
+ try {
3039
+ // Run attach in the controller's scope
3040
+ await scope.runOperation(async () => {
3041
+ await controllerInstance.attach(element);
3042
+ });
3043
+ // @context has the same managed lifecycle on controllers as @on,
3044
+ // @observe and @respond. Catch up controllers whose async attach completed
3045
+ // after the Router announced the current navigation.
3046
+ await setupContextHandler(controllerInstance, 'microtask');
3047
+ // Setup @observe observers for controller
3048
+ setupObservers(controllerInstance, element);
3049
+ // Second @respond pass: registers only what the pre-gate pass deferred —
3050
+ // daemon-scoped responders whose app context was not resolvable yet.
3051
+ setupResponseHandlers(controllerInstance, element);
3052
+ // Setup @on event handlers for controller
3053
+ setupEventHandlers(controllerInstance, element);
3054
+ }
3055
+ catch (error) {
3056
+ // A claimed request must not wait on an attachment that never completed.
3057
+ failAttach(error);
3058
+ throw error;
3059
+ }
3060
+ // Answer whatever was claimed while the host was still becoming ready.
3061
+ completeAttach();
2963
3062
  element.dispatchEvent(new CustomEvent('controller-attached', {
2964
3063
  detail: { name: controllerName, controller: controllerInstance }
2965
3064
  }));