ReviewOS

stacks/stx

refactor(native): converge component resolution on shared implementations with cross-target tests

#2091
Open glennmichael123 opened this 8 days ago ·
8 days ago

Problem

Native compilation currently prefers target-specific primitives ahead of some conventional app/plugin component directories. The same component name can therefore select different source files by target, masking portability gaps.

The intended architecture is one STX component model and reusable component source, with platform-specific rendering owned by the host.

Audited at v0.2.546, commit 0013ce66e3.

Evidence

compileScreenSource configures _targetComponentDirs with nativePrimitivesDir(). Its documented precedence is explicit screen components, then native primitives, then conventional app/plugin components.

Tests currently intentionally assert native primitive selection for overlapping names such as Checkbox. Meanwhile, explicitly compiling the shared-library implementations exposed setup failures and wrong control translation.

This does not mean every component is substituted: explicit screen-local components already override primitives.

Requested change

Converge public component behavior and resolution toward shared implementations after their native compatibility is established. Preserve legitimate low-level host primitives and documented overrides. Avoid changing precedence globally before the shared replacements work.

Acceptance

  • Define and document resolution precedence and compatibility behavior.
  • Tests prove which source file is selected for shared component names on each target.
  • A cross-target suite explicitly loads the same shared component files.
  • Cover reactive props, emitted events/models, conditional lifecycle, controls, and teardown.
  • Existing screen-local overrides remain supported.
  • Migrate overlapping names in verified batches without breaking native screens.

Priority

Sixth in the portability audit sequence, gated by the earlier behavior and translation fixes. Build the same-source test coverage alongside those fixes; change resolution only when the affected shared components pass. Existing baseline: 360 native tests and 58 selected browser/component tests passed, despite failures in additional shared-component probes.

3 days ago

Host-prerequisite update from today's issue audit: craft-native/craft#427 is now closed and the Image/ImageBackground fit/position contract is released in Craft 0.0.396, per its release verification comment. The previous host-implementation blocker is therefore cleared.

This issue remains open for the STX compiler/runtime integration and shared Image resolution decision, preserving legacy source/resizeMode callers, plus the required cross-target tests and consumer/device qualification. No Image migration or Craft changes were made during this audit.

3 days ago

Shared Image integration and compatibility selection are now on main in four pooled commits, each five files: 5e60fc9, 6999c8a, d14c583, caa08e1. Integrated tip: caa08e1. The concurrent upstream slot regression 007bf83 is preserved and its test passes.

Version: unreleased source after v0.2.754. npm 0.2.754 does not include this batch. No STX release or replacement tag was cut. Native geometry requires Craft 0.0.396+ and a regenerated shell (craft-native/craft#427); no Craft code was edited.

  • CSS/object/serialized styles retain explicit objectFit/objectPosition, supporting fill/contain/cover/none/scale-down and one/two alignment keywords or percentages. Fit remains separate from resizeMode; clearing explicit geometry restores the legacy path. Invalid fits, pixel offsets and unsupported multi-token positions retain diagnostics. Canonical native prop spellings survive HTML/kebab-case normalization.
  • Shared Image presentation calls select the same shipped file on web/native when authored src and a presentation prop have a valid SSR alt/decorative fallback. Empty/initially undefined source bindings do not change selection. Empty alt/client-only captions without SSR fallbacks keep their previous selection instead of acquiring a new SSR exception.
  • Plain calls, source/resizeMode/defaultSource, mixed APIs, native-only events and builtin transformation/preload options retain their prior contracts. Explicit imports, local overrides, absent-library fallbacks and StxImage remain supported.
  • STX browser and standalone renderers understand explicit geometry. The browser peer honors prop-over-style precedence and restores legacy modes on clear.
  • Tests cover shared/legacy images together, geometry/source updates without replacing images, no source writes/reloads on geometry changes, original event payloads, clearing/defaults, conditional reinsertion, fresh screen mounts, stale callbacks and disposal. Real Chrome observes one load per source and no reload during fit/position writes. The actual standalone Swift frame algorithm executes small/large intrinsic-size, upscaling, scale-down and crop-alignment checks.

Validation: 3,004 native/component tests passed, zero failures (284 files); 324 normal release-preflight checks passed, including build/archive/export/performance gates; typecheck, lint and formatting pass. Regression controls fail with the old style parser, old browser peer or disabled shared selection. New empty-alt compatibility cases also failed before the selection safeguard. The user's unrelated Map test edit remains byte-identical.

Hosted CI for the pushed tip is queued/running: https://github.com/stacksjs/stx/actions/runs/41095542981 . This is not a claim of completed hosted CI or publication.

Keeping the broader #2091 OPEN for its remaining device/consumer acceptance. This batch verifies compiler selection, native bridge behavior, real web behavior and standalone Swift geometry; it does not claim new physical iOS/Android drawing, VoiceOver/TalkBack or pixel-parity certification. The previous Image host-implementation and STX-integration blockers are now resolved; the resolution inventory and remaining acceptance boundary are updated in docs/proposals/native-component-resolution-audit.md and docs/guide/native.md.

3 days ago

Published in STX / components / bun-plugin-stx 0.2.755: https://github.com/stacksjs/stx/releases/tag/v0.2.755 . Release commit: 9e4186a. All three packages are public and tagged latest.

Verified each original npm archive against its published SHA-512 integrity, then installed the exact public versions in a new consumer project (no workspace links or local archive substitutes). 41 checks passed, zero failures, 484 assertions across the maintained image fit, renderer, resolution, shared-image and real Chrome scenarios. They cover source/presentation selection, legacy/builtin compatibility, prop/style precedence and clears, retained identity, unchanged source/loading during geometry writes, events, defaults and remounts. All nine focused version-control checks fail against the original public 0.2.754 packages.

The source CI passed all five jobs before the tag was pushed: https://github.com/stacksjs/stx/actions/runs/41095542981 . The exact release gate also passed: 17,492 tests, 55 skips, zero failures, plus all 324 preflight checks and macOS signing: https://github.com/stacksjs/stx/actions/runs/41099337281 .

Status caveat: the separate automatic post-tag CI hit a 25-second cold browser-startup readiness timeout. Its logs show one startup failure; the release gate and installed-package Chrome checks passed. GitHub refused attempts to rerun that older record while its reported state lagged, so a clean CI run was dispatched against the immutable v0.2.755 tag: https://github.com/stacksjs/stx/actions/runs/41103024828 . That run is queued at this check; this comment does not claim every post-tag CI check is green or every icon collection has finished publishing. No timeout was raised and no release tag was replaced.

Native consumers need Craft 0.0.396+ and a regenerated shell for the explicit Image geometry contract. Craft code and the unrelated Map test edit were untouched. The broader device/consumer acceptance boundary recorded above remains open; this publication verifies the STX integration and public-package floor, not new physical-device/VoiceOver/TalkBack certification.

5 hours ago

Shared Image simulator verification and two STX fixes are now on main through 1309815:

  • 813c0ee preserves the native containing box through the Image component scope. A 100-point parent previously collapsed to the decoded image's 10-point intrinsic height.
  • 9a0c5c7 creates remounted images with their settled source, geometry and handlers, preventing false missing-source errors before parent prop replay. Existing-image updates keep their ordinary microtask timing. Explicit source clearing still forwards the real native error; legacy callers remain covered.
  • 645096c adds the maintained Image simulator gate, including actual shared Button interaction, current values after hidden updates, defaults/fresh mounts, retained native views and decoded-image identity, and a strict raster comparison.

Version: unreleased source after 0.2.784. No release or tag was cut.

Validation: 3,088 native/components tests passed, zero failures (298 files, 20,825 assertions); focused new contracts 11 passed / 128 assertions. Typecheck, lint and formatting pass. These are local results, not a claim that hosted CI has completed.

Against clean Craft 1e141f1d3a02c9e5798cb3d816bcffc2d0d66d55 (source version 0.0.424), the six-case iOS Simulator suite reports 5 passed / 1 failed / 0 skipped in both light and dark appearances. The sole failure is the new strict raw-host raster assertion: the fitted child has the correct frame, but Craft also paints the original image behind it. A plain UIKit control paints correctly. The reproducer uses no STX compiler/runtime; filed separately as craft-native/craft#441. No Craft code was edited.

Reproduction and raster comparison · source/bundle hashes and exact results · maintained native tests.

Keeping #2091 OPEN for that host drawing defect and the remaining Android/device/accessibility acceptance. The simulator gate intentionally remains red for the host defect; ordinary Bun CI does not run this XCTest gate. The user's unrelated Map test edit is unchanged.

5 hours ago

Shared Video native-host verification and its resulting STX fix are now on main through e0b8668:

  • a6f1b4a fixes stale initial Video source/playback flags during conditional remount. The first host creation now contains settled props and handlers. Existing-media updates retain their ordinary microtask timing, and legacy source callers remain supported.
  • fdaada6 adds --suite videos to the maintained simulator gate, using locally generated silent H.264 clips and actual AVPlayer/AVPlayerLayer behavior. No playback events are simulated in those native tests.
  • e0b8668 records the source/bundle hashes and before/after receipts.

Version: unreleased source after 0.2.784. No release/tag was cut. Craft source was untouched.

Before the fix, all four iOS/Android normal/minified protocol variants failed because createNode carried the old source/autoplay/loop values. The strengthened native regression also failed against the original bundle: 5 load starts instead of 4. It passes with the fix on the same unchanged generated host.

Final iPhone 17 Pro / iOS 26.5 simulator results against clean Craft 2140bc72e6033f0706312c6577819a5d97957d84 (source version 0.0.424): 5 passed / 0 failed / 0 skipped in light, and 5 / 0 / 0 in dark, using the same build. Four native tests verify real playback/display readiness, live controls/autoplay/muted/pause flags, actual loop/end behavior, null defaults, source replacement/clearing, object-URI legacy callers, missing-file recovery, fresh screens and teardown. A UI test drives actual shared Buttons and conditional views without a WebView; it uses an empty source, so playback evidence comes from the native tests.

Flag changes preserve the physical video view, controller, AVPlayer and current item. Source replacement preserves the view/controller while intentionally creating a new player. Removed players are stopped/disposed, and remounts use current values without an extra old-source load.

Local regression checks: 3,092 passed / 0 failed, 299 native/components files, 20,901 assertions. Focused contracts: 10 passed / 104 assertions. Typecheck, lint and formatting pass. The initial exploratory simulator run contained two fixture mistakes (null diagnostic text and checking an asynchronous pause callback too early); both are documented, with the final assertions retained. No timeout or gate was relaxed. Hosted CI for these new commits is separate and pending.

Maintained Swift tests · reproduction and limits · receipt.

Keeping #2091 OPEN for its broader acceptance, including the separately recorded Image host blocker. This does not certify Android/physical-device playback, provider embeds/streams, DRM, PiP/background interruption, audio mixing, accessibility or pixel parity. The user's Map test edit remains byte-identical.

5 hours ago

Shared Checkbox/Switch native-host verification and its resulting fix are on main through 96c1659.

  • 641dda7 fixes the shared native Switch remaining on when its checked model clears. The shared binding now sends an explicit boolean off value. Legacy primitive omission/retention semantics are unchanged. Four iOS/Android normal/minified protocol cases failed before this change; the original native remount check also retained 1 instead of 0.
  • 44b887f adds the maintained --suite toggles gate: three native tests and two actual-tap UI tests, using normal resolution for shared and legacy calls together.
  • 96c1659 retains the receipt and inspected light/dark screenshots.

Version: unreleased source after 0.2.784. No release/tag was cut.

Final iPhone 17 Pro / iOS 26.5 Simulator runs against clean Craft c7984f088be396f9b2c9bedab4a7d0a64e3fab4e: 5 passed / 0 failed / 0 skipped in each appearance, using the same build. Checks cover live checked/disabled/mixed state and glyphs, exact boolean shared versus object legacy event payloads, disabled touches, defaults, native-view identity during updates, hidden updates/remounts, fresh screens and teardown. Uncontrolled legacy attempts remain set without parent writeback; clearing the shared model causes no extra user events.

The final native/components run passes 3,098 tests / zero failures, 300 files, 21,067 assertions. Focused browser/native/legacy checks pass 21 tests / 356 assertions. Typecheck, lint and formatting pass. The first broad run caught the required template snapshot update and an unchanged backend timeout; the reviewed snapshot and both affected full-file retries pass, followed by the clean complete rerun. No deadline or native assertion was relaxed.

Maintained native tests · reproduction, screenshots and fixture corrections · source/bundle hashes and exact receipts.

Keeping #2091 OPEN for its broader acceptance. This is not Android/physical-device drawing, VoiceOver/TalkBack, hardware-keyboard, broad pixel-parity or native navigation-chrome certification. Craft source was untouched, and the user's Map edit remains byte-identical. Hosted CI for these new commits is separate from the completed local checks.

7 days ago

Shared Image follow-up is on main in ee19109 (reactive dimensions/layout) and 7de7424 (load/error forwarding).

  • Shared width/height now update the physical image attributes/native props; aspectRatio updates its frame. Native fractions such as 9 / 16 become finite numeric ratios. No intrinsic ratio is inferred when none was authored.
  • Browser objectPosition and position are reactive, with primary/alias/default precedence.
  • Physical image load/error callbacks now reach callers once, preserving the original browser Event or native payload. Detached images cannot forward stale callbacks. Empty initial URLs omit src to avoid a spurious image error.

Maintained regressions: packages/stx/test/native/shared-image-layout.test.ts and shared-image-events.test.ts, using normal/minified native bundles and real Bun.WebView browser decoding, network errors, computed styles, clearing, identity, hide/reopen and disposal. Existing image-source and remaining-component-resolution tests verify legacy behavior.

Validation: 24 image/resolution tests passed, then 13 event/legacy-source tests passed after the final empty-src guard; typecheck, changed-test lint and formatting passed. Final broader run: 3,257 passed, one five-second CLI compile timeout under local load average ~68; that CLI suite passed 12/12 on isolated retry, with the affected case completing in 0.76 seconds. Main CI is pending.

Version: UNRELEASED after v0.2.707. Craft is untouched. Native non-centred positions, scale-down, contain upscaling/device validation and the browser builtin/shared precedence decision remain gates. Resolution is intentionally unchanged; this is protocol/browser evidence, not device pixel certification. Keeping #2091 open.

7 days ago

Shared Image accessibility follow-up is on main in b153658 (native alt-label projection) and 42026c7 (reactive shared accessibility).

  • Shared alt/decorative update the physical image. Decorative status masks alt, clearing it restores current text, including text initially masked by static decorative. Browser aria-hidden is added/removed with the live flag. Typed/string boolean values follow the server boolean contract; null alt becomes an empty alt attribute rather than a filename fallback.
  • Native Image alt supplies accessibilityLabel behind explicit native and ARIA labels. Compiler creation and runtime updates/removal share this projection; wire patches explicitly clear stale labels. Other native types and legacy source/event contracts are unchanged.
  • Existing SSR meaningful-alt validation remains. Native aria-hidden exclusion is NOT implemented by this projection: clearing the label is not device screen-reader exclusion.

Regression evidence: maintained native image-accessibility.test.ts and shared-image-accessibility.test.ts cover creation, override precedence, wire clears, normal/minified bundles, a real browser, quoted/Unicode text, static-decorative recovery, clearing, retained identity, hide/reopen and disposal. No redundant browser aria-label is generated.

Validation: 3,264 passed / 0 failed across 315 native/component files; typecheck, changed-file lint/formatting and release preflight passed, including builds, installed-package smoke, exports, budgets and 324 targeted tests. Image event checks also passed 10 repeated runs (30/30). The previous main CI failed its browser event observation assertion; this batch observes the actual event in capture phase before component forwarding and keeps identity/target/decoding assertions, with clearer diagnostics. New main CI is still running its full suite; lint, typecheck and native signing passed: https://github.com/stacksjs/stx/actions/runs/40327686426

Version: UNRELEASED after v0.2.707. Craft is untouched; Image resolution and device accessibility/visual validation remain gates. Keeping #2091 open.

7 days ago

CI correction: the capture-order adjustment alone did not fix the browser assertion. Run 40327686426 finished with 17,204 passed / 1 failed, and the field-by-field diagnostic isolated the failure to the stored event target; event identity and image decoding both passed.

Reproduced locally with the Chrome backend: the physical image is the target both in the raw listener and in the component caller, but Chrome clears the original resource Event.target to null after dispatch. WebKit retains it. Commit 13cbc9c therefore records the target inside the caller and asserts that delivered target alongside the original Event identity, actual decoding, error delivery, stale callbacks and remount behavior. It does not clone or mutate the original event, skip a browser, or remove the target assertion.

The corrected maintained test passed 10 repeated runs in Chrome and 10 in WebKit (60 tests / 0 failed). Typecheck, lint and formatting passed again. The test now supports STX_IMAGE_BROWSER=chrome for reproducing the CI engine on macOS in a fresh headless browser. A fresh main CI run is pending; this is still unreleased after v0.2.707.

7 days ago

Final verification: main CI is fully green at 13cbc9c, including all five jobs (lint, typecheck, native signing, test and commit-package publishing): https://github.com/stacksjs/stx/actions/runs/40330151218

Actual runner results: 17,205 unit tests passed / 0 failed (44 skipped), 1,770 component coverage tests passed / 0 failed, and 45 integration tests passed / 0 failed. This includes the real Chrome resource-event assertion that failed in the preceding two runs. The built-distribution native Image probe also verified initial labels, live updates and decorative label clearing without mutation errors.

Commits b153658, 42026c7 and 13cbc9c are pushed. Version remains UNRELEASED after v0.2.707; no patch release was cut. Craft remains untouched. Keeping the broader issue open for the remaining resolution and host/device validation gates; native screen-reader exclusion is not certified by this label projection.

7 days ago

Shared Image responsive bindings landed in 131445f, unreleased after v0.2.708.

srcSet/srcset and sizes are reactive. A changed candidate set disables stale initial picture sources; original catalog candidates survive hydration and return when defaults/original src are restored. Source-specific sizes retain precedence. Empty string/null clears, undefined restores defaults, and clearing src clears candidates. The source list itself is still setup-time. Native continues using src rather than browser responsive URLs; component resolution and Craft are unchanged.

Regression evidence: the new real-browser test failed on the pre-fix component (both spellings selected the stale source and kept static sizes), then passed on WebKit and Chrome, including 3 repeated Chrome runs. Maintained tests: packages/components/test/image-responsive-browser.test.ts, packages/components/test/image-delivery.test.ts, packages/stx/test/native/shared-image-source.test.ts. The broader native/component suite passed 3,266 tests; release preflight passed build, installed-package smoke, exports, budgets and 324 tests; typecheck/lint/format passed. CI is pending, not yet claimed green. #2091 stays open for the remaining migration/host gates.

7 days ago

Follow-up commits: 8b7c8fe (typed prop bridge) and 0dbc5a0 (Image candidate ownership), both unreleased after v0.2.708. The first responsive commit 131445f passed all five CI jobs.

An additional real-browser case caught source-only pictures reviving stale sources after an empty srcSet. It also exposed lowercase alias null clears reverting to serialized defaults, and null -> undefined missing the default reset when the DOM attribute was already absent. The bridge now retains typed alias clears and explicitly restores parsed defaults, including numeric/boolean defaults; Image distinguishes missing candidates from explicit null/empty clears. Source-only null/empty -> default restoration is tested through actual decoded currentSrc, not just DOM attributes.

Final local verification: 4,934 native/component/router/signal tests passed with zero failures (533 files); Chrome responsive cases passed 3 repeats (9 tests); final WebKit/typed-default tests passed; release preflight passed build, installed-consumer smoke, exports, budgets and 324 targeted tests; typecheck/lint/format passed. Final-head CI is pending. Craft and legacy component resolution are unchanged, and #2091 remains open for the documented migration/host gates.

7 days ago

Final-head CI is green for 0dbc5a0: https://github.com/stacksjs/stx/actions/runs/40346504057

All five jobs passed: lint, typecheck, native-signing, test and publish-commit. Test logs confirm 17,211 unit tests passed (44 skipped, zero failures), 1,773 component coverage tests passed, and 45 integration tests passed. Local expanded verification was 4,934 passing tests, plus successful release preflight and repeated Chrome/WebKit checks.

The responsive Image and typed-clear/default follow-ups are pushed in 131445f, 8b7c8fe and 0dbc5a0. These are source/commit-artifact changes after v0.2.708; no new npm release was cut or claimed here. #2091 remains open for its remaining migration/host gates. Craft is untouched.

7 days ago

The responsive Image/typed-prop fixes now have a published version floor: v0.2.709.

Verified public npm metadata and complete archives for @stacksjs/stx, @stacksjs/components, bun-plugin-stx, stx-router, @stacksjs/desktop and @stacksjs/sanitizer at 0.2.709, including SHA-512, SHA-1 and the packaged manifest versions. The published shared Image template is byte-identical (SHA-256) to the tested release source; the published framework contains the prop-default reset bridge.

A fresh consumer outside the repository installed 0.2.709 directly from npm, then passed actual browser srcset/sizes updates, lowercase typed clears and source-only default restoration. Normal and minified native bundles passed src fallback, clearing, retained identity and disposal checks. These tests use published package exports, not source aliases. Main CI is green on the release commit cc72d2f: https://github.com/stacksjs/stx/actions/runs/40359269359 . Release-source and native-signing gates also passed: https://github.com/stacksjs/stx/actions/runs/40359268711 . The wider release batch is still publishing; this confirms the framework/components and their runtime dependencies, not completion of every icon package.

Craft was not changed. #2091 stays open for the remaining migration and host/device validation gates.

7 days ago

Shared Image loading policy is implemented in b42dec7 and 5163cf8, unreleased after v0.2.709.

priority, lazy, loading and fetchpriority now update the physical shared image. Priority forces eager/high; clearing it reveals the current authored hints, including defaults masked by initial SSR priority. Empty/null loading uses lazy, empty/null fetchpriority uses auto, and undefined restores authored defaults. Boolean strings follow $bool semantics. Hints are applied without replacing the image or disturbing src/responsive candidates.

Testing reproduced the old frozen hints in both initial-priority cases and an offscreen image that never requested when priority turned on. Real WebKit and Chrome tests now pass, including an actual deferred-to-eager request and decoding. Repeated Chrome testing: 9 passes. Native normal/minified tests preserve source, labels, identity, remount and teardown; no native host request-priority support is claimed.

Remount tests also caught stale typed clears when the outer attribute remained absent. The prerequisite prop bridge now replays pending typed writes and missing/default markers through setup/remount, with parsed numeric/boolean defaults. Maintained regressions are in packages/components/test/image-loading-browser.test.ts, packages/stx/test/native/shared-image-loading.test.ts, and packages/stx/test/router/reactive-prop-attribute-types.test.ts.

Local verification: 4,939 expanded native/component/router/signal tests passed (535 files, zero failures); release preflight passed build, installed-archive smoke, exports, budgets and 324 tests; post-build focused tests, typecheck, lint and formatting passed. Final-head CI is pending. No npm release was cut. Craft, legacy component resolution and device-support gates are unchanged, so #2091 remains open.

7 days ago

CI follow-up: the first run on 5163cf8 passed 17,215 tests and failed one historical #1704 assertion that matched the exact text if (raw === lastRaw) return. That unconditional early return is the behavior the pending typed-write/remount fix must widen; the new real browser/native cases passed in that run.

182c61c replaces the implementation-text check with execution of the generated authoring runtime: unchanged raw/parsed values cause zero redundant signal writes, while number-to-string changes with identical serialized text consume their pending typed value. Actual router tests verify one notification for that type change and none for a repeated identical value. It does not change production code or skip the observer guard coverage.

All 73 focused tests passed, along with typecheck/lint/format. Final-head CI is pending again. The work remains unreleased after v0.2.709; Craft is unchanged.

7 days ago

Final verification for the shared Image loading/priority work: main is pushed through 182c61c.

Commits:

  • b42dec7: preserve typed clears/defaults across hidden component remounts.
  • 5163cf8: make shared Image loading/priority hints reactive, with browser and native regression coverage.
  • 182c61c: replace the historical #1704 source-shape assertion with execution-based observer deduplication coverage.

CI is fully green: https://github.com/stacksjs/stx/actions/runs/40370538931 All five jobs passed (lint, typecheck, native-signing, test, publish-commit). Unit suite: 17,216 passed, 44 skipped, 0 failed across 1,364 files. Component coverage ran 1,776 tests with 0 failures; integration: 45 passed, 0 failed. Local release preflight and repeated Chrome checks also passed.

Version status: unreleased changes after v0.2.709, not part of the published 0.2.709 package. Priority hints do not guarantee host/network scheduling or cancel an already-started request. Native tests verify compatibility, not a new host scheduling capability.

Craft remains untouched. Keeping #2091 open for its remaining convergence gates.

7 days ago

Shared Image decoding is now reactive in 5c6e525 (pushed to main; unreleased after v0.2.709).

The physical browser image updates its decoding attribute/property for async, sync and auto without replacing the node or changing src/srcset. Empty/null falls back to async; undefined restores the authored default. SSR behavior is retained.

Regression coverage:

  • packages/components/test/image-decoding-browser.test.ts: actual browser property and attribute, default and authored sync cases, typed clears, hidden writes, retained-node remounts, and updates after remount. Both cases failed against the old implementation.
  • packages/stx/test/native/shared-image-decoding.test.ts: normal/minified native source, labels, identity, remount, source clearing and teardown compatibility. No native host decoding-policy support is claimed.

Verification: 1,811 component/Image/router tests passed, six repeated Chrome runs passed, four post-build focused tests passed, and component build/typecheck/lint/format checks passed.

CI is currently running: https://github.com/stacksjs/stx/actions/runs/40374482693

Craft and component resolution remain untouched. #2091 stays open for the remaining convergence gates.

8 days ago

First shared-resolution batch is now on main through febbec7 (seven commits: 97e86bb, f7a95e1, ee481cb, 9bbb4b0, 0cb0be2, cd51970, febbec7).

Version: source changes after v0.2.583, NOT a released version yet.

  • Button resolves to the same app/plugin implementation on web and native, with the native primitive retained as a fallback. Other overlapping names have not been switched globally.
  • Shared Button retains native text/:text and @press spellings. Composed buttons use child-capable Pressable nodes; explicit leaf primitives remain available.
  • Reactive caller classes, styles, accessibility props and test IDs reach the physical root, preserve component-owned styling, and clear correctly. Tests include minification, hide/remount, separate instances, consumed-prop privacy and teardown.
  • Native compilation now honors the configured componentsDir, including root: resources. Explicit compiler and screen-local overrides remain supported.

Maintained tests: packages/stx/test/native/shared-component-resolution.test.ts, native-conditional-toggle.test.ts, native-host.test.ts, plus shared form/control tests and browser Button tests. Latest native suite: 507 passed. Native/component batch: 2,636 passed. Component coverage run: 1,730 passed; integration: 45 passed. Build, installed-package smoke, lint, formatting and typecheck passed.

Full local suite: 16,360 passed / 1 skipped / 1 failed. The sole failure was the unchanged image-pass-off-thread.test.ts responsiveness threshold (189ms versus 150ms) under high machine load. Its complete file subsequently passed 11/11, and the exact failing case passed a second isolated run. No assertion or threshold was relaxed. New CI is still required; this is not a claim of a green full run.

Keeping this issue OPEN: remaining component names still need verified compatibility batches. A fresh probe of the actual shared Checkbox confirms that a parent disabled=true update does not disable its physical native control; a change event still changes the model. That must be fixed before migrating Checkbox. Craft implementation remains owned by the other agent.

8 days ago

Two shared-control compatibility fixes are now on main:

  • 54e1ba0: Checkbox disabled/indeterminate props are reactive on browser and native hosts. Disabled interaction is rejected; enabled interaction emits one boolean payload. Declaring the change event prevents the underlying DOM event from being mistaken for the component payload without stopping normal form-event bubbling.
  • ead5ba2: Switch disabling is reactive, updates presentation/accessibility bindings, and disables its hidden form field so disabled switches are omitted from form data. Re-enabling and remounting preserve the model.

Version: unreleased source after v0.2.583. These changes do NOT yet switch Checkbox/Switch resolution priority. Legacy native value/value-change compatibility and host-attribute targeting still need coverage before migration.

Maintained same-source browser/native tests: packages/stx/test/native/shared-checkbox-reactivity.test.ts and shared-switch-reactivity.test.ts. Both run normal and minified builds, parent-driven updates, rejected disabled events, re-enabling, hide/remount and native teardown. The test DOM now models input.indeterminate as a property independent of its HTML attribute. Native tests verify bridge/control state, not device rendering of the mixed-state glyph.

Validation: focused control/form/snapshot batch 22 passed; combined native/components/reactivity/signals batch 4,263 passed, 1 failed. That failure was the unchanged TransitionGroup fixed-400ms timing assertion during machine load average ~81; its complete test file passed all 6 cases on isolated rerun. Assertions were not relaxed. Build, installed-package smoke, lint, formatting and typecheck passed.

The preceding Button-resolution batch passed CI lint/typecheck/test in run 39289994202. This new pair awaits its own CI. Keeping #2091 open.

8 days ago

Progress pushed to main (unreleased after v0.2.605):

  • f54c91a keeps hidden form fields outside native layout, including reactive styles, appearance changes, and reinsertion.
  • 2d32247 retains native Switch value changes without requiring parent writeback and pins legacy value/value-change versus shared checked/boolean-change/form-value contracts.
  • 74eb646 and 33addea build on the ARIA mapping already landed upstream: authored native accessibility overrides are retained separately from generated fields, removal restores the ARIA fallback or clears stale state, and cloned rows receive current projected state. Generated fields no longer masquerade as authored binding attributes. Existing role/state mappings are preserved.

Tests: control-compatibility.test.ts, accessibility-projection.test.ts, accessibility.test.ts, plus existing aria-accessibility.test.ts and shared-control suites. Includes actual shared Switch, normal/minified bundles, reactive updates, keyed row creation, removal, explicit overrides, and teardown. Rebased native suite plus exported-return-types guard: 614 passed, 0 failed; typecheck and changed-file Pickier lint passed. New CI is pending.

Keeping this open: Checkbox/Switch default resolution is not switched yet. Label association/screen-reader-only content and maintained-device accessibility validation remain incomplete; protocol-level tests are not VoiceOver/TalkBack certification. No Craft code was changed.

8 days ago

Pushed 629c6d7 and 839bc42 (unreleased after v0.2.605): the same shared Checkbox/Switch templates now give their physical controls reactive accessible labels, keep visible labels synchronized through late values/removal/remount, and avoid rendering the Switch screen-reader-only duplicate as visible native text. The served Switch button has its name before hydration, including the no-label fallback.

Regression tests failed on the preceding templates and pass on browser/native in normal and minified bundles, including teardown. Updated the two template snapshots and replaced sr-only implementation assertions with physical-button name assertions. Combined native/component/build-guard suite: 2,373 passed, zero failed; typecheck and lint passed. Prior pushed batch CI 39412289382 passed all four jobs; this batch has a new CI run pending.

Still open: physical-control attribute targeting and legacy API compatibility must be proven before Checkbox/Switch default resolution changes. Generic label association and device accessibility certification are not claimed.

8 days ago

Pushed 4540517 and 9b15650 (unreleased after v0.2.605): shared Checkbox/Switch now explicitly designate their physical native control as the target for unconsumed caller attributes. Static and reactive testID/accessibility props/classes/styles no longer land on layout wrappers or the hidden Switch form field. Consumed API props and component event ownership stay unchanged; browser fallthrough is unchanged.

Target selection respects ownership, including stateless child components without signal scope wrappers. Tests cover scoped/stateless nesting, quoted marker-like text, caller updates/removal, restored labels and component styling, checked-state events, remount and teardown in normal/minified bundles. Combined native/component/build-guard suite passed 2,790 tests; strengthened control targeting regressions also pass. Lint and typecheck passed. Prior batch CI 39413957600 is fully green; this push starts new CI.

Default resolution remains unchanged. Native value/value-change and shared checked/change/form-value contracts still need an explicit migration policy; no compatibility-breaking resolution change is included.

8 days ago

Additional prerequisite fixed in 137fa73 (unreleased after v0.2.605): explicit shared TextInput now forwards native host attributes to the physical input, not its wrapper. The regression first failed with testID selecting View instead of TextInput, then passed with the explicit host-root marker. test/native/shared-control-attributes.test.ts covers caller style/class/accessibilityLabel, typing, clear-button isolation, hide/remount, override removal and teardown, normal/minified. Broad suite: 2,800 pass / 0 fail; lint/typecheck pass. Default TextInput/Checkbox/Switch resolution is intentionally unchanged pending compatibility policy; this is not a migration-completion claim.

8 days ago

The chosen compatibility policy is now implemented on main in 1a89af0 (unreleased after v0.2.605). Checkbox/Switch calls using checked or @change select shared app/plugin implementations; value-only, @value-change, and unmarked calls retain native primitives. A legacy value-change handler wins mixed usage. Explicit imports and screen-owned overrides still win, and shared Checkbox form value remains separate from checked state. test/native/checked-control-resolution.test.ts covers source dependencies, bound/braced checked, event modifiers, Switch v-model:checked, mixed legacy/shared instances, remount, and browser discovery/interaction. Focused suite: 32 pass / 0 fail; broad native/component/build suite: 2,837 pass / 0 fail; lint/typecheck passed. No Craft code changed. Leaving the issue open for the remaining acceptance audit rather than treating this batch as all-platform certification.

8 days ago

Completed an evidence-based resolution audit in e7e3d8d (unreleased after v0.2.634), without changing default precedence or legacy native APIs.

Inventory: six shared/native filename overlaps: Button, Checkbox, Switch, TextInput, Image, Video. New remaining-component-resolution.test.ts enumerates both directories, verifies actual selected dependency paths and explicit/screen-local overrides, and exercises legacy field/media updates, events, remount and teardown in normal/minified native bundles. Existing Button and usage-based Checkbox/Switch suites remain green.

Three concrete gates before further migration, reproduced by explicitly compiling and executing the shared files:

  • TextInput: reactive value updates, but reactive disabled=true never reaches the physical native input; the shared template still treats disabled server-side.
  • Image: changing :src leaves the physical Image with src="" and no source; objectFit/objectPosition are unsupported style diagnostics. Also, browser Image currently resolves through the StxImage builtin alias ahead of the shared plugin file: this is a third path, not two identical implementations.
  • Video: native execution throws ReferenceError: customElements is not defined from browser player registration.

Full findings, reproducible probe shapes and acceptance boundaries: docs/proposals/native-component-resolution-audit.md. Recommended next compatibility fix is shared TextInput disabled/event handling, then Image source/fit and builtin precedence, then Video host separation. Do not globally move primitives behind these files yet.

Combined validation: 2,516 passed / 0 failed across 189 native/component test files; typecheck, lint and formatting passed. No Craft edits and no device/accessibility certification. The audit is complete for the current six overlaps; the broader migration issue remains open because the gates above are real.

8 days ago

The shared TextInput reactive-disabled gap is fixed in 6d1fa5e (pushed to main). Disabled state, styling, and clear-button visibility now follow the live prop; disabled input/change/clear actions cannot mutate the model. Parent-driven value updates still work, and re-enabling restores editing.

Verification: 2,526 tests passed, zero failures across the native and components suites; typecheck and changed-test lint/format checks passed. New coverage: packages/stx/test/native/shared-text-input-disabled.test.ts (web/native, normal/minified, updates, events, remount, native teardown), plus real-browser FormData and lazy-model checks in packages/components/test/form-controls-browser.test.ts. The four cross-target regressions failed before the fix.

Version status: unreleased after v0.2.634; no new npm release was cut. Remote CI is pending. Native primitive resolution and legacy event payloads are unchanged; Craft was not modified. Keeping #2091 open for the remaining migration/API and device-validation gates.

8 days ago

The shared Image source-binding gap is fixed on main in 67e148c and 3b9a5bf.

  • Native Image src now maps to source on initial compilation, live writes, reads and removal. Existing native source strings/objects and default primitive resolution remain unchanged.
  • The actual shared Image binds src on its inner img. URL replacement, explicit null/empty clearing, remount and teardown are covered on web/native, in normal/minified builds.
  • Initial optimized URLs and responsive candidates survive hydration. Old candidates and placeholder backgrounds stop being used when src changes and restore when the original URL returns. Catalog tests and a real-browser currentSrc/decode test verify this; static SSR markup remains intact.

Verification: 2,536 passed, zero failed across native/components (195 files); typecheck and changed-file lint/format checks passed. Maintained tests: image-source.test.ts, shared-image-source.test.ts, image-delivery.test.ts and image-source-browser.test.ts. Remote CI is pending for 3b9a5bf.

Version: unreleased after v0.2.634; no npm release cut. Craft was not modified. Keeping #2091 open: Image fit/position support, builtin/shared resolution policy, Video and remaining device/API gates are not completed by this fix.

8 days ago

Shared Video crash fixed on main in 4585829 and c5db290.

Reproduced ReferenceError: customElements is not defined with the actual shared Video.stx in both normal and minified native bundles. Registration is now browser-only. Native compilation emits one Video leaf, omitting browser fallback players/controls; src updates and removal map to native source. Native playback callbacks are relayed to the caller, and fractional aspect ratios become numeric host styles.

Evidence: packages/stx/test/native/shared-video.test.ts covers source replacement/clearing, paused state, play/pause/end/progress/error delivery, identity, conditional remount, stale events and teardown. packages/stx/test/native/video-source.test.ts protects media translation and legacy typed source values. packages/components/test/video-browser.test.ts uses a real browser to verify custom-element registration, single event delivery and pause-on-clear. Existing browser media and primitive-resolution tests also pass.

Validation: 2,541 tests passed / 0 failed across 198 component/native test files; typecheck, lint and formatting passed. This is compiler/runtime protocol and browser evidence, not device playback certification. Craft was not modified.

Version: source is still 0.2.634; these commits are UNRELEASED and must not be treated as available in the published 0.2.634 package. Default native Video resolution remains the primitive. Native YouTube/Vimeo embeds, source-list selection, tracks and DRM are not implemented by this adapter. Keeping #2091 open for the remaining migration gates.

8 days ago

Shared Image fit support landed on main in cece0c8.

  • Both objectFit and its fit alias now update the physical image reactively on web/native. Nonempty objectFit takes precedence; clearing it reveals fit; clearing both restores cover. SSR styling and unrelated image/source styles remain intact.
  • Native inline style parsing maps cover/contain/fill/none to resizeMode cover/contain/stretch/center. String styles, object styles, CSS variables and layer/declaration precedence use the same translation.
  • Native scale-down and invalid values produce diagnostics instead of an invented approximation. Unsupported bindings are omitted, so an authored fallback may remain. Browser scale-down is preserved.

Regression evidence: image-fit.test.ts and shared-image-fit.test.ts were red before the change. They cover static translation, supported/unsupported values, live updates, alias precedence, clearing, identity, remount and teardown in normal/minified native bundles, plus computed styles in a real browser. Full native/components run: 2,659 passed / 0 failed across 221 files. Typecheck, changed-file lint and formatting passed.

Version: UNRELEASED after v0.2.661; no patch release cut. Native primitive selection and legacy resizeMode usage are unchanged. Craft was not modified. This is compiler/protocol and browser evidence, not device pixel certification: the inspected Craft Android contain mode uses CENTER_INSIDE (no upscaling of small images), while iOS uses aspect-fit. objectPosition, portable scale-down, host visual validation and builtin/shared precedence remain separate gates, so #2091 stays open.

8 days ago

Shared TextInput event compatibility landed on main in ad01009.

Native onEndEditing now commits shared lazy models from the final host text and emits event-shaped change callbacks. Shared input callbacks keep receiving model values (including trim/number modifiers), not raw draft events. Repeated end-editing callbacks and non-lazy trimmed edits do not double-emit. Existing synthetic onValueChange calls remain supported.

Legacy native primitive input/end-editing/submit-editing payloads and default component resolution are unchanged. Craft was not modified.

Evidence: shared-text-input-events.test.ts executes the real shared component and legacy primitive in normal/minified native bundles, covering lazy/non-lazy edits, modifiers, clearing, disabled commits, remount, stale events and teardown. shared-text-input-events-browser.test.ts checks the browser contract in a real WebView. Final component/native suite: 2,664 passed, 0 failed across 223 files. Typecheck and formatting passed; lint has no errors (existing warnings remain). This is compiler/protocol and browser evidence, not device certification.

Version: UNRELEASED after v0.2.661; no patch release cut. Keeping #2091 open for the remaining shared-resolution migration gates.

8 days ago

Shared TextInput reactive readonly handling landed on main in a2d9e13.

The shared physical input now follows readonly changes on web/native, including unlocking an initially readonly field. Browser styles and clear-button visibility follow the same state. Shared input/change/end-editing/clear handlers reject locked edits and restore the model value after stray native or synthetic callbacks. Parent-driven value updates remain allowed. Readonly remains distinct from disabled: readonly browser fields still submit their value, and unlocking readonly does not bypass disabled state.

Regression evidence: shared-text-input-readonly.test.ts failed on the prior main in normal/minified native bundles and a real browser. It now covers live toggles, literal/initial readonly, lazy commits, clear actions, parent writes, disabled interaction, form submission, remount and native teardown. Final component/native suite: 2,667 passed / 0 failed across 224 files. Typecheck, changed-test lint and formatting passed.

Version: UNRELEASED after v0.2.661; no release cut. Uses the existing readonly-to-editable host mapping. Legacy native primitive selection and event payloads are unchanged, and Craft was not modified. This is compiler/protocol and browser evidence, not device certification. Keeping #2091 open for the remaining migration gates.

8 days ago

Shared TextInput focus/blur and native submit forwarding landed on main in 9dfa829.

Shared focus/blur handlers now receive the physical input event on web/native. Browser FocusEvent identity, target and relatedTarget are preserved; native payloads are forwarded intact. Existing focusin/focusout paths still deliver once. Native onSubmitEditing relays submit-editing and submit once each without committing a lazy model; end-editing retains that responsibility. Browser form submission is unchanged: the input does not synthesize submit events from Enter. Removed inputs cannot forward stale lifecycle events.

Regression evidence: shared-text-input-lifecycle-events.test.ts reproduces the missing shared native focus callback and verifies focus/blur/submit, alias delivery, lazy timing, readonly focus, disabled events, remount and teardown in normal/minified bundles. It separately verifies legacy primitive payloads. shared-text-input-focus-browser.test.ts reproduces missing browser focus, then verifies actual browser focus/blur, relatedTarget, bubbling focusin/out, normal form submission and remount. The browser waits for window focus before asserting input-event counts.

Validation: 2,672 passed / 0 failed across 226 component/native files; typecheck, changed-test lint and formatting passed. Version: UNRELEASED after v0.2.661; no patch release cut. Craft and default native primitive selection are untouched. This is compiler/protocol and browser evidence, not device keyboard certification. Keeping #2091 open for the remaining migration gates.

8 days ago

TextInput resolution batch landed in c2ad7a4 (UNRELEASED after v0.2.661).

Native v-model:value (including modifiers) and explicit update:value usage now select conventional/plugin shared TextInput when no input listener is present. Calls with input listeners remain primitive because existing handlers may require the raw event object; no handler-source heuristics are used. Plain value calls remain primitive too. Explicit imports and screen-local overrides still win, and absent shared libraries fall back to the primitive.

text-input-resolution.test.ts proves actual web/native dependency paths and executes shared model and legacy object-payload fields together, in normal/minified bundles, including lazy/trim, readonly, submit, remount and teardown. All 3,104 component/native/rendering tests passed across 285 files; typecheck, changed-file lint and formatting passed. Craft is untouched.

This is progress toward #2091, not completion: Image/Video resolution and maintained-host validation remain. A separate device-validation fixture is now being exercised against the maintained Craft iOS harness without editing Craft.

8 days ago

Video migration batch is on main in 78619f9 (reactive playback flags) and 1506dc0 (usage-based shared resolution).

  • Native src calls select the app/plugin shared Video when installed, provided their listeners use its shared media events (play, pause, ended, timeupdate, loadedmetadata, loadstart, error).
  • Legacy source (including object payloads), mixed src/source, bare calls and other/native-event listeners retain primitive resolution. Empty or initially undefined bindings do not change the choice. Explicit imports and screen-local overrides remain first; missing shared libraries still fall back to the primitive.
  • The prerequisite fix makes controls/autoplay/muted/loop reactive on the physical shared player. Browser fallback controls/mute/loop follow their props too.

Evidence: test/native/video-resolution.test.ts has 22 passing cases, checks actual shared dependency paths on web/native, and executes shared URL, legacy object-source and legacy-event players together in normal/minified bundles. It covers default/bare/"false" flags, updates, event payloads/counts, retained identity, clearing, conditional hide/reopen, stale events and disposal. test/native/shared-video.test.ts and packages/components/test/video-browser.test.ts exercise the playback flags through explicit shared imports and a real browser.

Validation: 3,202 passed / 0 failed across 300 native/component files. Typecheck, changed-file lint/formatting and release preflight passed, including installed-package smoke, export validation, performance budgets and 319 targeted tests.

Version: UNRELEASED after v0.2.674; no release cut. Image resolution is intentionally unchanged: builtin/shared precedence, object-position/scale-down and host validation remain gates. This is compiler/protocol and browser evidence, not native device playback certification; provider embeds, source-list/track selection and DRM support are not added. Craft is untouched. Keeping #2091 open for the remaining migration/device requirements.

4 days ago

Shared Textarea multiline semantics landed on main in 783f0b3 (five files, including all new regression fixtures/tests).

The compiler now defaults an HTML textarea translated to TextInput to multiline=true. It does not change plain inputs/native TextInput defaults or override an explicit native multiline choice. This keeps the shared component multiline and prevents its keyboard return from acquiring the implicit single-line form-submit listener.

Real Chrome coverage through the browser peer host exposed a second STX-owned issue: physical input/change/Enter events were also forwarded by ancestor records, producing duplicate input callbacks and an accidental form submit. The peer renderer now forwards input/change from the originating control only and routes Enter submission only from a single-line TextInput itself. Authored legacy submit callbacks still own Enter; ordinary single-line inputs retain exactly one implicit form submission.

Tests cover leading/trailing/repeated newlines, parent writes, lazy/trim behavior, retained identity, conditional reinsertion, fresh screen remounts, stale/disposed callbacks and legacy TextInput payloads. Both normal/minified builds are exercised on iOS/Android protocol test hosts and real Chrome. Before the compiler fix, all five native regression cases failed. After the fixes, the full native suite passes 1,133 tests with zero failures. Typecheck, lint, formatting and build/archive release preflight also pass (324 preflight tests).

Version status: unreleased source after v0.2.743; no patch release cut here. No physical-device result is newly claimed. Craft and the unrelated Map test edit were untouched. Keeping the broad convergence issue open for its remaining acceptance work.

4 days ago

Release follow-up: the shared Textarea multiline and browser peer-event fixes in 783f0b3d3b are now published in STX / @stacksjs/components v0.2.744 (release commit 4e7fdeaeef62c6ccfadd054bc72ad3ef0752d27e).

Verified the public npm archives against their integrity checksums, then installed @stacksjs/stx, bun-plugin-stx, and @stacksjs/components at 0.2.744 in a fresh consumer project. Both consumer tests passed (16 assertions), including multiline input/newline preservation, retained field identity, conditional reinsertion, unchanged single-line legacy TextInput behavior, and no mutation errors. The same multiline assertion fails against published 0.2.743, confirming the version floor.

All five release-commit CI jobs passed: https://github.com/stacksjs/stx/actions/runs/40876235685 . The release source gate passed all 17,403 tests (48 existing skips, zero failures): https://github.com/stacksjs/stx/actions/runs/40876235604 . Remaining package publication is still running at the time of this comment.

Keeping the broader #2091 acceptance work open. Craft and the unrelated Map test edit remain untouched; no new physical-device result is claimed.

4 days ago

Current audit and acceptance updates are on main (7b3749e82e, d321e4fd8d, integrated tip 77d8cd6702). Version: unreleased source after v0.2.746; no extra release was cut.

The actual shared Button/TextInput/Card fixture now passes a real browser, normal/minified iOS/Android protocol execution, and maintained iOS control checks in light/dark appearance (2/2 each, zero failures/skips). It covers typed input/value and keyboard retention across button updates, real density-driven layout, appearance variables and retained identities. The shared-overlay gate also passes 4/4 per iOS appearance after the STX DialogPanel flow-wrapper fix. #2089/#2090 are being closed against those acceptance results.

Remaining migration gate: Image. Preserve existing native source/resizeMode callers and the browser builtin until there is a compatible shared selection and faithful object-position/scale-down support. Read-only inspection of current Craft confirms the missing host input contract; filed /craft-native/craft/issues/427 for the assigned Craft agent. STX currently diagnoses those values rather than approximating them. No Craft code was edited.

Keeping #2091 OPEN for that host contract, its STX integration/resolution decision, and the remaining broader device acceptance. The audit no longer incorrectly calls TextInput/Video unmigrated: their tested compatibility selections and explicit/screen-local overrides remain documented. Full combined-tip CI is queued: https://github.com/stacksjs/stx/actions/runs/40915422118 .

6 days ago

Shared Image rounding is reactive in 600c130 (pushed to main; unreleased after v0.2.719).

Both the physical image and picture frame update without replacement. Existing boolean/$bool behavior is preserved: enabled uses 0.5rem, false-like strings/null disable it, an empty attribute enables it, and undefined restores the authored default. This does not add a radius-token API.

The regression tests caught an SSR ownership trap: removing a bound style normally restores its original inline value. Component setup now transfers its own SSR rounding to the binding using existing host APIs, so an initially rounded image can become unrounded. Browser application CSS can take over when the inline radius clears. No shared style-runtime behavior changed.

Coverage:

  • packages/components/test/image-rounded-browser.test.ts: actual computed/inline styles, CSS fallback, both defaults, typed clears, hidden writes/remounts and retained image/frame identity.
  • packages/stx/test/native/shared-image-rounded.test.ts: both node radii, defaults, source/labels, identity, hidden writes/remounts and teardown in normal/minified bundles. Protocol evidence, not device clipping certification.

All six new cases failed before the fix and pass after it. The broader suite passed 1,826 tests with zero failures; six repeated Chrome runs, six post-build focused tests, full baseline build, updated component build, typecheck, lint and formatting also passed.

CI is running: https://github.com/stacksjs/stx/actions/runs/40501037609

Craft and resolution remain untouched. Keeping #2091 open for remaining convergence work.

6 days ago

Shared Image placeholder/placeholderColor reactivity is implemented in 77b3ad1, pushed to main and unreleased after v0.2.720.

Existing mode semantics are preserved: none/empty disables the component placeholder, null/empty mode uses automatic delivery behavior, and undefined restores the authored mode. Empty/null color falls back to the prepared palette or #e5e7eb; undefined restores the authored color. Prepared previews, color/blur/thumbhash modes, custom URLs/gradients and transparency suppression are covered.

The background remains on the picture, never the physical image. Component setup transfers its own SSR background to the binding so clearing reveals application CSS instead of restoring stale SSR paint. The existing original-source/candidate guard is retained: replacement drops the placeholder and restoring the original uses the latest props. No replacement metadata is fetched client-side.

Maintained tests:

  • packages/components/test/image-placeholder-browser.test.ts: actual computed backgrounds, prepared/missing metadata, defaults, transparency, source/candidate guards, hidden writes/remounts and retained frame/image identity.
  • packages/stx/test/native/shared-image-placeholder.test.ts: normal/minified source, labels, rounding, identity, remount and teardown compatibility. Browser background shorthand is omitted from the new native binding; native placeholder painting is not implemented or certified here.
  • The existing media-parity SSR guard now inspects the rendered picture tag instead of matching background keys inside client JavaScript.

Verification: 1,835 broader tests passed, zero failures; 12 repeated Chrome runs and 16 post-build focused tests passed. Component build, typecheck, lint and formatting passed.

CI is running: https://github.com/stacksjs/stx/actions/runs/40513781273

Craft and resolution remain unchanged. #2091 stays open for remaining convergence work.

6 days ago

Shared Image zoomOnHover now performs actual reactive hover zoom in 0ddb800, pushed to main and unreleased after v0.2.720.

A reactive marker enables CSS 1.05 scale inside the clipped picture frame, with a 0.3-second transition. Individual CSS scale preserves authored transforms. False/null disables the feature, undefined restores the authored default, and false-like strings retain $bool semantics. Prop changes while hovered, source replacement, hidden writes and remounts preserve image/frame identity.

CSS gates both scale and transition on hover capability and prefers-reduced-motion: no-preference. Live motion/touch preference changes work without pointer/media listeners. The old unconditional SSR transition hook is removed. Native markup omits the marker, and native style projection skips these browser media rules; no native zoom or Craft change is introduced.

Maintained coverage:

  • packages/components/test/image-zoom-browser.test.ts: actual Chrome pointer input plus motion/touch emulation, actual computed scale and enlarged bounds, clipping, defaults, authored transforms, initial reduced motion, live preference changes, source changes and retained-node remounts.
  • packages/stx/test/native/shared-image-zoom.test.ts: normal/minified source, labels, rounding, identity, remount and teardown, with no browser scale/transition fields.
  • Public Image README now describes actual behavior rather than only a transition hook.

Verification: 1,840 broader tests passed with zero failures; six repeated Chrome cases and five post-build focused tests passed. Component build, typecheck, lint and formatting passed. The long real-input lifecycle scenario has a 60-second test deadline to accommodate cold Chrome startup; assertions are unchanged. The CDP test API was verified in CI-pinned Bun 1.4.2.

CI is queued: https://github.com/stacksjs/stx/actions/runs/40517332366

Craft and component resolution remain untouched. #2091 stays open for remaining convergence work.

6 days ago

Landed shared Image named rounding tokens in 7354cf6 (unreleased after v0.2.720).

full, sm, md, lg, xl and 2xl now survive SSR and reactive parsing instead of all becoming 8px. A single radius table supplies SSR and client bindings. Boolean callers, false-like strings, null clears, the legacy truthy fallback and undefined restoring the authored default remain supported.

Maintained regressions: packages/components/test/image-rounded-browser.test.ts and packages/stx/test/native/shared-image-rounded.test.ts cover every token/default, direct browser attributes, hidden updates, remounts, source replacement, retained frame/image identity and native teardown in normal/minified bundles. Native evidence is numeric protocol behavior, not device clipping certification. Craft and resolution are untouched.

Verification: 1,859 broader tests, 26 repeated browser tests and 29 post-build focused tests passed; component build, typecheck and targeted lint/format checks passed. Also corrected the prior hover-test CI setup: run its three existing Chrome cases in an isolated Bun process with explicit desktop pointer flags, retaining touch and reduced-motion assertions. Verified with an existing parent browser forced to hover:none. New main CI is pending; no release cut. This remains incremental progress, not closure of #2091.

6 days ago

Landed shared Video boolean normalization in 4466e09 (unreleased after v0.2.720).

controls, autoplay, loop and muted now retain server $bool semantics through parsed attributes, hydration, typed parent writes and default replay. False-like strings disable, empty strings enable, null uses intrinsic defaults (controls true; others false), and undefined restores the authored default. The no-JavaScript fallback now includes static muted. Player instances, legacy resolution/callers and Craft are unchanged.

Regression files: packages/components/test/video-booleans-browser.test.ts and packages/stx/test/native/shared-video-booleans.test.ts. Covers omitted/enabled/disabled/false-like/mixed defaults; independent flags; direct attributes; hidden updates; retained element, player and fallback identity; source replacement; live browser muted state; single forwarding of a dispatched event; native stale handlers and teardown in normal/minified bundles. Verified 1,844 related tests, 10 repeated browser cases and 19 post-build focused tests; build, typecheck and targeted lint/format checks pass.

Important boundary: ts-video-player@0.1.9 does not reconfigure existing-provider autoplay/loop policy from changed attributes and lacks public controls/autoplay/loop setters. Normalized attributes are not evidence of actual live provider policy changes. That remains a separate dependency task, documented in the audit; no private-field mutation or player replacement was introduced. Native results are protocol evidence, not device playback certification.

Also corrected the remaining hover-test CI assumption exposed by the previous main run: disabling touch can restore a physical no-mouse capability on headless Linux. All desktop default/remount/source assertions now run before touch; restoration checks scale/transition against actual restored hover capability. Six isolated Chrome cases passed locally, with no production zoom change. New main CI is pending; no release cut. #2091 remains open for its broader gates.

6 days ago

Live shared Video controls/autoplay/loop support is now on main in 79dd105. The upstream player fixes (d3063fe and 20ad960 in stacksjs/ts-video-player) are published on npm as ts-video-player@0.1.11; the shared package now requires ^0.1.11 and the registry lock pins that version. No new STX release was cut: this integration is after STX v0.2.723.

The existing normalized attributes now update public player policy setters rather than private fields or a replacement player/provider. Tests verify actual computed controls visibility/inertness, real local media autoplay/ended/loop behavior, source changes, authored-default restoration, and retained element/player/provider/media identity through conditional remounts. Legacy native resolution is unchanged; Craft is untouched.

Verification on latest main: 8 browser tests passed across video-booleans-browser.test.ts, video-playback-policy-browser.test.ts and video-browser.test.ts; 41 native/legacy tests passed across video-player.test.ts, video-source.test.ts, shared-video-booleans.test.ts, shared-video.test.ts and video-resolution.test.ts. Typecheck, lint and format checks passed. Push CI is running separately. Browser autoplay restrictions still apply; native protocol tests are not device playback certification.

Leaving this broader component-convergence issue open.

6 days ago

Release update: the live shared Video controls/autoplay/loop integration is now published in @stacksjs/stx@0.2.724 and @stacksjs/components@0.2.724. Release tag v0.2.724 points at 71de8c5 and includes fix 79dd105. Components now requires ts-video-player ^0.1.11.

Verified a clean registry-only consumer installation: STX, components, router, sanitizer, desktop and bun-plugin-stx all resolved to 0.2.724; the player resolved to 0.1.11. The installed Video source rendered successfully and its browser bundle included the live-policy implementation. The tagged-source gate passed 17,305 tests with zero failures (45 skipped), and all five main CI jobs passed. Craft remains untouched. The release workflow is still finishing peripheral package publication; this broader convergence issue remains open.

5 days ago

Shared Textarea follow-up landed on main in 47ed5a3. Version: unreleased after STX/components 0.2.736; no release cut in this batch.

Disabled/readonly now reach the physical field reactively on web/native, including initially true bindings, clearing bindings, boolean-string spellings (false/0/off/no), hidden updates and remounts. Blocked input/change callbacks restore the current model value; parent writes still work. Presentation follows the flags, readonly fields remain in browser FormData, and disabled fields are omitted. Toggling flags retains field identity. Existing string-valued input callbacks, event-shaped change callbacks and lazy/trim models are preserved; input is reserved before the first edit so blocked/lazy DOM events cannot leak as payloads.

Maintained regression: packages/stx/test/native/shared-textarea-flags.test.ts, actual shared source in normal/minified native bundles and real Chrome. Original-source native/browser checks fail; deliberately removing edit guards also fails browser assertions. Final focused run: 4 passed / 226 assertions. Broad native/components run: 2,927 passed across 274 files, zero failures. Release preflight passed (build, exports, installed-package smoke, budgets, 324 focused tests); typecheck, lint and formatting passed. Remote push CI is separate and still pending.

No component-resolution/legacy primitive policy changes, no Craft edits and no device visual certification. The broader #2091 remains open.

5 days ago

CI follow-up for the Textarea batch: its original test-job failure was the Image fixture's first Chrome navigation, not a Textarea assertion. Reproduced on a fresh Linux runner with an empty browser URL, loading=true and zero HTTP requests at the 15-second deadline: https://github.com/stacksjs/stx/actions/runs/40697515663/job/122848141369 .

Landed 711c879 on main. Browser cold initialization is now an explicit once-per-test-process CI/release prerequisite: an owned renderer must render a real readiness document within a bounded 25-second startup budget. Failures remain fatal and resources are closed. The Image test and its existing timeout helper are byte-identical; its 15-second navigation and 30-second case limits are unchanged. This fixes setup/timing attribution, not Chrome's internal startup performance.

Validation of this exact commit passed all five CI jobs: https://github.com/stacksjs/stx/actions/runs/40698871998 . Results: 17,351 source tests passed (46 existing skips), 1,813 coverage tests passed, 45 integration tests passed, zero failures. Four fresh Linux runners also passed all 16 unchanged Image cases. Local guards: 20 passed; release preflight: 324 passed. A controlled 16-second startup passed readiness and all Image assertions; a deliberately stalled page after readiness still failed at 15 seconds.

The automatic main-push rerun is separate and in progress: https://github.com/stacksjs/stx/actions/runs/40701623966 . No release was cut; source is after 0.2.736. Craft and the user's Map edit were untouched. The broader component-convergence issue remains open.

Sign in to comment on this issue.