@adukiorg/anza 0.2.0 → 0.2.2
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/CHANGELOG.md +81 -4
- package/README.md +97 -133
- package/bin/anza/anza +0 -0
- package/bin/anza/anza.exe +0 -0
- package/bin/anza/find.js +35 -0
- package/bin/anza/index.js +34 -0
- package/bin/anza/launch.js +19 -0
- package/bin/common/index.js +7 -0
- package/bin/common/logs.js +62 -0
- package/bin/create/copy.js +18 -0
- package/bin/create/index.js +45 -0
- package/bin/create/run.js +210 -0
- package/bin/create/write.js +19 -0
- package/importmap.json +4 -0
- package/package.json +16 -10
- package/src/core/offline/{usage.md → notes/usage.md} +11 -1
- package/src/core/router/boot.js +82 -0
- package/src/core/router/cascade.js +76 -0
- package/src/core/router/container.js +63 -72
- package/src/core/router/graph.js +144 -0
- package/src/core/router/index.js +12 -2
- package/src/core/router/intercept.js +26 -7
- package/src/core/router/lca.js +58 -0
- package/src/core/router/match.js +49 -36
- package/src/core/router/notes/audit-old.md +887 -0
- package/src/core/router/notes/audti.md +773 -0
- package/src/core/router/notes/tasks.md +473 -0
- package/src/core/router/{usage.md → notes/usage.md} +57 -35
- package/src/core/router/sync/tab.js +6 -4
- package/src/core/router/transitions.js +35 -8
- package/src/core/router/trie.js +130 -0
- package/src/core/security/{usage.md → notes/usage.md} +1 -2
- package/src/core/storage/{usage.md → notes/usage.md} +6 -6
- package/src/core/theme/index.js +78 -0
- package/src/core/ui/define/index.js +2 -1
- package/src/core/ui/define/orchestrator.js +10 -4
- package/src/core/ui/defs/dock.js +134 -0
- package/src/core/ui/defs/index.js +20 -0
- package/src/core/ui/defs/page.js +89 -0
- package/src/core/ui/defs/part.js +28 -0
- package/src/core/ui/defs/spec.js +96 -0
- package/src/core/ui/defs/view.js +23 -0
- package/src/core/ui/index.js +16 -3
- package/src/core/ui/notes/definations.md +979 -0
- package/src/tokens/index.css +1 -0
- package/src/tokens/semantic/contrast.css +18 -0
- package/src/tokens/semantic/transitions.css +32 -0
- package/types/core/platform/index.d.ts +39 -10
- package/types/core/router/index.d.ts +9 -0
- package/types/core/theme/index.d.ts +18 -0
- package/types/core/ui/index.d.ts +11 -0
- package/types/index.d.ts +1 -0
- package/bin/anza.js +0 -63
- package/bin/create.js +0 -150
- package/src/core/api/plan.md +0 -209
- package/src/core/events/missing.md +0 -103
- package/src/core/events/plan.md +0 -177
- package/src/core/offline/missing.md +0 -89
- package/src/core/offline/plan.md +0 -143
- package/src/core/platform/missing.md +0 -119
- package/src/core/platform/platform.d.ts +0 -88
- package/src/core/router/missing.md +0 -716
- package/src/core/router/outlet.js +0 -139
- package/src/core/router/plan.md +0 -370
- package/src/core/security/missing.md +0 -97
- package/src/core/state/missing.md +0 -165
- package/src/core/storage/missing.md +0 -165
- package/src/core/storage/plan.md +0 -69
- package/src/core/ui/implementation.md +0 -170
- package/src/core/ui/plan.md +0 -510
- package/src/core/ui/ui.types.md +0 -890
- /package/src/core/animations/{usage.md → notes/usage.md} +0 -0
- /package/src/core/api/{usage.md → notes/usage.md} +0 -0
- /package/src/core/events/{usage.md → notes/usage.md} +0 -0
- /package/src/core/platform/{usage.md → notes/usage.md} +0 -0
- /package/src/core/state/{usage.md → notes/usage.md} +0 -0
- /package/src/core/ui/{usage.md → notes/usage.md} +0 -0
- /package/src/core/ui/{watch.md → notes/watch.md} +0 -0
- /package/src/core/workers/{plan.md → notes/plan.md} +0 -0
- /package/src/core/workers/{usage.md → notes/usage.md} +0 -0
package/src/core/ui/plan.md
DELETED
|
@@ -1,510 +0,0 @@
|
|
|
1
|
-
# Native UI Runtime Implementation Plan
|
|
2
|
-
|
|
3
|
-
This plan turns the existing `tags`, `refs`, `on`, and proposed `watch` APIs into a robust implementation track for `src/core/ui`. It covers the browser-side primitive JavaScript runtime, the Rust template scanner, lifecycle integration, cache invalidation, safety rules, and documentation work.
|
|
4
|
-
|
|
5
|
-
## 1. Scope
|
|
6
|
-
|
|
7
|
-
Implement a small native-first component runtime around `ui.element` and `ui.container`:
|
|
8
|
-
|
|
9
|
-
- `refs`: O(1) named element anchors from `ref="name"`.
|
|
10
|
-
- `tags`: cached Shadow DOM selector access.
|
|
11
|
-
- `on`: lifecycle-safe delegated event binding.
|
|
12
|
-
- `watch`: lifecycle-safe scoped `MutationObserver` binding.
|
|
13
|
-
- `anza` HTML scan: Rust-generated `.tags.json` descriptors for runtime prewarming and optional type generation.
|
|
14
|
-
- `usage.md`: complete public API examples and usage rules.
|
|
15
|
-
|
|
16
|
-
This is not a virtual DOM. Rendering remains direct DOM mutation inside a component-owned shadow root, scheduled through the existing scheduler when work is not part of initial mount.
|
|
17
|
-
|
|
18
|
-
## 2. Files Read
|
|
19
|
-
|
|
20
|
-
Runtime:
|
|
21
|
-
|
|
22
|
-
- `src/core/ui/base.js`
|
|
23
|
-
- `src/core/ui/index.js`
|
|
24
|
-
- `src/core/ui/observe.js`
|
|
25
|
-
- `src/core/ui/schedule.js`
|
|
26
|
-
- `src/core/ui/template.js`
|
|
27
|
-
- `src/core/ui/transitions.js`
|
|
28
|
-
- `src/core/ui/define/element.js`
|
|
29
|
-
- `src/core/ui/define/container.js`
|
|
30
|
-
- `src/core/ui/define/proxy.js`
|
|
31
|
-
- `src/core/ui/define/utils.js`
|
|
32
|
-
- `src/core/ui/define/state.js`
|
|
33
|
-
- `src/core/ui/define/orchestrator.js`
|
|
34
|
-
|
|
35
|
-
Tooling:
|
|
36
|
-
|
|
37
|
-
- `tools/src/main.rs`
|
|
38
|
-
- `tools/src/extract/runner.rs`
|
|
39
|
-
- `tools/src/extract/html.rs`
|
|
40
|
-
- `tools/src/watcher/runner.rs`
|
|
41
|
-
- `tools/src/server/runner.rs`
|
|
42
|
-
- `tools/src/types/mod.rs`
|
|
43
|
-
|
|
44
|
-
Architecture notes consulted:
|
|
45
|
-
|
|
46
|
-
- `docs/notes/04 - Web Components.md`
|
|
47
|
-
- `docs/notes/06 - Rendering.md`
|
|
48
|
-
- `docs/notes/07 - Reactivity.md`
|
|
49
|
-
- `docs/notes/10 - Event Architecture.md`
|
|
50
|
-
- `docs/notes/12 - Performance.md`
|
|
51
|
-
- `docs/notes/14 - Memory Management.md`
|
|
52
|
-
- `docs/notes/17 - Browser API.md`
|
|
53
|
-
- `docs/notes/18 - Limitations, Browser Gaps, and Polyfill Strategy.md`
|
|
54
|
-
|
|
55
|
-
## 3. Current State
|
|
56
|
-
|
|
57
|
-
The codebase already has the core skeleton:
|
|
58
|
-
|
|
59
|
-
| Area | Current state | Required change |
|
|
60
|
-
| --- | --- | --- |
|
|
61
|
-
| Lifecycle | `BaseElement` creates `this.ctrl` on connect and aborts on disconnect. | Keep. Ensure all injected helpers derive cleanup from this signal. |
|
|
62
|
-
| Template/style loading | `preloadResources` fetches HTML, CSS, and optional `.tags.json`. | Keep. Add stricter descriptor validation and inline-template fallback scanning where possible. |
|
|
63
|
-
| `refs` | Built in `element.js` from descriptor refs. Frozen after mount. | Support descriptor-free fallback scan and document stable-template semantics. |
|
|
64
|
-
| `tags` | `TagsCache` has `one`, `all`, `each`, `prewarmId`, `clear`. | Split single/all caches to avoid selector shape collisions. Add safer invalidation hooks. |
|
|
65
|
-
| `on` | Proxy creates a new listener on the shadow root for every call. | Change to one root listener per event type, with registration records per selector. Add `.once` and options support. |
|
|
66
|
-
| `watch` | Spec exists in `watch.md`; no runtime helper is injected. | Implement `createMutationWatcher` and inject it into mount/update. |
|
|
67
|
-
| Rust scan | `parse_and_emit` emits refs, ids, classes, tags, compound selectors. | Return results/errors, scan more selector hints, add duplicate-ref warnings, optional type output. |
|
|
68
|
-
| Watcher | HTML changes trigger `.tags.json` regeneration. | Keep. Add removal cleanup and better debounce result handling. |
|
|
69
|
-
| Usage docs | `usage.md` documents element/container/tags/on/scheduler. | Add watch, observe, transition, template, signal/options, direct listener escape hatches. |
|
|
70
|
-
|
|
71
|
-
## 4. Target Injection Shape
|
|
72
|
-
|
|
73
|
-
Every `mount` and `update` receives the same base context. Update additionally receives the changed property data.
|
|
74
|
-
|
|
75
|
-
```javascript
|
|
76
|
-
mount({ el, ctrl, tags, on, refs, watch, internals }) {}
|
|
77
|
-
|
|
78
|
-
update({
|
|
79
|
-
el,
|
|
80
|
-
ctrl,
|
|
81
|
-
tags,
|
|
82
|
-
on,
|
|
83
|
-
refs,
|
|
84
|
-
watch,
|
|
85
|
-
internals,
|
|
86
|
-
name,
|
|
87
|
-
val,
|
|
88
|
-
prev
|
|
89
|
-
}) {}
|
|
90
|
-
|
|
91
|
-
unmount({ el, tags, refs, internals }) {}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Implementation rule: build this context once per connected lifecycle and reuse the same helper instances for mount and every scheduled update during that connection.
|
|
95
|
-
|
|
96
|
-
## 5. Primitive JavaScript Runtime Plan & Optimizations
|
|
97
|
-
|
|
98
|
-
### 5.1 Context Builder & Runtime Core Optimizations
|
|
99
|
-
|
|
100
|
-
Create a small internal helper in `src/core/ui/define/proxy.js` or a new `context.js`:
|
|
101
|
-
|
|
102
|
-
```javascript
|
|
103
|
-
export function createComponentContext({ el, shadowRoot, ctrl, descriptor, internals }) {
|
|
104
|
-
const tags = new TagsCache(shadowRoot);
|
|
105
|
-
const refs = createRefs(shadowRoot, descriptor);
|
|
106
|
-
const on = createEventDelegator(shadowRoot, ctrl.signal);
|
|
107
|
-
const watch = createMutationWatcher(shadowRoot, ctrl.signal);
|
|
108
|
-
|
|
109
|
-
prewarmTags(tags, shadowRoot, descriptor);
|
|
110
|
-
installInvalidationHooks(shadowRoot, tags, refs, watch);
|
|
111
|
-
|
|
112
|
-
return Object.freeze({ el, ctrl, tags, on, refs, watch, internals });
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
#### Core Runtime Performance Optimizations
|
|
117
|
-
|
|
118
|
-
1. **Single-Allocation Symbol Backing Store**: Component attributes and values are cached in local `Symbol` properties bound to the element prototype at instantiation. This guarantees $O(1)$ property access and prevents repetitive layout invalidations from reading native attributes.
|
|
119
|
-
2. **Visual vs. Non-Visual State Microtask Batching**: ARIA attributes and custom non-visual properties are scheduled using `queueMicrotask` to instantly execute without animation frame delay. Visual mutations continue using `requestAnimationFrame` for stutter-free frame-synced drawing.
|
|
120
|
-
3. **Constructable Stylesheet HMR Memory Leak Protection**: Standard styles hot-swapping is handled via a global cache map `hmrStyleListeners` on the runtime, instead of attaching style listeners on individual custom elements. This stops active stylesheets from accumulating memory leak records across hot reloads.
|
|
121
|
-
4. **Synchronous Connected Lifecycle Connection Check**: If standard custom element templates or stylesheets are already in memory, the connection lifecycle executes connection hooks synchronously to avoid microtask paint delays and enable instantaneous first paint.
|
|
122
|
-
5. **Scroll-Blocking Passive Delegator**: The `on` event delegator registers shadow-root events with `passive: true` by default. It upgrades listeners to `passive: false` only if the registered event specifically overrides browser defaults via `preventDefault()`, maximizing touch and scroll responsiveness.
|
|
123
|
-
6. **Target-Specific MutationObservers**: Low-level observers bind directly to target elements with `{ subtree: false }` unless tree traversal watches are specifically requested.
|
|
124
|
-
7. **Cache Invalidation MutationObserver**: A dedicated `childList` MutationObserver is placed on the shadowRoot to safely flush tags selector maps and refs lists whenever DOM elements are mutated, replacing slow prototype monkey-patching of `innerHTML` or `replaceChildren`.
|
|
125
|
-
8. **Single-Pass Ref fallback**: Descriptor fallback extraction utilizes a single `querySelectorAll('[ref]')` query to compile anchors in one pass, avoiding multiple traversal runs.
|
|
126
|
-
|
|
127
|
-
### 5.2 Context Helper Mapping
|
|
128
|
-
|
|
129
|
-
Target behavior:
|
|
130
|
-
|
|
131
|
-
```javascript
|
|
132
|
-
refs.submit
|
|
133
|
-
refs.email
|
|
134
|
-
refs.status
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Rules:
|
|
138
|
-
|
|
139
|
-
- `refs` is a plain frozen object.
|
|
140
|
-
- Descriptor refs are preferred.
|
|
141
|
-
- If no descriptor exists, fallback to `shadowRoot.querySelectorAll('[ref]')`.
|
|
142
|
-
- Missing refs return `undefined`.
|
|
143
|
-
- Duplicate refs are a development warning; the first element wins.
|
|
144
|
-
- Runtime does not remove the `ref` attribute. It remains inspectable in DevTools.
|
|
145
|
-
|
|
146
|
-
Implementation:
|
|
147
|
-
|
|
148
|
-
```javascript
|
|
149
|
-
function createRefs(root, descriptor) {
|
|
150
|
-
const names = descriptor?.refs;
|
|
151
|
-
const out = Object.create(null);
|
|
152
|
-
|
|
153
|
-
if (Array.isArray(names) && names.length) {
|
|
154
|
-
for (const name of names) {
|
|
155
|
-
const found = root.querySelectorAll(`[ref="${CSS.escape(name)}"]`);
|
|
156
|
-
if (found[0]) out[name] = found[0];
|
|
157
|
-
if (found.length > 1) warnDuplicateRef(name, found.length);
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
for (const node of root.querySelectorAll('[ref]')) {
|
|
161
|
-
const name = node.getAttribute('ref');
|
|
162
|
-
if (name && !(name in out)) out[name] = node;
|
|
163
|
-
else if (name) warnDuplicateRef(name);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return Object.freeze(out);
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### 5.3 `tags`
|
|
172
|
-
|
|
173
|
-
Target methods:
|
|
174
|
-
|
|
175
|
-
```javascript
|
|
176
|
-
tags.one(selector) // Element | null
|
|
177
|
-
tags.all(selector) // Element[]
|
|
178
|
-
tags.each(selector, fn) // void
|
|
179
|
-
tags.has(selector) // boolean, planned convenience
|
|
180
|
-
tags.clear() // internal/public escape hatch
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Required fix: the current `TagsCache` stores `one()` and `all()` results in the same `Map`. Calling `tags.one('button')` and then `tags.all('button')` can return the wrong shape. Use separate maps or namespace keys:
|
|
184
|
-
|
|
185
|
-
```javascript
|
|
186
|
-
class TagsCache {
|
|
187
|
-
#one = new Map();
|
|
188
|
-
#all = new Map();
|
|
189
|
-
|
|
190
|
-
one(selector) {
|
|
191
|
-
if (!this.#one.has(selector)) {
|
|
192
|
-
this.#one.set(selector, this.root.querySelector(selector));
|
|
193
|
-
}
|
|
194
|
-
return this.#one.get(selector);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
all(selector) {
|
|
198
|
-
if (!this.#all.has(selector)) {
|
|
199
|
-
this.#all.set(selector, Array.from(this.root.querySelectorAll(selector)));
|
|
200
|
-
}
|
|
201
|
-
return this.#all.get(selector);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Invalidation:
|
|
207
|
-
|
|
208
|
-
- Clear caches after `shadowRoot.replaceChildren(...)`.
|
|
209
|
-
- Clear caches after writes to `shadowRoot.innerHTML`.
|
|
210
|
-
- Clear caches after appending a full template fragment during first mount is not needed if caches are created after append.
|
|
211
|
-
- Clear caches when `watch` observes structural changes caused by component code only if the component opts in, because clearing on every mutation defeats caching for dynamic lists.
|
|
212
|
-
- Document that `tags` is best for stable anchors. Dynamic collections should call `tags.all()` after rendering or use delegated `on`.
|
|
213
|
-
|
|
214
|
-
Prewarming:
|
|
215
|
-
|
|
216
|
-
- `descriptor.ids`: set `#id` entries using `shadowRoot.getElementById(id)` where available.
|
|
217
|
-
- `descriptor.refs`: optionally set `[ref="name"]` entries after refs are built.
|
|
218
|
-
- Do not prewarm every class/tag by default. Large templates would do unnecessary work.
|
|
219
|
-
|
|
220
|
-
### 5.4 `on`
|
|
221
|
-
|
|
222
|
-
Target usage:
|
|
223
|
-
|
|
224
|
-
```javascript
|
|
225
|
-
on.click('button', handler)
|
|
226
|
-
on.click('button', handler, ctrl.signal)
|
|
227
|
-
on.click('button', handler, { signal: ctrl.signal, passive: true })
|
|
228
|
-
on.click.once('button', handler)
|
|
229
|
-
on['nav:change']('[data-tab]', handler)
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
Handler signature:
|
|
233
|
-
|
|
234
|
-
```javascript
|
|
235
|
-
(event, matchedElement) => void
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Implementation model:
|
|
239
|
-
|
|
240
|
-
- One native `addEventListener` per event type on the shadow root.
|
|
241
|
-
- Each event type has a registry of bindings.
|
|
242
|
-
- Each binding stores `{ selector, handler, signal, once, options }`.
|
|
243
|
-
- The shared root listener dispatches to matching bindings using `event.target.closest(selector)`.
|
|
244
|
-
- The match must satisfy `shadowRoot.contains(match)`.
|
|
245
|
-
- If a binding signal aborts, remove only that binding.
|
|
246
|
-
- If all bindings for an event type are removed, the root listener can remain until component abort, or be removed for tidiness.
|
|
247
|
-
|
|
248
|
-
Options:
|
|
249
|
-
|
|
250
|
-
```javascript
|
|
251
|
-
on.click(selector, handler)
|
|
252
|
-
on.click(selector, handler, signal)
|
|
253
|
-
on.click(selector, handler, {
|
|
254
|
-
signal,
|
|
255
|
-
once,
|
|
256
|
-
capture,
|
|
257
|
-
passive
|
|
258
|
-
})
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
Non-delegated escape hatch:
|
|
262
|
-
|
|
263
|
-
Use raw platform APIs for events that cannot be delegated cleanly, such as `scroll`, `resize`, `load`, and some focus flows:
|
|
264
|
-
|
|
265
|
-
```javascript
|
|
266
|
-
refs.scroller.addEventListener('scroll', handleScroll, { signal: ctrl.signal });
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
### 5.5 `watch`
|
|
270
|
-
|
|
271
|
-
Implement `createMutationWatcher(shadowRoot, defaultSignal)` as documented in `watch.md`.
|
|
272
|
-
|
|
273
|
-
Public methods:
|
|
274
|
-
|
|
275
|
-
```javascript
|
|
276
|
-
watch.attr(target, attr, handler, signalOrOptions?)
|
|
277
|
-
watch.kids(target, handler, signalOrOptions?)
|
|
278
|
-
watch.kids(target, { deep: true }, handler, signalOrOptions?)
|
|
279
|
-
watch.text(target, handler, signalOrOptions?)
|
|
280
|
-
watch.tree(target, handler, signalOrOptions?)
|
|
281
|
-
|
|
282
|
-
watch.attr.once(...)
|
|
283
|
-
watch.kids.once(...)
|
|
284
|
-
watch.text.once(...)
|
|
285
|
-
watch.tree.once(...)
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
Core data structure:
|
|
289
|
-
|
|
290
|
-
```javascript
|
|
291
|
-
{
|
|
292
|
-
id,
|
|
293
|
-
kind: 'attr' | 'kids' | 'text' | 'tree',
|
|
294
|
-
target, // Element
|
|
295
|
-
selector, // string | null
|
|
296
|
-
attrs, // Set<string> | '*'
|
|
297
|
-
deep, // boolean
|
|
298
|
-
handler,
|
|
299
|
-
signal,
|
|
300
|
-
once
|
|
301
|
-
}
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
Observer model:
|
|
305
|
-
|
|
306
|
-
- One `MutationObserver` per component instance.
|
|
307
|
-
- Observe the shadow root with the union of options required by all active registrations.
|
|
308
|
-
- Recompute observer options when registrations are added or removed.
|
|
309
|
-
- Disconnect on lifecycle abort.
|
|
310
|
-
- Guard callback with `if (defaultSignal.aborted) return;` because queued mutation callbacks can still run after `disconnect()`.
|
|
311
|
-
|
|
312
|
-
Matching:
|
|
313
|
-
|
|
314
|
-
- Selector targets are resolved to element lists at registration time.
|
|
315
|
-
- Direct element targets must be inside the shadow root.
|
|
316
|
-
- `watch.attr` matches `attributes` records whose target is the watched element or a matching descendant when appropriate.
|
|
317
|
-
- `watch.kids` matches `childList` records on the target, or descendants when `{ deep: true }`.
|
|
318
|
-
- `watch.text` matches `characterData` records whose parent is the target or inside the target.
|
|
319
|
-
- `watch.tree` receives the raw batch after filtering to the target subtree.
|
|
320
|
-
|
|
321
|
-
### 5.6 Error Behavior
|
|
322
|
-
|
|
323
|
-
Development:
|
|
324
|
-
|
|
325
|
-
- Invalid selector: warn and skip registration.
|
|
326
|
-
- Direct target outside shadow root: throw `WatchError` / `EventBindingError`.
|
|
327
|
-
- Missing target selector: warn and return a no-op disposer.
|
|
328
|
-
- Duplicate refs: warn.
|
|
329
|
-
|
|
330
|
-
Production:
|
|
331
|
-
|
|
332
|
-
- Invalid bindings return a no-op disposer where possible.
|
|
333
|
-
- Do not throw for missing optional DOM. Components may render conditionally.
|
|
334
|
-
|
|
335
|
-
### 5.7 Disposer Return Values
|
|
336
|
-
|
|
337
|
-
All `on.*` and `watch.*` calls should return a disposer:
|
|
338
|
-
|
|
339
|
-
```javascript
|
|
340
|
-
const off = on.click('button', handler);
|
|
341
|
-
off();
|
|
342
|
-
|
|
343
|
-
const stop = watch.attr(refs.submit, 'disabled', handler);
|
|
344
|
-
stop();
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
This does not replace signal cleanup. It gives component code an explicit way to stop a temporary binding early.
|
|
348
|
-
|
|
349
|
-
## 6. Rust Tooling Plan
|
|
350
|
-
|
|
351
|
-
The current Rust scanner lives in `tools/src/extract/html.rs` and is invoked from `extract::run` and the dev watcher.
|
|
352
|
-
|
|
353
|
-
### 6.1 Make HTML Parsing Testable
|
|
354
|
-
|
|
355
|
-
Refactor:
|
|
356
|
-
|
|
357
|
-
```rust
|
|
358
|
-
pub fn parse_html(content: &str) -> TagsDescriptor
|
|
359
|
-
pub fn parse_file(path: &Path) -> anyhow::Result<TagsDescriptor>
|
|
360
|
-
pub fn emit_descriptor(html_path: &Path, descriptor: &TagsDescriptor) -> anyhow::Result<PathBuf>
|
|
361
|
-
pub fn parse_and_emit(html_path: &Path) -> anyhow::Result<PathBuf>
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
Why: the current `parse_and_emit` logs and silently returns. Returning `Result` lets build/watch modes surface real compiler feedback.
|
|
365
|
-
|
|
366
|
-
### 6.2 Descriptor Shape
|
|
367
|
-
|
|
368
|
-
Keep the current fields and add optional metadata:
|
|
369
|
-
|
|
370
|
-
```json
|
|
371
|
-
{
|
|
372
|
-
"version": 1,
|
|
373
|
-
"refs": ["email", "submit"],
|
|
374
|
-
"ids": ["email"],
|
|
375
|
-
"classes": ["btn", "primary"],
|
|
376
|
-
"tags": ["form", "input", "button"],
|
|
377
|
-
"compound": ["button.btn", "input.field"],
|
|
378
|
-
"attrs": ["aria-expanded", "data-state"],
|
|
379
|
-
"refTypes": {
|
|
380
|
-
"email": "HTMLInputElement",
|
|
381
|
-
"submit": "HTMLButtonElement"
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
Required behavior:
|
|
387
|
-
|
|
388
|
-
- Deterministic sorted arrays.
|
|
389
|
-
- HTML5-compliant parsing through `scraper` initially.
|
|
390
|
-
- Duplicate ref diagnostics with file path and ref name.
|
|
391
|
-
- Invalid ref names diagnostics if the name is not a valid JS property identifier; still emit the descriptor so bracket access can be used in the future.
|
|
392
|
-
|
|
393
|
-
### 6.3 CLI and Watch Methods
|
|
394
|
-
|
|
395
|
-
Current binary:
|
|
396
|
-
|
|
397
|
-
```bash
|
|
398
|
-
anza --src src --dist dist --build
|
|
399
|
-
anza --src src --port 3000
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
Target methods:
|
|
403
|
-
|
|
404
|
-
```bash
|
|
405
|
-
anza scan --src src
|
|
406
|
-
anza scan --src src --watch
|
|
407
|
-
anza build --src src --dist dist
|
|
408
|
-
anza dev --src src --port 3000
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
If command subcommands are too large for the first pass, keep the current flags but document them clearly in `usage.md`:
|
|
412
|
-
|
|
413
|
-
```bash
|
|
414
|
-
anza --src src --build
|
|
415
|
-
anza --src src --port 3000
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
Watcher requirements:
|
|
419
|
-
|
|
420
|
-
- On `.html` change: regenerate adjacent `.tags.json`.
|
|
421
|
-
- On `.js` change: regenerate element typings.
|
|
422
|
-
- On `.css` change: send HMR CSS payload.
|
|
423
|
-
- On deleted `.html`: remove stale `.tags.json` if it was generated.
|
|
424
|
-
- Debounce per path, not only per batch, to avoid losing rapid save events.
|
|
425
|
-
|
|
426
|
-
### 6.4 Optional Type Output
|
|
427
|
-
|
|
428
|
-
Generate `index.tags.d.ts` next to `index.tags.json`:
|
|
429
|
-
|
|
430
|
-
```typescript
|
|
431
|
-
export interface TemplateRefs {
|
|
432
|
-
email: HTMLInputElement;
|
|
433
|
-
submit: HTMLButtonElement;
|
|
434
|
-
}
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
This is optional for the runtime but valuable for editor completion.
|
|
438
|
-
|
|
439
|
-
## 7. Integration Steps
|
|
440
|
-
|
|
441
|
-
1. Fix `TagsCache` shape caching.
|
|
442
|
-
2. Extract context creation from `element.js`.
|
|
443
|
-
3. Add descriptor-free `refs` fallback and duplicate diagnostics.
|
|
444
|
-
4. Replace `createEventDelegator` internals with a registry per event type.
|
|
445
|
-
5. Implement `createMutationWatcher`.
|
|
446
|
-
6. Inject `watch` into `mount` and `update`.
|
|
447
|
-
7. Add invalidation hooks for `replaceChildren` and `innerHTML`.
|
|
448
|
-
8. Refactor Rust `parse_and_emit` into testable methods.
|
|
449
|
-
9. Add scanner tests for refs, ids, classes, tags, compound, duplicate refs, and malformed HTML.
|
|
450
|
-
10. Add browser tests for `tags`, `refs`, `on`, and `watch`.
|
|
451
|
-
11. Update `usage.md` with all supported public usage.
|
|
452
|
-
|
|
453
|
-
## 8. Test Plan
|
|
454
|
-
|
|
455
|
-
JavaScript runtime tests:
|
|
456
|
-
|
|
457
|
-
- `tags.one()` returns an element and caches it.
|
|
458
|
-
- `tags.all()` returns an array and does not collide with `one()`.
|
|
459
|
-
- `tags.each()` iterates with stable indexes.
|
|
460
|
-
- Cache clears after `replaceChildren`.
|
|
461
|
-
- `refs` builds from descriptor.
|
|
462
|
-
- `refs` falls back to scanning `[ref]`.
|
|
463
|
-
- Duplicate refs warn in development.
|
|
464
|
-
- `on.click` delegates from child content to the matching ancestor.
|
|
465
|
-
- Multiple `on.click` bindings share one root listener.
|
|
466
|
-
- `on.click.once` removes itself after first fire.
|
|
467
|
-
- Custom signal abort removes only that binding.
|
|
468
|
-
- Lifecycle abort removes all bindings.
|
|
469
|
-
- `watch.attr` receives `(attr, next, prev, el)`.
|
|
470
|
-
- `watch.kids` receives arrays for added/removed nodes.
|
|
471
|
-
- `watch.kids({ deep: true })` sees descendant child changes.
|
|
472
|
-
- `watch.text` receives text changes.
|
|
473
|
-
- `watch.tree` receives raw records.
|
|
474
|
-
- `watch.*.once` removes only its registration.
|
|
475
|
-
- `watch` ignores queued mutations after lifecycle abort.
|
|
476
|
-
|
|
477
|
-
Rust tests:
|
|
478
|
-
|
|
479
|
-
- Parses refs, ids, class names, tag names, and compound selectors.
|
|
480
|
-
- Emits sorted deterministic JSON.
|
|
481
|
-
- Handles malformed fragments without panicking.
|
|
482
|
-
- Warns on duplicate refs.
|
|
483
|
-
- Rebuilds descriptors on HTML watcher events.
|
|
484
|
-
- Build mode copies source then emits descriptors.
|
|
485
|
-
|
|
486
|
-
## 9. Usage Documentation Requirements
|
|
487
|
-
|
|
488
|
-
`usage.md` must cover:
|
|
489
|
-
|
|
490
|
-
- `ui.element`
|
|
491
|
-
- `ui.container`
|
|
492
|
-
- `props` and `update`
|
|
493
|
-
- `refs`
|
|
494
|
-
- `tags.one`, `tags.all`, `tags.each`, `tags.clear`
|
|
495
|
-
- `on.event`, custom event names, `.once`, signals/options
|
|
496
|
-
- `watch.attr`, `watch.kids`, `watch.text`, `watch.tree`, `.once`, direct refs, custom signals/options
|
|
497
|
-
- `ui.schedule`, `ui.scheduleFrame`, `ui.yield`
|
|
498
|
-
- `ui.observe.resize`, `intersection`, `mutation`, `performance`
|
|
499
|
-
- `ui.transition`
|
|
500
|
-
- `ui.template`
|
|
501
|
-
- `anza` scan/build/dev behavior
|
|
502
|
-
- Escape hatches and when to use raw platform APIs
|
|
503
|
-
|
|
504
|
-
## 10. Non-Goals
|
|
505
|
-
|
|
506
|
-
- No runtime WASM DOM access. Rust stays in build/dev tooling.
|
|
507
|
-
- No virtual DOM.
|
|
508
|
-
- No framework-style global reactive auto-tracking in the UI runtime.
|
|
509
|
-
- No closed shadow roots.
|
|
510
|
-
- No cross-component DOM watching. Components communicate outward with composed `CustomEvent`s and downward through properties or methods.
|