@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
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
# Router Architecture Audit (Original Draft)
|
|
2
|
+
|
|
3
|
+
> **Superseded by `myaudit.md`.** This file is the original machine-generated draft. The canonical audit is `myaudit.md`, which incorporates all findings from this document plus the unified `page`/`dock`/`view` definitions from `../ui/definations.md`. Refer to `myaudit.md` for the authoritative analysis and implementation plan.
|
|
4
|
+
|
|
5
|
+
Critical analysis of the Anza client-side router architecture, proposed fixes using native browser APIs, and high-performance data structures for container graph traversal and route matching.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Critical Analysis
|
|
10
|
+
|
|
11
|
+
### 1.1. Hard Refresh Failures
|
|
12
|
+
|
|
13
|
+
**The Bug:**
|
|
14
|
+
On hard refresh (Ctrl+R / direct URL entry), `setup()` in `intercept.js` fires its initial route match via `Promise.resolve().then(...)` — a single microtask delay. This races against:
|
|
15
|
+
|
|
16
|
+
1. Routes not yet registered (ES module graph hasn't evaluated `ui.element(...)` calls)
|
|
17
|
+
2. Container elements not yet in the DOM (Custom Elements haven't connected)
|
|
18
|
+
3. The orchestrator listener not yet attached (if `define/index.js` loads after `router/index.js`)
|
|
19
|
+
|
|
20
|
+
The microtask fires *before* `DOMContentLoaded`, *before* Custom Element `connectedCallback`, and often *before* downstream modules finish executing. The result: `match()` returns `null` (no routes registered) or emits `'found'` to an orchestrator that calls `getContainer()` on a node that hasn't mounted.
|
|
21
|
+
|
|
22
|
+
**Evidence in code:**
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
// intercept.js line 248
|
|
26
|
+
Promise.resolve().then(async () => {
|
|
27
|
+
const url = window.navigation.currentEntry?.url || window.location.href;
|
|
28
|
+
const routeMatch = await match(url); // routes[] is empty on cold boot
|
|
29
|
+
...
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
// orchestrator.js line 28
|
|
35
|
+
const containerEl = router.getContainer(spec.container);
|
|
36
|
+
if (!containerEl) {
|
|
37
|
+
console.warn(...); // Silent failure — page never renders
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Edge cases:**
|
|
43
|
+
|
|
44
|
+
- Script loaded via `<script type="module">` (deferred by spec) — route registration happens in a subsequent microtask after `setup()` already fired.
|
|
45
|
+
- `<script type="module" async>` — execution order is completely non-deterministic.
|
|
46
|
+
- Service Worker serving stale cached HTML while JS bundles update — container tags may mismatch.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### 1.2. Global Initialization Timing
|
|
51
|
+
|
|
52
|
+
**The Bug:**
|
|
53
|
+
The auto-bootstrap block in `router/index.js` runs at module evaluation time:
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
if (typeof window !== 'undefined') {
|
|
57
|
+
registerNavigator(navigate);
|
|
58
|
+
setup();
|
|
59
|
+
setupTabSync(router);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This means:
|
|
64
|
+
|
|
65
|
+
- `setup()` attaches the Navigation API listener *before* any route is registered.
|
|
66
|
+
- The initial match fires in the same microtask queue tick as module evaluation.
|
|
67
|
+
- There is no `window.router` global — users must import the object, but the docs propose a global instance. Currently there is no bridge.
|
|
68
|
+
|
|
69
|
+
**The Conflict:**
|
|
70
|
+
You want the router available globally *before* app code loads. But ES modules are deferred and non-blocking. A `<script type="module">` that imports the router cannot guarantee its evaluation precedes other modules that register routes unless you enforce a strict import chain (which defeats parallelism).
|
|
71
|
+
|
|
72
|
+
**Edge cases:**
|
|
73
|
+
|
|
74
|
+
- Two entry points both importing `router/index.js` — `setup()` is idempotent, but route registration order is undefined.
|
|
75
|
+
- Dynamic `import()` of the router after page load — `setup()` fires, emits initial match, but DOMContentLoaded already passed.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### 1.3. Container Graph Is Flat
|
|
80
|
+
|
|
81
|
+
**The Bug:**
|
|
82
|
+
`container.js` stores `Map<string, WeakRef<HTMLElement>>` — a flat key-value registry. There is no concept of:
|
|
83
|
+
|
|
84
|
+
- Parent-child relationships between containers
|
|
85
|
+
- Depth or nesting level
|
|
86
|
+
- Sibling adjacency
|
|
87
|
+
- Path from root to a given container
|
|
88
|
+
|
|
89
|
+
The `route.meta.parent` field in `match.js` establishes a *route chain* (parent pattern strings), but this chain has zero connection to the *container hierarchy*. A route declares `container: 'sidebar'` and that's the only topological information. If `sidebar` lives inside `main` which lives inside `body`, the router doesn't know.
|
|
90
|
+
|
|
91
|
+
**Consequence:**
|
|
92
|
+
Cross-branch navigation (from a view mounted in container A to a view mounted in container B, where A and B share a common ancestor C) requires understanding the tree shape. Without a graph, the router cannot:
|
|
93
|
+
|
|
94
|
+
1. Determine which containers to unmount (everything below divergence point)
|
|
95
|
+
2. Determine which containers to mount (everything on the new branch)
|
|
96
|
+
3. Identify the Lowest Common Ancestor (LCA) to minimize DOM churn
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### 1.4. Cross-Branch Reconciliation Is Absent
|
|
101
|
+
|
|
102
|
+
**The Bug:**
|
|
103
|
+
The current resolution logic in `intercept.js` is binary:
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
if (routeMatch?.route?.meta?.container) {
|
|
107
|
+
const containerName = routeMatch.route.meta.container;
|
|
108
|
+
if (!getContainer(containerName)) {
|
|
109
|
+
throw new Error(...); // Hard failure, no recovery
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If the target container is not in the DOM, the router *throws*. It makes no attempt to:
|
|
115
|
+
|
|
116
|
+
1. Walk up to a common ancestor that IS in the DOM
|
|
117
|
+
2. Sequentially render intermediate containers downward
|
|
118
|
+
3. Await their registration before proceeding
|
|
119
|
+
|
|
120
|
+
**Example failure:**
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
URL: /settings/profile
|
|
124
|
+
Container: 'settings-panel'
|
|
125
|
+
'settings-panel' lives inside 'app-sidebar'
|
|
126
|
+
'app-sidebar' lives inside 'main'
|
|
127
|
+
|
|
128
|
+
Current page: /dashboard (rendered in 'main')
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Navigation to `/settings/profile` fails because `settings-panel` doesn't exist yet — it would only exist after `app-sidebar` is mounted, which itself requires `main` to render the sidebar layout.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### 1.5. Route-to-Container Mapping Is One-Dimensional
|
|
136
|
+
|
|
137
|
+
**The Bug:**
|
|
138
|
+
Each route declares a single `container` string:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
router.register('/settings/profile', 'page-profile', { container: 'settings-panel' });
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
But there's no mechanism to express:
|
|
145
|
+
- "To reach `settings-panel`, first ensure `app-sidebar` is mounted in `main`"
|
|
146
|
+
- "The chain of containers from root to target is: `body` → `main` → `app-sidebar` → `settings-panel`"
|
|
147
|
+
|
|
148
|
+
The `meta.parent` field in `match.js` chains *routes* by pattern string — it does NOT chain *containers*. The `chain` array built during matching represents the route ancestry, not the DOM ancestry.
|
|
149
|
+
|
|
150
|
+
**Mismatch:**
|
|
151
|
+
Route hierarchy ≠ Container hierarchy. A deeply nested route (`/a/b/c/d`) might render in a top-level container (`main`). Conversely, a shallow route (`/settings`) might need three nested containers. The current architecture conflates these two orthogonal trees.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 2. Proposed Fixes
|
|
156
|
+
|
|
157
|
+
### 2.1. Deferred Initial Match (Hard Refresh Survival)
|
|
158
|
+
|
|
159
|
+
**Problem:** The initial match fires before routes exist or containers mount.
|
|
160
|
+
|
|
161
|
+
**Fix:** Replace the microtask-based initial emit with a gated boot sequence that waits for readiness signals.
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// boot.js — new module
|
|
165
|
+
const gates = [];
|
|
166
|
+
let booted = false;
|
|
167
|
+
|
|
168
|
+
export function gate(promise) {
|
|
169
|
+
if (booted) return;
|
|
170
|
+
gates.push(promise);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function boot(emitter) {
|
|
174
|
+
// Wait for DOM to be interactive
|
|
175
|
+
if (document.readyState === 'loading') {
|
|
176
|
+
await new Promise(r => document.addEventListener('DOMContentLoaded', r, { once: true }));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Wait for all registered gates (route files, container definitions)
|
|
180
|
+
await Promise.all(gates);
|
|
181
|
+
gates.length = 0;
|
|
182
|
+
booted = true;
|
|
183
|
+
|
|
184
|
+
// Now safe to emit initial match
|
|
185
|
+
emitter();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function ready() {
|
|
189
|
+
return booted;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Integration in `intercept.js`:**
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
import { boot, ready } from './boot.js';
|
|
197
|
+
|
|
198
|
+
export function setup() {
|
|
199
|
+
if (ready()) return;
|
|
200
|
+
// ...attach listeners...
|
|
201
|
+
|
|
202
|
+
boot(async () => {
|
|
203
|
+
const url = window.navigation.currentEntry?.url || location.href;
|
|
204
|
+
const found = await match(url);
|
|
205
|
+
if (found) emit('found', { ...found, direction: 'load' });
|
|
206
|
+
else emit('notfound', { url });
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Route registration gates itself:**
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
// In element.js, after router.register():
|
|
215
|
+
import { gate } from '../../router/boot.js';
|
|
216
|
+
gate(customElements.whenDefined(tag));
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Why native:**
|
|
220
|
+
- `document.readyState` — synchronous, zero-cost check
|
|
221
|
+
- `DOMContentLoaded` — standard event, fires once DOM is parsed
|
|
222
|
+
- `Promise.all` — microtask scheduling, no timers
|
|
223
|
+
- `customElements.whenDefined` — native Custom Elements API
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### 2.2. Global Router Attachment
|
|
228
|
+
|
|
229
|
+
**Fix:** Freeze a non-configurable property on `window` during auto-bootstrap.
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
// router/index.js — at auto-bootstrap
|
|
233
|
+
if (typeof window !== 'undefined') {
|
|
234
|
+
Object.defineProperty(window, 'router', {
|
|
235
|
+
value: router,
|
|
236
|
+
writable: false,
|
|
237
|
+
enumerable: false,
|
|
238
|
+
configurable: false
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
registerNavigator(navigate);
|
|
242
|
+
setup();
|
|
243
|
+
setupTabSync(router);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Why this is safe:**
|
|
248
|
+
- `Object.defineProperty` with `writable: false` prevents accidental overwrite.
|
|
249
|
+
- `enumerable: false` keeps it out of `for...in` loops and `Object.keys(window)`.
|
|
250
|
+
- `configurable: false` makes it permanent — no library can `delete window.router`.
|
|
251
|
+
- Code that imports `{ router }` still works identically.
|
|
252
|
+
- Code that references `window.router` works without import.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### 2.3. Hierarchical Container Graph
|
|
257
|
+
|
|
258
|
+
**Fix:** Replace the flat `Map<string, WeakRef>` with a tree structure that stores parent-child relationships and depth.
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
// graph.js — new module
|
|
262
|
+
|
|
263
|
+
class Node {
|
|
264
|
+
constructor(name, ref, parent = null) {
|
|
265
|
+
this.name = name;
|
|
266
|
+
this.ref = ref; // WeakRef<HTMLElement>
|
|
267
|
+
this.parent = parent; // Node | null
|
|
268
|
+
this.children = new Set(); // Set<Node>
|
|
269
|
+
this.depth = parent ? parent.depth + 1 : 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
alive() {
|
|
273
|
+
return this.ref.deref() !== undefined;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const nodes = new Map(); // string -> Node
|
|
278
|
+
const root = new Node('body', null, null); // virtual root
|
|
279
|
+
nodes.set('body', root);
|
|
280
|
+
|
|
281
|
+
export function add(name, element, parent = 'body') {
|
|
282
|
+
const ref = new WeakRef(element);
|
|
283
|
+
const parentNode = nodes.get(parent) || root;
|
|
284
|
+
const node = new Node(name, ref, parentNode);
|
|
285
|
+
parentNode.children.add(node);
|
|
286
|
+
nodes.set(name, node);
|
|
287
|
+
return node;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function remove(name) {
|
|
291
|
+
const node = nodes.get(name);
|
|
292
|
+
if (!node) return;
|
|
293
|
+
if (node.parent) node.parent.children.delete(node);
|
|
294
|
+
// Reparent orphans to grandparent
|
|
295
|
+
for (const child of node.children) {
|
|
296
|
+
child.parent = node.parent;
|
|
297
|
+
child.depth = child.parent ? child.parent.depth + 1 : 0;
|
|
298
|
+
if (node.parent) node.parent.children.add(child);
|
|
299
|
+
}
|
|
300
|
+
nodes.delete(name);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function get(name) {
|
|
304
|
+
return nodes.get(name) || null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function element(name) {
|
|
308
|
+
return nodes.get(name)?.ref.deref() || null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function clear() {
|
|
312
|
+
nodes.clear();
|
|
313
|
+
root.children.clear();
|
|
314
|
+
nodes.set('body', root);
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Registration changes in `container.js`:**
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
import { add, remove } from './graph.js';
|
|
322
|
+
|
|
323
|
+
export function registerContainer(name, element, parent = 'body') {
|
|
324
|
+
// ...existing singleton check...
|
|
325
|
+
add(name, element, parent);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function unregisterContainer(name, element) {
|
|
329
|
+
// ...existing safety check...
|
|
330
|
+
remove(name);
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Container spec gains a `parent` field:**
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
ui.container('settings-panel', {
|
|
338
|
+
parent: 'sidebar', // <-- declares hierarchy
|
|
339
|
+
template: '<slot></slot>',
|
|
340
|
+
style: ':host { display: block; }'
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
### 2.4. Lowest Common Ancestor Algorithm
|
|
347
|
+
|
|
348
|
+
**Problem:** Navigate from container A to container B — which containers must unmount and which must mount?
|
|
349
|
+
|
|
350
|
+
**Algorithm:** Walk both nodes up to equal depth, then walk both up simultaneously until they meet.
|
|
351
|
+
|
|
352
|
+
```javascript
|
|
353
|
+
// lca.js
|
|
354
|
+
|
|
355
|
+
import { get } from './graph.js';
|
|
356
|
+
|
|
357
|
+
export function lca(a, b) {
|
|
358
|
+
let nodeA = get(a);
|
|
359
|
+
let nodeB = get(b);
|
|
360
|
+
if (!nodeA || !nodeB) return null;
|
|
361
|
+
|
|
362
|
+
// Equalize depth
|
|
363
|
+
while (nodeA.depth > nodeB.depth) nodeA = nodeA.parent;
|
|
364
|
+
while (nodeB.depth > nodeA.depth) nodeB = nodeB.parent;
|
|
365
|
+
|
|
366
|
+
// Walk up together
|
|
367
|
+
while (nodeA !== nodeB) {
|
|
368
|
+
nodeA = nodeA.parent;
|
|
369
|
+
nodeB = nodeB.parent;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return nodeA; // The common ancestor node
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function path(from, to) {
|
|
376
|
+
const ancestor = lca(from, to);
|
|
377
|
+
if (!ancestor) return null;
|
|
378
|
+
|
|
379
|
+
// Build path: [ancestor, ..., target]
|
|
380
|
+
const segments = [];
|
|
381
|
+
let current = get(to);
|
|
382
|
+
while (current && current !== ancestor) {
|
|
383
|
+
segments.unshift(current);
|
|
384
|
+
current = current.parent;
|
|
385
|
+
}
|
|
386
|
+
segments.unshift(ancestor);
|
|
387
|
+
return segments;
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Complexity:** O(d) where d is the maximum depth of the tree. For typical UIs (depth 3-6), this is effectively O(1).
|
|
392
|
+
|
|
393
|
+
**Memory:** One `Node` object per registered container. Each node holds a name string, a WeakRef, a parent pointer, a Set of children, and an integer depth. For 20 containers: ~2KB.
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
### 2.5. Cascade Mount Sequence
|
|
398
|
+
|
|
399
|
+
**Problem:** Target container is not in the DOM. Must sequentially render containers from the nearest mounted ancestor down to the target.
|
|
400
|
+
|
|
401
|
+
```javascript
|
|
402
|
+
// cascade.js
|
|
403
|
+
|
|
404
|
+
import { get, element as resolve } from './graph.js';
|
|
405
|
+
import { lca, path } from './lca.js';
|
|
406
|
+
|
|
407
|
+
export async function ensure(target, current) {
|
|
408
|
+
// Find the lowest mounted ancestor on the path to target
|
|
409
|
+
const segments = path(current || 'body', target);
|
|
410
|
+
if (!segments) throw new Error(`No path from '${current}' to '${target}'`);
|
|
411
|
+
|
|
412
|
+
let mounted = null;
|
|
413
|
+
|
|
414
|
+
// Walk down the path, find first unmounted node
|
|
415
|
+
for (const node of segments) {
|
|
416
|
+
const el = node.ref?.deref();
|
|
417
|
+
if (el && el.isConnected) {
|
|
418
|
+
mounted = node;
|
|
419
|
+
} else {
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (!mounted) {
|
|
425
|
+
mounted = get('body'); // Fallback to root
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Now render each unmounted container sequentially from mounted downward
|
|
429
|
+
const start = segments.indexOf(mounted) + 1;
|
|
430
|
+
for (let i = start; i < segments.length; i++) {
|
|
431
|
+
const node = segments[i];
|
|
432
|
+
const parent = node.parent;
|
|
433
|
+
const parentEl = parent.ref?.deref();
|
|
434
|
+
if (!parentEl || !parentEl.isConnected) {
|
|
435
|
+
throw new Error(`Parent '${parent.name}' disconnected during cascade`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Create the container element
|
|
439
|
+
const tag = node.name;
|
|
440
|
+
if (tag.includes('-') && !customElements.get(tag)) {
|
|
441
|
+
await customElements.whenDefined(tag);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const el = document.createElement(tag);
|
|
445
|
+
|
|
446
|
+
// Delegate to parent's swap interface or append directly
|
|
447
|
+
if (typeof parentEl.swapView === 'function') {
|
|
448
|
+
await parentEl.swapView(el, { direction: 'push' });
|
|
449
|
+
} else {
|
|
450
|
+
parentEl.replaceChildren(el);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Wait for connectedCallback to fire and register the container
|
|
454
|
+
await new Promise(r => requestAnimationFrame(r));
|
|
455
|
+
|
|
456
|
+
// Verify registration
|
|
457
|
+
if (!resolve(node.name)) {
|
|
458
|
+
throw new Error(`Container '${node.name}' failed to register after mount`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return resolve(target);
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Why native:**
|
|
467
|
+
- `customElements.whenDefined` — awaits CE registration without polling
|
|
468
|
+
- `document.createElement` — zero-cost DOM node factory
|
|
469
|
+
- `requestAnimationFrame` — yields to layout, guarantees `connectedCallback` fires
|
|
470
|
+
- No timers, no polling, no `setTimeout`
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
### 2.6. Route-to-Graph Binding
|
|
475
|
+
|
|
476
|
+
**Fix:** Routes declare a `chain` of containers from root to target, not just a leaf container.
|
|
477
|
+
|
|
478
|
+
```javascript
|
|
479
|
+
router.register('/settings/profile', 'page-profile', {
|
|
480
|
+
containers: ['main', 'sidebar', 'settings-panel']
|
|
481
|
+
// Ordered root-to-leaf. The last element is the rendering target.
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**During navigation:**
|
|
486
|
+
|
|
487
|
+
```javascript
|
|
488
|
+
// In intercept.js handler():
|
|
489
|
+
const target = meta.containers?.at(-1) || meta.container;
|
|
490
|
+
const chain = meta.containers || [target];
|
|
491
|
+
|
|
492
|
+
// Verify or cascade-mount the full container chain
|
|
493
|
+
for (let i = 0; i < chain.length; i++) {
|
|
494
|
+
const name = chain[i];
|
|
495
|
+
if (!resolve(name)) {
|
|
496
|
+
await ensure(name, chain[i - 1] || 'body');
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Now safe to render into the target
|
|
501
|
+
const containerEl = resolve(target);
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**Backward compatibility:** The single `container` field still works — it's sugar for `containers: [container]`. When only one container is specified and it's already mounted, behavior is identical to current code.
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## 3. Data Structures & Algorithms
|
|
509
|
+
|
|
510
|
+
### 3.1. Container Tree Node
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
Node {
|
|
514
|
+
name: string // 'main', 'sidebar', 'settings-panel'
|
|
515
|
+
ref: WeakRef<Element> // Garbage-collectable DOM reference
|
|
516
|
+
parent: Node | null // Pointer up the tree
|
|
517
|
+
children: Set<Node> // Immediate children
|
|
518
|
+
depth: uint // Distance from root (body = 0)
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**Operations:**
|
|
523
|
+
|
|
524
|
+
| Operation | Complexity | Method |
|
|
525
|
+
|---------- |------------|--------|
|
|
526
|
+
| Add | O(1) | `add(name, el, parent)` |
|
|
527
|
+
| Remove | O(c) where c = children count | `remove(name)` |
|
|
528
|
+
| Lookup | O(1) | `get(name)` via Map |
|
|
529
|
+
| LCA | O(d) where d = max depth | `lca(a, b)` |
|
|
530
|
+
| Path | O(d) | `path(from, to)` |
|
|
531
|
+
| Cascade | O(d) * await | `ensure(target)` |
|
|
532
|
+
|
|
533
|
+
### 3.2. Trie-Based Route Matcher
|
|
534
|
+
|
|
535
|
+
The current linear scan through sorted routes is O(n) per navigation. For applications with 100+ routes, a Radix Trie achieves O(k) matching where k = number of URL segments.
|
|
536
|
+
|
|
537
|
+
```javascript
|
|
538
|
+
// trie.js
|
|
539
|
+
|
|
540
|
+
class Segment {
|
|
541
|
+
constructor() {
|
|
542
|
+
this.static = new Map(); // literal -> Segment
|
|
543
|
+
this.param = null; // Segment (for :named)
|
|
544
|
+
this.wild = null; // Segment (for *)
|
|
545
|
+
this.route = null; // matched route entry
|
|
546
|
+
this.key = null; // param name (e.g. 'member')
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const trie = new Segment();
|
|
551
|
+
|
|
552
|
+
export function insert(pattern, route) {
|
|
553
|
+
const parts = pattern.split('/').filter(Boolean);
|
|
554
|
+
let node = trie;
|
|
555
|
+
|
|
556
|
+
for (const part of parts) {
|
|
557
|
+
if (part.startsWith(':')) {
|
|
558
|
+
if (!node.param) {
|
|
559
|
+
node.param = new Segment();
|
|
560
|
+
node.param.key = part.slice(1);
|
|
561
|
+
}
|
|
562
|
+
node = node.param;
|
|
563
|
+
} else if (part === '*') {
|
|
564
|
+
if (!node.wild) node.wild = new Segment();
|
|
565
|
+
node = node.wild;
|
|
566
|
+
} else {
|
|
567
|
+
if (!node.static.has(part)) {
|
|
568
|
+
node.static.set(part, new Segment());
|
|
569
|
+
}
|
|
570
|
+
node = node.static.get(part);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
node.route = route;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export function find(pathname) {
|
|
578
|
+
const parts = pathname.split('/').filter(Boolean);
|
|
579
|
+
const params = {};
|
|
580
|
+
const result = walk(trie, parts, 0, params);
|
|
581
|
+
return result ? { route: result, params } : null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function walk(node, parts, index, params) {
|
|
585
|
+
if (index === parts.length) {
|
|
586
|
+
return node.route;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const segment = parts[index];
|
|
590
|
+
|
|
591
|
+
// Priority 1: static match
|
|
592
|
+
if (node.static.has(segment)) {
|
|
593
|
+
const result = walk(node.static.get(segment), parts, index + 1, params);
|
|
594
|
+
if (result) return result;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Priority 2: param match
|
|
598
|
+
if (node.param) {
|
|
599
|
+
params[node.param.key] = segment;
|
|
600
|
+
const result = walk(node.param, parts, index + 1, params);
|
|
601
|
+
if (result) {
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
604
|
+
delete params[node.param.key];
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Priority 3: wildcard match
|
|
608
|
+
if (node.wild) {
|
|
609
|
+
params['*'] = parts.slice(index).join('/');
|
|
610
|
+
return node.wild.route;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
**Comparison:**
|
|
618
|
+
|
|
619
|
+
| Approach | Time (n routes, k segments) | Memory |
|
|
620
|
+
|----------|----------------------------|--------|
|
|
621
|
+
| Linear scan (current) | O(n) | O(n) route objects |
|
|
622
|
+
| Sorted + binary search | O(k * log n) | O(n) |
|
|
623
|
+
| Radix trie | O(k) | O(total segments) |
|
|
624
|
+
|
|
625
|
+
For hot-path navigation, the trie eliminates URLPattern compilation overhead on the fast path. URLPattern remains available as a fallback for complex patterns (regex groups, modifiers) that the trie cannot express.
|
|
626
|
+
|
|
627
|
+
### 3.3. LCA Algorithm (Detailed)
|
|
628
|
+
|
|
629
|
+
```
|
|
630
|
+
FUNCTION lca(nameA, nameB):
|
|
631
|
+
nodeA ← graph.get(nameA)
|
|
632
|
+
nodeB ← graph.get(nameB)
|
|
633
|
+
|
|
634
|
+
IF nodeA is null OR nodeB is null:
|
|
635
|
+
RETURN null
|
|
636
|
+
|
|
637
|
+
// Phase 1: Equalize depths
|
|
638
|
+
WHILE nodeA.depth > nodeB.depth:
|
|
639
|
+
nodeA ← nodeA.parent
|
|
640
|
+
|
|
641
|
+
WHILE nodeB.depth > nodeA.depth:
|
|
642
|
+
nodeB ← nodeB.parent
|
|
643
|
+
|
|
644
|
+
// Phase 2: Walk up in tandem
|
|
645
|
+
WHILE nodeA ≠ nodeB:
|
|
646
|
+
nodeA ← nodeA.parent
|
|
647
|
+
nodeB ← nodeB.parent
|
|
648
|
+
|
|
649
|
+
RETURN nodeA
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
**Correctness proof:** After depth equalization, both pointers are at the same depth. Since the tree is finite and connected to a single root, walking up in tandem must converge at the LCA.
|
|
653
|
+
|
|
654
|
+
**Worst case:** O(d) where d = tree depth. For UI container trees, d rarely exceeds 5.
|
|
655
|
+
|
|
656
|
+
### 3.4. Cascade Render Sequence
|
|
657
|
+
|
|
658
|
+
```
|
|
659
|
+
FUNCTION ensure(target, source):
|
|
660
|
+
segments ← path(source || 'body', target)
|
|
661
|
+
|
|
662
|
+
// Find deepest mounted node on the path
|
|
663
|
+
mounted ← null
|
|
664
|
+
FOR node IN segments:
|
|
665
|
+
IF node.ref.deref() AND node.ref.deref().isConnected:
|
|
666
|
+
mounted ← node
|
|
667
|
+
ELSE:
|
|
668
|
+
BREAK
|
|
669
|
+
|
|
670
|
+
IF mounted is null:
|
|
671
|
+
mounted ← root
|
|
672
|
+
|
|
673
|
+
// Render from mounted+1 down to target
|
|
674
|
+
start ← indexOf(mounted) + 1
|
|
675
|
+
FOR i FROM start TO segments.length - 1:
|
|
676
|
+
node ← segments[i]
|
|
677
|
+
parentEl ← node.parent.ref.deref()
|
|
678
|
+
|
|
679
|
+
ASSERT parentEl.isConnected
|
|
680
|
+
|
|
681
|
+
tag ← node.name
|
|
682
|
+
IF tag contains '-':
|
|
683
|
+
AWAIT customElements.whenDefined(tag)
|
|
684
|
+
|
|
685
|
+
el ← document.createElement(tag)
|
|
686
|
+
|
|
687
|
+
IF parentEl has swapView:
|
|
688
|
+
AWAIT parentEl.swapView(el)
|
|
689
|
+
ELSE:
|
|
690
|
+
parentEl.replaceChildren(el)
|
|
691
|
+
|
|
692
|
+
AWAIT animationFrame // Yield for connectedCallback
|
|
693
|
+
|
|
694
|
+
ASSERT graph.element(node.name) is defined
|
|
695
|
+
|
|
696
|
+
RETURN graph.element(target)
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## 4. Additional Edge Cases & Bugs
|
|
702
|
+
|
|
703
|
+
### 4.1. MutationObserver Starvation
|
|
704
|
+
|
|
705
|
+
**Bug:** The `MutationObserver` in `container.js` is initialized inside `requestIdleCallback`. Under heavy load (60fps animation), `requestIdleCallback` may never fire — the observer never attaches and standard-element containers never get discovered.
|
|
706
|
+
|
|
707
|
+
**Fix:** Use `requestAnimationFrame` as the fallback when `requestIdleCallback` doesn't fire within 100ms:
|
|
708
|
+
|
|
709
|
+
```javascript
|
|
710
|
+
function ensureObserver() {
|
|
711
|
+
if (observer || typeof window === 'undefined') return;
|
|
712
|
+
|
|
713
|
+
const attach = () => { /* ...create and observe... */ };
|
|
714
|
+
|
|
715
|
+
if (typeof requestIdleCallback !== 'undefined') {
|
|
716
|
+
const id = requestIdleCallback(attach, { timeout: 100 });
|
|
717
|
+
} else {
|
|
718
|
+
requestAnimationFrame(attach);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
### 4.2. WeakRef Timing Hole
|
|
724
|
+
|
|
725
|
+
**Bug:** Between `disconnectedCallback` firing and `FinalizationRegistry` callback executing, a `getContainer()` call can return `undefined` even though the element was just removed intentionally (not GC'd). The consumer cannot distinguish "GC'd unexpectedly" from "unmounted normally."
|
|
726
|
+
|
|
727
|
+
**Fix:** `unregisterContainer()` should *immediately* delete the Map entry (it currently does). But `getContainer()` should NOT fall through to `querySelector` for non-selector names after an explicit unregister. Track explicit unregisters in a `Set<string>`:
|
|
728
|
+
|
|
729
|
+
```javascript
|
|
730
|
+
const unregistered = new Set();
|
|
731
|
+
|
|
732
|
+
export function unregisterContainer(name, element) {
|
|
733
|
+
// ...existing logic...
|
|
734
|
+
unregistered.add(name);
|
|
735
|
+
// Clear after one macrotask to handle rapid remount
|
|
736
|
+
setTimeout(() => unregistered.delete(name), 0);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export function getContainer(name) {
|
|
740
|
+
if (unregistered.has(name)) return undefined;
|
|
741
|
+
// ...existing logic...
|
|
742
|
+
}
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
### 4.3. Route Chain Infinite Loop
|
|
746
|
+
|
|
747
|
+
**Bug:** In `match.js`, the chain builder walks `meta.parent` without cycle detection:
|
|
748
|
+
|
|
749
|
+
```javascript
|
|
750
|
+
while (currentRoute) {
|
|
751
|
+
const parentPattern = currentRoute.meta?.parent;
|
|
752
|
+
if (parentPattern) {
|
|
753
|
+
const parentRoute = routes.find(r => r.patternStr === parentPattern);
|
|
754
|
+
currentRoute = parentRoute;
|
|
755
|
+
} else {
|
|
756
|
+
currentRoute = null;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
If route A declares parent B and route B declares parent A, this loops forever.
|
|
762
|
+
|
|
763
|
+
**Fix:** Track visited patterns:
|
|
764
|
+
|
|
765
|
+
```javascript
|
|
766
|
+
const visited = new Set();
|
|
767
|
+
while (currentRoute) {
|
|
768
|
+
if (visited.has(currentRoute.patternStr)) break; // Cycle detected
|
|
769
|
+
visited.add(currentRoute.patternStr);
|
|
770
|
+
// ...rest...
|
|
771
|
+
}
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### 4.4. Concurrent Navigation Race
|
|
775
|
+
|
|
776
|
+
**Bug:** Two rapid navigations can both pass the container check, then both attempt `swapView` on the same container. The first `swapView` starts a View Transition. The second `swapView` calls `startViewTransition` while the first is still animating — this *skips* the first transition (browser spec: "if a view transition is running, starting a new one aborts the previous"). The result is visual jank.
|
|
777
|
+
|
|
778
|
+
**Fix:** Containers should track in-flight transitions and queue or abort:
|
|
779
|
+
|
|
780
|
+
```javascript
|
|
781
|
+
async swapView(element, options = {}) {
|
|
782
|
+
// Abort previous transition if still running
|
|
783
|
+
if (this._transition) {
|
|
784
|
+
this._transition.skipTransition();
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const doSwap = () => this.replaceChildren(element);
|
|
788
|
+
|
|
789
|
+
if (typeof this.startViewTransition === 'function') {
|
|
790
|
+
this._transition = this.startViewTransition({ callback: doSwap });
|
|
791
|
+
await this._transition.finished;
|
|
792
|
+
this._transition = null;
|
|
793
|
+
} else {
|
|
794
|
+
doSwap();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### 4.5. Tab Sync Amplification Storm
|
|
800
|
+
|
|
801
|
+
**Bug:** With N tabs open and sync enabled, a navigation in tab 1 broadcasts to N-1 tabs. Each of those tabs navigates, which triggers `navigatesuccess`, which could broadcast again. The `sent` variable prevents echo, but only for exact URL match. If a guard redirects the URL (e.g., `/admin` → `/login`), the redirect URL differs from `sent`, causing a re-broadcast.
|
|
802
|
+
|
|
803
|
+
**Fix:** Add a `syncing` flag guard on outbound broadcasts:
|
|
804
|
+
|
|
805
|
+
```javascript
|
|
806
|
+
navHandler = () => {
|
|
807
|
+
if (isSyncing) return; // Already guarded
|
|
808
|
+
// ...existing code...
|
|
809
|
+
};
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
This is already implemented. But the *redirect* case slips through because `isSyncing` is reset in `.finally()` before the redirect navigation completes. The fix: keep `isSyncing = true` for the full redirect chain:
|
|
813
|
+
|
|
814
|
+
```javascript
|
|
815
|
+
channel.onmessage = (event) => {
|
|
816
|
+
const { type, url } = event.data || {};
|
|
817
|
+
if (type === 'sync-navigate') {
|
|
818
|
+
if (sent === url) return;
|
|
819
|
+
sent = url;
|
|
820
|
+
isSyncing = true;
|
|
821
|
+
|
|
822
|
+
const finish = () => {
|
|
823
|
+
// Delay unsync by one macrotask to cover redirects
|
|
824
|
+
setTimeout(() => { isSyncing = false; }, 0);
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
const result = router.navigate(url);
|
|
828
|
+
result?.finished?.then(finish, finish) ?? finish();
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## 5. Integration Roadmap
|
|
836
|
+
|
|
837
|
+
| Phase | Change | Risk | Files |
|
|
838
|
+
|-------|--------|------|-------|
|
|
839
|
+
| 1 | Add `boot.js` deferred gate | Low — additive, no breaking changes | `boot.js`, `intercept.js`, `element.js` |
|
|
840
|
+
| 2 | Attach `window.router` | Low — additive | `index.js` |
|
|
841
|
+
| 3 | Add `graph.js` tree structure | Medium — new module, container API gains `parent` param | `graph.js`, `container.js` |
|
|
842
|
+
| 4 | Add `lca.js` algorithm | Low — additive utility | `lca.js` |
|
|
843
|
+
| 5 | Add `cascade.js` mount sequence | Medium — changes intercept behavior on missing containers | `cascade.js`, `intercept.js` |
|
|
844
|
+
| 6 | Route `containers` array field | Low — backward-compatible extension | `intercept.js`, `match.js` |
|
|
845
|
+
| 7 | Optional trie matcher | Low — can run alongside URLPattern | `trie.js`, `match.js` |
|
|
846
|
+
| 8 | Fix edge-case bugs (4.1–4.5) | Low — targeted patches | Various |
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
## 6. Performance Budget
|
|
851
|
+
|
|
852
|
+
| Operation | Target | Native API |
|
|
853
|
+
|-----------|--------|-----------|
|
|
854
|
+
| Route match | < 0.1ms (warm) | URLPattern.exec / Trie walk |
|
|
855
|
+
| LCA computation | < 0.01ms | Pointer arithmetic |
|
|
856
|
+
| Cascade mount (per level) | < 16ms (one frame) | createElement + rAF |
|
|
857
|
+
| Container lookup | < 0.01ms | Map.get + WeakRef.deref |
|
|
858
|
+
| Initial boot gate | < 50ms after DOMContentLoaded | Promise.all |
|
|
859
|
+
|
|
860
|
+
All operations target zero GC pressure on the hot path. WeakRef derefs are non-allocating. Map lookups are O(1) amortized. The trie avoids creating intermediate objects during traversal.
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
## 7. Naming Convention Adherence
|
|
865
|
+
|
|
866
|
+
All proposed code follows the one-word naming rule:
|
|
867
|
+
|
|
868
|
+
| Concept | Name | Rationale |
|
|
869
|
+
|---------|------|-----------|
|
|
870
|
+
| Container tree node | `Node` | One word, scoped to `graph.js` |
|
|
871
|
+
| Tree module | `graph` | One word, describes the structure |
|
|
872
|
+
| LCA function | `lca` | Acronym, universally understood |
|
|
873
|
+
| Path function | `path` | One word, returns root-to-target |
|
|
874
|
+
| Boot gate | `gate` | One word, registers a prerequisite |
|
|
875
|
+
| Boot trigger | `boot` | One word, fires the initial match |
|
|
876
|
+
| Cascade function | `ensure` | One word, guarantees container exists |
|
|
877
|
+
| Trie segment | `Segment` | One word, represents one URL part |
|
|
878
|
+
| Trie insert | `insert` | One word |
|
|
879
|
+
| Trie lookup | `find` | One word |
|
|
880
|
+
| Node alive check | `alive` | One word, boolean method |
|
|
881
|
+
| Node element resolve | `element` | One word, returns DOM node |
|
|
882
|
+
|
|
883
|
+
Files: `graph.js`, `lca.js`, `cascade.js`, `boot.js`, `trie.js` — all single-word, lowercase.
|
|
884
|
+
|
|
885
|
+
---
|
|
886
|
+
|
|
887
|
+
*End of audit.*
|