snice 7.0.3 → 7.1.1
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 +64 -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 +3825 -3337
- 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 +3827 -3336
- 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/controller.d.ts +5 -0
- package/dist/daemon-target.d.ts +10 -0
- package/dist/daemon.d.ts +10 -0
- package/dist/index.cjs +3821 -3330
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +3819 -3331
- package/dist/index.esm.js.map +1 -1
- package/dist/index.iife.js +3821 -3330
- 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 +2 -19
- 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 +19 -2
- package/dist/symbols.cjs.map +1 -1
- package/dist/symbols.d.ts +5 -0
- package/dist/symbols.esm.js +15 -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 +51 -2
- package/docs/ai/bindings.md +1 -1
- package/docs/ai/cli.md +9 -0
- package/docs/ai/controllers.md +1 -1
- package/docs/ai/daemons.md +80 -0
- package/docs/ai/decorators.md +7 -5
- package/docs/ai/events.md +6 -0
- package/docs/ai/patterns.md +49 -4
- package/docs/ai/request-response.md +26 -9
- package/docs/ai/routing.md +16 -1
- package/docs/bindings.md +1 -1
- package/docs/cli.md +7 -0
- package/docs/controllers.md +8 -2
- 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/ai/README.md
CHANGED
|
@@ -41,6 +41,7 @@ for depth; read the aggregates when you want the whole surface at once.
|
|
|
41
41
|
- `bindings.md` - Exact node, attribute, property, event, class, style, spread, controller, sentinel, and form semantics
|
|
42
42
|
- `events.md` - Template event syntax, `@on`, `@dispatch`, delegation, keyboard filters
|
|
43
43
|
- `controllers.md` - Attaching by class or registry name, lifecycle, cleanup
|
|
44
|
+
- `daemons.md` - Explicit app-context daemon instances and communication
|
|
44
45
|
- `routing.md` - Router setup, pages, params, navigation
|
|
45
46
|
- `guards-and-layouts.md` - Route guards, layouts, page transitions
|
|
46
47
|
|
|
@@ -104,6 +105,9 @@ Runtime first, then one bundle per component (any order after runtime):
|
|
|
104
105
|
- Elements receive data via properties, emit events for actions
|
|
105
106
|
- **Pages orchestrate** - handle routing, call APIs, coordinate elements
|
|
106
107
|
- **Controllers add behavior** - attach to elements for reusable non-visual logic; bind by class in templates (`controller=${MyController}`, preferred) or by name in raw HTML (`controller="name"`)
|
|
108
|
+
- **Daemons hold app-owned state/lifecycle** - construct explicitly, provide through `context.daemons`, communicate by address without importing implementations
|
|
109
|
+
- Conventional folders: `src/pages`, `src/components`, `src/controllers`, `src/daemons`; construct the Router in `src/router.ts` and initialize it from `src/main.ts`
|
|
110
|
+
- Several server/storage/timer operations in one page, or substantial logic copied across pages, belongs in a controller; `snice check` suggests the extraction conservatively
|
|
107
111
|
- Put API calls in pages/controllers/services, not in elements
|
|
108
112
|
|
|
109
113
|
**Properties:**
|
|
@@ -198,7 +202,7 @@ class MyCounter extends HTMLElement {
|
|
|
198
202
|
- Factory: `const hasRole = (role) => (ctx, params) => ctx.user?.role === role`
|
|
199
203
|
|
|
200
204
|
**Custom AppContext Types:**
|
|
201
|
-
- Snice
|
|
205
|
+
- Snice assigns meaning only to `AppContext.daemons?`; other keys are `unknown`
|
|
202
206
|
- For custom fields (like `user`), extend snice's AppContext:
|
|
203
207
|
```typescript
|
|
204
208
|
import type { AppContext as SniceAppContext } from 'snice';
|
package/docs/ai/api.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// attachController(el, MyController) | el.controller = MyController
|
|
16
16
|
// controller="name" string attr (only channel in raw HTML; still supported everywhere)
|
|
17
17
|
// Class binding: reference-deduped, removes controller attr, attr writes ignored while bound
|
|
18
|
+
@daemon // Explicitly constructed app-context state/lifecycle object; no name or global registration
|
|
18
19
|
@layout('name') // Define page wrapper
|
|
19
20
|
```
|
|
20
21
|
|
|
@@ -112,13 +113,13 @@ html`
|
|
|
112
113
|
```typescript
|
|
113
114
|
@on(event: string | string[], selector?: string, options?: OnOptions)
|
|
114
115
|
// Works in elements + controllers
|
|
115
|
-
// Options: { debounce?, throttle?, preventDefault?, stopPropagation?, once?, capture?, passive?, target?, scope? }
|
|
116
|
+
// Options: { debounce?, throttle?, preventDefault?, stopPropagation?, once?, capture?, passive?, target?, scope?, daemon? }
|
|
116
117
|
// target: CSS selector for shadow DOM event delegation
|
|
117
118
|
// scope: 'global' | selector | EventTarget | () => EventTarget | null — redirects listener attachment
|
|
118
119
|
// Keyboard: 'keydown:Enter', 'keydown.escape', 'keydown:ctrl+s', 'keydown:~Space'
|
|
119
120
|
// Supports both ':' and '.' notation
|
|
120
121
|
|
|
121
|
-
@dispatch(eventName: string, options?: { debounce?, throttle?, dispatchOnUndefined?, scope?, ...EventInit })
|
|
122
|
+
@dispatch(eventName: string, options?: { debounce?, throttle?, dispatchOnUndefined?, scope?, daemon?, ...EventInit })
|
|
122
123
|
// Fires CustomEvent after method, detail = return value
|
|
123
124
|
// Supports async methods (dispatches after promise resolves)
|
|
124
125
|
// dispatchOnUndefined: false (default) — skips dispatch if method returns undefined
|
|
@@ -142,7 +143,7 @@ html`
|
|
|
142
143
|
## Communication
|
|
143
144
|
|
|
144
145
|
```typescript
|
|
145
|
-
@request(channel: string, options?: { timeout?, discoveryTimeout?, debounce?, throttle?, bubbles?, cancelable? })
|
|
146
|
+
@request(channel: string, options?: { daemon?, timeout?, discoveryTimeout?, debounce?, throttle?, bubbles?, cancelable? })
|
|
146
147
|
// Request pattern using async generator syntax
|
|
147
148
|
// Method must be async generator that yields payload and receives response
|
|
148
149
|
// Returns Promise<T>
|
|
@@ -166,7 +167,7 @@ html`
|
|
|
166
167
|
// }
|
|
167
168
|
// // Usage: const user = await this.fetchUser('123');
|
|
168
169
|
|
|
169
|
-
@respond(channel: string, options?: { debounce?, throttle? })
|
|
170
|
+
@respond(channel: string, options?: { daemon?, debounce?, throttle? })
|
|
170
171
|
// Respond to requests from @request decorators
|
|
171
172
|
// Method receives payload and returns response
|
|
172
173
|
// Works in both elements and controllers
|
|
@@ -182,6 +183,14 @@ html`
|
|
|
182
183
|
// }
|
|
183
184
|
```
|
|
184
185
|
|
|
186
|
+
Daemon addressing:
|
|
187
|
+
```typescript
|
|
188
|
+
const release = provideContext(appRoot, { daemons: { session: new SessionDaemon() } });
|
|
189
|
+
const context = getContext(elementOrController);
|
|
190
|
+
// Consumers use { daemon: 'session' }; daemon methods default to their own private target.
|
|
191
|
+
// Provide before connect/attach. Call release() during app/test teardown.
|
|
192
|
+
```
|
|
193
|
+
|
|
185
194
|
## Observers
|
|
186
195
|
|
|
187
196
|
```typescript
|
package/docs/ai/architecture.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
- **Pages:** Orchestrate elements, handle URLs
|
|
8
8
|
- **Elements:** Generic visual building blocks — no fetch(), no API calls, no business logic
|
|
9
9
|
- **Controllers:** Specific behavior (data, APIs, business rules) — swappable per element
|
|
10
|
+
- **Daemons:** Explicitly constructed app-owned state/lifecycle objects — addressable through context, never global singletons
|
|
10
11
|
|
|
11
12
|
**Generic vs Specific:** Elements say *what* they need, controllers decide *how*.
|
|
12
13
|
Swap controllers to change behavior without touching the component.
|
|
@@ -16,6 +17,26 @@ Mock controller for tests, real API controller in production — same element.
|
|
|
16
17
|
- Down: Properties
|
|
17
18
|
- Up: Events
|
|
18
19
|
- Sideways: Request/Response channels
|
|
20
|
+
- App services: daemon-addressed request/response or dispatch/on
|
|
21
|
+
|
|
22
|
+
## Project Structure
|
|
23
|
+
|
|
24
|
+
Conventional application layout:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
src/
|
|
28
|
+
main.ts
|
|
29
|
+
router.ts
|
|
30
|
+
pages/ # @page classes; route orchestration and presentation
|
|
31
|
+
components/ # reusable @element visual components
|
|
32
|
+
controllers/ # @controller data, API, and reusable business behavior
|
|
33
|
+
daemons/ # explicitly constructed @daemon state/lifecycle services
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Pages may coordinate a route and perform a small one-off load. Move behavior to
|
|
37
|
+
a controller when a page owns several storage/server/timer operations or when
|
|
38
|
+
substantial non-visual logic is copied across pages. `snice check` reports these
|
|
39
|
+
as architecture suggestions, not errors.
|
|
19
40
|
|
|
20
41
|
## Rendering System
|
|
21
42
|
|
|
@@ -71,9 +92,18 @@ Mock controller for tests, real API controller in production — same element.
|
|
|
71
92
|
4. Attach new controller (class refs skip the registry; reference-deduped)
|
|
72
93
|
5. setupEventHandlers for controller
|
|
73
94
|
6. On detach: cleanupEventHandlers
|
|
74
|
-
- Class bindings own the element
|
|
95
|
+
- Class bindings own the element; their decorator name is reflected as a diagnostic-only `controller="name"` marker
|
|
75
96
|
- Native elements: class bindings attach via ControllerPart; string attrs via MutationObserver
|
|
76
97
|
|
|
98
|
+
**Daemon lifecycle:**
|
|
99
|
+
1. Application constructs an `@daemon` class with `new`
|
|
100
|
+
2. `provideContext(root, { daemons })` activates its private communication target
|
|
101
|
+
3. Elements/controllers resolve the nearest context by string address
|
|
102
|
+
4. Provider release removes daemon handlers and deactivates that target
|
|
103
|
+
- Router provides its `context` through the same mechanism
|
|
104
|
+
- Multiple instances of the same class remain independent
|
|
105
|
+
- No implicit construction, registry scan, singleton, start, or stop hook
|
|
106
|
+
|
|
77
107
|
## Router System
|
|
78
108
|
|
|
79
109
|
**Registration:**
|
|
@@ -136,7 +166,7 @@ onChange() { return { value: this.value }; }
|
|
|
136
166
|
```typescript
|
|
137
167
|
// Element requests (async generator: yield sends, await receives)
|
|
138
168
|
@request('fetch-data')
|
|
139
|
-
async *fetchData():
|
|
169
|
+
async *fetchData(): Response<Data> {
|
|
140
170
|
return await (yield { id: this.dataId }); // single yield per call
|
|
141
171
|
}
|
|
142
172
|
|
|
@@ -148,6 +178,25 @@ async handleFetch(payload: { id: string }) {
|
|
|
148
178
|
// Wiring: html`<my-element controller=${MyController}></my-element>` (or controller="my-controller" in raw HTML)
|
|
149
179
|
```
|
|
150
180
|
|
|
181
|
+
**Element/Controller ↔ Daemon:** Addressed communication
|
|
182
|
+
```typescript
|
|
183
|
+
@daemon
|
|
184
|
+
class SessionDaemon {
|
|
185
|
+
@respond('session/get') get() { return this.session; }
|
|
186
|
+
@dispatch('session/changed') changed() { return this.session; }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const session = new SessionDaemon();
|
|
190
|
+
const release = provideContext(appRoot, { daemons: { session } });
|
|
191
|
+
|
|
192
|
+
// Consumer imports no daemon implementation class.
|
|
193
|
+
@request('session/get', { daemon: 'session' })
|
|
194
|
+
async *getSession(): Response<Session | null> { return yield {}; }
|
|
195
|
+
|
|
196
|
+
@on('session/changed', { daemon: 'session' })
|
|
197
|
+
sessionChanged(event: CustomEvent<Session | null>) {}
|
|
198
|
+
```
|
|
199
|
+
|
|
151
200
|
**Global State:** Context
|
|
152
201
|
```typescript
|
|
153
202
|
@context() handleContext(ctx: Context) {
|
package/docs/ai/bindings.md
CHANGED
|
@@ -59,7 +59,7 @@ Ordinary attributes/comments support multiple expressions. Property, boolean, ev
|
|
|
59
59
|
|
|
60
60
|
### Controller `controller=${value}`
|
|
61
61
|
|
|
62
|
-
- Class decorated with `@controller` (decorator required): attaches directly, registry skipped. Reference-deduped — same class re-render is a no-op; different class or null detaches first.
|
|
62
|
+
- Class decorated with `@controller` (decorator required): attaches directly, registry skipped. Reference-deduped — same class re-render is a no-op; different class or null detaches first. Reflects the decorator name as a read-only diagnostic `controller="name"` marker; the marker never creates a registry attachment.
|
|
63
63
|
- Works on snice elements AND native elements in templates. Not-yet-upgraded custom elements park the class until connectedCallback.
|
|
64
64
|
- String: delegates to `setAttribute` — identical to static `controller="name"`. Interpolated `controller="a-${x}"` is plain attribute interpolation, not this channel.
|
|
65
65
|
- nothing/null/undefined/false/`''`: detach. `noChange`: keep current controller.
|
package/docs/ai/cli.md
CHANGED
|
@@ -44,6 +44,15 @@ npx snice validate # source analyzer only
|
|
|
44
44
|
`validate` runs the analyzer over source. Catches mistakes that compile but never work:
|
|
45
45
|
- an `@element`-decorated class that does not extend `HTMLElement` (or a Snice element subclass) — Snice registers/renders only element subclasses
|
|
46
46
|
- deep imports that were never released package paths, e.g. `snice/decorators`
|
|
47
|
+
- Router missing `target`, `type`, or a project-wide `initialize()` call
|
|
48
|
+
- a routed class combining `@page` with redundant `@element`
|
|
49
|
+
|
|
50
|
+
Non-blocking architecture suggestions:
|
|
51
|
+
- `@page` -> `src/pages/`
|
|
52
|
+
- `@element` -> `src/components/`
|
|
53
|
+
- `@controller` -> `src/controllers/`
|
|
54
|
+
- `@daemon` -> `src/daemons/`
|
|
55
|
+
- several external-effect methods or substantial logic repeated across pages -> extract a controller
|
|
47
56
|
|
|
48
57
|
Both `doctor` and `validate` accept `--json`.
|
|
49
58
|
|
package/docs/ai/controllers.md
CHANGED
|
@@ -40,7 +40,7 @@ html`<div controller=${UserController}></div>` // native elements work too
|
|
|
40
40
|
- `@controller('name')` decorator is still required: registers the class, marks it, flushes pending attachments.
|
|
41
41
|
- Re-binding the same class reference is a no-op.
|
|
42
42
|
- Binding a different class (or `null`) detaches the old controller first.
|
|
43
|
-
- While a class is bound,
|
|
43
|
+
- While a class is bound, its decorator name is reflected as `controller="name"` for DOM inspection. This is a read-only diagnostic marker, not a registry attachment; the class reference owns the element until unbound.
|
|
44
44
|
|
|
45
45
|
Imperative equivalents:
|
|
46
46
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Daemons
|
|
2
|
+
|
|
3
|
+
Human reference: `docs/daemons.md`.
|
|
4
|
+
|
|
5
|
+
Ordinary explicitly constructed stateful objects with app-owned lifecycles.
|
|
6
|
+
`@daemon` adds per-instance Snice communication; it never constructs, caches,
|
|
7
|
+
globally registers, starts, or stops an instance.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
@daemon
|
|
11
|
+
class SessionDaemon {
|
|
12
|
+
session: Session | null = null;
|
|
13
|
+
|
|
14
|
+
@respond('get-session')
|
|
15
|
+
getSession() { return this.session; }
|
|
16
|
+
|
|
17
|
+
@on('set-session')
|
|
18
|
+
setSession(e: CustomEvent<Session>) {
|
|
19
|
+
this.session = e.detail;
|
|
20
|
+
this.changed();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@dispatch('session-changed')
|
|
24
|
+
changed() { return this.session; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const session = new SessionDaemon();
|
|
28
|
+
const release = provideContext(appRoot, { daemons: { session } });
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Context contract:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
type DaemonMap = Readonly<Record<string, object>>;
|
|
35
|
+
|
|
36
|
+
interface AppContext {
|
|
37
|
+
readonly daemons?: DaemonMap;
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Extend `AppContext` to type application-specific fields; Snice assigns meaning
|
|
43
|
+
only to `daemons`.
|
|
44
|
+
|
|
45
|
+
Router automatically provides its `context` beneath `target`. Non-router apps
|
|
46
|
+
call `provideContext(root, context)`. `getContext(elementOrController)` returns
|
|
47
|
+
the raw visible app context.
|
|
48
|
+
|
|
49
|
+
Consumer; no daemon implementation import:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
@request<Session | null>('get-session', { daemon: 'session' })
|
|
53
|
+
async *load(): Response<Session | null> { return yield {}; }
|
|
54
|
+
|
|
55
|
+
@dispatch('set-session', { daemon: 'session' })
|
|
56
|
+
set(session: Session) { return session; }
|
|
57
|
+
|
|
58
|
+
@on('session-changed', { daemon: 'session' })
|
|
59
|
+
changed(e: CustomEvent<Session | null>) {}
|
|
60
|
+
|
|
61
|
+
@respond('confirm-logout', { daemon: 'session' })
|
|
62
|
+
confirm() { return true; }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Daemon methods without `{ daemon }` use that instance's private target.
|
|
66
|
+
Controllers resolve through their attached host.
|
|
67
|
+
|
|
68
|
+
Resolution: participant -> nearest provided app context ->
|
|
69
|
+
`context.daemons[name]` -> instance target. No globals, implicit construction,
|
|
70
|
+
fallback, scanning, or late binding.
|
|
71
|
+
|
|
72
|
+
Rules:
|
|
73
|
+
- Provide before element connect/controller attach (`@on`/`@respond` bind then).
|
|
74
|
+
- `@request`/`@dispatch` resolve at invocation.
|
|
75
|
+
- `daemon` and `scope` are mutually exclusive.
|
|
76
|
+
- No selector delegation on daemon targets.
|
|
77
|
+
- Release function is idempotent and makes daemon traffic inert.
|
|
78
|
+
- Multiple instances of the same class and same address under different roots
|
|
79
|
+
are independent.
|
|
80
|
+
- Missing/undecorated/inactive entries fail explicitly.
|
package/docs/ai/decorators.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
## Class
|
|
4
4
|
- `@element('tag-name', options?)` - Custom element. Options: `{ formAssociated?, renderRoot?: 'shadow'|'light', shadow?: 'open'|'closed'|false, delegatesFocus? }`
|
|
5
5
|
- `@page({ tag, routes, guards?, placard? })` - Routable page
|
|
6
|
-
- `@controller('name')` - Swappable behavior. Required on every controller class. Attach by class (preferred): `controller=${MyController}` in templates, `attachController(el, MyController)`, or `el.controller = MyController`; by string in raw HTML: `controller="name"
|
|
6
|
+
- `@controller('name')` - Swappable behavior. Required on every controller class. Attach by class (preferred): `controller=${MyController}` in templates, `attachController(el, MyController)`, or `el.controller = MyController`; by string in raw HTML: `controller="name"`. A class attachment reflects this name in the DOM as a diagnostic marker without attaching through the registry.
|
|
7
|
+
- `@daemon` - Marks an explicitly constructed app-context daemon. No name argument; context key is address. Never constructs/registers globally.
|
|
7
8
|
- `@layout('tag-name')` - Page wrapper for routing system
|
|
8
9
|
|
|
9
10
|
## Rendering
|
|
@@ -30,8 +31,8 @@
|
|
|
30
31
|
- `@queryAll('selector', { light? })` - NodeList
|
|
31
32
|
|
|
32
33
|
## Events
|
|
33
|
-
- `@on('event', 'selector?', options?)` - Delegation, auto-bound. Options: `{ capture?, once?, passive?, preventDefault?, stopPropagation?, debounce?, throttle?, scope? }`
|
|
34
|
-
- `@dispatch('event-name', options?)` - Emit CustomEvent, detail = return value. Supports async. Options: `{ debounce?, throttle?, dispatchOnUndefined?, scope?, ...EventInit }`
|
|
34
|
+
- `@on('event', 'selector?', options?)` - Delegation, auto-bound. Options: `{ capture?, once?, passive?, preventDefault?, stopPropagation?, debounce?, throttle?, scope?, daemon? }`
|
|
35
|
+
- `@dispatch('event-name', options?)` - Emit CustomEvent, detail = return value. Supports async. Options: `{ debounce?, throttle?, dispatchOnUndefined?, scope?, daemon?, ...EventInit }`
|
|
35
36
|
|
|
36
37
|
### scope (on both `@on` and `@dispatch`)
|
|
37
38
|
|
|
@@ -63,9 +64,10 @@ Unresolved scope → `console.warn`, listener not attached / event not dispatche
|
|
|
63
64
|
Resolver re-runs on reconnect — listeners track DOM moves.
|
|
64
65
|
|
|
65
66
|
## Communication
|
|
66
|
-
- `@request(channel, options?)` - Async generator request pattern
|
|
67
|
-
- `@respond(channel, options?)` - Handle requests from `@request`
|
|
67
|
+
- `@request(channel, options?)` - Async generator request pattern; `{ daemon: 'name' }` targets an app-context daemon
|
|
68
|
+
- `@respond(channel, options?)` - Handle requests from `@request`; `{ daemon: 'name' }` installs on that daemon target
|
|
68
69
|
- `@context(options?)` - Receive router navigation context updates
|
|
70
|
+
- `provideContext(root, context)` / `getContext(participant)` - Explicit raw app-context provisioning and lookup
|
|
69
71
|
|
|
70
72
|
## Observers
|
|
71
73
|
- `@observe(target, selector?, options?)` - Watch intersection, resize, media query, mutation
|
package/docs/ai/events.md
CHANGED
|
@@ -371,6 +371,12 @@ Prefer the narrowest scope that works; `'global'` means every instance hears eve
|
|
|
371
371
|
|
|
372
372
|
Need a reply rather than a broadcast → [@request / @respond](./request-response.md).
|
|
373
373
|
|
|
374
|
+
For stateful objects explicitly supplied by an application context, use
|
|
375
|
+
`{ daemon: 'name' }`. Both halves must target the same daemon address; daemon
|
|
376
|
+
methods themselves default to their own private target. See
|
|
377
|
+
[daemons](./daemons.md). Daemon targets are not DOM scopes: `scope` and
|
|
378
|
+
`daemon` cannot be combined, and selector delegation is unavailable.
|
|
379
|
+
|
|
374
380
|
## Custom events
|
|
375
381
|
|
|
376
382
|
Dispatching manually:
|
package/docs/ai/patterns.md
CHANGED
|
@@ -80,9 +80,10 @@ class DataLoader implements IController {
|
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
**Class vs string:** class binding is reference-deduped (re-render no-op, swap
|
|
83
|
-
detaches old)
|
|
84
|
-
|
|
85
|
-
supported and behave exactly like the static
|
|
83
|
+
detaches old) and owns the element. It reflects the decorator name as a
|
|
84
|
+
diagnostic `controller="name"` marker, but that marker does not attach through
|
|
85
|
+
the registry. Strings stay fully supported and behave exactly like the static
|
|
86
|
+
attribute.
|
|
86
87
|
|
|
87
88
|
**Native element controllers** — attach controllers to any HTML element (div, form, table, etc.).
|
|
88
89
|
Enabled automatically when Snice loads — no setup needed.
|
|
@@ -94,6 +95,50 @@ Enabled automatically when Snice loads — no setup needed.
|
|
|
94
95
|
// <table controller="table-loader"></table>
|
|
95
96
|
```
|
|
96
97
|
|
|
98
|
+
## Daemon
|
|
99
|
+
|
|
100
|
+
Ordinary state/lifecycle object owned by the application. Construction and
|
|
101
|
+
teardown stay explicit; consumers use a context address instead of importing
|
|
102
|
+
the implementation class.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
@daemon
|
|
106
|
+
class SessionDaemon {
|
|
107
|
+
session: Session | null = null;
|
|
108
|
+
|
|
109
|
+
@respond('session/get')
|
|
110
|
+
getSession() { return this.session; }
|
|
111
|
+
|
|
112
|
+
@on('session/set')
|
|
113
|
+
setSession(event: CustomEvent<Session>) {
|
|
114
|
+
this.session = event.detail;
|
|
115
|
+
this.changed();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@dispatch('session/changed')
|
|
119
|
+
changed() { return this.session; }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const session = new SessionDaemon();
|
|
123
|
+
const release = provideContext(appRoot, { daemons: { session } });
|
|
124
|
+
|
|
125
|
+
@request('session/get', { daemon: 'session' })
|
|
126
|
+
async *getSession(): Response<Session | null> { return yield {}; }
|
|
127
|
+
|
|
128
|
+
@dispatch('session/set', { daemon: 'session' })
|
|
129
|
+
setSession(session: Session) { return session; }
|
|
130
|
+
|
|
131
|
+
@on('session/changed', { daemon: 'session' })
|
|
132
|
+
sessionChanged(event: CustomEvent<Session | null>) {}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- Provide before element connection/controller attachment.
|
|
136
|
+
- Router `context.daemons` uses the same provider automatically.
|
|
137
|
+
- `@request`/`@respond` = one reply; `@dispatch`/`@on` = notifications.
|
|
138
|
+
- `daemon` cannot be combined with DOM `scope`; daemon listeners cannot delegate selectors.
|
|
139
|
+
- Call `release()` during app/test teardown.
|
|
140
|
+
- See `docs/ai/daemons.md`.
|
|
141
|
+
|
|
97
142
|
## Page + Router
|
|
98
143
|
|
|
99
144
|
**Module Structure (avoids circular imports):**
|
|
@@ -185,7 +230,7 @@ class API {
|
|
|
185
230
|
@element('user-profile')
|
|
186
231
|
class UserProfile extends HTMLElement {
|
|
187
232
|
@request('fetch-user')
|
|
188
|
-
async *fetchUser(id: string):
|
|
233
|
+
async *fetchUser(id: string): Response<User> {
|
|
189
234
|
const user = await (yield { id }); // yield = send request, await = get response
|
|
190
235
|
return user;
|
|
191
236
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Mirrors `docs/request-response.md`.
|
|
4
4
|
|
|
5
|
-
Request/response communication between elements and
|
|
5
|
+
Request/response communication between elements, controllers, and provided daemons using async generators.
|
|
6
6
|
|
|
7
7
|
## Why
|
|
8
8
|
|
|
@@ -27,6 +27,7 @@ function request(requestName: string, options?: RequestOptions): MethodDecorator
|
|
|
27
27
|
function respond(requestName: string, options?: RespondOptions): MethodDecorator
|
|
28
28
|
|
|
29
29
|
interface RequestOptions extends EventInit {
|
|
30
|
+
daemon?: string; // named daemon from nearest provided app context
|
|
30
31
|
timeout?: number; // response timeout ms, default 120000 (2 min)
|
|
31
32
|
discoveryTimeout?: number; // handler discovery timeout ms, default 50
|
|
32
33
|
debounce?: number;
|
|
@@ -36,14 +37,20 @@ interface RequestOptions extends EventInit {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
interface RespondOptions {
|
|
40
|
+
daemon?: string; // install responder on named daemon target
|
|
39
41
|
debounce?: number;
|
|
40
42
|
throttle?: number;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
//
|
|
44
|
-
type
|
|
45
|
+
// Public return type for methods decorated with @request:
|
|
46
|
+
type Response<T = any> = T | any;
|
|
45
47
|
```
|
|
46
48
|
|
|
49
|
+
TypeScript cannot model a method decorator changing an async generator into a
|
|
50
|
+
promise-returning method. This is a deliberate pragmatic annotation: it keeps
|
|
51
|
+
strict consumers usable and documents `T`; the decorated runtime method
|
|
52
|
+
returns a promise for `T`.
|
|
53
|
+
|
|
47
54
|
### Response debounce/throttle
|
|
48
55
|
|
|
49
56
|
```typescript
|
|
@@ -65,6 +72,16 @@ class ProcessingController implements IController {
|
|
|
65
72
|
}
|
|
66
73
|
```
|
|
67
74
|
|
|
75
|
+
Daemon communication uses the same protocol without DOM bubbling:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
@request('get-session', { daemon: 'session' })
|
|
79
|
+
async *getSession() { return yield {}; }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The daemon's plain `@respond('get-session')` handles it. See
|
|
83
|
+
[daemons](./daemons.md).
|
|
84
|
+
|
|
68
85
|
## Element-Side Requests
|
|
69
86
|
|
|
70
87
|
```typescript
|
|
@@ -75,7 +92,7 @@ class ProductCard extends HTMLElement {
|
|
|
75
92
|
@property() price = '';
|
|
76
93
|
|
|
77
94
|
@request('fetch-product')
|
|
78
|
-
async *loadProduct():
|
|
95
|
+
async *loadProduct(): Response<void> {
|
|
79
96
|
const product = await (yield { id: this.productId });
|
|
80
97
|
this.name = product.name;
|
|
81
98
|
this.price = product.price;
|
|
@@ -121,17 +138,17 @@ Elements never call `fetch()` or manage data directly — they yield requests up
|
|
|
121
138
|
|
|
122
139
|
```typescript
|
|
123
140
|
@request('heavy-computation', { discoveryTimeout: 50, timeout: 30000 })
|
|
124
|
-
async *compute():
|
|
141
|
+
async *compute(): Response<any> { return await (yield data); }
|
|
125
142
|
```
|
|
126
143
|
|
|
127
144
|
### Debounce/Throttle
|
|
128
145
|
|
|
129
146
|
```typescript
|
|
130
147
|
@request('search', { debounce: 300 })
|
|
131
|
-
async *search():
|
|
148
|
+
async *search(): Response<any[]> { return await (yield { query: this.searchTerm }); }
|
|
132
149
|
|
|
133
150
|
@request('track', { throttle: 1000 })
|
|
134
|
-
async *trackEvent():
|
|
151
|
+
async *trackEvent(): Response<void> { await (yield { event: 'scroll', position: window.scrollY }); }
|
|
135
152
|
```
|
|
136
153
|
|
|
137
154
|
## Error Handling
|
|
@@ -140,7 +157,7 @@ async *trackEvent(): RequestResult<void> { await (yield { event: 'scroll', posit
|
|
|
140
157
|
|
|
141
158
|
```typescript
|
|
142
159
|
@request('load-data', { timeout: 5000 })
|
|
143
|
-
async *loadData():
|
|
160
|
+
async *loadData(): Response<void> {
|
|
144
161
|
try {
|
|
145
162
|
this.data = await (yield { id: this.dataId });
|
|
146
163
|
this.error = '';
|
|
@@ -201,7 +218,7 @@ class LiveTicker extends HTMLElement {
|
|
|
201
218
|
@property() symbol = 'BTC';
|
|
202
219
|
|
|
203
220
|
@request('subscribe-ticker')
|
|
204
|
-
async *subscribe():
|
|
221
|
+
async *subscribe(): Response<void> { await (yield { symbol: this.symbol }); }
|
|
205
222
|
|
|
206
223
|
@on('ticker-update')
|
|
207
224
|
onUpdate(e: CustomEvent) { this.price = e.detail.price; }
|
package/docs/ai/routing.md
CHANGED
|
@@ -25,7 +25,7 @@ interface RouterOptions {
|
|
|
25
25
|
document?: Document; // override document object (testing)
|
|
26
26
|
transition?: Transition; // global transition config
|
|
27
27
|
layout?: string; // default layout tag for all pages
|
|
28
|
-
context?: any; //
|
|
28
|
+
context?: any; // app context; may expose daemons
|
|
29
29
|
fetcher?: Fetcher; // optional fetch middleware — see docs/ai/fetcher.md
|
|
30
30
|
}
|
|
31
31
|
```
|
|
@@ -45,6 +45,19 @@ class AppContext {
|
|
|
45
45
|
const { page, initialize } = Router({ target: '#app', type: 'hash', context: new AppContext() });
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
Router also provides the raw app context beneath `target`, using the same
|
|
49
|
+
`provideContext()` mechanism available to non-router applications. Explicitly
|
|
50
|
+
constructed `@daemon` instances may be addressed through `context.daemons`:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const session = new SessionDaemon();
|
|
54
|
+
Router({
|
|
55
|
+
target: '#app',
|
|
56
|
+
type: 'hash',
|
|
57
|
+
context: { daemons: { session } }
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
48
61
|
### Module structure (avoids circular imports)
|
|
49
62
|
|
|
50
63
|
```typescript
|
|
@@ -176,4 +189,6 @@ function Router(options: RouterOptions): {
|
|
|
176
189
|
|
|
177
190
|
- `navigate(path: string): Promise<void>` — navigates to `path`; uses hash or pushstate depending on router `type`.
|
|
178
191
|
- `initialize(): void` — starts listening for route changes; must be called after all pages are defined.
|
|
192
|
+
- Do not add `@element` to an `@page` class. The Router-returned page decorator
|
|
193
|
+
already registers the custom element and applies Snice element behavior.
|
|
179
194
|
- `register(route: string, tag: string, transition?: Transition, guards?: Guard | Guard[], layout?: string | false, placard?: Placard | ((ctx: AppContext) => Placard)): void` — manually registers a route without the `@page` decorator. Note: the return-type shape above lists `register` with 4 params; this fuller 6-param signature (adding `layout`, `placard`) is documented separately in the source.
|
package/docs/bindings.md
CHANGED
|
@@ -163,7 +163,7 @@ html`<user-list controller=${DataLoader}></user-list>`;
|
|
|
163
163
|
html`<div controller=${DataLoader}></div>`; // native elements too
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
Class values skip the registry and are deduped by reference: re-rendering with the same class is a no-op; a different class (or `null`) detaches the previous controller first. The `@controller('name')` decorator is still required on the class. While a class is bound it owns the element
|
|
166
|
+
Class values skip the registry and are deduped by reference: re-rendering with the same class is a no-op; a different class (or `null`) detaches the previous controller first. The `@controller('name')` decorator is still required on the class. While a class is bound it owns the element. Snice reflects its decorator name as a diagnostic `controller="name"` attribute for DevTools, but that marker never resolves the registry or creates another attachment; treat it as read-only. Custom elements that have not upgraded yet hold the class until their `connectedCallback` runs.
|
|
167
167
|
|
|
168
168
|
String values delegate to the attribute channel and behave exactly like a static `controller="name"` attribute. Interpolated forms (`controller="user-${kind}"`) are ordinary attribute interpolation, not this channel.
|
|
169
169
|
|
package/docs/cli.md
CHANGED
|
@@ -50,6 +50,13 @@ npx snice validate # source analyzer only
|
|
|
50
50
|
|
|
51
51
|
- an `@element`-decorated class that does not extend `HTMLElement` (or a Snice element subclass) — Snice registers and renders only element subclasses
|
|
52
52
|
- deep imports that were never released package paths, such as `snice/decorators`
|
|
53
|
+
- a Router without `target`, `type`, or a project-wide `initialize()` call
|
|
54
|
+
- a routed class combining `@page` with redundant `@element`
|
|
55
|
+
|
|
56
|
+
It also gives non-blocking architecture suggestions: keep `@page`, `@element`,
|
|
57
|
+
`@controller`, and `@daemon` classes under `src/pages`, `src/components`,
|
|
58
|
+
`src/controllers`, and `src/daemons`; extract clearly service-heavy or repeated
|
|
59
|
+
page logic into a controller. Small route orchestration remains valid page code.
|
|
53
60
|
|
|
54
61
|
Both accept `--json` for CI.
|
|
55
62
|
|
package/docs/controllers.md
CHANGED
|
@@ -54,7 +54,14 @@ The `@controller('name')` decorator is still required — it registers the class
|
|
|
54
54
|
marks it, and flushes pending attachments. Re-binding the same class reference
|
|
55
55
|
is a no-op; binding a different class (or `null`) detaches the old controller
|
|
56
56
|
first. While a class is bound, the class binding owns the element: `controller`
|
|
57
|
-
attribute writes
|
|
57
|
+
attribute writes cannot switch it until the class is unbound.
|
|
58
|
+
|
|
59
|
+
For inspection in DevTools, Snice reflects the decorator name as a
|
|
60
|
+
`controller="name"` attribute while the class is attached. This marker is
|
|
61
|
+
diagnostic only: it does not resolve the registry or create a second
|
|
62
|
+
attachment. Treat it as read-only; the class reference remains authoritative.
|
|
63
|
+
Snice removes the marker when the class detaches or replaces it when another
|
|
64
|
+
controller is bound.
|
|
58
65
|
|
|
59
66
|
Imperative equivalents:
|
|
60
67
|
|
|
@@ -467,4 +474,3 @@ element.addEventListener('controller-attached', (e: CustomEvent) => {
|
|
|
467
474
|
### Auto-Cleanup
|
|
468
475
|
|
|
469
476
|
The framework automatically cleans up `@on` handlers, observers, and `@respond` handlers during detach. Manual cleanup in `detach()` is only needed for resources you manage yourself (WebSockets, intervals, manual event listeners).
|
|
470
|
-
|