The theme of this release: the page cache became one honest, observable, self-defending thing — and AjaxSlim finished its pre-adoption hardening.
Page cache: measured and self-defending
- Reuse statistics — every restore records the instance's idle time and LRU depth into app-wide histograms on the session cache overview page, with a reverse-cumulative "broken by bound" column that reads directly as "a TTL or cap at this bound would have broken N restores". Restores broken by session expiry — invisible to in-cache counters — are counted too, at the
restoreSessionWithIDchoke point. A notable-reaches list names the pages users actually come back for. ERXPageCachePressureValve— caches trim themselves under real memory pressure instead of paying an always-on TTL: when a GC completes with old gen still above 85%, sessions are trimmed to 50% of the page cache cap (LRU-first); above 90%, to 25%. Never below a session's current page. Push-based (collection-usage notifications — no polling, no forced GCs), throttled, loudly logged, surfaced on the overview page and the startup banner. Thresholds and fractions are properties;er.extensions.ERXPageCachePressureValve.enabled=falseopts out. It also finally subscribes toERXLowMemoryHandler's long-unsubscribed low-memory notifications.
AjaxSlim
AjaxExpansion— the disclosure-section element rebuilt on native<details>/<summary>: four bindings, instant client-side toggling, and open state that survives morphs of enclosing containers (each toggle pings the server, so every re-render carries the correct open attribute). Legacy effect bindings and lazy content loading are gone.AjaxModalContainercloses on backdrop click — clicks inside the dialog's box, and drags that merely end outside, never dismiss.- Pre-adoption hardening: an open modal survives morphs of enclosing regions; a full-document ajax response (the expired-session shape) is diverted to the error contract instead of being swallowed or morphed into a container;
AjaxSelfUpdatingContainer.actionno longer fires when the container is merely another trigger's render target; field observers no longer fire into a real form submission; focus survives tabbing out of a field that triggers a morph, including multi-container updates. - Full
.apiextadoption — typed cross-binding constraints, defaults, deprecations, unknown-attribute and content policies on every element.
Development mode
/eval— a JShell REPL inside the running app (loopback-only), and/problems— the rendered binding-error boxes as JSON; both aligned with ng-objects' dev endpoints. Dev-loop machinery consolidated underer.extensions.dev; apps report runtime and pid to the dev server; the launch banner prints external direct-connect URLs.
Dependencies
- Releases now depend only on published artifacts: parsley pinned to 1.6.0, and the ng-core compile dependency severed by temporarily vendoring four small dev-mode classes into
er.extensions.dev.ng(each file documents its deletion condition). Also: json 20260719, junit 6.1.3, vermilingua 1.1.7.
Smaller things
- The empty-update 500 explains itself instead of shrugging; experimental SVG-aware
ERXWOImage(opt-in);ERXWOTextFieldcleanups;RouteTablenames its unhandled-response userInfo key.