Repository navigation
Datastar 1.0.4: vendor + patch anchors, @query, CSP mode, declaration-order hoist, StarElements leg - #85
Open
banditburai wants to merge 18 commits into
Open
Datastar 1.0.4: vendor + patch anchors, @query, CSP mode, declaration-order hoist, StarElements leg#85banditburai wants to merge 18 commits into
banditburai wants to merge 18 commits into
Conversation
…nchors Refactor patch_definitions.py to capture volatile minified tokens (scan-fn name, event consts, handler vars, action export alias) from stable string/ structural landmarks, then fill «token» placeholders in the search/replace operations. The same definitions now patch 1.0.1 and 1.0.2 unchanged — the bump required no manual anchor edits. Verified: rebuilding the prior 1.0.1 upstream with the refactor reproduces the old datastar-core.js byte-for-byte (no behavior change); 1.0.2 applies clean; node --check passes on core + wrapper + 14 plugins; browser suite 42 passed/6 skipped (incl. shadow-DOM scan + outside-click race); unit suite 1458 passed. Handles the 1.0.2 c<->l handler-variable swap automatically via positional capture. Built core/wrapper JS remain gitignored (rebuilt by bun run build).
…r 1.0.2 #1154) Add view_transition_selector to format_element_event() and elements(); it emits a viewTransitionSelector data line, validated with the same selector check, and threads through the elements payload tuple + process_sse_item unpacking. Scoped to the SSE path: the header-based Fragment path is left unchanged because Datastar reads only selector/mode/namespace/useViewTransition from response headers — viewTransitionSelector has no header form upstream.
- patch_definitions.py: trim module docstring to the why + guillemet rationale; fold count check into a walrus in apply_patch - realtime.py: extract _warn_if_unsafe_selector so the selector and viewTransitionSelector paths share one validation check - drop a redundant what-comment in the SSE ordering test
…e + retry-semantics coverage Bundle: patches/datastar-upstream.js -> v1.0.4 (subsumes 1.0.3). Both patches re-anchored because 1.0.4 aliases `document` to a minified local: shadow-dom-scan and outside-race-fix now capture the alias («doc») and the outside-handler param («arg») from their landmarks; the definitions apply to 1.0.2 and 1.0.4 alike (regression-checked). DATASTAR_VERSION 1.0.4+starhtml. @query() (1.0.4): `query()` action in the DSL; `query` in all_meths so `@rt(..., methods="query")` and `app.query(...)` route it (default routes stay GET/POST); QUERY is body-bearing for form fallback and Datastar-signal extraction (_BODY_METHODS); handle_request forwards QUERY bodies. Retry semantics (1.0.3, #1174): upstream now rebuilds HTTP-status/network/GET/form/submitter retries from current state; the five migration tests are renamed *_rebuilds_* and assert that for vanilla and StarHTML runtimes. CSP mode (1.0.3/1.0.4): browser test proves the vendored bundle compiles expressions under a strict CSP with a nonce on <html>; app-level per-request nonce plumbing is a follow-up. Browser matrix: page fixture honours STARHTML_BROWSER=firefox|webkit; migration suite (42) passes on Chromium, Firefox and WebKit. Per-changelog-item decisions (incl. Rocket vs StarElements investigation) in docs/designs/2026-09-21-datastar-1.0.4-integration.md.
…ntics, un-hang compat suite Review fixes: patch-introduced locals are `_`-prefixed with a `reserved` collision guard in _resolve_captures (a minifier reusing u/f/g/h/d would have produced a SyntaxError that verify() could not see); `arg` capture backreferences its own name; Client.query shortcut; WASM handle_request forwards bodies for every _BODY_METHODS entry; README retry section rewritten for 1.0.3's current-payload retries; the five *_rebuilds_* tests use retryInterval 400ms so the signal patch reliably lands before the retry. View transitions (1.0.3 #1181): viewTransitionSelector now targets the element's scoped startViewTransition with no document fallback, so on engines without scoped view transitions (Firefox, WebKit) a selector means no transition. realtime.py docstrings say so; new browser test test_patch_elements_view_transition_target[document|selector] spies both entry points and passes on Chromium/Firefox/WebKit. tests/browser/test_browser_compatibility.py hung forever: the class-scoped async fixture and the function-scoped tests ran on different pytest-asyncio (1.1) event loops, so Playwright awaits never resolved. loop_scope="class" on both fixes it; the suite then exposed page.touch_screen (fixed to touchscreen) and a scroll test that can never pass (harness inlines the ES-module plugin as a classic script, never loads Datastar) -> strict xfail with reason. Suite: 7 pass, 1 xfail.
… CSP mode; Rocket vs StarElements study
csp=True (or a policy template containing {nonce}) makes every HTML response carry a fresh
128-bit nonce: data-nonce on <html> (Datastar reads it once and compiles expressions through
nonced <script>s instead of Function()), nonce on every rendered <script>/<style> (import map,
Datastar loader, plugin modules, theme, devtools, the handler's own tags; explicit nonces are
kept), and a Content-Security-Policy header, default
script-src 'nonce-{nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'self'
('strict-dynamic' lets the nonced module scripts import datastar-core.js, plugin chunks and CDN
deps without host allow-lists). csp_nonce(req) exposes the value; exception pages get one too;
JSON/plain responses are untouched. Not covered by nonces, documented: Datastar's inline style=
writes (policy constrains scripts only), raw NotStr HTML, and StarElements' new Function.
Tests: tests/unit/test_csp_nonce.py (9) and test_star_app_csp_mode_end_to_end, which proxies the
real ASGI app through Playwright so the browser enforces StarHTML's own header; passes on
Chromium, Firefox and WebKit with zero CSP violations.
docs/designs/2026-09-21-rocket-vs-starelements.md: source-level comparison of the new free
Rocket bundle with StarElements; seven adoption items (data-scope-children, attribute-aware $$
rewrite, pre-upgrade data-ignore guard, CSP-safe setup compile, named errors, manifests, nested
paths), what to keep, what is not applicable, and the risk to the shadow-dom-scan patch.
… readers Datastar evaluates an element's attributes in markup order; a reader (data-text, data-attr, data-bind, ...) that runs before its declaration auto-creates the signal as '' and __ifmissing then keeps it. process_datastar_kwargs now returns declarations first so every emit path renders them ahead of readers.
… before the deferred first scan Datastar defers its first scan to a timeout and then scans roots.size ? [...roots] : [documentElement]. A StarElements host connecting before that timeout dispatched datastar:scan, the patched listener added the host to roots, and the document was never scanned (no datastar-ready, page-level data-* dead). Light-DOM hosts are now left to the pending document scan; shadow roots are scanned once datastar-ready fires. tests/browser/test_starelements.py: StarElements leg (run with both editables, see docstring).
…patches, computed keys and refs
…clares signals ifmissing now); tests/browser/test_starelements: CSP leg (star_app(csp=True), setup+static scripts, no unsafe-eval)
…allback + declared-event warnings
…tion); Signal docs the ifmissing=False reset form; hoist orders data-signals before data-computed - starhtml.lint.check_signal_order(ft) walks a rendered tree in document order and reports $x reads on an element before the one declaring x (data-signals:x, data-signals object keys, data-computed:x). Undeclared reads are not reported (layout/persist/patch may own them); $$locals are ignored. - Signal docstring: ifmissing=False emits a plain data-signals object (server- authoritative reset), signals(name=value) resets from a handler. - _declarations_first: data-signals* ahead of data-computed* ahead of readers.
…trs keep declarations first; deferred shadow scan skips disconnected hosts; stronger StarElements browser tests - lint: $user.name reads the root signal; bare data-bind/ref/indicator values are reads (an undeclared data_bind ahead of the declaration was missed); only depth-1 object keys declare; string literals masked; data-signals string values are literals (StarHTML escapes $). - html._apply_slot_attrs_to_children: slot dicts merged after a child's readers now go through _declarations_first. - shadow-dom-scan patch: the datastar-ready-deferred scan checks host.isConnected (roots are never removed from Datastar's Set). - Signal docstring: ifmissing=False is the reset form only when declared. - tests/browser/test_starelements: assertions no longer satisfied by SSR fallback text; datastar-ready counted; new tests for __root across patches + nested hosts, re-patched outer markup, empty int default registration.
…scoped (7 shapes); design record: patch modes + apply() export investigation
…rElements defines elements after it Rocket queues element definitions until Datastar's document observer is active; StarElements defined at DOMContentLoaded, which is the root cause of the first-scan starvation. The wrapper registers its datastar-ready listener before the first-scan timeout can fire (Datastar has no 'already fired?' query), and StarElements now waits on it. With the original naive datastar:scan listener all 16 StarElements browser tests pass, so the guarded listener is defence in depth.
…ener; wrapper ready promise removed; StarElements browser tests for the final boot/scan contract Light-DOM hosts are covered by Datastar's document observer; shadow roots are bound by StarElements calling apply(shadowRoot, true) after Datastar's first scan (ordered by a macrotask after its static import), so the listener's ready/ShadowRoot deferral is gone. New tests: multi-host selector not cross-wired, inserted host with $$ children scoped before apply, <pre> newline through the pre-scope, light hosts never enter observedRoots (late attribute() applies once per element).
…double-script investigations
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Vendors Datastar 1.0.4 (from the pre-1.0.2 bundle on
main) with durable stable-landmark patch anchors, and carries the follow-ups that the 1.0.2 → 1.0.4 changelog audit surfaced.Datastar upgrade
shadow-dom-scan,outside-race-fix) re-anchored on stable landmarks so minifier renames stop breaking them (patches/patch_definitions.py).@query()support;viewTransitionSelectorfor patch-elements (1.0.2 #1154); view-transition semantics documented (1.0.3 dropped the document fallback, soview_transition_selector=yields no transition on Firefox/WebKit).star_app(csp=True): per-response nonces,Content-Security-Policyheader,data-noncefor Datastar's CSP mode; end-to-end browser test with nounsafe-eval.Fixes found on the way
process_datastar_kwargshoistsdata-signals*(thendata-computed*) ahead of same-element readers: Datastar applies attributes in markup order and an early read created the signal as""(2b7d83e).datastar:scanlistener no longer registers a host as a root before Datastar's deferred first scan; a StarElements host on the page previously left the whole document unscanned (7d4a3ea).starhtml.lint.check_signal_order(ft)reports a$xread on an earlier element than its declaration;Signaldocs theifmissing=Falsereset form.data-signals__ifmissingfrom its host (StarElements now declares host signals ifmissing).Tests
tests/browser/test_starelements.py: StarElements leg (first-scan starvation light/shadow, scope-children rescope, Local bind/ref namespacing +__root, CSP without unsafe-eval, late host + codec fallback + declared events). Run withuv run --with playwright --with-editable ../starelements --with-editable . pytest tests/browser/test_starelements.py(see the module docstring for why both editables).uv run pytest tests/unit tests/integration→ 1747 passed with starelements installed (thetest_devtools_*cases skip-fail without it);tests/browser→ 58 passed, 1 xfailed (Chromium); the migration file also passes on Firefox/WebKit.Design records (docs/ is gitignored; force-added):
docs/designs/2026-09-21-datastar-1.0.4-integration.md,docs/designs/2026-09-21-rocket-vs-starelements.md.Companion StarElements runtime changes (separate repo, not required to merge this) implement the Rocket adoption list:
data-scope-children, attribute-aware$$rewrite, nonced-script compile, named errors + codec fallback, manifest.🤖 Generated with Claude Code