Runtime Changelog & Coverage
What changed in pp-reactive-v2.min.js since the docs were last revised, what an
existing backend integration should add, where each runtime feature is documented,
and the known gaps in the runtime itself. Everything here was checked against the
runtime's TypeScript source and test suite, not inferred from older docs.
What changed
| Date | Change | Affects | Docs |
|---|---|---|---|
| 2026-10-05 | A component rendered inside a parent's pp-for keeps its own pp-for: the inner loop is compiled in the component's scope, not the row's, so its collection is no longer undefined. | Component authors | Loops |
| 2026-10-04 | Discrete events (submit, click, keydown, …) commit input renders deferred to the next frame before their handler runs, so a fill + Enter or an autofill + submit in one frame reads up-to-date state, as in React. input, change, blur and checkbox/radio clicks are left alone. | Component authors | Forms |
| 2026-09-30 | A controlled value on a <select> that is a nested component's root (<select pp-component value="{fruit}">) is applied once the child renders its options, instead of leaving nothing selected. | Component authors | Forms |
| 2026-09-29 | Markup and inline handlers read the component's own pp: {pp.props.label} and onclick="pp.rpc(...)" work in the template, not only in the script. Inside slot content, pp is the author's, the way props is inside a React parent's JSX. | Component authors | pp in markup |
| 2026-09-29 | A native handler on a component's own root runs in that component's scope when the server marks the root pp-event-owner="<its own id>". Same-id siblings (servers that name boundaries per component type) each resolve to their own instance. | Backends, component authors | Root event handlers |
| 2026-09-29 | Children can sit inside a component's own pp-for body (<li><slot /></li> repeats per row), and children a wrapper passes on together with markup of its own (<x-inner><h3>{title}</h3><slot /></x-inner>) keep their original owner's scope and update when it re-renders. | Component authors | Children |
| 2026-09-29 | Bound src, srcset, sizes, imagesrcset, imagesizes and poster are kept off the live element while a deferred template materializes, so the browser never fetches a placeholder or logs a srcset parse warning. | Everyone | Deferred roots |
| 2026-09-29 | Fixes: a row component's <script> inside <tr>/<tbody> stays in the row; a pp-for written directly at a component's root keeps per-row reuse; script-less wrappers evaluate {pp.props.x} against their own props. | Component authors | Loops |
| 2026-09-21 | pp.socket stays connected: a {"__pp": "ping"} heartbeat every 25 s, automatic reconnect with backoff after unexpected closes (not after close(), an error frame, 1000 or policy codes), sends buffered while reconnecting, and new onReconnecting, onOpen({ reconnected }) and onClose({ willReconnect }). Servers must answer the ping and close idle peers with 4000. | Component authors, backends with sockets | Staying connected |
| 2026-09-15 | pp.rpc rejects with a typed RpcError exposing status, errors (field messages), requestId and body. message is unchanged. | Component authors, backends | RPC errors |
| 2026-09-15 | SPA navigation replaces every <head> element marked data-pp-meta (description, canonical, Open Graph, Twitter) with the next page's set. | Backends | Navigation contract |
| 2026-08-31 | A server layout may wrap its page as <template pp-component="layout_x"> whose root is the page's own boundary (a different id). Both identities are kept; the layout gets a display: contents element of its own. | Backends with nested layouts | Layouts |
| 2026-08-28 | Loops inside slot content (children passed into a component) no longer share the owner's row cache; fixes a stray <li pp-keep-run> and a 'Loop row reuse/patching was abandoned' warning. | Component authors | Components |
| 2026-08-22 | A controlled checked is applied to the element's property in both directions, including keyed pp-for rows, so state false now unticks a box the user ticked. | Component authors | Forms |
| 2026-08-22 | Hyphenated attributes that only contain a boolean name (aria-disabled, data-open, aria-checked) are ordinary string attributes and render "true"/"false". | Component authors | Forms |
| 2026-08-22 | SPA navigation no longer hides the body during a swap. The swap and hydration happen in one task, and scroll is corrected before the first paint. | Everyone | Scroll restoration |
| 2026-08-21 | pp-reset-scroll="true" panes reset even when their scroll key changed between pages; a cross-page #hash is applied after the reset. | Everyone | Scroll restoration |
| 2026-08-18 | Keyed rows keep being patched after they are portaled out of the component's root (dropdowns, menus). | Component authors | Portals |
| 2026-08-15 | An uncontrolled <textarea> keeps what the user typed across re-renders; a textarea becomes controlled once its bound content changes. | Component authors | Forms |
| 2026-08-14 – 08-16 | Internal speedups: component body plans, direct row construction, fused child mounts, flyweight rows. No API change. | Nobody (faster) | Performance |
| 2026-08-11 | HTML comments authored in templates are stripped before compiling (except inside <pre>/<code>), so a {…} inside a comment is never evaluated. | Component authors | Directives |
What an existing backend integration should add
- When a component writes a native on* handler on its own root element, stamp that root with pp-event-owner="<its own pp-component id>". Root attributes are otherwise evaluated in the parent's scope, where the handler's function does not exist.
- If you serve named sockets, answer {"__pp": "ping"} with {"__pp": "pong"} (never pass it to the handler) and close idle peers with 4000, not 1000. Without the pong the client replaces the connection about every 45 s.
- Mark per-page <head> tags (description, canonical, robots, og:*, twitter:*) with data-pp-meta, or they stay frozen on the first page after an SPA navigation.
- Send X-PP-Root-Layout and render <meta name="pp-root-layout" content="…"> for each group of pages with its own <head> assets. Both are needed for the check to run.
- Return validation failures as a 4xx with { "error": "…", "errors": { field: ["…"] } }, and optionally a "requestId".
- Return JSON from every RPC, including null for a function with no result. The client parses every 2xx body.
- Keep the pp_csrf cookie readable by JavaScript (not HttpOnly).
- If your layouts wrap pages in their own <template pp-component>, nothing extra is needed any more; ids no longer have to match.
None of these break an older integration; each one makes a feature work that did not before. The full contract, with reference code in four languages, is in Implement In Your Backend.
Where every feature is documented
| Runtime surface | Page |
|---|---|
| Template syntax, directives, handler identifiers | Directives & Public API |
| pp.state, pp.reducer | State |
| pp.effect | Effect |
| pp.ref, callback refs | Ref |
| pp-for, keys, per-row reuse | Loops & Keyed Lists |
| Boundaries, props, children, composition, fragments, context | Components |
| memo, callback, layoutEffect, id, syncExternalStore, imperativeHandle, transition, deferredValue, optimistic, errorBoundary | Advanced Hooks |
| Controlled/uncontrolled fields, checkbox, select, textarea, reset | Forms |
| pp.portal | Portals |
| pp.rpc options, results, RpcError, races, uploads, streaming, CSRF | RPC, Errors & Uploads |
| pp.mount, pp.redirect, link interception, head sync, scroll, loading UI, navigation events | SPA Navigation |
| pp.socket | WebSockets |
| pp.enablePerf, getPerfStats, console messages | Performance & Debugging |
| ComponentInit.bootstrap vs pp.mount, static pages | Plain HTML |
| Browser APIs from component scripts: WebGPU, workers, Web Audio, libraries via import() | Native JavaScript & Web APIs |
| The complete server contract | Implement In Your Backend |
| v2 release status, CDN URL, pinning a build; v1 end-of-life | Releases |
| Machine-readable summary of all of the above | llms.md |
Known gaps in the runtime
These are how the current runtime behaves, not documentation omissions. Each has a workaround that works today. They are candidates for a future runtime release.
| Gap | Impact | Workaround |
|---|---|---|
| pp-loading-transition does not fade | fadeIn/fadeOut set a CSS transition and wait, but the runtime never changes opacity, so they only delay the swap. | Add your own opacity rule to the loading markup, or treat the values as delays. |
| abortPrevious is page-wide | One cancel slot is shared by every pp.rpc call, so two independent search boxes cancel each other. | Use a generation counter in pp.ref for independent lists. |
| onStreamError catches every failure | When set, HTTP and network errors also go to it, and the promise resolves undefined instead of rejecting. | Only set it on streaming calls, and branch on error.status inside it. |
| SSE events are line-based | Each data: line is its own chunk; multi-line data fields are not joined, and event:/id: fields are ignored. | Send one JSON value per data: line. |
| No head asset loading on SPA navigation | New stylesheets or scripts in the next page's <head> are not loaded, and plain <script> tags in the new body do not run. | Use X-PP-Root-Layout to force a full load between sections, and keep page behavior in component scripts. |
| Numeric pp.optimistic bases cannot be rolled back | Pending guesses clear only when the base changes identity, so a failed call on a primitive base leaves the guess shown. | Keep the base in an object and set a fresh copy on failure. |
| Fixed loading container id | Loading UI is looked up under the hard-coded id loading-file-1B87E. | Render that exact id; it is part of the contract. |