snice 4.12.0 → 4.14.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 (253) hide show
  1. package/dist/cdn/accordion/snice-accordion.js +1 -1
  2. package/dist/cdn/accordion/snice-accordion.min.js +1 -1
  3. package/dist/cdn/alert/snice-alert.js +1 -1
  4. package/dist/cdn/alert/snice-alert.min.js +1 -1
  5. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  6. package/dist/cdn/app-tiles/snice-app-tiles.min.js +1 -1
  7. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  8. package/dist/cdn/audio-recorder/snice-audio-recorder.min.js +1 -1
  9. package/dist/cdn/avatar/snice-avatar.js +1 -1
  10. package/dist/cdn/avatar/snice-avatar.min.js +1 -1
  11. package/dist/cdn/badge/snice-badge.js +1 -1
  12. package/dist/cdn/badge/snice-badge.min.js +1 -1
  13. package/dist/cdn/banner/snice-banner.js +1 -1
  14. package/dist/cdn/banner/snice-banner.min.js +1 -1
  15. package/dist/cdn/book/snice-book.js +1 -1
  16. package/dist/cdn/book/snice-book.min.js +1 -1
  17. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  18. package/dist/cdn/breadcrumbs/snice-breadcrumbs.min.js +1 -1
  19. package/dist/cdn/button/snice-button.js +1 -1
  20. package/dist/cdn/button/snice-button.min.js +1 -1
  21. package/dist/cdn/calendar/snice-calendar.js +1 -1
  22. package/dist/cdn/calendar/snice-calendar.min.js +1 -1
  23. package/dist/cdn/camera/snice-camera.js +1 -1
  24. package/dist/cdn/camera/snice-camera.min.js +1 -1
  25. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  26. package/dist/cdn/camera-annotate/snice-camera-annotate.min.js +1 -1
  27. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  28. package/dist/cdn/candlestick/snice-candlestick.min.js +1 -1
  29. package/dist/cdn/card/snice-card.js +1 -1
  30. package/dist/cdn/card/snice-card.min.js +1 -1
  31. package/dist/cdn/carousel/snice-carousel.js +1 -1
  32. package/dist/cdn/carousel/snice-carousel.min.js +1 -1
  33. package/dist/cdn/chart/snice-chart.js +1 -1
  34. package/dist/cdn/chart/snice-chart.min.js +1 -1
  35. package/dist/cdn/chat/snice-chat.js +1 -1
  36. package/dist/cdn/chat/snice-chat.min.js +1 -1
  37. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  38. package/dist/cdn/checkbox/snice-checkbox.min.js +1 -1
  39. package/dist/cdn/chip/snice-chip.js +1 -1
  40. package/dist/cdn/chip/snice-chip.min.js +1 -1
  41. package/dist/cdn/code-block/snice-code-block.js +2 -2
  42. package/dist/cdn/code-block/snice-code-block.js.map +1 -1
  43. package/dist/cdn/code-block/snice-code-block.min.js +2 -2
  44. package/dist/cdn/code-block/snice-code-block.min.js.map +1 -1
  45. package/dist/cdn/color-display/snice-color-display.js +1 -1
  46. package/dist/cdn/color-display/snice-color-display.min.js +1 -1
  47. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  48. package/dist/cdn/color-picker/snice-color-picker.min.js +1 -1
  49. package/dist/cdn/command-palette/snice-command-palette.js +1 -1
  50. package/dist/cdn/command-palette/snice-command-palette.min.js +1 -1
  51. package/dist/cdn/comments/snice-comments.js +1 -1
  52. package/dist/cdn/comments/snice-comments.min.js +1 -1
  53. package/dist/cdn/countdown/snice-countdown.js +1 -1
  54. package/dist/cdn/countdown/snice-countdown.min.js +1 -1
  55. package/dist/cdn/cropper/snice-cropper.js +1 -1
  56. package/dist/cdn/cropper/snice-cropper.min.js +1 -1
  57. package/dist/cdn/date-picker/snice-date-picker.js +1 -1
  58. package/dist/cdn/date-picker/snice-date-picker.min.js +1 -1
  59. package/dist/cdn/diff/snice-diff.js +1 -1
  60. package/dist/cdn/diff/snice-diff.min.js +1 -1
  61. package/dist/cdn/divider/snice-divider.js +1 -1
  62. package/dist/cdn/divider/snice-divider.min.js +1 -1
  63. package/dist/cdn/doc/snice-doc.js +1 -1
  64. package/dist/cdn/doc/snice-doc.min.js +1 -1
  65. package/dist/cdn/draw/snice-draw.js +1 -1
  66. package/dist/cdn/draw/snice-draw.min.js +1 -1
  67. package/dist/cdn/drawer/snice-drawer.js +1 -1
  68. package/dist/cdn/drawer/snice-drawer.min.js +1 -1
  69. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  70. package/dist/cdn/empty-state/snice-empty-state.min.js +1 -1
  71. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  72. package/dist/cdn/file-gallery/snice-file-gallery.min.js +1 -1
  73. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  74. package/dist/cdn/file-upload/snice-file-upload.min.js +1 -1
  75. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  76. package/dist/cdn/flip-card/snice-flip-card.min.js +1 -1
  77. package/dist/cdn/flow/snice-flow.js +1 -1
  78. package/dist/cdn/flow/snice-flow.min.js +1 -1
  79. package/dist/cdn/funnel/snice-funnel.js +1 -1
  80. package/dist/cdn/funnel/snice-funnel.min.js +1 -1
  81. package/dist/cdn/gantt/snice-gantt.js +1 -1
  82. package/dist/cdn/gantt/snice-gantt.min.js +1 -1
  83. package/dist/cdn/gauge/snice-gauge.js +1 -1
  84. package/dist/cdn/gauge/snice-gauge.min.js +1 -1
  85. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  86. package/dist/cdn/heatmap/snice-heatmap.min.js +1 -1
  87. package/dist/cdn/image/snice-image.js +1 -1
  88. package/dist/cdn/image/snice-image.min.js +1 -1
  89. package/dist/cdn/input/snice-input.js +1 -1
  90. package/dist/cdn/input/snice-input.min.js +1 -1
  91. package/dist/cdn/kanban/snice-kanban.js +1 -1
  92. package/dist/cdn/kanban/snice-kanban.min.js +1 -1
  93. package/dist/cdn/kpi/snice-kpi.js +1 -1
  94. package/dist/cdn/kpi/snice-kpi.min.js +1 -1
  95. package/dist/cdn/layout/snice-layout.js +1 -1
  96. package/dist/cdn/layout/snice-layout.min.js +1 -1
  97. package/dist/cdn/link/snice-link.js +1 -1
  98. package/dist/cdn/link/snice-link.min.js +1 -1
  99. package/dist/cdn/link-preview/snice-link-preview.js +1 -1
  100. package/dist/cdn/link-preview/snice-link-preview.min.js +1 -1
  101. package/dist/cdn/list/snice-list.js +1 -1
  102. package/dist/cdn/list/snice-list.min.js +1 -1
  103. package/dist/cdn/location/snice-location.js +1 -1
  104. package/dist/cdn/location/snice-location.min.js +1 -1
  105. package/dist/cdn/login/snice-login.js +1 -1
  106. package/dist/cdn/login/snice-login.min.js +1 -1
  107. package/dist/cdn/map/snice-map.js +1 -1
  108. package/dist/cdn/map/snice-map.min.js +1 -1
  109. package/dist/cdn/markdown/snice-markdown.js +1 -1
  110. package/dist/cdn/markdown/snice-markdown.min.js +1 -1
  111. package/dist/cdn/masonry/snice-masonry.js +1 -1
  112. package/dist/cdn/masonry/snice-masonry.min.js +1 -1
  113. package/dist/cdn/menu/snice-menu.js +1 -1
  114. package/dist/cdn/menu/snice-menu.min.js +1 -1
  115. package/dist/cdn/modal/snice-modal.js +1 -1
  116. package/dist/cdn/modal/snice-modal.min.js +1 -1
  117. package/dist/cdn/music-player/snice-music-player.js +1 -1
  118. package/dist/cdn/music-player/snice-music-player.min.js +1 -1
  119. package/dist/cdn/nav/snice-nav.js +1 -1
  120. package/dist/cdn/nav/snice-nav.min.js +1 -1
  121. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  122. package/dist/cdn/network-graph/snice-network-graph.min.js +1 -1
  123. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  124. package/dist/cdn/notification-center/snice-notification-center.min.js +1 -1
  125. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  126. package/dist/cdn/org-chart/snice-org-chart.min.js +1 -1
  127. package/dist/cdn/pagination/snice-pagination.js +1 -1
  128. package/dist/cdn/pagination/snice-pagination.min.js +1 -1
  129. package/dist/cdn/paint/snice-paint.js +1 -1
  130. package/dist/cdn/paint/snice-paint.min.js +1 -1
  131. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  132. package/dist/cdn/pdf-viewer/snice-pdf-viewer.min.js +1 -1
  133. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  134. package/dist/cdn/podcast-player/snice-podcast-player.min.js +1 -1
  135. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  136. package/dist/cdn/pricing-table/snice-pricing-table.min.js +1 -1
  137. package/dist/cdn/progress/snice-progress.js +1 -1
  138. package/dist/cdn/progress/snice-progress.min.js +1 -1
  139. package/dist/cdn/qr-code/README.md +2 -2
  140. package/dist/cdn/qr-code/snice-qr-code.js +149 -20
  141. package/dist/cdn/qr-code/snice-qr-code.js.map +1 -1
  142. package/dist/cdn/qr-code/snice-qr-code.min.js +3 -3
  143. package/dist/cdn/qr-code/snice-qr-code.min.js.map +1 -1
  144. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  145. package/dist/cdn/qr-reader/snice-qr-reader.min.js +1 -1
  146. package/dist/cdn/radio/snice-radio.js +1 -1
  147. package/dist/cdn/radio/snice-radio.min.js +1 -1
  148. package/dist/cdn/rating/snice-rating.js +1 -1
  149. package/dist/cdn/rating/snice-rating.min.js +1 -1
  150. package/dist/cdn/recipe/snice-recipe.js +1 -1
  151. package/dist/cdn/recipe/snice-recipe.min.js +1 -1
  152. package/dist/cdn/runtime/snice-runtime.esm.js +4 -4
  153. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  154. package/dist/cdn/runtime/snice-runtime.esm.min.js +3 -3
  155. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  156. package/dist/cdn/runtime/snice-runtime.js +4 -4
  157. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  158. package/dist/cdn/runtime/snice-runtime.min.js +3 -3
  159. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  160. package/dist/cdn/sankey/snice-sankey.js +1 -1
  161. package/dist/cdn/sankey/snice-sankey.min.js +1 -1
  162. package/dist/cdn/select/snice-select.js +1 -1
  163. package/dist/cdn/select/snice-select.min.js +1 -1
  164. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  165. package/dist/cdn/skeleton/snice-skeleton.min.js +1 -1
  166. package/dist/cdn/slider/snice-slider.js +2 -2
  167. package/dist/cdn/slider/snice-slider.js.map +1 -1
  168. package/dist/cdn/slider/snice-slider.min.js +5 -5
  169. package/dist/cdn/slider/snice-slider.min.js.map +1 -1
  170. package/dist/cdn/sortable/snice-sortable.js +1 -1
  171. package/dist/cdn/sortable/snice-sortable.min.js +1 -1
  172. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  173. package/dist/cdn/sparkline/snice-sparkline.min.js +1 -1
  174. package/dist/cdn/spinner/snice-spinner.js +1 -1
  175. package/dist/cdn/spinner/snice-spinner.min.js +1 -1
  176. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  177. package/dist/cdn/split-pane/snice-split-pane.min.js +1 -1
  178. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  179. package/dist/cdn/spotlight/snice-spotlight.min.js +1 -1
  180. package/dist/cdn/spreadsheet/snice-spreadsheet.js +1 -1
  181. package/dist/cdn/spreadsheet/snice-spreadsheet.min.js +1 -1
  182. package/dist/cdn/stepper/snice-stepper.js +1 -1
  183. package/dist/cdn/stepper/snice-stepper.min.js +1 -1
  184. package/dist/cdn/switch/snice-switch.js +1 -1
  185. package/dist/cdn/switch/snice-switch.min.js +1 -1
  186. package/dist/cdn/table/snice-table.js +1 -1
  187. package/dist/cdn/table/snice-table.min.js +1 -1
  188. package/dist/cdn/tabs/snice-tabs.js +1 -1
  189. package/dist/cdn/tabs/snice-tabs.min.js +1 -1
  190. package/dist/cdn/tag-input/snice-tag-input.js +1 -1
  191. package/dist/cdn/tag-input/snice-tag-input.min.js +1 -1
  192. package/dist/cdn/terminal/snice-terminal.js +1 -1
  193. package/dist/cdn/terminal/snice-terminal.min.js +1 -1
  194. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  195. package/dist/cdn/testimonial/snice-testimonial.min.js +1 -1
  196. package/dist/cdn/textarea/snice-textarea.js +1 -1
  197. package/dist/cdn/textarea/snice-textarea.min.js +1 -1
  198. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  199. package/dist/cdn/time-range-picker/snice-time-range-picker.min.js +1 -1
  200. package/dist/cdn/timeline/snice-timeline.js +1 -1
  201. package/dist/cdn/timeline/snice-timeline.min.js +1 -1
  202. package/dist/cdn/timer/snice-timer.js +1 -1
  203. package/dist/cdn/timer/snice-timer.min.js +1 -1
  204. package/dist/cdn/toast/snice-toast.js +1 -1
  205. package/dist/cdn/toast/snice-toast.min.js +1 -1
  206. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  207. package/dist/cdn/tooltip/snice-tooltip.min.js +1 -1
  208. package/dist/cdn/tree/snice-tree.js +1 -1
  209. package/dist/cdn/tree/snice-tree.min.js +1 -1
  210. package/dist/cdn/treemap/snice-treemap.js +1 -1
  211. package/dist/cdn/treemap/snice-treemap.min.js +1 -1
  212. package/dist/cdn/video-player/snice-video-player.js +1 -1
  213. package/dist/cdn/video-player/snice-video-player.min.js +1 -1
  214. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  215. package/dist/cdn/virtual-scroller/snice-virtual-scroller.min.js +1 -1
  216. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  217. package/dist/cdn/waterfall/snice-waterfall.min.js +1 -1
  218. package/dist/cdn/weather/snice-weather.js +1 -1
  219. package/dist/cdn/weather/snice-weather.min.js +1 -1
  220. package/dist/components/code-block/snice-code-block.js +1 -1
  221. package/dist/components/code-block/snice-code-block.js.map +1 -1
  222. package/dist/components/qr-code/qrcode.d.ts +1 -0
  223. package/dist/components/qr-code/qrcode.js +16 -8
  224. package/dist/components/qr-code/qrcode.js.map +1 -1
  225. package/dist/components/qr-code/snice-qr-code.d.ts +5 -2
  226. package/dist/components/qr-code/snice-qr-code.js +132 -11
  227. package/dist/components/qr-code/snice-qr-code.js.map +1 -1
  228. package/dist/components/qr-code/snice-qr-code.types.d.ts +3 -2
  229. package/dist/components/slider/snice-slider.js +1 -1
  230. package/dist/components/slider/snice-slider.js.map +1 -1
  231. package/dist/index.cjs +2 -2
  232. package/dist/index.esm.js +2 -2
  233. package/dist/index.iife.js +2 -2
  234. package/dist/symbols.cjs +1 -1
  235. package/dist/symbols.esm.js +1 -1
  236. package/dist/transitions.cjs +1 -1
  237. package/dist/transitions.esm.js +1 -1
  238. package/dist/types/request-options.d.ts +1 -1
  239. package/docs/ai/api.md +1 -1
  240. package/docs/ai/components/code-block.md +1 -1
  241. package/docs/ai/decorators.md +2 -2
  242. package/docs/ai/patterns.md +17 -6
  243. package/docs/code-block.md +1 -3
  244. package/docs/controllers.md +98 -391
  245. package/docs/elements.md +131 -117
  246. package/docs/events.md +74 -83
  247. package/docs/fetcher.md +64 -76
  248. package/docs/observe.md +13 -33
  249. package/docs/placards.md +6 -16
  250. package/docs/request-response.md +171 -693
  251. package/docs/routing.md +67 -136
  252. package/package.json +1 -1
  253. package/docs/migration-v2-to-v3.md +0 -569
package/docs/fetcher.md CHANGED
@@ -22,12 +22,12 @@ import { Router, ContextAwareFetcher } from 'snice';
22
22
  // Create a fetcher instance
23
23
  const fetcher = new ContextAwareFetcher();
24
24
 
25
- // Add request middleware (runs before fetch)
25
+ // Attach JWT to outgoing requests
26
26
  fetcher.use('request', function(request, next) {
27
27
  // `this` is bound to the Context instance
28
- const token = this.application.user?.token;
29
- if (token) {
30
- request.headers.set('Authorization', `Bearer ${token}`);
28
+ const jwt = this.application.principal?.token;
29
+ if (jwt) {
30
+ request.headers.set('Authorization', `Bearer ${jwt}`);
31
31
  }
32
32
  return next();
33
33
  });
@@ -43,7 +43,7 @@ fetcher.use('response', async function(response, next) {
43
43
  // Pass fetcher to Router
44
44
  const router = Router({
45
45
  target: '#app',
46
- context: { user: null },
46
+ context: { auth: null },
47
47
  fetcher
48
48
  });
49
49
 
@@ -101,26 +101,43 @@ type RequestMiddleware = (
101
101
  - Adding custom headers (CSRF tokens, API keys, etc.)
102
102
  - Request validation
103
103
 
104
- **Example - Authentication:**
104
+ **Example - JWT Bearer Token:**
105
+
106
+ `this` in middleware is the `Context` instance. Store auth state in `application` (your `AppContext`):
107
+
105
108
  ```typescript
109
+ // Attach JWT to every request
106
110
  fetcher.use('request', function(request, next) {
107
- const token = this.application.auth?.token;
108
- if (token) {
109
- request.headers.set('Authorization', `Bearer ${token}`);
111
+ const jwt = this.application.principal?.token;
112
+ if (jwt) {
113
+ request.headers.set('Authorization', `Bearer ${jwt}`);
110
114
  }
111
115
  return next();
112
116
  });
113
- ```
114
117
 
115
- **Example - Base URL:**
116
- ```typescript
117
- fetcher.use('request', function(request, next) {
118
- const url = new URL(request.url);
119
- if (!url.hostname) {
120
- // Relative URL, add base
121
- const baseUrl = this.application.config?.apiBaseUrl || 'https://api.example.com';
122
- const newRequest = new Request(`${baseUrl}${request.url}`, request);
123
- return next(); // Note: modifying request in place doesn't work, would need to reconstruct
118
+ // Handle expired tokens — refresh or redirect to login
119
+ fetcher.use('response', async function(response, next) {
120
+ if (response.status === 401) {
121
+ const refreshToken = this.application.principal?.refreshToken;
122
+ if (refreshToken) {
123
+ const res = await fetch('/api/auth/refresh', {
124
+ method: 'POST',
125
+ headers: { 'Content-Type': 'application/json' },
126
+ body: JSON.stringify({ refreshToken })
127
+ });
128
+ if (res.ok) {
129
+ const { token } = await res.json();
130
+ this.application.principal.token = token;
131
+ // Retry original request with new token
132
+ const retry = new Request(response.url, {
133
+ headers: { 'Authorization': `Bearer ${token}` }
134
+ });
135
+ return fetch(retry);
136
+ }
137
+ }
138
+ this.application.principal = null;
139
+ window.location.hash = '#/login';
140
+ throw new Error('Session expired');
124
141
  }
125
142
  return next();
126
143
  });
@@ -177,18 +194,19 @@ fetcher.use('response', async function(response, next) {
177
194
 
178
195
  **Example - Performance Metrics:**
179
196
  ```typescript
180
- const timings = new WeakMap();
197
+ const timings = new Map<string, number>();
181
198
 
182
199
  fetcher.use('request', function(request, next) {
183
- timings.set(request, Date.now());
200
+ timings.set(request.url, Date.now());
184
201
  return next();
185
202
  });
186
203
 
187
204
  fetcher.use('response', async function(response, next) {
188
- const request = timings.get(response); // Note: this won't work as response doesn't reference request
189
- // Better approach: use a Map with URL as key
190
- const duration = Date.now() - startTime;
191
- console.log(`Request took ${duration}ms`);
205
+ const start = timings.get(response.url);
206
+ if (start) {
207
+ console.log(`${response.url} took ${Date.now() - start}ms`);
208
+ timings.delete(response.url);
209
+ }
192
210
  return next();
193
211
  });
194
212
  ```
@@ -207,7 +225,7 @@ fetcher.use('response', async function(response, next) {
207
225
  if (response.status === 401) {
208
226
  // Unauthorized - clear user and redirect to login
209
227
  this.application.user = null;
210
- this.application.router?.navigate('/login');
228
+ window.location.hash = '#/login';
211
229
  throw new Error('Authentication required');
212
230
  }
213
231
 
@@ -269,22 +287,20 @@ Here's a complete example with authentication, error handling, and logging:
269
287
  import { Router, ContextAwareFetcher } from 'snice';
270
288
 
271
289
  interface AppContext {
272
- user?: {
273
- token: string;
274
- id: string;
275
- };
276
- config: {
277
- apiBaseUrl: string;
290
+ auth?: {
291
+ token: string; // JWT access token
292
+ refreshToken: string;
293
+ userId: string;
278
294
  };
279
295
  }
280
296
 
281
297
  const fetcher = new ContextAwareFetcher();
282
298
 
283
- // Add authentication token
299
+ // Attach JWT to every request
284
300
  fetcher.use('request', function(request, next) {
285
- const token = this.application.user?.token;
286
- if (token) {
287
- request.headers.set('Authorization', `Bearer ${token}`);
301
+ const jwt = this.application.principal?.token;
302
+ if (jwt) {
303
+ request.headers.set('Authorization', `Bearer ${jwt}`);
288
304
  }
289
305
  return next();
290
306
  });
@@ -296,13 +312,12 @@ fetcher.use('request', function(request, next) {
296
312
  return next();
297
313
  });
298
314
 
299
- // Handle errors globally
315
+ // Handle 401 — attempt token refresh, then redirect on failure
300
316
  fetcher.use('response', async function(response, next) {
301
317
  if (response.status === 401) {
302
- // Clear auth and redirect
303
- this.application.user = undefined;
304
- window.location.href = '/#/login';
305
- throw new Error('Authentication required');
318
+ this.application.principal = undefined;
319
+ window.location.hash = '#/login';
320
+ throw new Error('Session expired');
306
321
  }
307
322
 
308
323
  if (response.status >= 400) {
@@ -314,19 +329,9 @@ fetcher.use('response', async function(response, next) {
314
329
  return next();
315
330
  });
316
331
 
317
- // Log responses
318
- fetcher.use('response', async function(response, next) {
319
- console.log(`[${this.navigation.route}] Response ${response.status}`);
320
- return next();
321
- });
322
-
323
332
  const router = Router({
324
333
  target: '#app',
325
- context: {
326
- config: {
327
- apiBaseUrl: 'https://api.example.com'
328
- }
329
- },
334
+ context: {},
330
335
  fetcher
331
336
  });
332
337
 
@@ -343,28 +348,17 @@ The `Context` instance is created once per Router and persists for the entire ap
343
348
  - `ctx.fetch` is initialized once and reused
344
349
  - Middleware can safely reference `this.application` and `this.navigation` as they update in place
345
350
 
346
- ### Request Objects are Immutable
351
+ ### Modifying Request Headers
347
352
 
348
- The `Request` object passed to middleware is immutable. To modify it, you need to create a new Request:
353
+ The `Request` object's `headers` property is mutable you can call `request.headers.set()` directly in middleware:
349
354
 
350
355
  ```typescript
351
356
  fetcher.use('request', function(request, next) {
352
- // This won't work - headers are read-only on Request
353
- // request.headers.set('X-Custom', 'value');
354
-
355
- // Instead, create a new Request
356
- const newRequest = new Request(request, {
357
- headers: new Headers(request.headers)
358
- });
359
- newRequest.headers.set('X-Custom', 'value');
360
-
361
- // But this is complex - better to set headers before creating Request
357
+ request.headers.set('X-Custom', 'value');
362
358
  return next();
363
359
  });
364
360
  ```
365
361
 
366
- In practice, it's better to set headers by modifying the `headers` property if it exists, or reconstructing the request entirely.
367
-
368
362
  ### No Fetcher Means Native Fetch
369
363
 
370
364
  If no `fetcher` is provided to the Router, `ctx.fetch` defaults to the native `fetch` function bound to the Context instance:
@@ -424,15 +418,11 @@ interface Fetcher {
424
418
  }
425
419
  ```
426
420
 
427
- ## Best Practices
428
-
429
- 1. **Configure middleware at startup** - Don't add middleware inside pages or components, as this would add duplicate middleware on each navigation.
421
+ ## Notes
430
422
 
431
- 2. **Keep middleware focused** - Each middleware should do one thing well (auth, logging, error handling, etc.).
423
+ - **Configure middleware at startup** don't add middleware inside pages, as it would duplicate on each navigation.
432
424
 
433
- 3. **Use `this.application` for app state** - Access user, config, and other app-wide state via the Context.
434
-
435
- 4. **Clone responses if needed** - If you need to read the response body in middleware, clone it first as streams can only be read once:
425
+ - **Clone responses before reading** streams can only be read once:
436
426
  ```typescript
437
427
  fetcher.use('response', async function(response, next) {
438
428
  const clone = response.clone();
@@ -442,6 +432,4 @@ interface Fetcher {
442
432
  });
443
433
  ```
444
434
 
445
- 5. **Handle errors gracefully** - Always consider what should happen when requests fail.
446
-
447
- 6. **Call `next()`** - Every middleware must call and return `next()` to continue the chain.
435
+ - **Always call `next()`** every middleware must call and return `next()` to continue the chain.
package/docs/observe.md CHANGED
@@ -336,35 +336,24 @@ class ViewportController implements IController {
336
336
 
337
337
  ## Options
338
338
 
339
- ### Common Options
340
-
341
- All observers support these options:
339
+ All observer types share a single options interface. Pass only the fields relevant to the observer type you're using:
342
340
 
343
341
  ```typescript
344
342
  interface ObserveOptions {
345
- throttle?: number; // Throttle callbacks by milliseconds
346
- }
347
- ```
348
-
349
- ### Specific Options
350
-
351
- ```typescript
352
- // Intersection Observer
353
- interface IntersectionOptions extends ObserveOptions {
354
- threshold?: number | number[]; // 0-1 visibility threshold
343
+ // Intersection Observer
344
+ threshold?: number | number[]; // 0-1 visibility percentage
355
345
  rootMargin?: string; // Margin around root
356
346
  root?: Element | null; // Viewport element
357
- }
358
347
 
359
- // Resize Observer
360
- interface ResizeOptions extends ObserveOptions {
348
+ // Resize Observer
361
349
  box?: 'content-box' | 'border-box'; // Box model to observe
362
- }
363
350
 
364
- // Mutation Observer
365
- interface MutationOptions extends ObserveOptions {
366
- subtree?: boolean; // Observe descendants
367
- maxDepth?: number; // Limit subtree depth
351
+ // Mutation Observer
352
+ subtree?: boolean; // Observe descendants (use with caution)
353
+ maxDepth?: number; // Safety limit for subtree depth
354
+
355
+ // All observers
356
+ throttle?: number; // Throttle callbacks by milliseconds
368
357
  }
369
358
  ```
370
359
 
@@ -547,16 +536,11 @@ class Dashboard extends HTMLElement {
547
536
  }
548
537
  ```
549
538
 
550
- ### Form Auto-Save
539
+ ### Dynamic Form Fields
551
540
 
552
541
  ```typescript
553
- @element('auto-save-form')
554
- class AutoSaveForm extends HTMLElement {
555
- @observe('mutation:attributes:value', 'input, textarea', { throttle: 1000 })
556
- handleInputChange(mutations: MutationRecord[]) {
557
- this.saveFormData();
558
- }
559
-
542
+ @element('dynamic-form')
543
+ class DynamicForm extends HTMLElement {
560
544
  @observe('mutation:childList', '.dynamic-fields')
561
545
  handleFieldsAdded(mutations: MutationRecord[]) {
562
546
  mutations.forEach(mutation => {
@@ -577,10 +561,6 @@ class AutoSaveForm extends HTMLElement {
577
561
  `;
578
562
  }
579
563
 
580
- saveFormData() {
581
- // Save logic
582
- }
583
-
584
564
  initializeField(field: Element) {
585
565
  // Initialize logic
586
566
  }
package/docs/placards.md CHANGED
@@ -17,7 +17,8 @@ The Placard system allows pages to declare metadata that describes their purpose
17
17
  Define a placard for your page using the `placard` option in the `@page` decorator:
18
18
 
19
19
  ```typescript
20
- import { page, Placard, render, html } from 'snice';
20
+ import { Placard, render, html } from 'snice';
21
+ import { page } from './router'; // page comes from Router(), not from 'snice'
21
22
 
22
23
  const placard: Placard<AppContext> = {
23
24
  name: 'dashboard',
@@ -151,8 +152,9 @@ parent: 'users' // Child of the 'users' page
151
152
  ### Dynamic Visibility
152
153
 
153
154
  **`visibleOn`** (optional)
154
- - Guard functions that determine if the page should appear in navigation
155
- - Reuses the same guard system as route protection
155
+ - Guard functions for layouts to evaluate when building navigation menus
156
+ - The router passes all placards to layouts unfiltered — layouts must check `visibleOn` themselves
157
+ - Reuses the same guard type as route protection
156
158
 
157
159
  ```typescript
158
160
  visibleOn: [isAuthenticated, hasAdminRole]
@@ -223,7 +225,7 @@ const settingsPlacard: Placard<AppContext> = {
223
225
  name: 'user-settings',
224
226
  title: 'Settings',
225
227
  parent: 'user-profile'
226
- // Breadcrumbs will be auto-resolved: Users > Profile > Settings
228
+ // Layout can resolve breadcrumbs using parent chain: Users > Profile > Settings
227
229
  };
228
230
 
229
231
  // Explicit breadcrumbs
@@ -387,15 +389,3 @@ class BreadcrumbLayout extends HTMLElement implements Layout {
387
389
  }
388
390
  ```
389
391
 
390
- ## Best Practices
391
-
392
- 1. **Use consistent naming**: Use kebab-case for placard names
393
- 2. **Provide helpful icons**: Visual indicators improve navigation UX
394
- 3. **Set meaningful order**: Lower numbers appear first in navigation
395
- 4. **Use groups wisely**: Group related pages for better organization
396
- 5. **Define search terms**: Help users discover features through search
397
- 6. **Leverage guards**: Use visibleOn to show/hide navigation based on permissions
398
- 7. **Explicit when needed**: Use explicit breadcrumbs for complex hierarchies
399
- 8. **Keep titles short**: Navigation labels should be concise
400
- 9. **Provide tooltips**: Add helpful context for ambiguous page names
401
- 10. **Use parent relationships**: Build hierarchical navigation automatically