snice 7.0.3 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/snice/SKILL.md +4 -0
- package/.agents/skills/snice/references/core-kitchen-sink.ts +39 -1
- package/README.md +59 -14
- package/adapters/react/SniceProvider.js +2 -2
- package/adapters/react/SniceRouter.js +2 -2
- package/adapters/react/matchRoute.js +2 -2
- package/adapters/react/useRequestHandler.js +2 -2
- package/bin/analyzer-contracts.js +6 -1
- package/bin/postinstall.js +59 -0
- package/bin/project-analyzer.js +466 -5
- package/bin/snice.js +3 -0
- package/dist/app-context.d.ts +11 -0
- package/dist/cdn/accordion/snice-accordion.js +2 -2
- package/dist/cdn/action-bar/snice-action-bar.js +2 -2
- package/dist/cdn/activity-feed/snice-activity-feed.js +2 -2
- package/dist/cdn/alert/snice-alert.js +2 -2
- package/dist/cdn/app-tiles/snice-app-tiles.js +2 -2
- package/dist/cdn/approval-flow/snice-approval-flow.js +2 -2
- package/dist/cdn/audio-recorder/snice-audio-recorder.js +2 -2
- package/dist/cdn/availability/snice-availability.js +2 -2
- package/dist/cdn/avatar/snice-avatar.js +2 -2
- package/dist/cdn/avatar-group/snice-avatar-group.js +2 -2
- package/dist/cdn/badge/snice-badge.js +2 -2
- package/dist/cdn/banner/snice-banner.js +2 -2
- package/dist/cdn/binpack/snice-binpack.js +2 -2
- package/dist/cdn/book/snice-book.js +2 -2
- package/dist/cdn/booking/snice-booking.js +2 -2
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +2 -2
- package/dist/cdn/button/snice-button.js +2 -2
- package/dist/cdn/calendar/snice-calendar.js +2 -2
- package/dist/cdn/camera/snice-camera.js +2 -2
- package/dist/cdn/camera-annotate/snice-camera-annotate.js +2 -2
- package/dist/cdn/candlestick/snice-candlestick.js +2 -2
- package/dist/cdn/card/snice-card.js +2 -2
- package/dist/cdn/carousel/snice-carousel.js +2 -2
- package/dist/cdn/cart/snice-cart.js +2 -2
- package/dist/cdn/chart/snice-chart.js +2 -2
- package/dist/cdn/chat/snice-chat.js +2 -2
- package/dist/cdn/checkbox/snice-checkbox.js +2 -2
- package/dist/cdn/chip/snice-chip.js +2 -2
- package/dist/cdn/code-block/snice-code-block.js +2 -2
- package/dist/cdn/color-display/snice-color-display.js +2 -2
- package/dist/cdn/color-picker/snice-color-picker.js +2 -2
- package/dist/cdn/command-palette/snice-command-palette.js +2 -2
- package/dist/cdn/comments/snice-comments.js +2 -2
- package/dist/cdn/countdown/snice-countdown.js +2 -2
- package/dist/cdn/cropper/snice-cropper.js +2 -2
- package/dist/cdn/data-card/snice-data-card.js +2 -2
- package/dist/cdn/date-picker/snice-date-picker.js +2 -2
- package/dist/cdn/date-range-picker/snice-date-range-picker.js +2 -2
- package/dist/cdn/date-time-picker/snice-date-time-picker.js +2 -2
- package/dist/cdn/diff/snice-diff.js +2 -2
- package/dist/cdn/divider/snice-divider.js +2 -2
- package/dist/cdn/doc/snice-doc.js +2 -2
- package/dist/cdn/draw/snice-draw.js +2 -2
- package/dist/cdn/drawer/snice-drawer.js +2 -2
- package/dist/cdn/empty-state/snice-empty-state.js +2 -2
- package/dist/cdn/estimate/snice-estimate.js +2 -2
- package/dist/cdn/file-gallery/snice-file-gallery.js +2 -2
- package/dist/cdn/file-upload/snice-file-upload.js +2 -2
- package/dist/cdn/flip-card/snice-flip-card.js +2 -2
- package/dist/cdn/flow/snice-flow.js +2 -2
- package/dist/cdn/form-layout/snice-form-layout.js +2 -2
- package/dist/cdn/funnel/snice-funnel.js +2 -2
- package/dist/cdn/gantt/snice-gantt.js +2 -2
- package/dist/cdn/gauge/snice-gauge.js +2 -2
- package/dist/cdn/grid/snice-grid.js +2 -2
- package/dist/cdn/heatmap/snice-heatmap.js +2 -2
- package/dist/cdn/image/snice-image.js +2 -2
- package/dist/cdn/input/snice-input.js +2 -2
- package/dist/cdn/invoice/snice-invoice.js +2 -2
- package/dist/cdn/kanban/snice-kanban.js +2 -2
- package/dist/cdn/key-value/snice-key-value.js +2 -2
- package/dist/cdn/kpi/snice-kpi.js +2 -2
- package/dist/cdn/layout/README.md +17 -2
- package/dist/cdn/layout/snice-layout.js +1656 -11
- package/dist/cdn/layout/snice-layout.js.map +1 -1
- package/dist/cdn/layout/snice-layout.min.js +379 -2
- package/dist/cdn/layout/snice-layout.min.js.map +1 -1
- package/dist/cdn/leaderboard/snice-leaderboard.js +2 -2
- package/dist/cdn/link/snice-link.js +2 -2
- package/dist/cdn/link-preview/snice-link-preview.js +2 -2
- package/dist/cdn/list/snice-list.js +2 -2
- package/dist/cdn/location/snice-location.js +2 -2
- package/dist/cdn/login/snice-login.js +2 -2
- package/dist/cdn/map/snice-map.js +2 -2
- package/dist/cdn/markdown/snice-markdown.js +2 -2
- package/dist/cdn/masonry/snice-masonry.js +2 -2
- package/dist/cdn/menu/snice-menu.js +2 -2
- package/dist/cdn/message-strip/snice-message-strip.js +2 -2
- package/dist/cdn/modal/snice-modal.js +2 -2
- package/dist/cdn/music-player/snice-music-player.js +2 -2
- package/dist/cdn/nav/snice-nav.js +2 -2
- package/dist/cdn/network-graph/snice-network-graph.js +2 -2
- package/dist/cdn/notification-center/snice-notification-center.js +2 -2
- package/dist/cdn/order-tracker/snice-order-tracker.js +2 -2
- package/dist/cdn/org-chart/snice-org-chart.js +2 -2
- package/dist/cdn/pagination/snice-pagination.js +2 -2
- package/dist/cdn/paint/snice-paint.js +2 -2
- package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +2 -2
- package/dist/cdn/permission-matrix/snice-permission-matrix.js +2 -2
- package/dist/cdn/podcast-player/snice-podcast-player.js +2 -2
- package/dist/cdn/popover/snice-popover.js +2 -2
- package/dist/cdn/pricing-table/snice-pricing-table.js +2 -2
- package/dist/cdn/product-card/snice-product-card.js +2 -2
- package/dist/cdn/progress/snice-progress.js +2 -2
- package/dist/cdn/progress-ring/snice-progress-ring.js +2 -2
- package/dist/cdn/qr-code/snice-qr-code.js +2 -2
- package/dist/cdn/qr-reader/snice-qr-reader.js +2 -2
- package/dist/cdn/radio/snice-radio.js +2 -2
- package/dist/cdn/range-slider/snice-range-slider.js +2 -2
- package/dist/cdn/rating/snice-rating.js +2 -2
- package/dist/cdn/receipt/snice-receipt.js +2 -2
- package/dist/cdn/recipe/snice-recipe.js +2 -2
- package/dist/cdn/runtime/README.md +2 -2
- package/dist/cdn/runtime/snice-runtime.esm.js +3732 -3343
- package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.js +3727 -3335
- package/dist/cdn/runtime/snice-runtime.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
- package/dist/cdn/sankey/snice-sankey.js +2 -2
- package/dist/cdn/segmented-control/snice-segmented-control.js +2 -2
- package/dist/cdn/select/snice-select.js +2 -2
- package/dist/cdn/skeleton/snice-skeleton.js +2 -2
- package/dist/cdn/slider/snice-slider.js +2 -2
- package/dist/cdn/sortable/snice-sortable.js +2 -2
- package/dist/cdn/sparkline/snice-sparkline.js +2 -2
- package/dist/cdn/spinner/snice-spinner.js +2 -2
- package/dist/cdn/split-button/snice-split-button.js +2 -2
- package/dist/cdn/split-pane/snice-split-pane.js +2 -2
- package/dist/cdn/spotlight/snice-spotlight.js +2 -2
- package/dist/cdn/stat-group/snice-stat-group.js +2 -2
- package/dist/cdn/step-input/snice-step-input.js +2 -2
- package/dist/cdn/stepper/snice-stepper.js +2 -2
- package/dist/cdn/switch/snice-switch.js +2 -2
- package/dist/cdn/table/README.md +6 -2
- package/dist/cdn/table/snice-table.esm.js +347 -54
- package/dist/cdn/table/snice-table.esm.js.map +1 -1
- package/dist/cdn/table/snice-table.esm.min.js +105 -44
- package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
- package/dist/cdn/table/snice-table.js +348 -57
- package/dist/cdn/table/snice-table.js.map +1 -1
- package/dist/cdn/table/snice-table.min.js +153 -92
- package/dist/cdn/table/snice-table.min.js.map +1 -1
- package/dist/cdn/tabs/snice-tabs.js +2 -2
- package/dist/cdn/tag/snice-tag.js +2 -2
- package/dist/cdn/tag-input/snice-tag-input.js +2 -2
- package/dist/cdn/terminal/snice-terminal.js +2 -2
- package/dist/cdn/testimonial/snice-testimonial.js +2 -2
- package/dist/cdn/textarea/snice-textarea.js +2 -2
- package/dist/cdn/time-picker/snice-time-picker.js +2 -2
- package/dist/cdn/time-range-picker/snice-time-range-picker.js +2 -2
- package/dist/cdn/timeline/snice-timeline.js +2 -2
- package/dist/cdn/timer/snice-timer.js +2 -2
- package/dist/cdn/toast/snice-toast.js +2 -2
- package/dist/cdn/tooltip/snice-tooltip.js +2 -2
- package/dist/cdn/tree/snice-tree.js +2 -2
- package/dist/cdn/treemap/snice-treemap.js +2 -2
- package/dist/cdn/user-card/snice-user-card.js +2 -2
- package/dist/cdn/video-player/snice-video-player.js +2 -2
- package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +2 -2
- package/dist/cdn/waterfall/snice-waterfall.js +2 -2
- package/dist/cdn/weather/snice-weather.js +2 -2
- package/dist/cdn/work-order/snice-work-order.js +2 -2
- package/dist/context-provider.d.ts +16 -0
- package/dist/daemon-target.d.ts +10 -0
- package/dist/daemon.d.ts +10 -0
- package/dist/index.cjs +3721 -3329
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +3719 -3330
- package/dist/index.esm.js.map +1 -1
- package/dist/index.iife.js +3721 -3329
- package/dist/index.iife.js.map +1 -1
- package/dist/keyboard-filter.d.ts +25 -0
- package/dist/on.d.ts +1 -1
- package/dist/parts.d.ts +0 -17
- package/dist/react/SniceProvider.js +2 -2
- package/dist/react/SniceRouter.js +2 -2
- package/dist/react/index.js +2 -2
- package/dist/react/matchRoute.js +2 -2
- package/dist/react/useRequestHandler.js +2 -2
- package/dist/request-response.d.ts +9 -1
- package/dist/symbols.cjs +11 -2
- package/dist/symbols.cjs.map +1 -1
- package/dist/symbols.d.ts +3 -0
- package/dist/symbols.esm.js +9 -3
- package/dist/symbols.esm.js.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/transitions.cjs +2 -2
- package/dist/transitions.esm.js +2 -2
- package/dist/types/app-context.d.ts +9 -58
- package/dist/types/daemon.d.ts +8 -0
- package/dist/types/dispatch-options.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/on-options.d.ts +5 -0
- package/dist/types/request-options.d.ts +2 -0
- package/dist/types/respond-options.d.ts +2 -0
- package/docs/ai/DEVELOPMENT.md +15 -1
- package/docs/ai/README.md +5 -1
- package/docs/ai/api.md +13 -4
- package/docs/ai/architecture.md +50 -1
- package/docs/ai/cli.md +9 -0
- package/docs/ai/daemons.md +80 -0
- package/docs/ai/decorators.md +6 -4
- package/docs/ai/events.md +6 -0
- package/docs/ai/patterns.md +45 -1
- package/docs/ai/request-response.md +26 -9
- package/docs/ai/routing.md +16 -1
- package/docs/cli.md +7 -0
- package/docs/daemons.md +200 -0
- package/docs/events.md +19 -2
- package/docs/request-response.md +20 -13
- package/docs/routing.md +25 -1
- package/package.json +6 -4
package/docs/daemons.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<!-- AI: For the AI-optimized version of this doc, see docs/ai/daemons.md -->
|
|
2
|
+
# Daemons
|
|
3
|
+
|
|
4
|
+
Daemons are ordinary, explicitly constructed objects with state and an
|
|
5
|
+
application-owned lifecycle. The `@daemon` decorator gives each instance a
|
|
6
|
+
private communication target so elements, controllers, and daemons can use
|
|
7
|
+
Snice's two communication models:
|
|
8
|
+
|
|
9
|
+
- `@request` / `@respond` for one request and one asynchronous response.
|
|
10
|
+
- `@dispatch` / `@on` for ephemeral notifications with zero or more listeners.
|
|
11
|
+
|
|
12
|
+
`@daemon` does not construct, cache, globally register, start, or stop
|
|
13
|
+
anything.
|
|
14
|
+
|
|
15
|
+
## Define and provide a daemon
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { daemon, dispatch, on, request, respond, provideContext } from 'snice';
|
|
19
|
+
import type { Response } from 'snice';
|
|
20
|
+
|
|
21
|
+
@daemon
|
|
22
|
+
class SessionDaemon {
|
|
23
|
+
session: Session | null = null;
|
|
24
|
+
|
|
25
|
+
@respond('get-session')
|
|
26
|
+
getSession() {
|
|
27
|
+
return this.session;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@on('set-session')
|
|
31
|
+
setSession(event: CustomEvent<Session>) {
|
|
32
|
+
this.session = event.detail;
|
|
33
|
+
this.sessionChanged();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@dispatch('session-changed')
|
|
37
|
+
sessionChanged() {
|
|
38
|
+
return this.session;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const session = new SessionDaemon();
|
|
43
|
+
const appContext = {
|
|
44
|
+
daemons: { session }
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const release = provideContext(document.querySelector('#app')!, appContext);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The context key is the daemon's address. The class decorator takes no name,
|
|
51
|
+
avoiding two sources of truth.
|
|
52
|
+
|
|
53
|
+
Application contexts expose only this daemon surface to Snice:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
type DaemonMap = Readonly<Record<string, object>>;
|
|
57
|
+
|
|
58
|
+
interface AppContext {
|
|
59
|
+
readonly daemons?: DaemonMap;
|
|
60
|
+
[key: string]: unknown;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
They may still contain application-specific state such as `user`, `theme`, or
|
|
65
|
+
configuration. Extend `AppContext` with those fields to give application code
|
|
66
|
+
their concrete types.
|
|
67
|
+
|
|
68
|
+
## Router integration
|
|
69
|
+
|
|
70
|
+
`Router` provides its `context` beneath its target before rendering a page.
|
|
71
|
+
No separate `provideContext()` call is needed:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const session = new SessionDaemon();
|
|
75
|
+
|
|
76
|
+
const router = Router({
|
|
77
|
+
target: '#app',
|
|
78
|
+
type: 'hash',
|
|
79
|
+
context: {
|
|
80
|
+
user: null,
|
|
81
|
+
daemons: { session }
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`provideContext(root, context)` is the same public mechanism used internally
|
|
87
|
+
by Router. Use it for applications without Router and for isolated tests. It
|
|
88
|
+
returns an idempotent cleanup function.
|
|
89
|
+
|
|
90
|
+
`getContext(elementOrController)` returns the raw application context visible
|
|
91
|
+
to that participant. It is separate from the method-form `@context()`, which
|
|
92
|
+
receives Router navigation updates.
|
|
93
|
+
|
|
94
|
+
## Communicate from elements and controllers
|
|
95
|
+
|
|
96
|
+
Consumers use the context address and never import the daemon implementation:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
@element('session-view')
|
|
100
|
+
class SessionView extends HTMLElement {
|
|
101
|
+
@request<Session | null>('get-session', { daemon: 'session' })
|
|
102
|
+
async *loadSession(): Response<Session | null> {
|
|
103
|
+
return yield {};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@dispatch('set-session', { daemon: 'session' })
|
|
107
|
+
setSession(session: Session) {
|
|
108
|
+
return session;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@on('session-changed', { daemon: 'session' })
|
|
112
|
+
sessionChanged(event: CustomEvent<Session | null>) {
|
|
113
|
+
this.renderSession(event.detail);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Controllers resolve the context through their attached host element, so the
|
|
119
|
+
same syntax works in controller methods.
|
|
120
|
+
|
|
121
|
+
The reverse direction is also supported. A daemon's `@request` dispatches on
|
|
122
|
+
its own communication target, while an element or controller may install a
|
|
123
|
+
responder there:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
@daemon
|
|
127
|
+
class SessionDaemon {
|
|
128
|
+
@request<boolean>('confirm-logout')
|
|
129
|
+
async *confirmLogout(): Response<boolean> {
|
|
130
|
+
return yield {};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class SessionView extends HTMLElement {
|
|
135
|
+
@respond('confirm-logout', { daemon: 'session' })
|
|
136
|
+
confirmLogout() {
|
|
137
|
+
return window.confirm('Log out?');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
As with DOM-scoped request channels, only one responder should own a daemon
|
|
143
|
+
request channel.
|
|
144
|
+
|
|
145
|
+
## Resolution and lifecycle
|
|
146
|
+
|
|
147
|
+
Resolution has one path:
|
|
148
|
+
|
|
149
|
+
```text
|
|
150
|
+
element/controller
|
|
151
|
+
-> nearest explicitly provided application context
|
|
152
|
+
-> context.daemons[name]
|
|
153
|
+
-> that instance's private communication target
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
There is no global fallback, implicit construction, registry scan, or delayed
|
|
157
|
+
registration.
|
|
158
|
+
|
|
159
|
+
- Construct every daemon explicitly with `new`.
|
|
160
|
+
- Provide the context before connecting elements or attaching controllers.
|
|
161
|
+
`@on` and `@respond` install listeners at those lifecycle boundaries.
|
|
162
|
+
- `@request` and `@dispatch` resolve their daemon when invoked.
|
|
163
|
+
- Disconnecting an element or detaching a controller removes its daemon
|
|
164
|
+
listeners automatically.
|
|
165
|
+
- Releasing the context deactivates its daemon communication. A later provision
|
|
166
|
+
starts with a fresh event target.
|
|
167
|
+
- The same daemon class may have any number of independently provided
|
|
168
|
+
instances.
|
|
169
|
+
- The same name may resolve to different instances under different context
|
|
170
|
+
roots.
|
|
171
|
+
|
|
172
|
+
Missing contexts, missing daemon names, undecorated values, and inactive
|
|
173
|
+
daemon instances produce explicit errors or setup warnings. `daemon` and DOM
|
|
174
|
+
`scope` options are mutually exclusive. Selector delegation is not available
|
|
175
|
+
on a daemon target because it is not a DOM tree.
|
|
176
|
+
|
|
177
|
+
## Testing
|
|
178
|
+
|
|
179
|
+
Each test owns its instance, context root, and cleanup:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const root = document.createElement('div');
|
|
183
|
+
document.body.appendChild(root);
|
|
184
|
+
|
|
185
|
+
const session = new SessionDaemon();
|
|
186
|
+
const release = provideContext(root, {
|
|
187
|
+
daemons: { session }
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const view = document.createElement('session-view');
|
|
191
|
+
root.appendChild(view);
|
|
192
|
+
await view.ready;
|
|
193
|
+
|
|
194
|
+
expect(await view.loadSession()).toBeNull();
|
|
195
|
+
|
|
196
|
+
root.remove();
|
|
197
|
+
release();
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
No shared singleton state or framework reset API is involved.
|
package/docs/events.md
CHANGED
|
@@ -414,6 +414,9 @@ interface OnOptions {
|
|
|
414
414
|
|
|
415
415
|
// Where to attach the listener (see scope section below)
|
|
416
416
|
scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
|
|
417
|
+
|
|
418
|
+
// Named daemon from the nearest provided app context
|
|
419
|
+
daemon?: string;
|
|
417
420
|
}
|
|
418
421
|
```
|
|
419
422
|
|
|
@@ -461,6 +464,9 @@ re-resolves and re-attaches, so resolver-based scopes track DOM moves correctly.
|
|
|
461
464
|
`scope` is compatible with the delegation selector — the listener attaches on the
|
|
462
465
|
scoped target and still matches the selector when an event fires within it.
|
|
463
466
|
|
|
467
|
+
`daemon` is a separate, non-DOM target. It cannot be combined with `scope` and does
|
|
468
|
+
not support selector delegation. See [Daemons](./daemons.md).
|
|
469
|
+
|
|
464
470
|
#### Throttling
|
|
465
471
|
|
|
466
472
|
```typescript
|
|
@@ -609,13 +615,15 @@ interface DispatchOptions extends EventInit {
|
|
|
609
615
|
throttle?: number; // Throttle dispatch by ms
|
|
610
616
|
// Where to dispatch the event (see scope section below)
|
|
611
617
|
scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
|
|
618
|
+
// Named daemon from the nearest provided app context
|
|
619
|
+
daemon?: string;
|
|
612
620
|
}
|
|
613
621
|
```
|
|
614
622
|
|
|
615
623
|
#### scope — controlling the dispatch target
|
|
616
624
|
|
|
617
|
-
By default, `@dispatch`
|
|
618
|
-
|
|
625
|
+
By default, `@dispatch` originates from the element or a controller's host; on a
|
|
626
|
+
daemon it uses that instance's private target. The `scope` option redirects a DOM dispatch so the
|
|
619
627
|
event behaves as if it originated there. Use this with `@on({ scope })` to express
|
|
620
628
|
cross-cutting events without going through bubbling.
|
|
621
629
|
|
|
@@ -651,6 +659,10 @@ the event is **not dispatched** and a `console.warn` is emitted. The method's re
|
|
|
651
659
|
value still flows through `dispatchOnUndefined` / `debounce` / `throttle` semantics
|
|
652
660
|
before the scope check.
|
|
653
661
|
|
|
662
|
+
Use `{ daemon: 'session' }` to dispatch on an explicitly provided daemon's private
|
|
663
|
+
communication target. `daemon` and `scope` are mutually exclusive. See
|
|
664
|
+
[Daemons](./daemons.md).
|
|
665
|
+
|
|
654
666
|
### Debounce/Throttle
|
|
655
667
|
|
|
656
668
|
```typescript
|
|
@@ -788,6 +800,11 @@ rather than silently binding to the wrong node. See
|
|
|
788
800
|
For a request that needs an answer rather than a broadcast, use
|
|
789
801
|
[@request / @respond](./request-response.md) instead.
|
|
790
802
|
|
|
803
|
+
For app-owned state with an explicit lifecycle, provide an `@daemon` instance and
|
|
804
|
+
use `{ daemon: 'name' }` on both publishers and subscribers. This keeps consumers
|
|
805
|
+
decoupled from the implementation class without introducing a global singleton.
|
|
806
|
+
See [Daemons](./daemons.md).
|
|
807
|
+
|
|
791
808
|
## Custom Events
|
|
792
809
|
|
|
793
810
|
### Dispatching Manually
|
package/docs/request-response.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
<!-- AI: For the AI-optimized version of this doc, see docs/ai/
|
|
1
|
+
<!-- AI: For the AI-optimized version of this doc, see docs/ai/request-response.md -->
|
|
2
2
|
# Request/Response API Documentation
|
|
3
3
|
|
|
4
|
-
Request/Response provides request/response communication between elements
|
|
4
|
+
Request/Response provides request/response communication between elements,
|
|
5
|
+
controllers, and explicitly provided daemons using async generators.
|
|
5
6
|
|
|
6
7
|
## Table of Contents
|
|
7
8
|
- [Why Request/Response?](#why-request-response)
|
|
@@ -45,6 +46,7 @@ function request(requestName: string, options?: RequestOptions): MethodDecorator
|
|
|
45
46
|
function respond(requestName: string, options?: RespondOptions): MethodDecorator
|
|
46
47
|
|
|
47
48
|
interface RequestOptions extends EventInit {
|
|
49
|
+
daemon?: string; // Named daemon from nearest provided app context
|
|
48
50
|
timeout?: number; // Response timeout in ms (default: 120000ms = 2 minutes)
|
|
49
51
|
discoveryTimeout?: number; // Handler discovery timeout in ms (default: 50ms)
|
|
50
52
|
debounce?: number; // Debounce requests by specified ms
|
|
@@ -54,15 +56,21 @@ interface RequestOptions extends EventInit {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
interface RespondOptions {
|
|
59
|
+
daemon?: string; // Install responder on named daemon target
|
|
57
60
|
debounce?: number; // Debounce responses by specified ms
|
|
58
61
|
throttle?: number; // Throttle responses by specified ms
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
//
|
|
62
|
-
type
|
|
63
|
-
// Define this in your project — it satisfies both the generator and the caller
|
|
64
|
+
// Public return type for methods decorated with @request:
|
|
65
|
+
type Response<T = any> = T | any;
|
|
64
66
|
```
|
|
65
67
|
|
|
68
|
+
TypeScript cannot model a method decorator changing an async generator into a
|
|
69
|
+
promise-returning method. `Response<T>` is the pragmatic annotation for that
|
|
70
|
+
boundary: it keeps strict projects usable while documenting the response value
|
|
71
|
+
for readers and tooling. At runtime, calling the decorated method returns a
|
|
72
|
+
promise for `T`.
|
|
73
|
+
|
|
66
74
|
#### Response Debounce/Throttle
|
|
67
75
|
|
|
68
76
|
Response handlers can be debounced or throttled:
|
|
@@ -92,8 +100,7 @@ Elements use async generators to make requests. The element stays visual — it
|
|
|
92
100
|
|
|
93
101
|
```typescript
|
|
94
102
|
import { element, request, property, render, html } from 'snice';
|
|
95
|
-
|
|
96
|
-
type RequestResult<T> = AsyncGenerator<any, T, any> | Promise<T>;
|
|
103
|
+
import type { Response } from 'snice';
|
|
97
104
|
|
|
98
105
|
@element('product-card')
|
|
99
106
|
class ProductCard extends HTMLElement {
|
|
@@ -102,7 +109,7 @@ class ProductCard extends HTMLElement {
|
|
|
102
109
|
@property() price = '';
|
|
103
110
|
|
|
104
111
|
@request('fetch-product')
|
|
105
|
-
async *loadProduct():
|
|
112
|
+
async *loadProduct(): Response<void> {
|
|
106
113
|
const product = await (yield { id: this.productId });
|
|
107
114
|
this.name = product.name;
|
|
108
115
|
this.price = product.price;
|
|
@@ -164,7 +171,7 @@ The timeout system has **two separate timeouts**:
|
|
|
164
171
|
discoveryTimeout: 50, // 50ms to find handler
|
|
165
172
|
timeout: 30000 // 30s for actual processing
|
|
166
173
|
})
|
|
167
|
-
async *compute():
|
|
174
|
+
async *compute(): Response<any> {
|
|
168
175
|
return await (yield data);
|
|
169
176
|
}
|
|
170
177
|
```
|
|
@@ -174,13 +181,13 @@ async *compute(): RequestResult<any> {
|
|
|
174
181
|
```typescript
|
|
175
182
|
// Debounce: wait for typing to stop before searching
|
|
176
183
|
@request('search', { debounce: 300 })
|
|
177
|
-
async *search():
|
|
184
|
+
async *search(): Response<any[]> {
|
|
178
185
|
return await (yield { query: this.searchTerm });
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
// Throttle: limit analytics to 1 per second
|
|
182
189
|
@request('track', { throttle: 1000 })
|
|
183
|
-
async *trackEvent():
|
|
190
|
+
async *trackEvent(): Response<void> {
|
|
184
191
|
await (yield { event: 'scroll', position: window.scrollY });
|
|
185
192
|
}
|
|
186
193
|
```
|
|
@@ -196,7 +203,7 @@ class SafeLoader extends HTMLElement {
|
|
|
196
203
|
@property() data: any = null;
|
|
197
204
|
|
|
198
205
|
@request('load-data', { timeout: 5000 })
|
|
199
|
-
async *loadData():
|
|
206
|
+
async *loadData(): Response<void> {
|
|
200
207
|
try {
|
|
201
208
|
this.data = await (yield { id: this.dataId });
|
|
202
209
|
this.error = '';
|
|
@@ -290,7 +297,7 @@ class LiveTicker extends HTMLElement {
|
|
|
290
297
|
@property() symbol = 'BTC';
|
|
291
298
|
|
|
292
299
|
@request('subscribe-ticker')
|
|
293
|
-
async *subscribe():
|
|
300
|
+
async *subscribe(): Response<void> {
|
|
294
301
|
await (yield { symbol: this.symbol });
|
|
295
302
|
}
|
|
296
303
|
|
package/docs/routing.md
CHANGED
|
@@ -35,7 +35,7 @@ interface RouterOptions {
|
|
|
35
35
|
document?: Document; // Override document object (for testing)
|
|
36
36
|
transition?: Transition; // Global transition config
|
|
37
37
|
layout?: string; // Default layout for all pages
|
|
38
|
-
context?: any; //
|
|
38
|
+
context?: any; // App context (shared state and optional daemons)
|
|
39
39
|
fetcher?: Fetcher; // Optional fetch middleware (see docs/fetcher.md)
|
|
40
40
|
}
|
|
41
41
|
```
|
|
@@ -67,6 +67,27 @@ const { page, initialize } = Router({
|
|
|
67
67
|
});
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
Router provides this application context beneath its target before it connects a
|
|
71
|
+
page. That includes explicitly constructed daemon instances:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const session = new SessionDaemon();
|
|
75
|
+
|
|
76
|
+
Router({
|
|
77
|
+
target: '#app',
|
|
78
|
+
type: 'hash',
|
|
79
|
+
context: {
|
|
80
|
+
user: null,
|
|
81
|
+
daemons: { session }
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This uses the same `provideContext(root, context)` mechanism available to apps
|
|
87
|
+
without Router. Descendant elements and attached controllers address the instance
|
|
88
|
+
as `{ daemon: 'session' }`; they do not import `SessionDaemon`. See
|
|
89
|
+
[Daemons](./daemons.md).
|
|
90
|
+
|
|
70
91
|
## Page Components
|
|
71
92
|
|
|
72
93
|
### Basic Page
|
|
@@ -806,6 +827,9 @@ navigate(path: string): Promise<void>
|
|
|
806
827
|
|
|
807
828
|
Navigates to the specified path. Uses hash (#) or pushstate depending on router type.
|
|
808
829
|
|
|
830
|
+
Do not combine `@page` with `@element` on the same class. `@page` already
|
|
831
|
+
registers the custom element and applies Snice element behavior.
|
|
832
|
+
|
|
809
833
|
### initialize()
|
|
810
834
|
|
|
811
835
|
```typescript
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snice",
|
|
3
|
-
"version": "7.0
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "A decorator-driven web component library with
|
|
5
|
+
"description": "A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.esm.js",
|
|
8
8
|
"browser": "dist/index.iife.js",
|
|
@@ -112,11 +112,11 @@
|
|
|
112
112
|
"build:distribution": "rm -rf dist && npm run generate:metadata && rollup -c && npm run build:types && node tooling/build/embed-sourcemap-sources.mjs dist",
|
|
113
113
|
"build:types": "tsc --project packages/components/tsconfig.json --emitDeclarationOnly && cp packages/components/custom-elements.d.ts dist/components/custom-elements.d.ts",
|
|
114
114
|
"prebuild:cdn": "test -f dist/index.esm.js || npm run build:distribution",
|
|
115
|
-
"build:cdn": "rollup -c rollup.config.cdn.js &&
|
|
115
|
+
"build:cdn": "rollup -c rollup.config.cdn.js && node tooling/website/sync-component-assets.js && node tooling/website/update-cdn-sizes.js",
|
|
116
116
|
"prebuild:react": "test -f dist/index.d.ts || npm run build:distribution",
|
|
117
117
|
"build:react": "npm run generate:react-adapters && tsc --project adapters/react/tsconfig.json && node tooling/build/embed-sourcemap-sources.mjs adapters/react",
|
|
118
118
|
"build:testing": "rollup -c rollup.config.test.js && node tooling/build/embed-sourcemap-sources.mjs dist",
|
|
119
|
-
"build:website": "node tooling/website/build-website.js && node tooling/website/build-showcases.js",
|
|
119
|
+
"build:website": "node tooling/website/sync-component-assets.js && node tooling/website/build-website.js && node tooling/website/build-showcases.js",
|
|
120
120
|
"build:website:full": "npm run build:cdn && npm run build:website",
|
|
121
121
|
"build:storybook": "storybook build",
|
|
122
122
|
"generate:metadata": "node tooling/generators/generate-component-metadata.js && node tooling/generators/generate-analyzer-contracts.js",
|
|
@@ -142,6 +142,8 @@
|
|
|
142
142
|
"test:react": "npm run build:react && npm run test:react:prepared",
|
|
143
143
|
"test:react:prepared": "vitest run tests/react-adapters",
|
|
144
144
|
"test:website": "vitest run tests/website.test.ts",
|
|
145
|
+
"gauntlet": "node tooling/testing/run-gauntlet.js",
|
|
146
|
+
"postinstall": "node ./bin/postinstall.js",
|
|
145
147
|
"test:browser": "npm run test:browser:framework && npm run test:browser:website",
|
|
146
148
|
"test:browser:framework": "npm run build:distribution && npm run test:browser:framework:prepared",
|
|
147
149
|
"test:browser:framework:prepared": "node tooling/testing/run-live-tests.js --workers=4",
|