@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.
Files changed (80) hide show
  1. package/CHANGELOG.md +81 -4
  2. package/README.md +97 -133
  3. package/bin/anza/anza +0 -0
  4. package/bin/anza/anza.exe +0 -0
  5. package/bin/anza/find.js +35 -0
  6. package/bin/anza/index.js +34 -0
  7. package/bin/anza/launch.js +19 -0
  8. package/bin/common/index.js +7 -0
  9. package/bin/common/logs.js +62 -0
  10. package/bin/create/copy.js +18 -0
  11. package/bin/create/index.js +45 -0
  12. package/bin/create/run.js +210 -0
  13. package/bin/create/write.js +19 -0
  14. package/importmap.json +4 -0
  15. package/package.json +16 -10
  16. package/src/core/offline/{usage.md → notes/usage.md} +11 -1
  17. package/src/core/router/boot.js +82 -0
  18. package/src/core/router/cascade.js +76 -0
  19. package/src/core/router/container.js +63 -72
  20. package/src/core/router/graph.js +144 -0
  21. package/src/core/router/index.js +12 -2
  22. package/src/core/router/intercept.js +26 -7
  23. package/src/core/router/lca.js +58 -0
  24. package/src/core/router/match.js +49 -36
  25. package/src/core/router/notes/audit-old.md +887 -0
  26. package/src/core/router/notes/audti.md +773 -0
  27. package/src/core/router/notes/tasks.md +473 -0
  28. package/src/core/router/{usage.md → notes/usage.md} +57 -35
  29. package/src/core/router/sync/tab.js +6 -4
  30. package/src/core/router/transitions.js +35 -8
  31. package/src/core/router/trie.js +130 -0
  32. package/src/core/security/{usage.md → notes/usage.md} +1 -2
  33. package/src/core/storage/{usage.md → notes/usage.md} +6 -6
  34. package/src/core/theme/index.js +78 -0
  35. package/src/core/ui/define/index.js +2 -1
  36. package/src/core/ui/define/orchestrator.js +10 -4
  37. package/src/core/ui/defs/dock.js +134 -0
  38. package/src/core/ui/defs/index.js +20 -0
  39. package/src/core/ui/defs/page.js +89 -0
  40. package/src/core/ui/defs/part.js +28 -0
  41. package/src/core/ui/defs/spec.js +96 -0
  42. package/src/core/ui/defs/view.js +23 -0
  43. package/src/core/ui/index.js +16 -3
  44. package/src/core/ui/notes/definations.md +979 -0
  45. package/src/tokens/index.css +1 -0
  46. package/src/tokens/semantic/contrast.css +18 -0
  47. package/src/tokens/semantic/transitions.css +32 -0
  48. package/types/core/platform/index.d.ts +39 -10
  49. package/types/core/router/index.d.ts +9 -0
  50. package/types/core/theme/index.d.ts +18 -0
  51. package/types/core/ui/index.d.ts +11 -0
  52. package/types/index.d.ts +1 -0
  53. package/bin/anza.js +0 -63
  54. package/bin/create.js +0 -150
  55. package/src/core/api/plan.md +0 -209
  56. package/src/core/events/missing.md +0 -103
  57. package/src/core/events/plan.md +0 -177
  58. package/src/core/offline/missing.md +0 -89
  59. package/src/core/offline/plan.md +0 -143
  60. package/src/core/platform/missing.md +0 -119
  61. package/src/core/platform/platform.d.ts +0 -88
  62. package/src/core/router/missing.md +0 -716
  63. package/src/core/router/outlet.js +0 -139
  64. package/src/core/router/plan.md +0 -370
  65. package/src/core/security/missing.md +0 -97
  66. package/src/core/state/missing.md +0 -165
  67. package/src/core/storage/missing.md +0 -165
  68. package/src/core/storage/plan.md +0 -69
  69. package/src/core/ui/implementation.md +0 -170
  70. package/src/core/ui/plan.md +0 -510
  71. package/src/core/ui/ui.types.md +0 -890
  72. /package/src/core/animations/{usage.md → notes/usage.md} +0 -0
  73. /package/src/core/api/{usage.md → notes/usage.md} +0 -0
  74. /package/src/core/events/{usage.md → notes/usage.md} +0 -0
  75. /package/src/core/platform/{usage.md → notes/usage.md} +0 -0
  76. /package/src/core/state/{usage.md → notes/usage.md} +0 -0
  77. /package/src/core/ui/{usage.md → notes/usage.md} +0 -0
  78. /package/src/core/ui/{watch.md → notes/watch.md} +0 -0
  79. /package/src/core/workers/{plan.md → notes/plan.md} +0 -0
  80. /package/src/core/workers/{usage.md → notes/usage.md} +0 -0
@@ -1,139 +0,0 @@
1
- /**
2
- * src/core/router/outlet.js
3
- *
4
- * Nested layout outlet component.
5
- * Tracks hierarchy depth dynamically and resolves matching child segments.
6
- *
7
- * Source: docs/phase2.md
8
- */
9
-
10
- import { router } from './index.js';
11
- import { specRegistry } from '../ui/define/state.js';
12
-
13
- function getOutletDepth(element) {
14
- let depth = 0;
15
- let current = element;
16
- while (current) {
17
- if (current.parentNode) {
18
- current = current.parentNode;
19
- } else if (current.host) {
20
- current = current.host;
21
- } else {
22
- current = null;
23
- }
24
- if (current && current.tagName === 'ROUTE-OUTLET') {
25
- depth++;
26
- }
27
- }
28
- return depth;
29
- }
30
-
31
- class RouteOutlet extends HTMLElement {
32
- constructor() {
33
- super();
34
- this._dispose = null;
35
- }
36
-
37
- connectedCallback() {
38
- this._dispose = router.on('found', async (detail) => {
39
- await this.update(detail);
40
- });
41
-
42
- const currentUrl = window.navigation?.currentEntry?.url || window.location.href;
43
- router.match(currentUrl).then(matchResult => {
44
- if (matchResult && matchResult.chain) {
45
- this.update({
46
- chain: matchResult.chain,
47
- query: matchResult.query,
48
- hash: matchResult.hash
49
- });
50
- }
51
- }).catch(() => {});
52
- }
53
-
54
- disconnectedCallback() {
55
- this._dispose?.();
56
- this._dispose = null;
57
- }
58
-
59
- async update({ chain, query, hash }) {
60
- const depth = getOutletDepth(this);
61
- const index = depth + 1;
62
-
63
- if (index >= chain.length) {
64
- this.replaceChildren();
65
- return;
66
- }
67
-
68
- const { tag, params } = chain[index];
69
-
70
- if (typeof customElements !== 'undefined' && tag.includes('-') && !customElements.get(tag)) {
71
- await customElements.whenDefined(tag);
72
- }
73
-
74
- const spec = specRegistry.get(tag.toLowerCase());
75
- const props = {};
76
-
77
- // Cast parameter values
78
- for (const [key, val] of Object.entries(params)) {
79
- let casted = val;
80
- if (spec && spec.props && spec.props[key]) {
81
- const type = spec.props[key].type;
82
- if (type === Boolean) {
83
- casted = val === 'true' || val === '1' || val === '';
84
- } else if (type === Number) {
85
- const num = Number(val);
86
- casted = isNaN(num) ? 0 : num;
87
- }
88
- }
89
- props[key] = casted;
90
- }
91
-
92
- // Map query variables if declared in spec.query
93
- if (spec && spec.query && Array.isArray(spec.query)) {
94
- for (const key of spec.query) {
95
- const val = query[key];
96
- if (val !== undefined) {
97
- let casted = val;
98
- if (spec.props && spec.props[key]) {
99
- const type = spec.props[key].type;
100
- if (type === Boolean) {
101
- casted = val === 'true' || val === '1' || val === '';
102
- } else if (type === Number) {
103
- const num = Number(val);
104
- casted = isNaN(num) ? 0 : num;
105
- }
106
- }
107
- props[key] = casted;
108
- }
109
- }
110
- }
111
-
112
- // Map hash string if hash property is defined
113
- if (spec && spec.props && spec.props.hash) {
114
- props.hash = hash;
115
- }
116
-
117
- const child = this.querySelector('.page-content');
118
- if (child && child.tagName.toLowerCase() === tag.toLowerCase()) {
119
- for (const [key, value] of Object.entries(props)) {
120
- child[key] = value;
121
- }
122
- return;
123
- }
124
-
125
- const page = document.createElement(tag);
126
- page.classList.add('page-content');
127
- for (const [key, value] of Object.entries(props)) {
128
- page[key] = value;
129
- }
130
-
131
- this.replaceChildren(page);
132
- }
133
- }
134
-
135
- if (typeof customElements !== 'undefined') {
136
- customElements.define('route-outlet', RouteOutlet);
137
- }
138
-
139
- export { RouteOutlet };
@@ -1,370 +0,0 @@
1
- # Client-Side Routing Architecture Plan
2
-
3
- This document outlines the consolidated, high-performance client-side routing engine architecture under `core.router` in the `@adukiorg/anza` library. It covers URL-to-view matching, dynamic component rendering, guards evaluation, the multi-container WeakRef registry topology, concurrent element-scoped transitions, and validation plans.
4
-
5
- ---
6
-
7
- ## 1. Architectural Strategy & Core Requirements
8
-
9
- The client-side router acts as the application's authoritative first-class navigation coordinator. It completely owns URL-to-view matching, dynamic component rendering, guards evaluation, history traversal checks, and visual transition orchestration.
10
-
11
- ### Key Pillars
12
-
13
- 1. **Unified Interception (Navigation API):** Register exactly one event listener on the native `window.navigation` event. Any link click, form submission, history traversal, or programmatic `navigation.navigate()` is intercepted at a single junction.
14
- 2. **Path Matching (URLPattern):** Utilize the compiled `URLPattern` matching engine to execute fast path parsing, parameterized path capture (`:id`), and modifier evaluations (`?`, `*`, `+`).
15
- 3. **Layout-Preserving Nested Routing:** Diff route segments to identify changes. Render nested elements into custom `<route-outlet>` Shadow DOM frames, ensuring that unchanged parent layouts are preserved and not unnecessarily unmounted.
16
- 4. **Transition Integration:** Seamlessly wrap DOM layout updates inside `document.startViewTransition()` to achieve GPU-accelerated same-document transitions, falling back to standard synchronous rendering when unsupported.
17
- 5. **Robust Safari Fallbacks:** Address Safari's lack of `precommitHandler` by running guard logic in both pre-commit and post-commit phases, applying immediate URL replacements to preserve authorization boundaries.
18
-
19
- ---
20
-
21
- ## 2. Component Blueprint & Files Layout
22
-
23
- The router architecture strictly aligns with the lowercase, single-word naming structure:
24
-
25
- ```
26
- src/core/router/
27
- ├── index.js # Public facade and programmatic router API
28
- ├── history.js # Wrapper for Navigation API push/replace/traversals
29
- ├── intercept.js # Global navigation listener and lifecycle guards evaluator
30
- ├── match.js # Route table registry and lazy URLPattern compiler
31
- ├── outlet.js # <route-outlet> Custom Element and layout renderer
32
- ├── transitions.js # Safe, fault-tolerant View Transitions orchestrator
33
- └── plan.md # This comprehensive, consolidated planning document
34
- ```
35
-
36
- ---
37
-
38
- ## 3. Detailed Component & Integration Designs
39
-
40
- ### 3.1. Matcher & Table Registry (`match.js`)
41
- * **Pre-Resolved Native URLPattern:** Compiles raw pattern strings into active `URLPattern` instances natively on module load, avoiding dynamic async microtask checks.
42
- * **Deterministic Route Specificity Sorting:** During `router.register()`, routes are automatically sorted by specificity: static paths first, parameterized paths next, and wildcards/catch-alls last. Paths within each category are sorted by length (longest pathname first) to guarantee deterministic matching.
43
- * **Match Resolution:** Match against full URLs by extracting pathname groups safely. If no route matches, fallback immediately to the configured `notFound` handler.
44
-
45
- ```javascript
46
- export async function match(url) {
47
- const Pattern = getURLPattern(); // Pre-resolved native or polyfilled URLPattern class
48
- const targetUrl = new URL(url, globalThis.location?.href || 'http://localhost');
49
-
50
- for (const route of routes) {
51
- if (!route.pattern) {
52
- route.pattern = route.patternStr.startsWith('http')
53
- ? new Pattern(route.patternStr)
54
- : new Pattern({ pathname: route.patternStr });
55
- }
56
-
57
- const result = route.pattern.exec(targetUrl.href);
58
- if (result) {
59
- return {
60
- route,
61
- params: result.pathname.groups || {},
62
- result
63
- };
64
- }
65
- }
66
- return null;
67
- }
68
- ```
69
-
70
- ### 3.2. Lifecycle Guards, Hydration Gate & Safari Mitigation (`intercept.js`)
71
- * **Precommit Interception:** For modern engines, execute registered guard hooks inside `precommitHandler`. Atomic redirections happen via `controller.redirect(url)` before the URL updates, preventing visual leaks of protected paths.
72
- * **Post-Commit Safari Fallback:** Since Safari currently ignores `precommitHandler`, re-execute guard checks at the start of the `handler()` callback. If a violation is caught post-commit, perform a silent `navigation.navigate(url, { history: 'replace' })` to correct the address bar without adding broken history entries.
73
- * **Async Custom Element Hydration Gate:** When a route is matched, the global orchestrator checks if the matched tag is a custom element (contains a hyphen `-`) and awaits its registration before committing the DOM mount:
74
- ```javascript
75
- if (typeof customElements !== 'undefined' && tag.includes('-') && !customElements.get(tag)) {
76
- await customElements.whenDefined(tag);
77
- }
78
- ```
79
- This protects standard HTML elements and ensures components are hydrated before rendering.
80
- * **TransitionController URL Comparison:** Handles absolute and relative URLs transparently using pathname matching with `new URL(u, window.location.href).pathname` to prevent callback execution timeouts.
81
- * **Loading & Error Transitions:** CENTRALIZED error boundary. Handle `navigateerror` globally to catch script load failures or guard rejections. Silence `AbortError` which occurs when a navigation is superseded by a newer one.
82
-
83
- ### 3.3. Reactivity & State Management Integration
84
- * **URL State Ownership:** The router owns all URL-related reads and writes. Components read from URL search parameters via standard `URLSearchParams` inside route handlers.
85
- * **Reactivity Hookups:** Components read route state from the Proxy-based Session Store. They subscribe to changes and schedule DOM updates via rendering pipelines to avoid UI glitches.
86
- * **Hydration State Modeling:** Manage three-phase loading transitions for remote data:
87
- * `loading`: Set when a route is matched but in-flight fetches are pending.
88
- * `hydrated`: Set when local state (IndexedDB/Session) is populated with complete payload.
89
- * `error`: Triggered if network or decoding fails, prompting an in-page recovery view.
90
-
91
- ### 3.4. Form Navigation Interception
92
- * **Interception:** `window.navigation` naturally intercepts form submissions (both `GET` and `POST` methods). The `navigate` event delivers a valid `event.formData` instance containing input values.
93
- * **Processing Pipeline:** The router passes `event.formData` directly to matched handlers, bypassing traditional page-reloading standard action submissions.
94
-
95
- ```javascript
96
- async handler() {
97
- if (event.formData) {
98
- // Process form payload asynchronously and render results
99
- const result = await processFormSubmit(event.formData, { signal: event.signal });
100
- outlet.render(resultView(result));
101
- } else {
102
- // Standard navigation render
103
- outlet.render(await loadRouteView(route));
104
- }
105
- }
106
- ```
107
-
108
- ### 3.5. Nested Routing & Shadow DOM Outlets (`outlet.js`)
109
- * **Nested Route Resolution:** Diff the resolved route segment chains on each navigation pass. Only update `<route-outlet>` elements whose active segment has changed, avoiding re-rendering of outer layout components.
110
- * **Custom `<route-outlet>` Element:** Opens a shadow root to isolate styles. Autodefines unregistered Custom Element ES classes on-the-fly using their class name:
111
-
112
- ```javascript
113
- async render(elementOrClass, props = {}) {
114
- this.#shadow.innerHTML = '';
115
- if (typeof elementOrClass === 'function') {
116
- let tagName = elementOrClass.name.toLowerCase();
117
- if (!tagName.includes('-')) {
118
- tagName = `route-${tagName}`;
119
- }
120
- if (!customElements.get(tagName)) {
121
- customElements.define(tagName, elementOrClass);
122
- }
123
- const el = document.createElement(tagName);
124
- Object.assign(el, props);
125
- this.#shadow.appendChild(el);
126
- return el;
127
- }
128
- // Handles string element tags & HTMLElement instances...
129
- }
130
- ```
131
-
132
- ### 3.6. View Transitions Integration (`transitions.js`)
133
- * **Fault-Tolerant Transitions:** Wrap DOM mutations in `document.startViewTransition()`. Add a try-catch guard to silence aborted view transition errors (common during rapid clicking) to ensure application stability.
134
- * **Unblocking Transition Engine:** Resolves transition execution instantly using `vt.ready` instead of `vt.finished`, preventing route management pipelines from being blocked by long-running paint frames.
135
- * **Synchronous Updates Bypass:** If no asynchronous promise is returned by the update callback, updates are run synchronously to avoid unneeded microtask scheduler ticks.
136
- * **Shared Element Morphing:** Assign unique `view-transition-name` strings programmatically to interactive elements before the transition, and reset them immediately on transition completion to achieve fluid list-to-detail transformations.
137
-
138
- ### 3.7. The `<nav-link>` Primitive Element
139
- * **Tag Naming & Registration:** The primitive router-aware anchor tag is explicitly named and registered as `nav-link` (not `ui-link`).
140
- * **Active State Auto-Synchronization:** The element listens to standard browser `navigate` events. When matched, it sets the `aria-current="page"` attribute automatically, enabling CSS pseudo-selector styles like `::slotted(nav-link[aria-current="page"])` to highlight active tabs.
141
- * **Cancelable External Click Events:** Clicking an external target triggers a custom `external` event:
142
-
143
- ```javascript
144
- const event = new CustomEvent('external', {
145
- detail: { href, target },
146
- bubbles: true,
147
- cancelable: true,
148
- composed: true
149
- });
150
- this.dispatchEvent(event);
151
- if (event.defaultPrevented) {
152
- e.preventDefault(); // HALT browser default anchor navigation
153
- }
154
- ```
155
- This enables parent applications to show alert boxes, warnings, or styled modal gates before allowing external page redirects.
156
-
157
- ---
158
-
159
- ## 4. Advanced Container-Driven Routing Architecture
160
-
161
- This architecture outlines a next-generation routing topology for `@adukiorg/anza`. It replaces the monolithic single-outlet model with a **multi-container, concurrent-transition architecture** that mirrors a real desktop OS: multiple named regions of the UI mount and animate independently, and navigation only activates when its required container is verifiably alive in the DOM at the moment of resolution.
162
-
163
- ### 4.1. The Container Model
164
-
165
- #### What Is a Container?
166
- A container is any DOM node (a standard element such as `#main-content`, or a custom element such as `<app-sidebar>`) that acts as a **first-class mounting slot** for routed views. Containers own their own animation, their own DOM swap strategy, and their own transition scope.
167
-
168
- The `container` field in a `ui.element` spec accepts either form:
169
- ```javascript
170
- container: '#main-content' // standard element resolved via CSS selector
171
- container: 'app-sidebar' // custom element resolved via the container registry
172
- ```
173
-
174
- Both forms resolve through the same internal registry. Selectors go through `document.querySelector` as a fallback only if no explicit registration is found.
175
-
176
- #### The Singleton Constraint
177
- **Two containers with the same name cannot coexist in the DOM at the same time.**
178
- If a second instance of `<app-sidebar>` attempts to connect while one is already registered, the router throws immediately:
179
- ```
180
- ContainerError: Singleton violation — 'app-sidebar' is already mounted.
181
- A second instance cannot register while the first is active.
182
- ```
183
- This is enforced in `connectedCallback` before any registration is accepted.
184
-
185
- ### 4.2. The Live Container Node Map
186
-
187
- #### Data Structure
188
- The router maintains a `Map<string, WeakRef<HTMLElement>>` — a **named, weakly-held reference map** of every currently active container node.
189
-
190
- `WeakRef` is chosen deliberately over a strong `Map`:
191
- - The router does **not** prevent containers from being garbage-collected if the rest of the app removes them from the DOM.
192
- - If a `WeakRef` dereferences to `undefined` (e.g. node GC'd before `disconnectedCallback` fires), the router treats the container as absent and yields a `RouteError`.
193
- - A `FinalizationRegistry` runs in the background as a passive safety net to prune stale map entries after GC.
194
-
195
- ```javascript
196
- const containerNodeMap = new Map(); // string → WeakRef<HTMLElement>
197
- const cleanupRegistry = new FinalizationRegistry((name) => {
198
- if (containerNodeMap.get(name)?.deref() === undefined) {
199
- containerNodeMap.delete(name);
200
- }
201
- });
202
- ```
203
-
204
- #### Background Updates: Non-Blocking, Lifecycle-Driven
205
- The node map is **never polled**. It updates reactively through two mechanisms:
206
-
207
- 1. **Custom Element Lifecycle Hooks**: Containers register themselves in `connectedCallback` and unregister in `disconnectedCallback`.
208
- 2. **MutationObserver Fallback (Standard Elements Only)**: For plain divs, a single `MutationObserver` watches `document.body` for child changes. Once all registered containers have been resolved, this observer is automatically disconnected to preserve thread resources.
209
-
210
- ### 4.3. Route Resolution Guard
211
-
212
- When navigation triggers, the target container is resolved **before** any DOM mutation:
213
- 1. Matched route declares `container: 'app-sidebar'`.
214
- 2. Router calls `containerNodeMap.get('app-sidebar')?.deref()`.
215
- 3. **If the node is live** → proceed to mount.
216
- 4. **If the node is absent or GC'd** → throw immediately:
217
- ```
218
- RouteError: Required layout container 'app-sidebar' is not active in the DOM.
219
- Navigation to '/settings/profile' was aborted.
220
- ```
221
- This prevents blind mounts and ensures safety.
222
-
223
- ### 4.4. The Container Interface
224
-
225
- Any element acting as a router container must expose a `swapView` method. The router **never touches the DOM directly** — it hands off the new element and options to the container's `swapView`.
226
-
227
- ```typescript
228
- interface RouterContainer extends HTMLElement {
229
- swapView(newElement: HTMLElement, options?: SwapOptions): Promise<void>;
230
- }
231
-
232
- interface SwapOptions {
233
- transitionName?: string;
234
- params?: Record<string, string>;
235
- direction?: 'push' | 'replace' | 'back' | 'forward';
236
- }
237
- ```
238
-
239
- ### 4.5. Element-Scoped View Transitions (Concurrent Transitions)
240
-
241
- Scoped view transitions enable simultaneous animations across different parts of the UI without blocking pointer events outside their respective subtrees:
242
- - Only the container's subtree rendering pauses during its transition.
243
- - Scoped transitions require `contain: layout` applied to the container.
244
- - The container tries element-scoped `startViewTransition` first, then falls back to `document.startViewTransition`, then to a synchronous swap.
245
-
246
- ### 4.6. The `cloneNode` / `replaceChildren` Swap Strategy
247
-
248
- Containers must **never use `innerHTML`** to mount views. `innerHTML` destroys all existing element identity and flushes Shadow DOM state. Direct `replaceChildren(newElement)` is the correct atomic path.
249
-
250
- ```javascript
251
- async swapView(newElement, options = {}) {
252
- const doSwap = () => {
253
- this.replaceChildren(newElement);
254
- };
255
-
256
- // Try element-scoped transition (Chrome 147+, concurrent-safe)
257
- if (typeof this.startViewTransition === 'function') {
258
- try {
259
- await this.startViewTransition({ callback: doSwap }).finished;
260
- } catch (err) {
261
- if (err?.name !== 'AbortError') console.warn('Scoped transition failed:', err);
262
- }
263
- return;
264
- }
265
-
266
- // Fall back to document-scoped transition (Baseline Oct 2025)
267
- if (typeof document.startViewTransition === 'function') {
268
- try {
269
- await document.startViewTransition(doSwap).finished;
270
- } catch (err) {
271
- if (err?.name !== 'AbortError') console.warn('Document transition failed:', err);
272
- }
273
- return;
274
- }
275
-
276
- doSwap();
277
- }
278
- ```
279
-
280
- ---
281
-
282
- ## 5. Sample Container Implementation: `<route-container>`
283
-
284
- ```javascript
285
- import { router } from '../core/router/index.js';
286
-
287
- export class RouteContainer extends HTMLElement {
288
- get containerName() {
289
- return this.getAttribute('name') || this.tagName.toLowerCase();
290
- }
291
-
292
- connectedCallback() {
293
- const existing = router.getContainer(this.containerName);
294
- if (existing && existing !== this) {
295
- throw new Error(`ContainerError: Singleton violation — '${this.containerName}' already mounted.`);
296
- }
297
- this.style.contain = 'layout';
298
- router.registerContainer(this.containerName, this);
299
- }
300
-
301
- disconnectedCallback() {
302
- router.unregisterContainer(this.containerName, this);
303
- }
304
-
305
- async swapView(newElement, options = {}) {
306
- const { direction = 'push' } = options;
307
- this.dataset.transitionDirection = direction;
308
-
309
- const doSwap = () => {
310
- this.replaceChildren(newElement);
311
- delete this.dataset.transitionDirection;
312
- };
313
-
314
- if (typeof this.startViewTransition === 'function') {
315
- try {
316
- await this.startViewTransition({ callback: doSwap }).finished;
317
- } catch (err) {
318
- if (err?.name !== 'AbortError') console.warn('[RouteContainer] Scoped VT aborted:', err);
319
- }
320
- return;
321
- }
322
-
323
- if (typeof document.startViewTransition === 'function') {
324
- try {
325
- await document.startViewTransition(doSwap).finished;
326
- } catch (err) {
327
- if (err?.name !== 'AbortError') console.warn('[RouteContainer] Document VT aborted:', err);
328
- }
329
- return;
330
- }
331
-
332
- doSwap();
333
- }
334
- }
335
-
336
- customElements.define('route-container', RouteContainer);
337
- ```
338
-
339
- ---
340
-
341
- ## 6. Polyfill & Browser Gap Strategy
342
-
343
- | API / Feature | Gap / Constraint | Native Support | Polyfill / Fallback Strategy |
344
- |---|---|---|---|
345
- | **Navigation API** | Missing in older engines | Baseline Jan 2026 | **History API Fallback:** Custom `pushState` / `popstate` overrides and link click event delegation. |
346
- | **precommitHandler** | Safari missing | Chrome / Firefox only | **Double-Execution Guards:** Run guards in both pre-commit and post-commit handlers, using URL replacement fallbacks. |
347
- | **URLPattern** | Missing in pre-2025 engines | Baseline Sep 2025 | **Path-to-Regexp Fallback:** Thin, compiled regular expression matcher that exposes identical match APIs. |
348
- | **View Transitions** | Safari < 18, Firefox < 133 | Baseline Oct 2025 | **Direct DOM Fallback:** Execute the update callback synchronously without transition animations. |
349
-
350
- ---
351
-
352
- ## 7. Verification & Testing Plan
353
-
354
- ### 7.1. Automated Unit Tests (`tests/core/router/`)
355
- The router module is verified across the following scopes:
356
- 1. **`match.test.js`**: Verify lazy compiles, segment parameters extraction (`/users/:id`), modifier matches (`?`, `*`, `+`), specificity route sorting, and catch-all fallbacks.
357
- 2. **`history.test.js`**: Verify programmatic operations (`navigate`, `replace`, `back`, `forward`), and validation flags (`canBack`, `canForward`).
358
- 3. **`intercept.test.js`**:
359
- - Verify guard triggers under modern pre-commit pipelines.
360
- - Verify post-commit guard mitigations (Safari fallbacks) and URL replacement corrections.
361
- - Verify TransitionController pathname comparison checks.
362
- - Verify form submissions interception and `event.formData` delivery.
363
- 4. **`outlet.test.js`**: Verify `<route-outlet>` dynamic rendering, class autodefining, properties hydration, and nested layout preservation.
364
- 5. **`transitions.test.js`**: Assert transitions execute correctly when supported and fallback instantly without throw on non-supporting engines.
365
-
366
- ### 7.2. Test Runner Execution
367
- Execute all tests using the project's zero-build browser runner:
368
- ```bash
369
- npm test
370
- ```
@@ -1,97 +0,0 @@
1
- # Security Missing Support
2
-
3
- This document tracks remaining support, runtime gaps, type mismatches, and storage/offline synchronization improvements for `src/core/security`. Implemented behavior belongs in `usage.md`; unsupported, under-tested, or performance-gated behavior belongs here until it is built and verified.
4
-
5
- ---
6
-
7
- ## 0. Toolchain and Naming Rules
8
-
9
- Heavy compilation, static analysis, and code generation belong in `tools/`, not in browser runtime modules.
10
-
11
- Current toolchain security requirements:
12
- - Ensure strict CSP conformance checking at build time.
13
- - Validate that all HTML insertion sites (sinks) in templates are wrapped in `sanitize` or Trusted Types.
14
-
15
- Naming rules for security support:
16
- - Prefer one-word files: `crypto.js`, `sanitize.js`, `permissions.js`, `index.js`.
17
- - Plural folders: `tests/`, `types/`.
18
- - Single-word methods where possible: `uuid`, `hash`, `encrypt`, `decrypt`, `sign`, `verify`, `sanitize`, `permission`.
19
-
20
- ---
21
-
22
- ## 1. Critical Runtime and Type Gaps
23
-
24
- ### 1.1. Missing Trusted Types Integration in Sanitizer
25
- - **Status**: Runtime gap.
26
- - **Files**:
27
- - `src/core/security/sanitize.js`
28
- - **Problem**:
29
- The `sanitize()` function returns a raw string. When a strict CSP enforces `require-trusted-types-for 'script'`, writing this string to DOM sinks like `innerHTML` throws a `TypeError`. The HTML sanitizer must yield a `TrustedHTML` object wrapped in a named policy (`core-sanitize`) rather than a plain string when Trusted Types are supported by the environment.
30
- - **Expected Support**:
31
- - Create a named Trusted Types policy `core-sanitize` inside `sanitize.js`.
32
- - Wrap the sanitized HTML string using the policy to return a `TrustedHTML` object (or fall back to a plain string if the browser does not support Trusted Types).
33
- - Implement a `createScriptURL(input)` policy rule to restrict dynamic script source URLs to safe origins.
34
-
35
- ---
36
-
37
- ## 2. Storage & Offline Integration Gaps
38
-
39
- ### 2.1. Direct crypto.randomUUID() Usage in Storage and Offline
40
- - **Status**: Consistency & synchronization gap.
41
- - **Files**:
42
- - `src/core/offline/clock.js`
43
- - `src/core/offline/queue.js`
44
- - `src/core/storage/opfs.js`
45
- - **Problem**:
46
- Multiple components call the browser's global `crypto.randomUUID()` directly instead of importing and consuming the centralized `uuid()` utility exported by `core/security`. This prevents unified mocking/stubbing of UUID generation during unit testing and breaks consistency.
47
- - **Expected Support**:
48
- - Refactor `clock.js` to import and use `uuid` from `core/security`.
49
- - Refactor `queue.js` to import and use `uuid` from `core/security`.
50
- - Refactor `storage/opfs.js` to import and use `uuid` from `core/security`.
51
-
52
- ### 2.2. Transparent Storage Encryption Interface
53
- - **Status**: Feature gap.
54
- - **Files**:
55
- - `src/core/storage/index.js`
56
- - `src/core/security/crypto.js`
57
- - **Problem**:
58
- Sensitive keys or offline sync payloads stored in IndexedDB or OPFS are written as raw plaintext. A secure offline synchronization layer requires a way to transparently encrypt and decrypt values before they cross the storage boundary.
59
- - **Expected Support**:
60
- - Expose helper utilities in `crypto.js` to securely encrypt and decrypt objects using derived AES-GCM keys.
61
-
62
- ---
63
-
64
- ## 3. Test Coverage Gaps
65
-
66
- ### 3.1. Missing Permissions Watcher Testing
67
- - **Status**: Test gap.
68
- - **Files**:
69
- - `tests/core/security/permissions.test.js` [NEW]
70
- - **Needed Coverage**:
71
- - Verify that `permission()` queries return states like `'granted'`, `'denied'`, or `'prompt'`.
72
- - Verify that `watchPermission()` registers a listener on permission changes, respects AbortSignal abort triggers, and cleans up event listeners correctly.
73
-
74
- ### 3.2. Missing Signature Verification Testing
75
- - **Status**: Test gap.
76
- - **Files**:
77
- - `tests/core/security/crypto.test.js`
78
- - **Needed Coverage**:
79
- - Test HMAC and ECDSA key generation, message signing, and signature verification via `sign()` and `verify()`.
80
-
81
- ---
82
-
83
- ## 4. Suggested Implementation Order
84
-
85
- 1. Implement the `core-sanitize` Trusted Types policy inside `src/core/security/sanitize.js`.
86
- 2. Refactor direct `crypto.randomUUID()` calls in `clock.js`, `queue.js`, and `storage/opfs.js` to use `uuid` from `core/security`.
87
- 3. Add permission query/watcher tests in a new file `tests/core/security/permissions.test.js`.
88
- 4. Add sign/verify tests in `tests/core/security/crypto.test.js`.
89
-
90
- ---
91
-
92
- ## 5. Done Criteria
93
-
94
- This missing-support list is complete when:
95
- - `sanitize()` returns a `TrustedHTML` object (when supported).
96
- - Direct `crypto.randomUUID()` calls are replaced with `uuid()` imports.
97
- - Permission query/watching and cryptographically signing/verifying features are fully tested.