pawajs 2.3.6 → 2.4.0-beta.11

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 (61) hide show
  1. package/AGENT.md +163 -0
  2. package/CHANGELOG.md +3 -1
  3. package/LICENSE +1 -1
  4. package/README.md +218 -71
  5. package/examples/Text.js +5 -0
  6. package/examples/index.html +64 -0
  7. package/examples/index.js +31 -0
  8. package/examples/tools.js +89 -0
  9. package/index.d.ts +400 -358
  10. package/index.js +70 -1413
  11. package/package.json +6 -5
  12. package/src/builtIn/Active.js +67 -0
  13. package/src/builtIn/viewTransition.js +42 -0
  14. package/src/component/index.js +194 -0
  15. package/src/component/utils.js +358 -0
  16. package/src/control-flow/attribute.js +82 -0
  17. package/src/control-flow/await.js +77 -0
  18. package/src/control-flow/for.js +269 -0
  19. package/src/control-flow/if.js +127 -0
  20. package/src/control-flow/key.js +87 -0
  21. package/src/control-flow/ref.js +34 -0
  22. package/src/control-flow/state.js +26 -0
  23. package/src/control-flow/template.js +25 -0
  24. package/src/control-flow/text.js +58 -0
  25. package/src/control-flow/timers.js +26 -0
  26. package/src/control-flow/utils.js +42 -0
  27. package/src/dev/index.js +61 -0
  28. package/src/dev/plugin.js +24 -0
  29. package/src/event/index.js +211 -0
  30. package/src/global.js +93 -0
  31. package/src/graph/graph.js +180 -0
  32. package/src/graph/index.js +154 -0
  33. package/src/graph/setup.js +28 -0
  34. package/src/hooks/index.js +217 -0
  35. package/src/hooks/registerComponent.js +101 -0
  36. package/src/hooks/state.js +296 -0
  37. package/src/hooks/utils.js +35 -0
  38. package/src/pawaElement.js +159 -0
  39. package/src/reactive.js +251 -0
  40. package/{server.js → src/server/index.js} +6 -4
  41. package/src/store.js +3 -0
  42. package/src/utils.js +56 -0
  43. package/cdn/index.html +0 -25
  44. package/cdn/index.js +0 -23
  45. package/merger/for.js +0 -203
  46. package/merger/if.js +0 -135
  47. package/merger/key.js +0 -117
  48. package/merger/switch.js +0 -134
  49. package/normal/For.js +0 -30
  50. package/normal/If.js +0 -29
  51. package/normal/Key.js +0 -27
  52. package/normal/Switch.js +0 -29
  53. package/normal/component.js +0 -418
  54. package/normal/template.js +0 -33
  55. package/pawaComponent.js +0 -72
  56. package/pawaElement.js +0 -513
  57. package/power.js +0 -667
  58. package/reactive.js +0 -190
  59. package/resumer.js +0 -19
  60. package/server.d.ts +0 -13
  61. package/utils.js +0 -309
package/AGENT.md ADDED
@@ -0,0 +1,163 @@
1
+ # AGENT.md — PawaJS
2
+
3
+ Context for any AI agent (or new contributor) working in this codebase. Read this before making changes — several of the rules below exist because of real bugs found and fixed during development, not theoretical concerns.
4
+
5
+ ---
6
+
7
+ ## What PawaJS is
8
+
9
+ PawaJS is a **reactive web runtime** — not a virtual-DOM framework. Its identity, unchanged across the whole rewrite:
10
+
11
+ - **Extends the browser rather than replacing it.** HTML with directive attributes (`if`, `for-each`, `state-*`, `@{ }`) is the interface. No JSX, no compiler, no build step required to author with it.
12
+ - **No VDOM, no diffing pass.** Reactive effects (`createEffect`) wire directly to the specific DOM node/attribute that needs to change. An update touches only what changed.
13
+ - **Fine-grained reactivity**, structurally close to Vue 3's own core (`track`/`trigger`/`activeEffect`, a `WeakMap` of target → key → effect `Set`) — but the rendering strategy on top of it is closer to Solid's (no VDOM, direct DOM wiring).
14
+ - **Components are synchronous by default.** There is no async-component mode (`useAsync`/`$async` are removed). All async lives in the `await` / `as-fallback` / `as-catch` directives.
15
+
16
+ ## The one organizing question
17
+
18
+ Nearly everything non-trivial in this codebase — rendering, SSR pruning, `.client` detection, what goes in the continuity payload — reduces to the same question, asked at different points in the pipeline: **did this specific evaluation touch reactive state (a Proxy read)?**
19
+
20
+ - At render time: decides whether something gets wrapped in `createEffect` at all.
21
+ - At SSR time (`isProxy`): decides whether a component becomes `.client`, and whether a given prop/expression needs to be sent to the client at all.
22
+ - At payload-build time: decides whether a construct earns a JSON entry — gated down to individual props and hook calls, not just per-component.
23
+
24
+ When adding a new construct or directive, ask this same question before inventing new detection logic. It is very likely the answer is "reuse `isProxy`," not "write a new check."
25
+
26
+ ---
27
+
28
+ ## Package layout
29
+
30
+ - **`pawajs` (core)** — the reactive engine, Graph/render system, control-flow directives, component handling, built-ins (`Active`, `Transition`). No SSR code, no `linkedom`. Ships to the client. Current size: **6.6kB gzipped** (down from 17.7kB pre-rewrite, with more capability — see `bundlephobia.com/package/pawajs`).
31
+ - **`pawa-ssr`** — server-side render pass. Produces HTML + a continuity JSON payload (SCP). Depends on `linkedom` for server-side DOM. Never ships to the client.
32
+ - **`pawajs-continue`** — client-side resumer (`PawaContinue`). Consumes the HTML + JSON pair `pawa-ssr` produces and attaches live reactivity without re-rendering.
33
+ - **`pawajs-dom-router`** — pure client-side router. No server involvement past initial load.
34
+ - **`supapawajs`** — full-stack meta-framework on Express. Owns delivery strategy: normal SSR, streaming, or SSG. Calls `pawa-ssr`/`pawajs-continue` but doesn't duplicate their logic. Route navigation fetches data only — it does **not** re-invoke `pawaServer`.
35
+ - **`pawajs-vite-plugin`** — build tooling, HMR, state preservation across reloads.
36
+
37
+ `pawa-ssr` and `pawajs-continue` don't know about delivery mode (streamed vs. cached vs. per-request) — that's entirely `supapawajs`'s job. Keep that boundary intact.
38
+
39
+ ---
40
+
41
+ ## Core reactivity (`reactive.js`)
42
+
43
+ - `track`/`trigger`/`activeEffect`, `targetMap: WeakMap<target, Map<key, Set<effect>>>`.
44
+ - **Every effect re-run must clear its own prior subscriptions before re-tracking.** `cleanupEffect(effect)` runs at the top of every effect invocation, unsubscribing from every dep set the effect was in from its *previous* run, so stale dependencies never accumulate. This was a real historical bug — don't reintroduce a version of `createEffect` that skips this.
45
+ - **Dead effects are tracked via a flag (`effect._killed`), not a global `Set`.** `stopEffect`/the `deletes` closure from `createEffect` set `_killed = true`; `trigger`/`runEffectNow` check the flag. There used to be a module-level `deleteEffect` Set that only grew — don't resurrect that pattern.
46
+ - **`graph.unActive`** — effects deferred while their owning `Graph` node is inactive (see `Active`, below) get pushed here by `runEffectNow` and flushed via `queueEffect`/`setTimeout` when the graph reactivates. Each `Graph` node owns its own `unActive`/timer list — never share one across nodes (see reentrancy note below).
47
+
48
+ ### Reentrancy — a recurring bug class, watch for it
49
+
50
+ Several real bugs today were the same shape: **a single module-level mutable variable standing in for what should be call-scoped or per-node state**, unsafe under nesting or reentrant calls (`inProxy`, `transfer`/`schedule`, `store`/`useStorage`, `Active`'s single shared `timer`). The fix pattern is `withSlot` (save previous value, set new, run, restore previous in `finally`) or — better — moving the state onto the specific object it belongs to (e.g., `Active`'s timers now live on `g.timers` per-graph-node, not one shared array). When adding new module-level state that gets mutated inside a callback, ask: **can this function be called while another call to the same function is still in progress (nested or nearly-concurrent)?** If yes, it needs `withSlot` or per-instance storage, not a bare `let`.
51
+
52
+ ---
53
+
54
+ ## The Graph model (`graph/graph.js`)
55
+
56
+ Every dynamic construct (`condition`, `for-each` item, `component`, `template`, `key`) gets a `Graph` node: `{ref, children, parent, move, insertAfter, getElement, remove, killDown, ...}`.
57
+
58
+ - **`ref`** can point to: a real DOM node, another `Graph` node (recursive — chase via `getElement()`), or be temporarily unset. `getElement()` walks the `ref` chain recursively until it finds a concrete node. This is the mechanism that lets deeply nested constructs collapse to a single anchor instead of needing bracket-comment pairs at every level.
59
+ - **`move(anchor)`** repositions a node's DOM before `anchor`. **`insertAfter`-style methods must mirror `move`'s recursive structure** (template-node branch vs. direct-ref branch) — don't hardcode a single anchor node inside a helper like `createItem`; always thread the current anchor through, or moves will silently land in the wrong position (see `for-each` reconciler notes below).
60
+ - **Directionality matters.** `insertBefore(el, anchor)` puts `el` before `anchor`; `anchor.after(el)` (or an `insertAfter` graph method) puts it after. Getting this backwards is a real, easy-to-make mistake — verify against a 3+ item sequence by hand or by testing, not by reading the code once.
61
+
62
+ ---
63
+
64
+ ## `for-each` reconciliation
65
+
66
+ `for.js`'s `initializer` does keyed, LIS-based reconciliation — not naive full-rebuild:
67
+
68
+ 1. On each evaluate, compare old key order to new key order (`sameArrangement` check). **If identical, do nothing — zero DOM cost.** This is the common case (an unrelated dependency triggered the effect) and must stay free.
69
+ 2. If the arrangement changed, compute the **longest increasing subsequence** of old-indices among surviving keys. LIS members are already in correct relative order and are *not* moved.
70
+ 3. Walk backward (`i = length-1` down to `0`), maintaining a **running anchor** (starts at the `comment`, updated to each processed item's own element after handling it) — never anchor every move against one fixed node. Anchoring everything against a single fixed node breaks correctness the moment some items are skipped (stable/LIS) and others aren't.
71
+ 4. `createItem` must accept and insert against the **passed-in anchor**, not a hardcoded `comment`. A hardcoded anchor in `createItem` was a real bug: it caused a second newly-created item to land behind the first instead of in its correct position.
72
+ 5. New pushes should land visually **after** the current last item (`children[length-1]`), matching `array.push()` intuition — this requires `.after()`, not `insertBefore`, when re-anchoring the marker comment (getting the direction backward here was also a real, caught bug).
73
+ 6. `enqueueUpdate` chains updates through a promise (`updatePromise`) so overlapping triggers (e.g., two removals in quick succession, each awaiting exit-animation time via `Transition`) queue correctly instead of racing. This path is less thoroughly tested than the single-update case — see Testing below.
74
+
75
+ When touching this file: **test with a running app, not just by reading the diff.** Every bug in this reconciler so far was only caught by actually running append/swap/remove/reinsert/filter sequences, never by code review alone.
76
+
77
+ ---
78
+
79
+ ## SSR / Continuity (SCP — Server Continuation Protocol)
80
+
81
+ The core idea: SSR produces (a) real HTML with `p:id` attributes marking construct boundaries, and (b) a flat, id-keyed JSON payload carrying only what's genuinely needed to resume without recomputation. The client resumer (`pawajs-continue`) walks the JSON, not the DOM — one targeted lookup per entry via `p:id`, never a DOM traversal.
82
+
83
+ ### Payload principles — do not violate these when adding a new construct
84
+
85
+ - **Flat, not tree-shaped.** No nested structural encoding in the JSON — position/ownership is discovered via `p:id` lookups and explicit `forkey` tags, not by JSON nesting. This is what keeps the format backend-portable (any language can emit a flat object; no language-specific tree-shape assumptions).
86
+ - **Gated by proxy-touch, at the finest available granularity.** A construct earns a JSON entry only if `isProxy` fired true for its evaluation — not because it's "inside a client boundary," not because its type is usually reactive. This applies down to individual props and hook calls on components, not just whole components.
87
+ - **`id` is a pure lookup handle. `key` is diff identity.** Never conflate them. A `for-key` item's `id` is how the resumer finds its DOM node; its `key` is what a future array diff uses to decide "same item, moved" vs. "new item."
88
+ - **`hydrate.ref` follows one of two patterns**, and both are legitimate — know which one you're in:
89
+ - **Sentinel (`ref: "ref"`)**: "resolve by chasing into children/self" — used by `condition`, `await`, `template`, `component` boundaries that don't correspond to a single fixed element yet.
90
+ - **Direct (`ref: "<own id>"`)**: the construct's own element is the final anchor (most components, `state`, `Active`-style self-binding via `useRef()`).
91
+ - Note: `ref="ref"` as a literal *HTML attribute* is a completely different thing — it's `useRef()`'s own binding syntax (the string is the JS variable name), unrelated to the hydrate sentinel. They share a string value coincidentally, not semantically.
92
+ - **Every hydrate node needs a `type` field.** The resumer dispatches on it. Don't add a construct type without one — relying on field-shape inference instead of an explicit `type` was a real gap that got fixed; don't reintroduce it.
93
+ - **`resolved` (on `await` nodes) means "settled and ready to attach," not "succeeded vs failed."** Success/error is carried by which branch's HTML actually rendered (`awaits` vs `awaitError`), not by this field — both branches correctly set `resolved:true`.
94
+ - **`forkey`** tags a nested construct with its owning `for-each` item's key, so a flat `children` array under a `for-each` node can still be correctly attributed per-item without nesting. Only relevant when the parent hydrate node looks like a for-each (`hydrate?.arrayName` present).
95
+ - **Region ownership, not per-element ids.** `p:id` marks the boundary of a *construct's region* — everything inside (plain, non-reactive content) shares that id. Don't expect one id per DOM node; expect one id per independently-resolvable reactive unit.
96
+
97
+ ### Per-construct resume strategy — don't unify these into one code path
98
+
99
+ - **`condition` / `for-each`** — genuinely paused: seed comparison state (`oldChain`/`current`, keyed `children`) from JSON, skip the first `evaluate()` entirely (`isHydrate?.enter` check), resume real computation only at the next genuine trigger.
100
+ - **`await`** — resolved/error value handed straight into context via the same merge `apply()` uses live. The promise function is never called. Retry (if the app needs it) goes through a `Key`-forced remount at the parent, not a bespoke retry mechanism inside `Await` itself.
101
+ - **`component`** — re-executes in full on the client (cheap; accepted cost) to rebuild `useInsert` context and effects, but the returned HTML string is discarded — the component attaches to the already-correct SSR element found via `p:id`, never rebuilding DOM.
102
+ - **`only-client`** — opts out of SSR entirely. Ships as an inert `<template on-client>` for the client to instantiate fresh. No JSON entry, since nothing was evaluated server-side.
103
+ - **`state-*` attributes** — SSR-evaluated inline; the resolved value **must** be sent (nothing re-derives it). Component-*internal* `$state` calls (inside a component's JS body) do **not** need to be sent — the component's re-execution naturally reproduces them for free. Don't send both; sending the internal ones is pure duplication.
104
+
105
+ ### Detection: `.client` / `force` / auto-inheritance
106
+
107
+ A component is treated as client if **any** of:
108
+ 1. Registration-time flag (`Component.client = true` on the function itself — e.g. `Active`, `Transition`).
109
+ 2. Internal hook usage (`$state`, `stateWatch`) — calling a client-reactive hook is itself what marks the component client, as a side effect of the call, via `AsyncLocalStorage`-scoped detection during SSR.
110
+ 3. Ancestor is client (transitive through non-client intermediaries).
111
+ 4. **`force`** — an SSR-time template directive (`el.hasAttribute('force')`), scoped per usage site, not a static component property. Exists specifically for the one case `isProxy` structurally cannot see: a function value (event handler, callback) passed as a prop or into `useInsert`'s context. Calling a function during SSR never executes its body, so state read *inside* that function is invisible to proxy-touch detection — `force` is the deliberate escape hatch, applied at the exact call site that needs it.
112
+
113
+ Once a component is client for any reason, **nested components under it do not need repeated manual `force` calls** just because they also carry function-valued context — a check inside already-confirmed client boundaries handles function-valued `useInsert`/context entries automatically. `force` is only for establishing the boundary in the first place, not for every nested case beneath it.
114
+
115
+ `$state` cannot be called in a component that isn't (or doesn't become) client — calling it is what makes a component client, there's no separate permission check.
116
+
117
+ ### Streaming
118
+
119
+ Raw chunked HTTP, not SSE. First chunk: fully resolved content + placeholders (`awaitsLoading`, `resolved:false`) for pending `await`s. Each pending item resolves independently and flushes its own chunk with an inline `<script>` on settling — never waits for siblings. Client/server rendezvous is a flat, id-keyed handshake (`window.awaits[id]` / `window.states[id]`): whichever side (server chunk arriving, or client resumer walking to that point) gets there *second* consumes what the *first* side left behind. Neither side polls or waits.
120
+
121
+ ---
122
+
123
+ ## Directive reference (current, post-rewrite)
124
+
125
+ - `if` / `else` / `else-if` — conditional rendering. (`switch`/`case`/`default` are **removed**.)
126
+ - `for-each` + `for-key` — keyed list rendering (see reconciliation notes above).
127
+ - `state-*` — inline reactive state scoped to an element and its children.
128
+ - `await` / `as-fallback` / `as-catch` — async handling. `await` must be given a **function that returns a promise**, never an already-invoked call (`await="fetchUser"`, not `await="fetchUser()"`). `as="name"` binds the resolved/caught value under a custom name (defaults: `res` / `error`).
129
+ - `key` — forces a full remount when its watched value changes. Used as the retry mechanism for failed `await`s (force a parent `Key` change rather than building retry into `Await`).
130
+ - `on-<event>` / `out-<event>` — event handling, with `.self`/`.prevent`/`.once`/`.capture` chain modifiers.
131
+ - `:prop` — reactive component prop.
132
+ - `<slot>` / `<slot name="...">` — content projection (not a `children` prop — this changed in the rewrite).
133
+ - `<template prop="name">` — pass a raw string/markup chunk as a prop value.
134
+ - `aschild` — component's root element merges into (rather than wraps) what the caller passed.
135
+ - `ref` — bind a `useRef()` object to an element. Unrelated to the SCP hydrate sentinel of the same string value — see note above.
136
+ - `force` — SSR directive forcing a component to client status at this usage site (see Detection above).
137
+ - ~~`is-exit`~~ — **removed.** Superseded by `Transition`'s own `onExit`/`onEnter` graph hooks, which wait for the exit animation before actual DOM removal automatically.
138
+ - ~~`useServer`, `useAsync`/`$async`~~ — **removed.** No async-component mode; use `await`/`as-fallback`/`as-catch` instead.
139
+
140
+ ## Built-in components
141
+
142
+ - **`Active`** — `:show="expr"` controls visibility (`display:none`) and pauses/resumes reactive effects underneath while hidden (via `graph.active` + `graph.unActive`, see reactivity notes above). Always client (`Active.client = true`).
143
+ - **`Transition`** — `:enter`/`:exit` (class-name arrays or inline style-string arrays, same shape as `el.animation()`) + `:duration` (default 300ms). Waits for the exit animation to finish before the element is actually removed. Always client.
144
+
145
+ ---
146
+
147
+ ## Testing conventions
148
+
149
+ There is no automated correctness harness yet (SSR-render → resume → compare against fresh client render is a known, not-yet-built gap — high priority if adding one). Until it exists:
150
+
151
+ - **Trust real, running output over code review for anything touching the reconciler, the Graph model, or resume seeding.** Every bug caught in these areas during development was found by actually running sequences (append/remove/reorder/filter), never by reading the diff.
152
+ - When testing `for-each` changes, exercise at minimum: append, remove-from-middle, filter-then-restore (items reappearing at original relative position), multi-item reorder (not just single swaps), and rapid back-to-back updates (overlapping `enqueueUpdate` calls, especially with `Transition`'s exit-animation delay in play).
153
+ - When testing SSR/continuity changes, check real payload output for: correct `type` per node, correct `resolved` semantics on `await`, `p:id` region boundaries matching expectations, and — critically — that pruning is actually happening (static/non-reactive content should produce **zero** hydrate entries, not just small ones).
154
+
155
+ ---
156
+
157
+ ## Things intentionally *not* built yet — don't assume otherwise
158
+
159
+ - No automated resume-correctness harness.
160
+ - No second (non-JS) backend implementation of SCP — portability is a design property, not yet a proven one.
161
+ - No dev-mode warning system beyond the initial `stripComponent` (statically-pruned-but-in-a-client-boundary) warning — a broader dev-mode/error-handling pass is planned but not started.
162
+ - No PML (partial re-render / DOM morph after initial resume) — designed in concept (compare live Graph tree against a parsed incoming tree, route first-boundary component updates through `reProps` rather than DOM diffing) but unimplemented.
163
+ - No large-list payload/perf measurement has been taken yet — treat "for-each cost at scale" as an open, unmeasured question, not a solved one.
package/CHANGELOG.md CHANGED
@@ -23,4 +23,6 @@ CHANGELOG.md
23
23
  + version 2.0.1 -2.0.2 made runEffect to be global or component capability
24
24
  + version 2.0.3 integrating HMR system
25
25
  + version 2.0.4 experimental of hmr
26
- + version 2.0.5 - 2.0.6 experimental of hmr
26
+ + version 2.0.5 - 2.0.6 experimental of hmr
27
+ + 2.4.0.beta.4 new pawajs Rewrite;
28
+ + 2.4.0.beta.5 - 2.4.0.beta.7 added LIS for ForEach directive
package/LICENSE CHANGED
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -3,24 +3,30 @@ pawajs - power the web (reactivity and html runtime)
3
3
 
4
4
  # PawaJS
5
5
 
6
- **A lightweight and reactive JavaScript library for building modern web interfaces with a simple, declarative syntax.**
6
+ **A lightweight and reactive JavaScript library for building modern web interfaces with a simple, declarative syntax (15kb).**
7
7
 
8
- PawaJS (reactive web runtime) is a JavaScript library designed for building dynamic user interfaces. It combines a component-based architecture and progressive enhancement with a powerful reactivity system no v-dom. Its intuitive, directive-based templating feels familiar and makes it easy to create interactive applications, from simple widgets to complex single-page apps. With built-in support for server-side rendering using pawa-ssr (server rendering runtime) and pawajs-continue (continuous rendering), PawaJS is equipped for performance and scalability.
8
+ PawaJS (reactive web runtime) is a JavaScript library designed for building dynamic user interfaces. It combines a component-based architecture and progressive enhancement with a powerful reactivity system, no v-dom. Its intuitive, directive-based templating feels familiar and makes it easy to create interactive applications, from simple widgets to complex single-page apps. With built-in support for server-side rendering using pawa-ssr (server rendering runtime) and pawajs-continue (continuity runtime), PawaJS is equipped for performance and scalability.
9
+
10
+ PawaJS is a **reactive web runtime** for HTML-first UIs: components, directives, and fine-grained `$state` updates with **no virtual DOM**.
11
+
12
+ On the client it powers widgets through SPAs. With **pawa-ssr** and **pawajs-continue** it uses **selective continue** (SCP): the server sends HTML plus a sparse index of reactive areas; the client continues only those areas. **Static components are stripped** from that index so they are not re-executed on the client.
9
13
 
10
14
  🌐 **Website:** [pawajs.vercel.app](https://pawajs.vercel.app)
11
15
 
12
- ---
16
+
13
17
 
14
18
  ## Features
15
19
 
16
- - **Declarative Rendering:** Use a clean, HTML-based template syntax with powerful directives (`if`, `for-each`, `on-event`, etc.) to describe your UI.
20
+ - **Declarative Rendering:** Use a clean, HTML-based template syntax with powerful directives (`if`, `for-each`, `await`, `on-event`, etc.) to describe your UI.
17
21
  - **Reactive State Management:** Effortlessly create reactive state that automatically updates the DOM when it changes using the `$state` utility.
18
- - **Component-Based Architecture:** Build encapsulated components with just js function that manage their own state, making your code more reusable and maintainable.
19
- - **Async Components:** First-class support for asynchronous components, allowing you to fetch data directly within your component definition.
22
+ - **Component-Based Architecture:** Build encapsulated components with just a JS function that manages its own state, making your code more reusable and maintainable. Components are synchronous by default — no async component mode to reason about.
23
+ - **First-Class Async Handling:** Fetch and await data directly in your templates with the `await` / `as-fallback` / `as-catch` directives — no special component wiring required.
20
24
  - **Efficient List Rendering:** Render lists of data with the `for-each` directive, including support for keyed updates for optimal performance.
21
25
  - **Lifecycle Hooks:** Tap into a component's lifecycle with `mount` and `unmount` directives, or the `runEffect` hook for more complex side effects.
22
26
  - **Context API:** Pass data through the component tree without having to pass props down manually at every level.
23
- - **Server-Side Rendering (SSR) Isomorphic architecture:** PawaJS is built with SSR in mind, featuring a "resuming" mechanism to efficiently continue server-rendered HTML on the client.
27
+
28
+ - **Selective continue (SSR):** `pawa-ssr` emits HTML + SCP; `pawajs-continue` binds reactive regions only. Static UI stays plain HTML (no client re-execution).
29
+ ---
24
30
  - **Plugin System:** Extend PawaJS's core functionality with custom directives and lifecycle behaviors.
25
31
 
26
32
  ---
@@ -33,7 +39,7 @@ Install PawaJS into your project using npm:
33
39
  npm install pawajs
34
40
  ```
35
41
  CDN
36
-
42
+
37
43
  ```html
38
44
  <!doctype html>
39
45
  <html lang="en">
@@ -86,7 +92,7 @@ CDN
86
92
 
87
93
  OR
88
94
 
89
- Then, you can import it into your application: through npm
95
+ Then, you can import it into your application through npm:
90
96
 
91
97
  ```javascript
92
98
  import { pawaStartApp, $state, RegisterComponent, html } from 'pawajs';
@@ -162,7 +168,7 @@ pawaStartApp(appElement);
162
168
 
163
169
  ### State Management with `$state`
164
170
 
165
- The `$state` function is the heart of PawaJS's reactivity. It creates a reactive object whose `value` property can be read and written to. Any changes to `.value` will automatically trigger updates in the parts of your application that depend on it (fine-granded) no component re-rendering or diffing. `$state` can be global when used outside a component. Also inline state in the html or template `state-*="any js types"`
171
+ The `$state` function is the heart of PawaJS's reactivity. It creates a reactive object whose `value` property can be read and written to. Any changes to `.value` will automatically trigger updates in the parts of your application that depend on it (fine-grained) — no component re-rendering or diffing. `$state` can be global when used outside a component. You can also declare inline state directly in HTML or a template with `state-*="any js value"`.
166
172
 
167
173
  ```html
168
174
  <!--- number-->
@@ -187,48 +193,37 @@ name.value = 'PawaJS'; // The UI will update automatically
187
193
  const user = $state({ name: 'Alex', loggedIn: false });
188
194
  user.value.loggedIn = true; // This is also reactive
189
195
 
190
- // Persist state to localStorage
196
+ // Persist state to localStorage — wrap the $state call in useStorage(),
191
197
  // The state will be saved under the key 'session' and reloaded on page refresh.
192
- const session = $state({ id: null }, 'session');
198
+ const session = useStorage(() => $state({ id: null }), 'session');
193
199
 
194
200
  // Compute state - must be used inside a component
195
- // when ever count changes the state update
201
+ // whenever count changes, the state updates
196
202
  const doubleCount=$state(()=>count.value * 2,[count])
197
203
 
198
204
  //AutoCompute
199
- // Any function that uses any reactive state for reactive bindings or direcives becomes computed function
205
+ // Any function that uses any reactive state for reactive bindings or directives becomes a computed function
200
206
  const doubleCount=()=>count.value * 2
207
+
208
+ // Schedule a batch of state updates through the frame-budgeted scheduler
209
+ // instead of the default immediate/microtask path — useful for grouping a
210
+ // burst of updates so they get spread across animation frames rather than
211
+ // forced through synchronously in one go.
212
+ schedule(() => {
213
+ count.value++
214
+ name.value = 'Updated'
215
+ })
201
216
  ```
202
217
 
203
218
  ### Components
204
219
 
205
- Components are the building blocks of your application. In PawaJS, a component is a JavaScript function that returns an HTML template string or not.
220
+ Components are the building blocks of your application. In PawaJS, a component is a **synchronous** JavaScript function that returns an HTML template string, or nothing.
206
221
 
207
222
  - **Defining:** Create a function that returns a template or not.
208
223
  - **Registering:** Use `RegisterComponent(MyComponent)` to make it available globally. In HTML, you can then use it as `<my-component>`.
209
224
  - **`useInsert`:** To make variables, state, and functions from your component's setup available in its template, pass them in an object to `useInsert()`.
210
225
 
211
- ### Asynchronous Components
212
-
213
- PawaJS supports async components ( `useAsync()` hook) out of the box. You can define a component as an `async` function, allowing you to perform asynchronous operations (like fetching data) before the component renders.
214
- wrap any pawajs hook with $async after any await call
215
-
216
- ```javascript
217
- const UserProfile = async () => {
218
- // Async hook store the current component instance for $async to use
219
- const {$async}=useAsync()
220
- // The component will wait for this promise to resolve before rendering
221
- const data = await fetch('/api/user').then(res => res.json());
222
- const user = $async(()=>$state(data);)
223
- $async(()=>useInsert({user}))
224
-
225
- return html`
226
- <div class="profile">
227
- <h1>@{user.value.name}</h1>
228
- </div>
229
- `;
230
- }
231
- ```
226
+ Components are always synchronous — there's no separate "async component" mode to learn. If a component needs to fetch or await data, reach for the [`await` directive](#await--as-fallback--as-catch) inside its template instead.
232
227
 
233
228
  ### Templating
234
229
 
@@ -249,7 +244,7 @@ PawaJS uses a simple `@{...}` syntax to embed dynamic JavaScript expressions dir
249
244
  Directives are special attributes that apply reactive behavior to DOM elements.
250
245
 
251
246
  #### `state-*`
252
- create inline state for the element and children
247
+ Create inline state for the element and its children.
253
248
 
254
249
  ```html
255
250
  <div state-count="0">
@@ -258,7 +253,6 @@ create inline state for the element and children
258
253
 
259
254
  ```
260
255
 
261
-
262
256
  #### `if` / `else` / `else-if`
263
257
  For conditional rendering.
264
258
 
@@ -273,36 +267,15 @@ For conditional rendering.
273
267
  Please log in to continue.
274
268
  </div>
275
269
  ```
276
- #### `switch` / `case` / `default`
277
- switch conditional rendering.
278
270
 
279
- ```html
280
- <div switch="user.value.type" case="'admin'">
281
- Welcome back, @{user.name.value}!
282
- </div>
283
- <div case="'guest">
284
- You are browsing as a guest.
285
- </div>
286
- <div default>
287
- Please log in to continue.
288
- </div>
289
- ```
290
271
  #### `key`
291
- re-renders the element/component when the reactivity changes
272
+ Re-renders the element/component when the value it watches changes.
292
273
 
293
274
  ```html
294
275
  <user-component key="user.value.type"></user-component>
295
276
 
296
277
  ```
297
278
 
298
- #### `is-exit`
299
- makes pawajs engine to wait before removing the element until the animation/transition is done.
300
-
301
- ```html
302
- <div if="user.value.loggedIn" class="user-card @{user.value.isActive ? 'active' : 'inactive'}" is-exit>
303
- <input value="@{user.value.name}" on-input="user.value.value = e.target.value">
304
- </div>
305
- ```
306
279
  #### `for-each`
307
280
  For rendering lists from an array. Use `for-key` to give each element a unique identity, which helps PawaJS optimize rendering.
308
281
 
@@ -314,7 +287,35 @@ For rendering lists from an array. Use `for-key` to give each element a unique i
314
287
  </ul>
315
288
  ```
316
289
 
317
- #### `on-<event>..chainModifiers`
290
+ #### `await` / `as-fallback` / `as-catch`
291
+ For handling asynchronous data directly in a template — no async component wiring needed. The `await` attribute must point to a **function that returns a promise**, not an already-invoked call.
292
+
293
+ The resolved value is exposed in scope under `res` by default (the caught error under `error`), or under whatever name you give with `as`.
294
+
295
+ ```html
296
+ <div await="fetchUser">
297
+ <p>Welcome, @{res.name}</p>
298
+ </div>
299
+ <div as-fallback>
300
+ <p>Loading...</p>
301
+ </div>
302
+ <div as-catch>
303
+ <p>Failed to load: @{error.message}</p>
304
+ </div>
305
+ ```
306
+
307
+ Use `as` to bind the resolved (or caught) value under a custom name:
308
+
309
+ ```html
310
+ <div await="fetchUser" as="user">
311
+ <p>Welcome, @{user.name}</p>
312
+ </div>
313
+ <div as-catch as="err">
314
+ <p>Failed to load: @{err.message}</p>
315
+ </div>
316
+ ```
317
+
318
+ #### `on-<event>..chainModifiers`
318
319
  modifiers - 'self','prevent','once','capture' etc
319
320
  For handling DOM events.
320
321
 
@@ -322,19 +323,46 @@ For handling DOM events.
322
323
  <button on-click="addTodo()">Add Todo</button>
323
324
  <input on-input="newTodoText.value = e.target.value" />
324
325
  ```
326
+
325
327
  #### `out-<event>.chainModifiers`
326
328
  modifiers - 'self','prevent','once','capture' etc
327
- For handling DOM events.
329
+ For handling events fired outside the element.
328
330
 
329
331
  ```html
330
332
  <button out-click.once="console.log('outside')">Add Todo</button>
331
333
  ```
332
334
 
335
+ #### `after-[ms]` / `every-[ms]`
336
+ Run a callback once after a delay or repeatedly on an interval. The expression is evaluated with the current render context and the timer is cleaned up when the component unmounts.
337
+
338
+ ```html
339
+ <div after-[2000]="console.log('done after 2s')"></div>
340
+ <div every-[1000]="count.value++"></div>
341
+ ```
342
+
343
+ This is the runtime timer directive path used by the graph scheduler and is cleaned up automatically on unmount.
344
+
345
+ ### Direct element property updates (JSA) - JavaScript Attribute
346
+ PawaJS also supports direct property assignment (javascript element attribute) with the `@` shorthand on an element. This avoids needing to write a full `on-*` event handler when you just want to set a DOM property or style field.
347
+
348
+ ```html
349
+ <div @style.color="red" @inner-text="inner text"></div>
350
+ ```
351
+
352
+ Nested properties are also supported:
353
+
354
+ ```html
355
+ <div @style.background-color="tomato" @dataset.role="admin"></div>
356
+ ```
357
+
358
+ The same shorthand works for native properties like `@value`, `@checked`, `@textContent`, and `@innerHTML`.
359
+
333
360
  ### Component Props
334
361
 
335
362
  You can pass data from a parent component to a child component using props. To declare a prop, prefix the attribute with a colon (`:`).
336
- Children are passed by default in pawajs, they are not functional prop.
337
- For rest props pass `--` to the element that needs the attributes.
363
+
364
+ Children are **not** passed as a prop in PawaJS — they're passed as a `<slot>`, the same way native web components handle content projection. For rest props, pass `--` to the element that needs the attributes.
365
+
338
366
  **Parent Component (`app.js`)**
339
367
  ```javascript
340
368
  // ...
@@ -342,13 +370,15 @@ const message = $state('This is a message from the parent!');
342
370
  useInsert({ message });
343
371
 
344
372
  return html`
345
- <todo-list title="My Todo List" :message="message.value" class="to the rest prop">Children in here</todo-list>
373
+ <todo-list title="My Todo List" :message="message.value" class="to the rest prop">
374
+ <p>Children go in here</p>
375
+ </todo-list>
346
376
  `;
347
377
  ```
348
378
 
349
379
  **Child Component (`todo-list.js`)**
350
380
  ```javascript
351
- export const TodoList = ({ title, message,children }) => {
381
+ export const TodoList = ({ title, message }) => {
352
382
  // Props are passed as functions that return the reactive value
353
383
  useInsert({ title, message });
354
384
 
@@ -356,7 +386,7 @@ export const TodoList = ({ title, message,children }) => {
356
386
  <div -->
357
387
  <h2>@{title()}</h2>
358
388
  <p>@{message()}</p>
359
- ${children}
389
+ <slot></slot>
360
390
  </div>
361
391
  `;
362
392
  }
@@ -374,6 +404,106 @@ useValidateComponent(TodoList, {
374
404
  });
375
405
  ```
376
406
 
407
+ ### Slots
408
+
409
+ A component's `<slot>` is where the caller's children land. A plain `<slot></slot>` (or a `<slot name="default">`) catches every child the caller passed that wasn't targeted at a specific named slot — this is what `TodoList` above uses.
410
+
411
+ You can have **more than one slot**, each with its own `name`, to project different pieces of content into different parts of a component's template. On the caller's side, target a named slot with a `<template slot="name">` wrapper:
412
+
413
+ **Parent (`app.js`)**
414
+ ```javascript
415
+ return html`
416
+ <card-panel>
417
+ <template slot="header">
418
+ <h2>Project status</h2>
419
+ </template>
420
+
421
+ <p>This is the default slot content — the body of the card.</p>
422
+
423
+ <template slot="footer">
424
+ <button on-click="dismiss()">Dismiss</button>
425
+ </template>
426
+ </card-panel>
427
+ `;
428
+ ```
429
+
430
+ **Child (`card-panel.js`)**
431
+ ```javascript
432
+ export const CardPanel = () => {
433
+ return html`
434
+ <div class="card">
435
+ <header>
436
+ <slot name="header"></slot>
437
+ </header>
438
+ <section class="card-body">
439
+ <slot></slot>
440
+ </section>
441
+ <footer>
442
+ <slot name="footer"></slot>
443
+ </footer>
444
+ </div>
445
+ `;
446
+ }
447
+ ```
448
+
449
+ Anything the caller passes that isn't wrapped in a `<template slot="...">` falls through to the unnamed default slot — so plain, un-templated children and a `<template slot="...">` block can be mixed freely on the same call site, as in the example above.
450
+
451
+ ### Passing a string prop with `<template prop="...">`
452
+
453
+ Sometimes you want to pass a chunk of raw markup or text to a component as a **prop value** (a string), not as projected content. Use `<template prop="name">` for that — its inner HTML becomes the string value of that prop, available on the child the same way any other prop is.
454
+
455
+ ```javascript
456
+ // Parent
457
+ return html`
458
+ <alert-box>
459
+ <template prop="message">Something went wrong.</template>
460
+ </alert-box>
461
+ `;
462
+ ```
463
+
464
+ ```javascript
465
+ // Child (alert-box.js)
466
+ export const AlertBox = ({ message }) => {
467
+ useInsert({ message });
468
+ return html`
469
+ <div class="alert">@{message()}</div>
470
+ `;
471
+ }
472
+ ```
473
+
474
+ A component's root element can also **become** whatever the caller passed, instead of wrapping it, by marking either the component's own root with `aschild` or letting a top-level `<slot>` take over as the root — useful when a component shouldn't add an extra wrapping element (e.g. a `Button` component that should render as the caller's own `<a>` if one is passed). In that case, the two elements' attributes are merged rather than one replacing the other — `class`/`style` are concatenated, everything else is kept from both (with collisions resolved by prefixing).
475
+
476
+ ---
477
+
478
+ ## Built-in Components
479
+
480
+ PawaJS ships two built-in components for common structural and animation needs.
481
+
482
+ ### `Active`
483
+
484
+ Controls whether its content is visible and reactive, toggling `display: none` and pausing/resuming the effects underneath it based on a `show` prop. Useful for tabs, panels, or any content that should stop doing reactive work while it's hidden.
485
+
486
+ ```html
487
+ <active :show="isOpen.value">
488
+ <expensive-panel></expensive-panel>
489
+ </active>
490
+ ```
491
+
492
+ ### `Transition`
493
+
494
+ Wraps its content with enter/exit animation. When the content is added to the DOM, `Transition` plays the `enter` animation; when it's about to be removed (e.g. an `if` flipping false), it plays `exit` first and waits for it to finish before the element is actually taken out — so removal-triggering content never just vanishes mid-animation.
495
+
496
+ `enter` and `exit` take the same frame arguments as `el.animation()` — either an array of class names or an array of inline style strings — and `duration` controls how long each takes (defaults to `300`ms).
497
+
498
+ ```html
499
+ <transition
500
+ :enter="['opacity-0 scale-95', 'opacity-100 scale-100']"
501
+ :exit="['opacity-100 scale-100', 'opacity-0 scale-95']"
502
+ :duration="250">
503
+ <div if="user.value.loggedIn">Welcome back!</div>
504
+ </transition>
505
+ ```
506
+
377
507
  ---
378
508
 
379
509
  ## API Reference
@@ -381,17 +511,34 @@ useValidateComponent(TodoList, {
381
511
  ### Core Functions
382
512
  - `pawaStartApp(rootElement, initialContext)`: Initializes the PawaJS application on a given root DOM element.
383
513
  - `RegisterComponent(...components)`: Registers one or more components to be used in templates.
384
- - `$state(initialValue, localStorageKey?)`: Creates a new reactive state object. Used inside or outside Component (module export)
385
- - `PluginSystem(plugin)`: Registers a plugin to extend PawaJS functionality (e.g., Routers, Global Stores).
514
+ - `Graph(parentGraph?)`: Creates a render graph node for a component or section of the DOM tree. This is the low-level runtime model used to track child nodes, lifecycle hooks, and reactive state.
515
+ - `PawaRender(graph, contexts?)`: Creates a render driver and returns `{ render, renderGraph, setContext }`. It walks a DOM subtree, resolves directives, mounts components, and attaches the current context graph to each node.
516
+ - `setComponentGraph(graph)` / `getComponentGraph()`: Set or read the active component graph from the current rendering context.
517
+ - `$state(initialValue, section?)`: Creates a new reactive state object. Used inside or outside a component (module export). `section` is used for compute dependencies (pass an array) or for naming an async batch — not for localStorage persistence.
518
+ - `useStorage(createCall, name)`: Wraps a `$state` creation call so its value is persisted to `localStorage` under `name` and restored on reload.
519
+ - `schedule(callback)`: Runs any state updates made inside `callback` through the frame-budgeted scheduler instead of the default immediate path — useful for batching a burst of updates across animation frames.
520
+ - `Plugin(plugin)`: Registers a plugin to extend PawaJS functionality (e.g., Routers, Global Stores).
386
521
  - `html`: A tagged template literal for syntax highlighting and potential future optimizations.
387
522
 
388
523
  ### Component Hooks
389
524
  - `useInsert(object)`: Exposes data and functions from a component's setup to its template.
390
- - `runEffect(callback, dependencies?)`: Runs a side effect after or before the component renders, and re-runs it when its dependencies change . Used inside or outside Component
525
+ - `runEffect(callback, dependencies?)`: Runs a side effect after or before the component renders, and re-runs it when its dependencies change. Used inside or outside a component.
391
526
  - `useContext(contextObject)` & `setContext()`: A mechanism for providing and consuming data throughout a component tree.
392
527
  - `useRef()`: Creates a reference object that can be attached to a DOM element using the `ref` directive.
393
528
  - `useValidateComponent(Component, rules)`: Defines validation rules for a component's props.
394
- - `useServer()`: Returns `{ setServerData, getServerData }` for handling server-side data serialization and client-side retrieval in SSR applications.
529
+
530
+ ### Low-level Render Graph
531
+ PawaJS keeps internal render metadata in a graph object attached to the DOM node tree. That graph tracks parent/child relationships, mount hooks, enter/exit transitions, storage for context, and the current render state. This is the runtime structure behind directives like `if`, `for-each`, `key`, `await`, and component mounting.
532
+
533
+ ```javascript
534
+ import { Graph, PawaRender } from 'pawajs';
535
+
536
+ const graph = Graph();
537
+ const { render, renderGraph } = PawaRender(graph, { user: { name: 'Pawa' } });
538
+ render(document.getElementById('app'));
539
+ ```
540
+
541
+ The graph is not usually something app code needs to manipulate directly, but it is the internal foundation for the library's reactive rendering pipeline and is exposed for advanced runtime integration and debugging.
395
542
 
396
543
  ---
397
544
 
@@ -0,0 +1,5 @@
1
+ import { html } from "../src/hooks/index.js"
2
+
3
+ export const Text=()=>{
4
+ return html`<h1>TEXTING COMPONENT LAZYLOADING</h1>`
5
+ }