@adukiorg/anza 0.2.0 → 0.2.3
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 +90 -4
- package/README.md +97 -133
- package/bin/anza/anza-linux-arm64 +0 -0
- package/bin/anza/anza-linux-x64 +0 -0
- package/bin/anza/anza-macos-arm64 +0 -0
- package/bin/anza/anza-macos-x64 +0 -0
- package/bin/anza/anza-windows-x64.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
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# Implementation Tasks
|
|
2
|
+
|
|
3
|
+
Comprehensive task list derived from `myaudit.md` and `../ui/definations.md`. Covers the router overhaul, the new definition layer (`page`/`dock`/`view`/`part`), and the Rust toolchain changes required to support them.
|
|
4
|
+
|
|
5
|
+
Companion documents:
|
|
6
|
+
|
|
7
|
+
- `myaudit.md` — architecture audit, proposed fixes, data structures, edge-case bugs
|
|
8
|
+
- `../ui/definations.md` — unified `page`/`dock`/`view`/`part` API specification
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Phase 1 — Boot Gate
|
|
13
|
+
|
|
14
|
+
**Goal.** Survive hard refresh. Defer the router's initial `'found'` emit until routes are registered and containers are mounted.
|
|
15
|
+
|
|
16
|
+
### 1.1 Create `router/boot.js`
|
|
17
|
+
|
|
18
|
+
New module. Exports `gate(promise)`, `boot(emitFn)`, `ready()`.
|
|
19
|
+
|
|
20
|
+
- `gate` accepts a `Promise` that must resolve before the router emits its first match.
|
|
21
|
+
- `boot` waits for `DOMContentLoaded` (if `document.readyState === 'loading'`), then `Promise.all(gates)`, then calls `emitFn`.
|
|
22
|
+
- `ready` returns `true` after boot completes.
|
|
23
|
+
|
|
24
|
+
**APIs used.** `document.readyState`, `DOMContentLoaded` event, `Promise.all`.
|
|
25
|
+
|
|
26
|
+
### 1.2 Update `router/intercept.js`
|
|
27
|
+
|
|
28
|
+
Replace the `Promise.resolve().then(...)` initial-emit block inside `setup()` with a call to `boot()`.
|
|
29
|
+
|
|
30
|
+
Remove the current microtask:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// DELETE
|
|
34
|
+
Promise.resolve().then(async () => { ... });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Replace with:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import { boot, ready } from './boot.js';
|
|
41
|
+
|
|
42
|
+
// inside setup(), after attaching listeners:
|
|
43
|
+
boot(async () => {
|
|
44
|
+
const url = window.navigation.currentEntry?.url || location.href;
|
|
45
|
+
const found = await match(url);
|
|
46
|
+
if (found) emit('found', { ...found, direction: 'load' });
|
|
47
|
+
else emit('notfound', { url });
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 1.3 Wire boot gate into element registration
|
|
52
|
+
|
|
53
|
+
In the new `defs/page.js` and `defs/dock.js` (Phase 6), after `customElements.define`, call:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
import { gate } from '../router/boot.js';
|
|
57
|
+
gate(customElements.whenDefined(tag));
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Until Phase 6 exists, wire it into the existing `ui/define/element.js` after `customElements.define(tag, DeclarativeElement)`:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
if (spec.url) {
|
|
64
|
+
gate(customElements.whenDefined(tag));
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Phase 2 — Window Bridge
|
|
71
|
+
|
|
72
|
+
**Goal.** Attach the router to `window` so non-module scripts and devtools can access it.
|
|
73
|
+
|
|
74
|
+
### 2.1 Update `router/index.js`
|
|
75
|
+
|
|
76
|
+
In the auto-bootstrap block, add before `setup()`:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
Object.defineProperty(window, 'router', {
|
|
80
|
+
value: router,
|
|
81
|
+
writable: false,
|
|
82
|
+
enumerable: false,
|
|
83
|
+
configurable: false
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Phase 3 — Container Graph
|
|
90
|
+
|
|
91
|
+
**Goal.** Replace the flat `Map<string, WeakRef>` with a hierarchical tree that enables LCA traversal and cascade mounting.
|
|
92
|
+
|
|
93
|
+
### 3.1 Create `router/graph.js`
|
|
94
|
+
|
|
95
|
+
New module. Data structure:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
Node { name, ref: WeakRef, parent: Node|null, children: Set<Node>, depth: number }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Exports:
|
|
102
|
+
|
|
103
|
+
- `add(name, element, parent = 'body')` — insert a node, update parent's children set
|
|
104
|
+
- `remove(name)` — delete a node, reparent orphans to grandparent
|
|
105
|
+
- `get(name)` — return `Node` or `null`
|
|
106
|
+
- `element(name)` — return `HTMLElement` via `WeakRef.deref()` or `null`
|
|
107
|
+
- `clear()` — reset the tree to root-only
|
|
108
|
+
|
|
109
|
+
Virtual root node `'body'` is created at module load. `FinalizationRegistry` prunes stale nodes.
|
|
110
|
+
|
|
111
|
+
### 3.2 Update `router/container.js`
|
|
112
|
+
|
|
113
|
+
Rewrite to delegate to `graph.js`. Keep the same public API signatures for backward compatibility:
|
|
114
|
+
|
|
115
|
+
- `registerContainer(name, element, parent)` → calls `graph.add`
|
|
116
|
+
- `unregisterContainer(name, element)` → calls `graph.remove`
|
|
117
|
+
- `getContainer(name)` → calls `graph.element`
|
|
118
|
+
- `clearContainers()` → calls `graph.clear`
|
|
119
|
+
|
|
120
|
+
Remove the `MutationObserver` logic (docks self-register in `connectedCallback`; no polling needed).
|
|
121
|
+
|
|
122
|
+
### 3.3 Add `parent` parameter to container registration
|
|
123
|
+
|
|
124
|
+
`registerContainer` gains an optional third argument `parent`. Default: `'body'`. Passed through to `graph.add`.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Phase 4 — LCA Algorithm
|
|
129
|
+
|
|
130
|
+
**Goal.** Enable cross-branch navigation by computing the lowest common ancestor between two containers.
|
|
131
|
+
|
|
132
|
+
### 4.1 Create `router/lca.js`
|
|
133
|
+
|
|
134
|
+
Exports:
|
|
135
|
+
|
|
136
|
+
- `lca(a, b)` — depth-equalisation + tandem walk, returns `Node` or `null`
|
|
137
|
+
- `path(from, to)` — returns `[Node, ...]` from ancestor to target
|
|
138
|
+
|
|
139
|
+
O(d) complexity where d = tree depth.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Phase 5 — Cascade Mount
|
|
144
|
+
|
|
145
|
+
**Goal.** When a target container is absent, sequentially mount intermediate containers from the nearest live ancestor down to the target.
|
|
146
|
+
|
|
147
|
+
### 5.1 Create `router/cascade.js`
|
|
148
|
+
|
|
149
|
+
Exports:
|
|
150
|
+
|
|
151
|
+
- `ensure(target, current)` — walk `path()`, find deepest mounted node, sequentially `createElement` + `swap`/`replaceChildren` + `rAF` yield for each missing level.
|
|
152
|
+
|
|
153
|
+
Uses `customElements.whenDefined`, `document.createElement`, `requestAnimationFrame`.
|
|
154
|
+
|
|
155
|
+
### 5.2 Update `router/intercept.js` handler
|
|
156
|
+
|
|
157
|
+
Replace the hard `throw` on missing container with cascade logic:
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
const chain = meta.via ?? (meta.container ? [meta.container] : []);
|
|
161
|
+
const target = chain.at(-1);
|
|
162
|
+
|
|
163
|
+
for (let i = 0; i < chain.length; i++) {
|
|
164
|
+
if (!element(chain[i])) {
|
|
165
|
+
await ensure(chain[i], chain[i - 1] ?? 'body');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Phase 6 — Definition Layer (`defs/`)
|
|
173
|
+
|
|
174
|
+
**Goal.** Implement the `page`, `dock`, `view`, `part` definition functions from `definations.md`. These supersede the split `ui.element` + `router.register` pattern.
|
|
175
|
+
|
|
176
|
+
### 6.1 Create `ui/defs/page.js`
|
|
177
|
+
|
|
178
|
+
`page(route, config, base)` — route-bound element.
|
|
179
|
+
|
|
180
|
+
Internal steps:
|
|
181
|
+
|
|
182
|
+
1. Normalize `config.template` — detect file paths (strings ending in `.html`/`.css` or starting with `./`) vs inline strings.
|
|
183
|
+
2. If file paths: resolve against `base` (the caller's `import.meta.url`).
|
|
184
|
+
3. Define an `HTMLElement` subclass. Attach shadow DOM, preload resources via `preloadResources()`.
|
|
185
|
+
4. Install `on.load`/`on.unload`/`on.connect`/`on.disconnect` lifecycle hooks.
|
|
186
|
+
5. Install `config.props` as reactive observed attributes/properties.
|
|
187
|
+
6. Call `customElements.define(config.tag, cls)`.
|
|
188
|
+
7. Call `router.register(route, config.tag, { via: config.via, props: config.props, query: config.query })`.
|
|
189
|
+
8. Register spec in `specRegistry`.
|
|
190
|
+
9. Call `gate(customElements.whenDefined(config.tag))`.
|
|
191
|
+
10. If `config.guard` is defined, wrap it as a route-scoped guard on `router.guard`.
|
|
192
|
+
|
|
193
|
+
### 6.2 Create `ui/defs/dock.js`
|
|
194
|
+
|
|
195
|
+
`dock(name, config)` — container element.
|
|
196
|
+
|
|
197
|
+
Internal steps:
|
|
198
|
+
|
|
199
|
+
1. Derive tag from `config.tag ?? 'dock-' + name`.
|
|
200
|
+
2. Define an `HTMLElement` subclass. If template omitted, use passthrough `<slot></slot>`.
|
|
201
|
+
3. In `connectedCallback`: call `graph.add(name, this, config.parent ?? 'body')`.
|
|
202
|
+
4. In `disconnectedCallback`: call `graph.remove(name)`.
|
|
203
|
+
5. Attach `swap` method — either `config.on.swap` override or the default view-transition implementation.
|
|
204
|
+
6. Call `customElements.define(tag, cls)`.
|
|
205
|
+
7. Call `gate(customElements.whenDefined(tag))`.
|
|
206
|
+
|
|
207
|
+
### 6.3 Create `ui/defs/view.js`
|
|
208
|
+
|
|
209
|
+
`view(tag, config, base)` — composable stateful component. No route, no graph.
|
|
210
|
+
|
|
211
|
+
Same element-definition logic as `page` minus routing and container registration. Supports `props`, `template`, `on.connect`/`on.disconnect`/`on.change`, `methods`.
|
|
212
|
+
|
|
213
|
+
### 6.4 Create `ui/defs/part.js`
|
|
214
|
+
|
|
215
|
+
`part(tag, config, base)` — atomic stateless primitive.
|
|
216
|
+
|
|
217
|
+
Minimal subset of `view`. No reactive updates, no `on.change`. Constructor-time attribute reads, static shadow DOM.
|
|
218
|
+
|
|
219
|
+
### 6.5 Create `ui/defs/index.js`
|
|
220
|
+
|
|
221
|
+
Facade exporting `{ page, dock, view, part }`.
|
|
222
|
+
|
|
223
|
+
### 6.6 Update `ui/define/orchestrator.js`
|
|
224
|
+
|
|
225
|
+
Update the `'found'` listener to understand `via` chains (from `meta.via`) instead of a single `container` string. Use the cascade logic from Phase 5.
|
|
226
|
+
|
|
227
|
+
### 6.7 Deprecate (do not remove) existing APIs
|
|
228
|
+
|
|
229
|
+
`ui.element` and `ui.container` remain functional. Add a console info message on first use suggesting migration to `page`/`dock`/`view`/`part`. Do not break existing code.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Phase 7 — Trie Route Matcher
|
|
234
|
+
|
|
235
|
+
**Goal.** O(k) route matching where k = URL segment count, replacing O(n) linear scan.
|
|
236
|
+
|
|
237
|
+
### 7.1 Create `router/trie.js`
|
|
238
|
+
|
|
239
|
+
`Segment` class with `static: Map`, `param: Segment|null`, `wild: Segment|null`, `route`, `key`.
|
|
240
|
+
|
|
241
|
+
Exports:
|
|
242
|
+
|
|
243
|
+
- `insert(pattern, route)` — decompose pattern into segments, walk/create trie nodes
|
|
244
|
+
- `find(pathname)` — walk trie with backtracking, return `{ route, params }` or `null`
|
|
245
|
+
|
|
246
|
+
Priority order: static > param > wildcard (matching the URLPattern specificity model).
|
|
247
|
+
|
|
248
|
+
### 7.2 Integrate into `router/match.js`
|
|
249
|
+
|
|
250
|
+
On `register()`, insert into the trie alongside the existing `routes[]` array. On `match()`, try trie first; fall back to URLPattern linear scan for patterns containing regex groups or modifiers the trie cannot express.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Phase 8 — Edge-Case Bug Fixes
|
|
255
|
+
|
|
256
|
+
### 8.1 MutationObserver starvation (`container.js`)
|
|
257
|
+
|
|
258
|
+
Add `{ timeout: 100 }` to `requestIdleCallback`. Fall back to `requestAnimationFrame` when `requestIdleCallback` is unavailable.
|
|
259
|
+
|
|
260
|
+
### 8.2 WeakRef timing hole (`graph.js`)
|
|
261
|
+
|
|
262
|
+
Track explicit unregisters in a `Set<string>`. Return `undefined` from `element()` if the name is in the set. Clear the entry after one macrotask via `setTimeout(..., 0)`.
|
|
263
|
+
|
|
264
|
+
### 8.3 Cycle detection (`match.js`)
|
|
265
|
+
|
|
266
|
+
Add a `visited` Set to the parent-chain walker. Break on cycle.
|
|
267
|
+
|
|
268
|
+
### 8.4 Concurrent navigation race (`defs/dock.js`)
|
|
269
|
+
|
|
270
|
+
In the default `swap` method, call `this._tx?.skipTransition()` before starting a new view transition. Clear `this._tx` on completion.
|
|
271
|
+
|
|
272
|
+
### 8.5 Tab sync redirect amplification (`sync/tab.js`)
|
|
273
|
+
|
|
274
|
+
Delay `isSyncing = false` by one macrotask using `setTimeout(() => { isSyncing = false }, 0)` to cover guard-triggered redirects.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Phase 9 — Rust Toolchain Changes
|
|
279
|
+
|
|
280
|
+
**Goal.** The `anza` CLI must understand the new `page`/`dock`/`view`/`part` definition functions and the native `.html`/`.css` file convention.
|
|
281
|
+
|
|
282
|
+
### 9.1 Update `ExtractedSpec` struct (`types/runner.rs`)
|
|
283
|
+
|
|
284
|
+
Add new fields to `ExtractedSpec`:
|
|
285
|
+
|
|
286
|
+
```rust
|
|
287
|
+
pub struct ExtractedSpec {
|
|
288
|
+
pub tag: String,
|
|
289
|
+
pub kind: String, // "page" | "dock" | "view" | "part" | "element" | "container"
|
|
290
|
+
pub props: HashMap<String, PropConfig>,
|
|
291
|
+
pub methods: Vec<String>,
|
|
292
|
+
pub url: Option<String>, // page only
|
|
293
|
+
pub container: Option<String>, // backward compat
|
|
294
|
+
pub via: Vec<String>, // page: ordered dock chain
|
|
295
|
+
pub parent: Option<String>, // dock: parent dock name
|
|
296
|
+
pub meta: HashMap<String, String>,
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### 9.2 Update element extraction (`extract/runner.rs`)
|
|
301
|
+
|
|
302
|
+
The `ElementVisitor` currently only recognises `ui.element(...)` and `ui.container(...)`. Expand to also match:
|
|
303
|
+
|
|
304
|
+
- `page(route, config)` — top-level function call, not `ui.page`
|
|
305
|
+
- `dock(name, config)` — top-level function call
|
|
306
|
+
- `view(tag, config)` — top-level function call
|
|
307
|
+
- `part(tag, config)` — top-level function call
|
|
308
|
+
|
|
309
|
+
For each, extract:
|
|
310
|
+
|
|
311
|
+
| Call | `tag` source | `url` source | `via` source | `parent` source | `kind` |
|
|
312
|
+
|------|-------------|-------------|-------------|----------------|--------|
|
|
313
|
+
| `page(route, { tag, ... })` | `config.tag` | first argument (`route`) | `config.via` | — | `"page"` |
|
|
314
|
+
| `dock(name, { tag, ... })` | `config.tag` or `'dock-' + name` | — | — | `config.parent` | `"dock"` |
|
|
315
|
+
| `view(tag, config)` | first argument | — | — | — | `"view"` |
|
|
316
|
+
| `part(tag, config)` | first argument | — | — | — | `"part"` |
|
|
317
|
+
|
|
318
|
+
Update `parse_spec_object` to also extract:
|
|
319
|
+
|
|
320
|
+
- `via` — array of string literals
|
|
321
|
+
- `parent` — string literal
|
|
322
|
+
- `template.html` and `template.css` — when `template` is an object with `html`/`css` string properties that are file paths
|
|
323
|
+
|
|
324
|
+
### 9.3 Update asset resolution in build graph (`build/graph.rs`)
|
|
325
|
+
|
|
326
|
+
The `Collector::scan_spec` method currently looks for top-level `template` and `style` string literals:
|
|
327
|
+
|
|
328
|
+
```rust
|
|
329
|
+
"template" | "style" => {
|
|
330
|
+
if let Expr::Lit(Lit::Str(s)) = &*kv.value { ... }
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Add handling for when `template` is an **object** with `html` and `css` properties:
|
|
335
|
+
|
|
336
|
+
```rust
|
|
337
|
+
"template" => {
|
|
338
|
+
match &*kv.value {
|
|
339
|
+
// Existing: template: './file.html'
|
|
340
|
+
Expr::Lit(Lit::Str(s)) => { /* existing logic */ }
|
|
341
|
+
// New: template: { html: './template.html', css: './style.css' }
|
|
342
|
+
Expr::Object(obj) => {
|
|
343
|
+
for prop in &obj.props {
|
|
344
|
+
if let PropOrSpread::Prop(p) = prop {
|
|
345
|
+
if let Prop::KeyValue(inner_kv) = &**p {
|
|
346
|
+
if let PropName::Ident(inner_key) = &inner_kv.key {
|
|
347
|
+
if (inner_key.sym == "html" || inner_key.sym == "css") {
|
|
348
|
+
if let Expr::Lit(Lit::Str(s)) = &*inner_kv.value {
|
|
349
|
+
if let Some(v) = str_value(s) {
|
|
350
|
+
if v.starts_with("./") || v.starts_with("../") || v.starts_with('/') {
|
|
351
|
+
self.assets.push((v, s.span));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
_ => {}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Also recognise the new top-level call patterns (`page(...)`, `dock(...)`, `view(...)`, `part(...)`) so that their `template` objects are scanned for asset paths. Currently only `ui.element(...)` and `ui.container(...)` trigger `scan_spec`.
|
|
367
|
+
|
|
368
|
+
### 9.4 Update route manifest (`extract/routes.rs`)
|
|
369
|
+
|
|
370
|
+
Extend `RouteRecord` to include `via`:
|
|
371
|
+
|
|
372
|
+
```rust
|
|
373
|
+
struct RouteRecord<'a> {
|
|
374
|
+
tag: &'a str,
|
|
375
|
+
path: &'a str,
|
|
376
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
377
|
+
container: Option<&'a str>,
|
|
378
|
+
#[serde(skip_serializing_if = "Vec::is_empty")]
|
|
379
|
+
via: &'a Vec<String>,
|
|
380
|
+
params: Vec<String>,
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Update `routes.json` output to include the `via` array for pages. Update `routes.d.ts` `RouteMap` interface to type-check `via`.
|
|
385
|
+
|
|
386
|
+
### 9.5 Update type generation
|
|
387
|
+
|
|
388
|
+
Generate interfaces for all four definition types, not just `ui.element`. The `kind` field in `ExtractedSpec` distinguishes them. For docks, generate a `swap` method in the interface:
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
export interface DockSidebarElement extends HTMLElement {
|
|
392
|
+
swap(el: HTMLElement, options?: { direction?: string }): Promise<void>;
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### 9.6 Update `doctor` command (`main.rs`)
|
|
397
|
+
|
|
398
|
+
Add checks for:
|
|
399
|
+
|
|
400
|
+
- `src/docks/` directory (dock definitions)
|
|
401
|
+
- `src/pages/` directory (page definitions)
|
|
402
|
+
- `src/views/` directory (view definitions)
|
|
403
|
+
- `src/parts/` directory (part definitions)
|
|
404
|
+
|
|
405
|
+
Warn (don't error) if `src/elements/` exists alongside `src/pages/` — suggest migration.
|
|
406
|
+
|
|
407
|
+
### 9.7 Update watcher HMR classification (`watcher/runner.rs`)
|
|
408
|
+
|
|
409
|
+
`.html` and `.css` changes inside component folders should trigger the correct HMR message kind (`Html` or `Css`). The existing classification already handles this by extension, but verify that changes to `template.html` and `style.css` inside `pages/profile/` correctly propagate.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Phase 10 — Cleanup & Migration
|
|
414
|
+
|
|
415
|
+
### 10.1 Update `<route-outlet>` (`router/outlet.js`)
|
|
416
|
+
|
|
417
|
+
`<route-outlet>` remains functional but is deprecated. Add a console info suggesting migration to `<dock-*>` elements on first `connectedCallback`.
|
|
418
|
+
|
|
419
|
+
### 10.2 Update `ui/define/index.js`
|
|
420
|
+
|
|
421
|
+
Export `page`, `dock`, `view`, `part` alongside `define`, `element`, `container`.
|
|
422
|
+
|
|
423
|
+
### 10.3 Update package exports (`library/package.json`)
|
|
424
|
+
|
|
425
|
+
Add `./defs` export path mapping to `src/core/ui/defs/index.js`.
|
|
426
|
+
|
|
427
|
+
### 10.4 Update documentation
|
|
428
|
+
|
|
429
|
+
- `router/usage.md` — add sections for `page`, `dock`, `via` chains, cascade mounting
|
|
430
|
+
- `ui/usage.md` — add `page`/`dock`/`view`/`part` API documentation
|
|
431
|
+
- `router/plan.md` — mark completed sections, add new architecture diagrams
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Dependency Order
|
|
436
|
+
|
|
437
|
+
```text
|
|
438
|
+
Phase 1 (boot.js)
|
|
439
|
+
└─ Phase 2 (window bridge) — independent
|
|
440
|
+
└─ Phase 3 (graph.js)
|
|
441
|
+
└─ Phase 4 (lca.js)
|
|
442
|
+
└─ Phase 5 (cascade.js)
|
|
443
|
+
└─ Phase 6 (defs/)
|
|
444
|
+
└─ Phase 9 (Rust toolchain)
|
|
445
|
+
└─ Phase 10 (cleanup)
|
|
446
|
+
└─ Phase 7 (trie.js) — independent
|
|
447
|
+
└─ Phase 8 (bug fixes) — independent
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Phases 1, 2, 7, and 8 can run in parallel. Phases 3-6 are sequential. Phase 9 (Rust) can start after Phase 6 API is finalized. Phase 10 runs last.
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Verification
|
|
455
|
+
|
|
456
|
+
Each phase must pass before proceeding:
|
|
457
|
+
|
|
458
|
+
| Phase | Verification |
|
|
459
|
+
| ----- | ------------ |
|
|
460
|
+
| 1 | Hard refresh on a registered route renders correctly. No console errors on cold boot. |
|
|
461
|
+
| 2 | `window.router` is available in devtools console. `window.router === router` is `true`. |
|
|
462
|
+
| 3 | `graph.add('a', el, 'body')` + `graph.get('a')` returns the node. `graph.element('a')` returns the element. |
|
|
463
|
+
| 4 | `lca('sidebar', 'settings')` returns the correct common ancestor. `path('body', 'settings')` returns the correct chain. |
|
|
464
|
+
| 5 | Navigate to a route whose dock chain has 3 levels, none mounted. All three mount sequentially. Page renders. |
|
|
465
|
+
| 6 | `page('/test', { tag: 'page-test', via: ['main'], template: { html: './t.html', css: './s.css' } }, import.meta.url)` defines element, registers route, gates boot. |
|
|
466
|
+
| 7 | 100 routes registered. `match('/route/99')` returns in < 0.1ms. |
|
|
467
|
+
| 8 | Tab sync with redirect guard does not loop. Rapid double-click navigation does not throw. |
|
|
468
|
+
| 9 | `anza scan` extracts specs from `page(...)` calls. `routes.json` includes `via` arrays. Build graph resolves `template.html` and `style.css` inside `template: {}` objects. |
|
|
469
|
+
| 10 | `<route-outlet>` still works. `ui.element` still works. No regressions. |
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
*End of tasks.*
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
## 1. Choosing an API
|
|
30
30
|
|
|
31
31
|
| Need | Use |
|
|
32
|
-
|
|
32
|
+
|------|-----|
|
|
33
33
|
| Register a route | `router.register` |
|
|
34
34
|
| Bulk register routes from JSON | `router.load` |
|
|
35
35
|
| Match a URL manually | `router.match` |
|
|
@@ -114,7 +114,7 @@ A handler is exactly one of the following shapes. This single contract is shared
|
|
|
114
114
|
by `match()` and the navigation interceptor, so a handler is never invoked twice:
|
|
115
115
|
|
|
116
116
|
| Shape | Meaning |
|
|
117
|
-
|
|
117
|
+
|-------|---------|
|
|
118
118
|
| `'page-tag'` | static element tag |
|
|
119
119
|
| `async () => 'page-tag'` | lazy tag factory (zero arity) |
|
|
120
120
|
| `{ tag: 'page-tag' }` | static tag (object form) |
|
|
@@ -399,44 +399,65 @@ router.miss.clear();
|
|
|
399
399
|
|
|
400
400
|
## 10. Declarative UI Routes
|
|
401
401
|
|
|
402
|
-
|
|
402
|
+
Import `page` and `dock` from the definition layer and declare the route graph
|
|
403
|
+
explicitly.
|
|
403
404
|
|
|
404
405
|
```javascript
|
|
405
|
-
import {
|
|
406
|
-
|
|
407
|
-
ui.element('page-member', {
|
|
408
|
-
url: '/members/:member',
|
|
409
|
-
container: 'main',
|
|
410
|
-
|
|
411
|
-
props: {
|
|
412
|
-
member: { type: String, default: '' }
|
|
413
|
-
},
|
|
406
|
+
import { page, dock } from '@adukiorg/anza/defs';
|
|
414
407
|
|
|
415
|
-
|
|
416
|
-
|
|
408
|
+
// A persistent container shell, registered in the graph under 'main'.
|
|
409
|
+
dock('main', { parent: 'body' });
|
|
417
410
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
411
|
+
// A route-bound page that renders through the 'main' → 'content' chain.
|
|
412
|
+
page('/members/:id', {
|
|
413
|
+
tag: 'page-member',
|
|
414
|
+
via: ['main', 'content'],
|
|
415
|
+
template: { html: './member.html', css: './member.css' },
|
|
416
|
+
props: { id: { type: Number } },
|
|
417
|
+
on: {
|
|
418
|
+
load({ params }) { return loadMember(params.id); }
|
|
426
419
|
}
|
|
427
420
|
}, import.meta.url);
|
|
428
421
|
```
|
|
429
422
|
|
|
430
423
|
What happens:
|
|
431
424
|
|
|
432
|
-
1. `
|
|
425
|
+
1. `page()` calls `router.register(url, tag, meta)`.
|
|
433
426
|
2. The router matches the URL.
|
|
434
427
|
3. The UI orchestrator listens for `found`.
|
|
435
|
-
4. The orchestrator finds the named container.
|
|
428
|
+
4. The orchestrator finds the named container (from the `via` chain).
|
|
436
429
|
5. The page element is created and params are assigned as properties.
|
|
437
430
|
6. If the same page tag is already mounted, params are updated on the existing instance.
|
|
438
431
|
|
|
439
|
-
|
|
432
|
+
### Why `via` chains matter
|
|
433
|
+
|
|
434
|
+
A page declares the ordered root-to-leaf container chain it renders through.
|
|
435
|
+
Missing containers are mounted automatically by the cascade rather than
|
|
436
|
+
throwing. Docks register parent/child relationships in a hierarchical graph,
|
|
437
|
+
enabling lowest-common-ancestor traversal on cross-branch navigation.
|
|
438
|
+
|
|
439
|
+
### Cascade mounting
|
|
440
|
+
|
|
441
|
+
When a navigation targets a container chain whose intermediate containers are
|
|
442
|
+
not yet mounted, the interceptor walks the graph path from the deepest live
|
|
443
|
+
ancestor down to the target, creating each missing dock in order and yielding a
|
|
444
|
+
frame between each so `connectedCallback` (and graph self-registration) runs.
|
|
445
|
+
This is what makes `via: ['main', 'sidebar', 'settings-panel']` work on a cold
|
|
446
|
+
hard refresh.
|
|
447
|
+
|
|
448
|
+
### Boot gate
|
|
449
|
+
|
|
450
|
+
`page()` and `dock()` register a prerequisite on the boot gate so the initial
|
|
451
|
+
match waits for element definitions. A hard refresh on a deep route builds its
|
|
452
|
+
own layout instead of erroring.
|
|
453
|
+
|
|
454
|
+
### Native files
|
|
455
|
+
|
|
456
|
+
`template: { html: './t.html', css: './s.css' }` resolves relative to the third
|
|
457
|
+
`import.meta.url` argument, giving full IDE support.
|
|
458
|
+
|
|
459
|
+
The native toolchain also emits `routes.json` for declarative routes during `scan`,
|
|
460
|
+
`build`, and `dev`.
|
|
440
461
|
|
|
441
462
|
```json
|
|
442
463
|
{
|
|
@@ -446,6 +467,7 @@ The native toolchain also emits `routes.json` for declarative routes during `sca
|
|
|
446
467
|
"tag": "page-member",
|
|
447
468
|
"path": "/members/:member",
|
|
448
469
|
"container": "main",
|
|
470
|
+
"via": ["main"],
|
|
449
471
|
"params": ["member"]
|
|
450
472
|
}
|
|
451
473
|
]
|
|
@@ -454,19 +476,19 @@ The native toolchain also emits `routes.json` for declarative routes during `sca
|
|
|
454
476
|
|
|
455
477
|
## 11. Containers
|
|
456
478
|
|
|
457
|
-
Use `
|
|
479
|
+
Use `dock` for router-owned layout slots. It registers the container in the
|
|
480
|
+
hierarchical graph and supports LCA traversal and cascade mounting.
|
|
458
481
|
|
|
459
482
|
```javascript
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
});
|
|
483
|
+
import { dock } from '@adukiorg/anza/defs';
|
|
484
|
+
|
|
485
|
+
dock('app-main', { parent: 'body' });
|
|
464
486
|
```
|
|
465
487
|
|
|
466
488
|
Mount it in HTML:
|
|
467
489
|
|
|
468
490
|
```html
|
|
469
|
-
<app-main
|
|
491
|
+
<app-main></app-main>
|
|
470
492
|
```
|
|
471
493
|
|
|
472
494
|
The container registers itself with the router using its `name` attribute or tag name.
|
|
@@ -633,7 +655,7 @@ The router wraps route event work in `transitions.run`.
|
|
|
633
655
|
import { transitions } from '@adukiorg/anza/router';
|
|
634
656
|
|
|
635
657
|
await transitions.run(() => {
|
|
636
|
-
|
|
658
|
+
container.replaceChildren(page);
|
|
637
659
|
});
|
|
638
660
|
```
|
|
639
661
|
|
|
@@ -641,7 +663,7 @@ Shared element source:
|
|
|
641
663
|
|
|
642
664
|
```javascript
|
|
643
665
|
await transitions.run(() => {
|
|
644
|
-
|
|
666
|
+
container.replaceChildren(detail);
|
|
645
667
|
}, {
|
|
646
668
|
sourceElement: card,
|
|
647
669
|
name: 'selected-card'
|
|
@@ -756,8 +778,8 @@ Polyfills exist in `core/platform` for Navigation API and URLPattern. Other APIs
|
|
|
756
778
|
|
|
757
779
|
- Use `router.register` for manual routes.
|
|
758
780
|
- Use `router.load` to bulk-register routes from JSON configuration.
|
|
759
|
-
- Use `
|
|
760
|
-
- Use `
|
|
781
|
+
- Use `page()` for declarative route-bound elements.
|
|
782
|
+
- Use `dock()` for named layout containers.
|
|
761
783
|
- Use `router.on('found')`, `router.on('notfound')`, and `router.on('error')` for events.
|
|
762
784
|
- Use `router.guard` for client-side route gates.
|
|
763
785
|
- Use `router.guards.clear()` in tests or teardown.
|
|
@@ -44,12 +44,14 @@ export function start(router) {
|
|
|
44
44
|
isSyncing = true;
|
|
45
45
|
const navResult = router.navigate(url, { state });
|
|
46
46
|
|
|
47
|
+
// Delay clearing isSyncing by one macrotask so a guard-triggered redirect
|
|
48
|
+
// (whose URL differs from `sent`) does not slip past the echo guard and
|
|
49
|
+
// re-broadcast, amplifying across every open tab (RT bug 8.5).
|
|
50
|
+
const finish = () => setTimeout(() => { isSyncing = false; }, 0);
|
|
47
51
|
if (navResult?.finished) {
|
|
48
|
-
navResult.finished.
|
|
49
|
-
isSyncing = false;
|
|
50
|
-
}).catch(() => {});
|
|
52
|
+
navResult.finished.then(finish, finish);
|
|
51
53
|
} else {
|
|
52
|
-
|
|
54
|
+
finish();
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
};
|