dalila 1.3.2 → 1.4.0

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/README.md CHANGED
@@ -1,691 +1,167 @@
1
- # Dalila Framework
1
+ # Dalila
2
2
 
3
- **UI driven by the DOM, not re-renders.**
3
+ **DOM-first reactivity without the re-renders.**
4
4
 
5
- Dalila is a **SPA**, **DOM-first**, **HTML natural** framework based on **signals**, created to eliminate the common pitfalls and workarounds of React.
5
+ Dalila is a reactive framework built on signals. No virtual DOM, no JSX required just HTML with declarative bindings.
6
6
 
7
- ## ✨ Status
8
-
9
- ### Core (runtime, stable)
10
- - 🚀 **Signals-based reactivity** - Automatic dependency tracking
11
- - 🎯 **DOM-first rendering** - Direct DOM manipulation, no Virtual DOM
12
- - 🔄 **Scope-based lifecycle** - Automatic cleanup (best effort)
13
- - 🧿 **DOM lifecycle watchers** — `watch()` + helpers (`useEvent`, `useInterval`, `useTimeout`, `useFetch`) with scope-based cleanup
14
- - 🛣️ **SPA router** - Basic routing with loaders and AbortSignal
15
- - 📦 **Context system** - Reactive dependency injection
16
- - 🔧 **Scheduler & batching** - Group updates into a single frame
17
- - 📚 **List rendering** - `createList` with keyed diffing for efficient updates
18
- - 🧱 **Resources** - Async data helpers with AbortSignal and scope cleanup
19
-
20
- ### Experimental
21
- - 🎨 **Natural HTML bindings** - Only in the example dev-server (not in core)
22
- - 🔍 **DevTools (console)** - Warnings and FPS monitor only
23
- - 🧪 **Low-level list API** - `forEach` (advanced control, use when you need fine-grained behavior like reactive index; `createList` is the default)
24
-
25
- ### Planned / Roadmap
26
- - 🧰 **DevTools UI** - Visual inspection tooling
27
- - 🧩 **HTML bindings runtime/compiler** - First-class template binding
28
- - 📊 **Virtualization** - Virtual lists/tables for very large datasets (10k+ items)
29
-
30
- ## 📦 Installation
7
+ ## Installation
31
8
 
32
9
  ```bash
33
10
  npm install dalila
34
11
  ```
35
12
 
36
- ## 🚀 Quick Start
37
-
38
- Dalila examples use HTML bindings and a controller:
13
+ ## Quick Start
39
14
 
40
15
  ```html
41
- <div>
42
- <span>{count}</span>
43
- <button on:click={increment}>+</button>
16
+ <div id="app">
17
+ <p>Count: {count}</p>
18
+ <button d-on-click="increment">+</button>
44
19
  </div>
45
20
  ```
46
21
 
47
22
  ```ts
48
23
  import { signal } from 'dalila';
24
+ import { bind } from 'dalila/runtime';
49
25
 
50
- export function createController() {
51
- const count = signal(0);
52
- const increment = () => count.update(c => c + 1);
53
-
54
- return { count, increment };
55
- }
56
- ```
57
-
58
- > Note: HTML bindings in this example are provided by the example dev-server (`npm run serve`),
59
- > not by the core runtime.
60
-
61
- ## 🧪 Local Demo Server
62
-
63
- Run a local server with HMR from the repo root:
64
-
65
- ```bash
66
- npm run serve
67
- ```
68
-
69
- Then open `http://localhost:4242/`.
70
-
71
- ## 📚 Core Concepts
72
-
73
- ### Signals
74
- ```typescript
75
26
  const count = signal(0);
76
27
 
77
- // Read value
78
- console.log(count()); // 0
79
-
80
- // Set value
81
- count.set(5);
82
-
83
- // Update with function
84
- count.update(c => c + 1);
85
-
86
- // Reactive effects
87
- effect(() => {
88
- console.log(`Count changed to: ${count()}`);
89
- });
90
-
91
- // Async effects with cleanup
92
- effectAsync(async (signal) => {
93
- const response = await fetch('/api/data', { signal });
94
- const data = await response.json();
95
- console.log(data);
96
- });
97
- ```
98
-
99
- ### Lifecycle / Cleanup (Scopes + DOM)
100
-
101
- Dalila is DOM-first. That means a lot of your "lifecycle" work is not React-like rendering —
102
- it's **attaching listeners, timers, and async work to real DOM nodes**.
103
-
104
- Dalila's rule is simple:
105
-
106
- - **Inside a scope** → cleanup is automatic (best-effort) on `scope.dispose()`
107
- - **Outside a scope** → you must call `dispose()` manually unless a primitive explicitly auto-disposes on DOM removal (Dalila warns in dev mode)
108
-
109
- #### `watch(node, fn)` — DOM lifecycle primitive
110
-
111
- `watch()` runs a reactive function while a DOM node is connected.
112
- When the node disconnects, the effect is disposed. If the node reconnects later, it starts again.
113
- It's the primitive that enables DOM lifecycle without a VDOM.
114
-
115
- ```ts
116
- import { watch, signal } from "dalila";
117
-
118
- const count = signal(0);
119
-
120
- const dispose = watch(someNode, () => {
121
- // Reactive while connected because watch() runs this inside an effect()
122
- someNode.textContent = String(count());
123
- });
124
-
125
- // later (optional if inside a scope)
126
- dispose();
127
- ```
128
-
129
- #### Lifecycle helpers
130
-
131
- Built on the same mental model, Dalila provides small helpers that always return an **idempotent** `dispose()`:
132
- `useEvent`, `useInterval`, `useTimeout`, `useFetch`.
133
-
134
- **Inside a scope (recommended)**
135
-
136
- ```ts
137
- import { createScope, withScope, useEvent, useInterval, useFetch } from "dalila";
138
-
139
- const scope = createScope();
140
-
141
- withScope(scope, () => {
142
- useEvent(button, "click", onClick);
143
- useInterval(tick, 1000);
144
-
145
- const user = useFetch("/api/user");
146
-
147
- // Optional manual cleanup:
148
- // user.dispose();
149
- });
28
+ const ctx = {
29
+ count,
30
+ increment: () => count.update(n => n + 1),
31
+ };
150
32
 
151
- scope.dispose(); // stops listener, interval, and aborts fetch
33
+ bind(document.getElementById('app')!, ctx);
152
34
  ```
153
35
 
154
- Disposing the scope stops listeners/timers and aborts in-flight async work created inside the scope.
36
+ ## Docs
155
37
 
156
- **Outside a scope (manual cleanup)**
38
+ ### Getting Started
157
39
 
158
- ```ts
159
- import { useInterval } from "dalila";
40
+ - [Overview](./docs/index.md) — Philosophy and quick start
41
+ - [Template Spec](./docs/template-spec.md) Binding syntax reference
42
+ - [Roadmap](./docs/roadmap.md) — Strengths, weaknesses, and goals
160
43
 
161
- const dispose = useInterval(() => console.log("tick"), 1000);
44
+ ### Core
162
45
 
163
- // later...
164
- dispose(); // required
165
- ```
46
+ - [Signals](./docs/core/signals.md) — `signal`, `computed`, `effect`
47
+ - [Scopes](./docs/core/scope.md) Lifecycle management and cleanup
48
+ - [Persist](./docs/core/persist.md) — Automatic storage sync for signals
49
+ - [Context](./docs/context.md) — Dependency injection
166
50
 
167
- #### Why helpers vs native APIs?
51
+ ### Runtime
168
52
 
169
- You *can* use `addEventListener` / `setTimeout` / `setInterval` directly, but then cleanup becomes "manual discipline".
170
- In a DOM-first app, listeners/timers are the #1 source of silent leaks when the UI changes.
171
- Scopes make cleanup a **default**, not a convention — so UI changes don't silently leak listeners, timers, or in-flight async work.
53
+ - [Template Binding](./docs/runtime/README.md) `bind()`, text interpolation, events
54
+ - [FOUC Prevention](./docs/runtime/fouc-prevention.md) Automatic token hiding
172
55
 
173
- ### Conditional Rendering
56
+ ### Rendering
174
57
 
175
- Dalila provides two primitives for branching UI:
58
+ - [when](./docs/core/when.md) Conditional visibility
59
+ - [match](./docs/core/match.md) — Switch-style rendering
60
+ - [for](./docs/core/for.md) — List rendering with keyed diffing
176
61
 
177
- - **`when`** — boolean conditions (`if / else`)
178
- - **`match`** — value-based branching (`switch / cases`)
62
+ ### Data
179
63
 
180
- They are intentionally separate to keep UI logic explicit and predictable.
64
+ - [Resources](./docs/core/resource.md) Async data with loading/error states
65
+ - [Query](./docs/core/query.md) — Cached queries
66
+ - [Mutations](./docs/core/mutation.md) — Write operations
181
67
 
182
- #### `when` — boolean conditions
68
+ ### Utilities
183
69
 
184
- Use `when` when your UI depends on a true/false condition.
70
+ - [Scheduler](./docs/core/scheduler.md) Batching and coordination
71
+ - [Keys](./docs/core/key.md) — Cache key encoding
72
+ - [Dev Mode](./docs/core/dev.md) — Warnings and helpers
185
73
 
186
- ```ts
187
- when(
188
- () => isVisible(),
189
- () => VisibleView(),
190
- () => HiddenView()
191
- );
192
- ```
193
-
194
- HTML binding example:
195
-
196
- ```html
197
- <div>
198
- <button on:click={toggle}>Toggle</button>
74
+ ## Features
199
75
 
200
- <p when={show}>🐒 Visible branch</p>
201
- <p when={!show}>🙈 Hidden branch</p>
202
- </div>
203
76
  ```
204
-
205
- - Tracks signals used inside the condition
206
- - Optional else branch runs when the condition is false
207
- - Each branch has its own lifecycle (scope cleanup)
208
-
209
- #### `match` — value-based branching
210
-
211
- Use `match` when your UI depends on a state or key, not just true/false.
212
-
213
- ```ts
214
- match(
215
- () => status(),
216
- {
217
- loading: Loading,
218
- error: Error,
219
- success: Success,
220
- _: Idle
221
- }
222
- );
223
- ```
224
-
225
- HTML binding example:
226
-
227
- ```html
228
- <div match={status}>
229
- <p case="idle">🟦 Idle</p>
230
- <p case="loading">⏳ Loading...</p>
231
- <p case="success">✅ Success!</p>
232
- <p case="error">❌ Error</p>
233
- <p case="_">🤷 Unknown</p>
234
- </div>
77
+ dalila → signal, computed, effect, batch, ...
78
+ dalila/runtime → bind() for HTML templates
79
+ dalila/context → createContext, provide, inject
80
+ dalila/router → (in development)
235
81
  ```
236
82
 
237
- - Each case maps a value to a render function
238
- - `_` is the default (fallback) case
239
- - Swaps cases only when the selected key changes
240
- - Each case has its own lifecycle (scope cleanup)
241
-
242
- #### Rule of thumb
243
-
244
- - `when` → booleans → optional else
245
- - `match` → values/keys → `_` as fallback
246
-
247
- These primitives are not abstractions over JSX.
248
- They are explicit DOM control tools, designed to make branching visible and predictable.
83
+ ### Signals
249
84
 
250
- ### Context (Dependency Injection)
251
85
  ```ts
252
- const Theme = createContext<'light' | 'dark'>('theme');
253
- provide(Theme, signal('light'));
254
- const theme = inject(Theme);
255
- ```
256
-
257
- ### SPA Router
258
- ```typescript
259
- const router = createRouter({
260
- routes: [
261
- {
262
- path: '/',
263
- view: HomePage,
264
- loader: async ({ signal }) => {
265
- const res = await fetch('/api/home', { signal });
266
- return res.json();
267
- }
268
- },
269
- { path: '/users/:id', view: UserPage }
270
- ]
271
- });
272
-
273
- router.mount(document.getElementById('app'));
274
- ```
275
-
276
- ### Batching & Scheduling
277
- ```typescript
278
- // Batch multiple updates - effects coalesce into a single frame
279
- batch(() => {
280
- count.set(1); // ✅ State updates immediately
281
- theme.set('dark'); // ✅ State updates immediately
282
- console.log(count()); // Reads new value: 1
283
-
284
- // Effects are deferred and run once at the end of the batch
285
- });
286
-
287
- // DOM read/write discipline
288
- const width = measure(() => element.offsetWidth);
289
- mutate(() => {
290
- element.style.width = `${width + 10}px`;
291
- });
292
- ```
86
+ import { signal, computed, effect } from 'dalila';
293
87
 
294
- **Batching semantics:**
295
- - `signal.set()` updates the value **immediately** (synchronous)
296
- - Effects are **deferred** until the batch completes
297
- - All deferred effects run once in a single animation frame
298
- - This allows reading updated values inside the batch while coalescing UI updates
299
-
300
- ### List Rendering with Keys
301
-
302
- Dalila provides efficient list rendering with keyed diffing, similar to React's `key` prop or Vue's `:key`.
303
-
304
- **Basic usage:**
305
- ```typescript
306
- import { signal } from 'dalila';
307
- import { createList } from 'dalila/core';
308
-
309
- const todos = signal([
310
- { id: 1, text: 'Learn Dalila', done: true },
311
- { id: 2, text: 'Build app', done: false }
312
- ]);
313
-
314
- const listFragment = createList(
315
- () => todos(),
316
- (todo) => {
317
- const li = document.createElement('li');
318
- li.textContent = todo.text;
319
- return li;
320
- },
321
- (todo) => todo.id.toString() // Key function
322
- );
323
-
324
- document.body.append(listFragment);
325
- ```
88
+ const count = signal(0);
89
+ const doubled = computed(() => count() * 2);
326
90
 
327
- **Key function best practices:**
328
- - ✅ Always provide a keyFn for dynamic lists
329
- - ✅ Use stable, unique identifiers (IDs, not indices)
330
- - ✅ Avoid using object references as keys
331
- - ⚠️ Without keyFn, items use index as key (re-renders on reorder)
332
-
333
- **How it works:**
334
- - Only re-renders items whose value changed (not items that only moved)
335
- - Preserves DOM nodes for unchanged items (maintains focus, scroll, etc)
336
- - Efficient for lists up to ~1000 items (informal guideline, not yet benchmarked)
337
- - Each item gets its own scope for automatic cleanup
338
- - Outside a scope, the list auto-disposes when removed from the DOM (or call `fragment.dispose()`)
339
- > **Note:** In `createList`, `index` is a snapshot (not reactive). Reorder moves nodes without re-render. Use `forEach()` if you need a reactive index.
340
-
341
- **Performance:**
342
- ```typescript
343
- // Bad: re-creates all items on every change
344
91
  effect(() => {
345
- container.innerHTML = '';
346
- todos().forEach(todo => {
347
- container.append(createTodoItem(todo));
348
- });
92
+ console.log('Count is', count());
349
93
  });
350
94
 
351
- // Good: only updates changed items
352
- const list = createList(
353
- () => todos(),
354
- (todo) => createTodoItem(todo),
355
- (todo) => todo.id.toString()
356
- );
95
+ count.set(5); // logs: Count is 5
357
96
  ```
358
97
 
359
- ### Data Fetching & Server State
360
-
361
- > **Scope rule (important):**
362
- > - `q.query()` / `createCachedResource()` cache **only within a scope**.
363
- > - Outside scope, **no cache** (safer).
364
- > - For explicit global cache, use `q.queryGlobal()` or `createCachedResource(..., { persist: true })`.
365
-
366
- Dalila treats async data as **state**, not as lifecycle effects.
367
-
368
- Instead of hooks or lifecycle-driven fetching, Dalila provides resources that:
369
-
370
- - Are driven by signals
371
- - Are abortable by default
372
- - Clean themselves up with scopes
373
- - Can be cached, invalidated, and revalidated declaratively
374
-
375
- There are three layers, from low-level to DX-focused:
376
-
377
- - `createResource` — primitive (no cache)
378
- - `createCachedResource` — shared cache + invalidation
379
- - `QueryClient` — ergonomic DX (queries + mutations)
380
-
381
- You can stop at any layer.
382
-
383
- #### 🧱 createResource — the primitive
384
-
385
- Use `createResource` when you want a single async source tied to reactive dependencies.
386
-
387
- ```ts
388
- const user = createResource(async (signal) => {
389
- const res = await fetch(`/api/user/${id()}`, { signal });
390
- return res.json();
391
- });
392
- ```
393
-
394
- **Behavior**
395
-
396
- - Runs inside effectAsync
397
- - Tracks any signal reads inside the fetch
398
- - Aborts the previous request on re-run
399
- - Aborts automatically on scope disposal
400
- - Exposes reactive state
401
-
402
- ```ts
403
- user.data(); // T | null
404
- user.loading(); // boolean
405
- user.error(); // Error | null
406
- ```
407
-
408
- **Manual revalidation:**
98
+ ### Template Binding
409
99
 
410
100
  ```ts
411
- user.refresh(); // deduped
412
- user.refresh({ force }); // abort + refetch
413
- ```
414
-
415
- **When to use**
416
-
417
- - Local data
418
- - One-off fetches
419
- - Non-shared state
420
- - Full control
421
-
422
- If you want sharing, cache, or invalidation, go up one level.
423
-
424
- #### 🗄️ Cached Resources
425
-
426
- > **Scoped cache (recommended):**
427
- ```ts
428
- withScope(createScope(), () => {
429
- const user = createCachedResource("user:42", fetchUser, { tags: ["users"] });
430
- });
431
- ```
432
-
433
- > **Global cache (explicit):**
434
- ```ts
435
- const user = createCachedResource("user:42", fetchUser, { tags: ["users"], persist: true });
436
- ```
437
-
438
- Dalila can cache resources by key, without introducing a global singleton or context provider.
439
-
440
- ```ts
441
- const user = createCachedResource(
442
- "user:42",
443
- async (signal) => fetchUser(signal, 42),
444
- { tags: ["users"] }
445
- );
446
- ```
447
-
448
- **What caching means in Dalila**
449
-
450
- - One fetch per key (deduped)
451
- - Shared across scopes (when using `persist: true`)
452
- - Automatically revalidated on invalidation
453
- - Still abortable and scope-safe
454
-
455
- **Invalidation by tag**
456
- ```ts
457
- invalidateResourceTag("users");
458
- ```
459
-
460
- All cached resources registered with "users" will:
461
- - Be marked stale
462
- - Revalidate in place (best-effort)
463
-
464
- This is the foundation used by the query layer.
465
-
466
- #### 🧠 Query Client (DX Layer)
467
-
468
- The QueryClient builds a React Query–like experience, but stays signal-driven and scope-safe.
469
-
470
- ```ts
471
- const q = createQueryClient();
472
-
473
- // Scoped query (recommended)
474
- const user = q.query({
475
- key: () => q.key("user", userId()),
476
- tags: ["users"],
477
- fetch: (signal, key) => apiGetUser(signal, key[1]),
478
- staleTime: 10_000,
479
- });
480
-
481
- // Global query (explicit)
482
- const user = q.queryGlobal({
483
- key: () => q.key("user", userId()),
484
- tags: ["users"],
485
- fetch: (signal, key) => apiGetUser(signal, key[1]),
486
- staleTime: 10_000,
487
- });
488
- ```
489
-
490
- **What this gives you**
491
-
492
- - Reactive key
493
- - Automatic caching by encoded key
494
- - Abort on key change
495
- - Deduped requests
496
- - Tag-based invalidation
497
- - Optional stale revalidation
498
- - No providers, no hooks
499
-
500
- ```ts
501
- user.data();
502
- user.loading();
503
- user.error();
504
- user.status(); // "loading" | "error" | "success"
505
- user.refresh();
506
- ```
101
+ import { bind } from 'dalila/runtime';
507
102
 
508
- #### 🔑 Query Keys
103
+ // Binds {tokens}, d-on-*, when, match to the DOM
104
+ // Dev-server auto-prevents FOUC (flash of tokens)
105
+ const dispose = bind(rootElement, ctx);
509
106
 
510
- Keys are data identity, not fetch parameters.
511
-
512
- ```ts
513
- q.key("user", userId());
107
+ // Cleanup when done
108
+ dispose();
514
109
  ```
515
110
 
516
- - Typed
517
- - Stable
518
- - Readonly
519
- - Encoded safely (no JSON.stringify)
520
- - If the key changes, the query refetches.
521
-
522
- #### 🔁 Stale Revalidation (staleTime)
523
-
524
- Dalila’s staleTime is intentionally simpler than React Query.
111
+ ### Scopes
525
112
 
526
113
  ```ts
527
- staleTime: 10_000
528
- ```
114
+ import { createScope, withScope, effect } from 'dalila';
529
115
 
530
- **Meaning:**
531
-
532
- - After a successful fetch
533
- - Schedule a best-effort revalidate
534
- - Cleared automatically on scope disposal
535
-
536
- This avoids background timers leaking or running after unmount.
537
-
538
- #### ✍️ Mutations
539
-
540
- Mutations represent intentional writes.
541
-
542
- They:
543
- - Are abortable
544
- - Deduplicate concurrent runs
545
- - Store last successful result
546
- - Invalidate queries declaratively
116
+ const scope = createScope();
547
117
 
548
- ```ts
549
- const saveUser = q.mutation({
550
- mutate: (signal, input) => apiSaveUser(signal, input),
551
- invalidateTags: ["users"],
118
+ withScope(scope, () => {
119
+ effect(() => { /* auto-cleaned when scope disposes */ });
552
120
  });
553
- ```
554
121
 
555
- **Running a mutation**
556
- ```ts
557
- await saveUser.run({ name: "Everton" });
122
+ scope.dispose(); // stops all effects
558
123
  ```
559
124
 
560
- **Reactive state:**
561
- ```ts
562
- saveUser.data(); // last success
563
- saveUser.loading();
564
- saveUser.error();
565
- ```
125
+ ### Context
566
126
 
567
- **Deduplication & force**
568
127
  ```ts
569
- saveUser.run(input); // deduped
570
- saveUser.run(input, { force }); // abort + restart
571
- ```
572
-
573
- **Invalidation**
574
-
575
- On success, mutations can invalidate:
576
- - Tags → revalidate all matching queries
577
- - Keys → revalidate a specific query
578
-
579
- This keeps writes explicit and reads declarative.
580
-
581
- #### 🧭 Mental Model
582
-
583
- Think in layers:
128
+ import { createContext, provide, inject } from 'dalila';
584
129
 
585
- | Layer | Purpose |
586
- |-------|---------|
587
- | createResource | Async signal |
588
- | Cached resource | Shared async state |
589
- | Query | Read model |
590
- | Mutation | Write model |
130
+ const ThemeContext = createContext<'light' | 'dark'>('theme');
591
131
 
592
- Dalila does not blur these layers.
593
-
594
- #### ✅ Rule of Thumb
595
-
596
- - Local async state → `createResource`
597
- - Shared server data → `query()`
598
- - Global cache → `queryGlobal()` / `persist: true`
599
- - Writes / side effects → `mutation`
600
- - UI branching → `when` / `match`
601
-
602
- Queries and mutations are just signals.
603
- They compose naturally with `when`, `match`, lists, and effects.
604
-
605
- #### 🧠 Philosophy
606
-
607
- Dalila’s data layer is designed to be:
608
-
609
- - Predictable
610
- - Abortable
611
- - Scope-safe
612
- - Explicit
613
- - Boring in the right way
614
-
615
- No magic lifecycles.
616
- No hidden background work.
617
- No provider pyramids.
618
-
619
- ## 🏗️ Architecture
620
-
621
- Dalila is built around these core principles:
622
-
623
- - **No JSX** - Core runtime doesn't require JSX
624
- - **No Virtual DOM** - Direct DOM manipulation
625
- - **No manual memoization** - Signals reduce manual memoization (goal)
626
- - **Scope-based cleanup** - Automatic resource management (best-effort)
627
- - **Signal-driven reactivity** - Localized updates where possible
628
-
629
- ## 📊 Performance
630
-
631
- - **Localized updates**: Signals update only subscribed DOM nodes (goal)
632
- - **Automatic cleanup**: Scope-based cleanup is best-effort
633
- - **Bundle size**: Not yet measured/verified
634
-
635
- ## 🤔 Why Dalila vs React?
636
-
637
- | Feature | React | Dalila |
638
- |---------|-------|--------|
639
- | Rendering | Virtual DOM diffing | Direct DOM manipulation |
640
- | Performance | Manual optimization | Runtime scheduling (best-effort) |
641
- | State management | Hooks + deps arrays | Signals + automatic tracking |
642
- | Side effects | `useEffect` + deps | `effect()` + automatic cleanup (best-effort) |
643
- | Bundle size | ~40KB | Not yet measured |
644
-
645
- ## 📁 Project Structure
132
+ // In parent scope
133
+ provide(ThemeContext, 'dark');
646
134
 
647
- ```
648
- dalila/
649
- ├── src/
650
- │ ├── core/ # Signals, effects, scopes
651
- │ ├── context/ # Dependency injection
652
- │ ├── router/ # SPA routing
653
- │ ├── dom/ # DOM utilities
654
- │ └── index.ts # Main exports
655
- ├── examples/ # Example applications
656
- └── dist/ # Compiled output
135
+ // In child scope
136
+ const theme = inject(ThemeContext); // 'dark'
657
137
  ```
658
138
 
659
- ## 🛠️ Development
139
+ ## Development
660
140
 
661
141
  ```bash
662
- # Install dependencies
142
+ # Install
663
143
  npm install
664
144
 
665
- # Build the framework
145
+ # Build
666
146
  npm run build
667
147
 
668
- # Development mode
669
- npm run dev
670
- ```
671
-
672
- ## 📖 Examples
148
+ # Dev server with HMR
149
+ npm run serve
673
150
 
674
- Check out the `examples/` directory for:
675
- - Counter app
151
+ # Tests
152
+ npm test
153
+ ```
676
154
 
677
- ## 🤝 Contributing
155
+ ## Architecture
678
156
 
679
- Contributions welcome! Focus on:
680
- - Maintaining core principles (no JSX, no VDOM, no manual optimization)
681
- - Adding features that reduce boilerplate
682
- - Improving performance without complexity
683
- - Enhancing developer experience
157
+ ```
158
+ src/
159
+ ├── core/ → Signals, effects, scopes, scheduler
160
+ ├── context/ → Dependency injection
161
+ ├── runtime/ → Template binding (bind, autoBind)
162
+ └── router/ → Routing (in development)
163
+ ```
684
164
 
685
- ## 📄 License
165
+ ## License
686
166
 
687
167
  MIT
688
-
689
- ---
690
-
691
- **Build UI, not workarounds.**