snice 7.0.2 → 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 +3 -3
- package/dist/cdn/recipe/snice-recipe.js.map +1 -1
- package/dist/cdn/recipe/snice-recipe.min.js +1 -1
- package/dist/cdn/recipe/snice-recipe.min.js.map +1 -1
- package/dist/cdn/runtime/README.md +2 -2
- package/dist/cdn/runtime/snice-runtime.esm.js +3725 -3336
- 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 +366 -56
- package/dist/cdn/table/snice-table.esm.js.map +1 -1
- package/dist/cdn/table/snice-table.esm.min.js +107 -46
- package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
- package/dist/cdn/table/snice-table.js +367 -59
- package/dist/cdn/table/snice-table.js.map +1 -1
- package/dist/cdn/table/snice-table.min.js +155 -94
- 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 +3 -2
- package/dist/cdn/terminal/snice-terminal.js.map +1 -1
- package/dist/cdn/terminal/snice-terminal.min.js +1 -0
- package/dist/cdn/terminal/snice-terminal.min.js.map +1 -1
- 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/README.md +1 -1
- package/dist/cdn/video-player/snice-video-player.js +3 -2
- package/dist/cdn/video-player/snice-video-player.js.map +1 -1
- package/dist/cdn/video-player/snice-video-player.min.js +1 -0
- package/dist/cdn/video-player/snice-video-player.min.js.map +1 -1
- 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/components/recipe/snice-recipe.js +1 -1
- package/dist/components/recipe/snice-recipe.js.map +1 -1
- package/dist/components/table/snice-table.d.ts +1 -0
- package/dist/components/table/snice-table.js +18 -2
- package/dist/components/table/snice-table.js.map +1 -1
- package/dist/components/table/table-tree-data.js +1 -0
- package/dist/components/table/table-tree-data.js.map +1 -1
- package/dist/components/terminal/snice-terminal.js +1 -0
- package/dist/components/terminal/snice-terminal.js.map +1 -1
- package/dist/components/video-player/snice-video-player.js +1 -0
- package/dist/components/video-player/snice-video-player.js.map +1 -1
- 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/components/avatar.md +8 -8
- package/docs/ai/components/breadcrumbs.md +2 -1
- package/docs/ai/components/button.md +2 -7
- package/docs/ai/components/calendar.md +1 -1
- package/docs/ai/components/camera.md +1 -1
- package/docs/ai/components/chart.md +3 -1
- package/docs/ai/components/code-block.md +2 -2
- package/docs/ai/components/date-picker.md +5 -19
- package/docs/ai/components/date-range-picker.md +4 -12
- package/docs/ai/components/draw.md +5 -5
- package/docs/ai/components/drawer.md +4 -4
- package/docs/ai/components/form-layout.md +2 -0
- package/docs/ai/components/icons.md +7 -16
- package/docs/ai/components/key-value.md +3 -22
- package/docs/ai/components/layout.md +8 -12
- package/docs/ai/components/leaderboard.md +2 -2
- package/docs/ai/components/link-preview.md +4 -3
- package/docs/ai/components/login.md +11 -0
- package/docs/ai/components/markdown.md +7 -0
- package/docs/ai/components/masonry.md +6 -0
- package/docs/ai/components/menu.md +1 -1
- package/docs/ai/components/modal.md +7 -0
- package/docs/ai/components/notification-center.md +5 -4
- package/docs/ai/components/radio.md +7 -0
- package/docs/ai/components/rating.md +3 -3
- package/docs/ai/components/select.md +4 -13
- package/docs/ai/components/spotlight.md +2 -0
- package/docs/ai/components/table.md +1 -4
- package/docs/ai/components/tag-input.md +6 -0
- package/docs/ai/components/terminal.md +6 -0
- package/docs/ai/components/tooltip.md +10 -16
- package/docs/ai/components/tree.md +2 -2
- package/docs/ai/components/video-player.md +5 -0
- package/docs/ai/components/waterfall.md +7 -0
- package/docs/ai/daemons.md +80 -0
- package/docs/ai/decorators.md +6 -4
- package/docs/ai/events.md +35 -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/components/breadcrumbs.md +2 -1
- package/docs/components/calendar.md +1 -1
- package/docs/components/camera.md +1 -1
- package/docs/components/chart.md +1 -1
- package/docs/components/code-block.md +13 -4
- package/docs/components/color-picker.md +6 -5
- package/docs/components/date-time-picker.md +14 -0
- package/docs/components/diff.md +2 -1
- package/docs/components/draw.md +5 -5
- package/docs/components/drawer.md +4 -4
- package/docs/components/input.md +1 -0
- package/docs/components/layout.md +20 -22
- package/docs/components/leaderboard.md +10 -3
- package/docs/components/link-preview.md +11 -9
- package/docs/components/login.md +4 -0
- package/docs/components/menu.md +1 -1
- package/docs/components/modal.md +1 -0
- package/docs/components/nav.md +1 -0
- package/docs/components/network-graph.md +1 -0
- package/docs/components/notification-center.md +2 -1
- package/docs/components/product-card.md +1 -0
- package/docs/components/qr-reader.md +1 -0
- package/docs/components/rating.md +4 -4
- package/docs/components/select.md +1 -0
- package/docs/components/spotlight.md +2 -0
- package/docs/components/tabs.md +1 -0
- package/docs/components/terminal.md +3 -0
- package/docs/components/textarea.md +1 -1
- package/docs/components/time-picker.md +14 -0
- package/docs/components/tooltip.md +2 -0
- package/docs/components/tree.md +2 -2
- package/docs/daemons.md +200 -0
- package/docs/events.md +92 -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
|
@@ -7,6 +7,7 @@ Event handling in Snice provides two powerful approaches: **template event synta
|
|
|
7
7
|
- [Template Event Syntax (Preferred for Elements)](#template-event-syntax-preferred-for-elements)
|
|
8
8
|
- [@on Decorator](#on-decorator)
|
|
9
9
|
- [@dispatch Decorator](#dispatch-decorator)
|
|
10
|
+
- [Event Bus](#event-bus)
|
|
10
11
|
- [Custom Events](#custom-events)
|
|
11
12
|
- [Event Delegation](#event-delegation)
|
|
12
13
|
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
@@ -413,6 +414,9 @@ interface OnOptions {
|
|
|
413
414
|
|
|
414
415
|
// Where to attach the listener (see scope section below)
|
|
415
416
|
scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
|
|
417
|
+
|
|
418
|
+
// Named daemon from the nearest provided app context
|
|
419
|
+
daemon?: string;
|
|
416
420
|
}
|
|
417
421
|
```
|
|
418
422
|
|
|
@@ -460,6 +464,9 @@ re-resolves and re-attaches, so resolver-based scopes track DOM moves correctly.
|
|
|
460
464
|
`scope` is compatible with the delegation selector — the listener attaches on the
|
|
461
465
|
scoped target and still matches the selector when an event fires within it.
|
|
462
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
|
+
|
|
463
470
|
#### Throttling
|
|
464
471
|
|
|
465
472
|
```typescript
|
|
@@ -608,13 +615,15 @@ interface DispatchOptions extends EventInit {
|
|
|
608
615
|
throttle?: number; // Throttle dispatch by ms
|
|
609
616
|
// Where to dispatch the event (see scope section below)
|
|
610
617
|
scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
|
|
618
|
+
// Named daemon from the nearest provided app context
|
|
619
|
+
daemon?: string;
|
|
611
620
|
}
|
|
612
621
|
```
|
|
613
622
|
|
|
614
623
|
#### scope — controlling the dispatch target
|
|
615
624
|
|
|
616
|
-
By default, `@dispatch`
|
|
617
|
-
|
|
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
|
|
618
627
|
event behaves as if it originated there. Use this with `@on({ scope })` to express
|
|
619
628
|
cross-cutting events without going through bubbling.
|
|
620
629
|
|
|
@@ -650,6 +659,10 @@ the event is **not dispatched** and a `console.warn` is emitted. The method's re
|
|
|
650
659
|
value still flows through `dispatchOnUndefined` / `debounce` / `throttle` semantics
|
|
651
660
|
before the scope check.
|
|
652
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
|
+
|
|
653
666
|
### Debounce/Throttle
|
|
654
667
|
|
|
655
668
|
```typescript
|
|
@@ -715,6 +728,83 @@ class ColorPicker extends HTMLElement {
|
|
|
715
728
|
}
|
|
716
729
|
```
|
|
717
730
|
|
|
731
|
+
## Event Bus
|
|
732
|
+
|
|
733
|
+
Snice has no bus object and no singleton. A bus is `@dispatch` publishing upward and
|
|
734
|
+
`@on` subscribing at a chosen ancestor -- the scope is a DOM node, so it is created and
|
|
735
|
+
torn down with the DOM.
|
|
736
|
+
|
|
737
|
+
There are two ways to publish, and the choice decides who can hear it.
|
|
738
|
+
|
|
739
|
+
**Bubble up from the host.** `@dispatch` is `bubbles: true, composed: true` by default, so
|
|
740
|
+
the event crosses shadow boundaries and passes every ancestor on its way to `document`.
|
|
741
|
+
Use this when the event is about *this* element and ancestors may care.
|
|
742
|
+
|
|
743
|
+
```typescript
|
|
744
|
+
@element('product-tile')
|
|
745
|
+
class ProductTile extends HTMLElement {
|
|
746
|
+
@dispatch('bus:cart-added')
|
|
747
|
+
addToCart(sku: string) {
|
|
748
|
+
return { sku, qty: 1 };
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
**Dispatch directly on a scope.** `@dispatch` takes the same `scope` option as `@on`, which
|
|
754
|
+
fires the event *on* that target instead of bubbling from the host. Use this when the
|
|
755
|
+
subscriber is not an ancestor -- a sibling subtree, or a host that may be detached.
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
@dispatch('bus:cart-added', { scope: 'global' }) // dispatched on document
|
|
759
|
+
add(id: string) { return { id }; }
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
Subscribe at the scope you want to share.
|
|
763
|
+
|
|
764
|
+
```typescript
|
|
765
|
+
// App-wide: listens on document
|
|
766
|
+
@on('bus:cart-added', { scope: 'global' })
|
|
767
|
+
onCartAdded(e: CustomEvent) { /* ... */ }
|
|
768
|
+
|
|
769
|
+
// Feature-scoped: listens on the nearest <cart-shell> ancestor, so a second
|
|
770
|
+
// <cart-shell> elsewhere on the page keeps its own traffic
|
|
771
|
+
@on('bus:cart-added', { scope: 'cart-shell' })
|
|
772
|
+
onCartAdded(e: CustomEvent) { /* ... */ }
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
Choosing a scope:
|
|
776
|
+
|
|
777
|
+
| Reach | `scope` | Use when |
|
|
778
|
+
|---|---|---|
|
|
779
|
+
| Whole document | `'global'` | Genuinely app-wide: auth expiry, theme change, save shortcut |
|
|
780
|
+
| A feature subtree | selector string | The event belongs to one shell and must not leak to a sibling instance |
|
|
781
|
+
| A specific node | `EventTarget` / resolver | You already hold the node, or the target moves and must re-resolve |
|
|
782
|
+
|
|
783
|
+
Prefer the narrowest scope that works. `'global'` means every instance on the page hears
|
|
784
|
+
every message, which is what makes singleton buses hard to reason about.
|
|
785
|
+
|
|
786
|
+
Match the two halves. A bubbling publish only reaches subscribers that sit on the host's
|
|
787
|
+
ancestor chain; if the subscriber lives in a sibling subtree, scope the dispatch too so
|
|
788
|
+
both meet on the same node. See
|
|
789
|
+
[scope on @dispatch](#scope-controlling-the-dispatch-target).
|
|
790
|
+
|
|
791
|
+
Name events so the routing is visible at the call site -- the `bus:` prefix above is a
|
|
792
|
+
convention, not a framework feature. Any string works.
|
|
793
|
+
|
|
794
|
+
Teardown is automatic: the listener is removed on disconnect from whichever target it
|
|
795
|
+
resolved to, and re-resolved on reconnect, so subscribers follow the host when it moves.
|
|
796
|
+
If a selector scope matches no ancestor the listener is skipped with a `console.warn`
|
|
797
|
+
rather than silently binding to the wrong node. See
|
|
798
|
+
[scope](#scope-controlling-the-listener-target) for the full resolution table.
|
|
799
|
+
|
|
800
|
+
For a request that needs an answer rather than a broadcast, use
|
|
801
|
+
[@request / @respond](./request-response.md) instead.
|
|
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
|
+
|
|
718
808
|
## Custom Events
|
|
719
809
|
|
|
720
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",
|