ReviewOS

stacks/stx

feat(native): adapt shared overlays, tabs, and tooltips to host capabilities

#2089
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Problem

Shared interactive components still rely on browser-specific focus, selectors, measurements, and overlay behavior. Successfully translating their markup does not establish native interaction parity.

Audited at v0.2.546, commit 0013ce66e3.

Source evidence

  • Dialog uses closest(), refs, and the browser overlay stack for dismissal/focus behavior.
  • TabList uses value selectors, closest(), focus(), and MutationObserver.
  • Tooltip uses getBoundingClientRect(), offsetParent, viewport dimensions, and hover events.
  • Native binding nodes expose only a limited DOM-shaped interface; their selector implementation does not support the TabList selector contract.
  • Native compilation of shared Tooltip reports unread mouseenter/mouseleave events and positioning/style diagnostics.
  • Shared Dialog currently fails earlier at MutationObserver during setup (tracked separately).

Requested change

Introduce reusable host-backed operations for focus, measurement, overlay presentation/dismissal, and component coordination. Adapt the shared components rather than creating independently maintained native copies. Define appropriate touch interactions for tooltip/popover behavior.

Coordinate actual platform operations with Craft where required; STX should own shared component behavior and the host contract.

Acceptance

  • Dialog/Drawer open, close, and nested-dismissal behavior work on both targets.
  • Focus handling is appropriate to each platform.
  • Tabs select panels and maintain state when items change.
  • Tooltip/popover positioning and touch behavior have an explicit supported contract.
  • Unsupported host capabilities produce actionable feedback.
  • Same-source interaction tests plus maintained Craft-host validation cover the implemented behavior.

Priority and scope

Fourth in the portability audit sequence, after portable props/events. This is a source-supported gap assessment, not a claim that every listed interaction was reproduced failing on a device. Split into focused implementation batches as needed.

9 days ago

Progress pushed to main in four commits: 57c0bea (structural screen roots and consumed props), fa218f5 (native overlay presenter), f096e63 (shared Drawer refs), and fdf076f (shared Dialog + same-source tests).

Local verification: 3,642 tests passed across native, signals, and components; final rollback/runtime checks passed 34/34; typecheck and targeted Pickier lint passed. Regression coverage includes nested top-layer dismissal, independent dismissal policies, reopening, removal while open, and failed updates preserving the working handler. This is post-v0.2.583 source work, not a claim of a newly published release. New main CI is pending.

Keeping this issue OPEN. Native focus/restoration and anchored popup measurement require the host contract filed as /craft-native/craft/issues/373; a separate agent owns Craft implementation. Full Tabs adaptation, tooltip/touch behavior, and maintained-host visual/accessibility validation are not yet complete. No Craft code was modified by this batch.

9 days ago

Tabs progress pushed in 076a4fd: the shared component now uses host tree operations for panel discovery/visibility, retains selection and node identity when panels change, isolates nested tabs, and preserves edited panel content. Legacy prop-mode instances have unique tab/panel IDs; browser keyboard focus stays within the correct instance. Same-source native development/minified tests and browser reorder/removal tests pass, included in the latest 3,654-test green local run. CI is queued at https://github.com/stacksjs/stx/actions/runs/39280602423. Still post-v0.2.583 and unreleased. Keeping #2089 open for the Craft #373 capability integration, tooltip/popover touch and positioning, and maintained-host validation.

9 days ago

Progress is now on main: ca40f9c connects mounted-view focus/measurement to Craft, bfd3075 focuses newly opened shared modal layers and cancels queued stale focus, and 89cb527 shares asynchronous Tooltip measurement/placement with stale-result guards. Each commit contains five files. No Craft source was modified.

Verification: 592 native + browser-overlay tests passed; 1,766 component + overlay tests passed after updating the intentional Tooltip source snapshot; typecheck and targeted lint/format checks passed. New regression coverage includes explicit/disabled focus targets, close-during-queue, native measurement units/parent origins, out-of-order placement replies, hide/disable/teardown, and unchanged browser placement. Bridge mocks and DOM tests are not maintained-device validation.

Unreleased source after STX v0.2.583. Craft #373 is implemented on its main but its released version floor is not verified. Keeping this issue OPEN: native non-modal popup presentation, touch dismissal, repositioning subscriptions, and maintained-host visual/accessibility validation remain incomplete. The asynchronous measurement adapter alone does not make native Tooltip/Popover supported. CI for this new tip has not yet been confirmed.

9 days ago

A further native presentation dependency is now isolated: /craft-native/craft/issues/382. Craft insertChild rejects attached nodes (NODE_ATTACHED), and moveChild only reorders within the same parent (NOT_A_CHILD across parents). An STX-only portal prototype passed our permissive JS mock but would violate that contract, so it was withdrawn before commit; no Craft code was changed. The prototype is preserved locally for integration once the host contract is available.

The existing Craft #373 focus/measurement work remains useful, but it does not supply identity-preserving non-modal presentation. I am tightening the maintained STX native test host to reject these invalid topology batches atomically and fail settle() even if the runtime recovers with a redraw. #2089 remains open; shared Tooltip/Popover support is not claimed.

9 days ago

STX adapter progress pushed in ba231a9 and fa8a407 (unreleased after v0.2.605). Craft now advertises viewNonModalPresentation (42e58f2); STX gates non-modal Modal-root presentation on that exact capability, exposes the flag, preserves logical ownership, routes only top-layer dismissal, enables outside-tap consumption only with onBackdrop, and never applies modal autofocus to a popup. Older hosts reject the stack change; viewReparent alone is not sufficient. Regression coverage: test/native/shared-overlays.test.ts and overlay-presenter.test.ts, including normal/minified bundles, nested dismissal, denial, reopen and queued-focus cancellation. Broad suite: 2,804 pass / 0 fail; final native/build suite: 640 pass / 0 fail; lint/typecheck pass. Still OPEN: actual shared Tooltip/Popover wiring, touch triggers, repositioning subscriptions and maintained-host validation. No Craft code changed.

9 days ago

Shared Tooltip/Popover wiring is now on main in three commits: 06e10be (native helper-module registry and viewport placement), 7f17ad6 (Tooltip native presentation), and e4bea8c (Popover native presentation and same-source regressions). Version: unreleased source after v0.2.612; these changes are NOT included in the 0.2.612 tag.

Both shared components now use existing non-modal Modal presentation when the host advertises viewMeasure and viewNonModalPresentation. Tooltip supports native press toggling, including bubbling from a slotted interactive control; PopoverButton drives its own physical PopoverPanel. Nested dismissal affects only the top layer. Panel/child identity survives reopen, disabled controls reject interaction, and older hosts receive actionable warnings without partial stack entries. Native panels are not moved across parents. Browser Tooltip portals/hover/focus and Popover click/outside-dismiss behavior remain covered.

The native compiler also now embeds the imported helper-module registry before component setup, rather than depending on external browser script loading. Regression coverage verifies shared module state and dependency tracking.

Placement uses viewport coordinates rather than the retained logical parent origin. With no host layout-change subscription available, it samples serially every 250 ms while open and only writes changed coordinates. Close, Tooltip disable, and removal cancel tracking and discard late replies. Native arrows are hidden. This is explicitly NOT frame-synchronous scroll tracking.

Maintained tests: test/native/shared-popups.test.ts, component-module-registry.test.ts, components/test/tooltip-measurement-races.test.ts and disclosure-expanded-state.test.ts. Final local run: 2,475 passed, 0 failed across native and component suites, using --max-concurrency=1 --timeout 30000 because of heavy machine load. Full lint (0 errors), formatting and typecheck passed. The initial run exposed a portal test-harness update (fixed) and three default-five-second timeouts; all passed subsequent verification without relaxed assertions. New remote CI is pending.

No Craft code changed. Keeping #2089 OPEN for maintained iOS/Android visual and accessibility validation (including clipping and actual touch routing), released Craft capability floor verification, and a host-driven repositioning subscription. The documentation records the current supported contract and limitations.

9 days ago

More #2089 work is on main:

  • b7d347c83e: shared popup placement now subscribes to anchor/panel/ancestor layout events, coalesces invalidations and discards stale bounds. Serial polling remains for scrolling and transforms that host onLayout does not report. Subscriptions are removed on close. This landed before v0.2.663.
  • 1cbb072a98: actually nested Dialog/Drawer close events no longer run the outer component's close handler. All four Dialog/Drawer combinations are covered on web/native, including repeated dismissal and independent callback counts.
  • 122674a739: Tooltip around the actual shared Button now toggles once. The Button's emitted click previously toggled it before the physical press bubbled, immediately closing it again. Replacing the old regression's bare button with the shared file reproduced the failure before the fix.

Version: the two component fixes are unreleased after STX v0.2.663. The issue is not being closed prematurely.

Verification: 3,123 broad native/component tests passed after the nested-close fix; 61 targeted tests passed after the Tooltip fix; the exact device fixture also passes normal/minified protocol execution. The dedicated real iOS Tooltip rerun passed (one XCTest, zero failures), including one action callback, visible content and outside dismissal. Popover clipping escape, actual action taps, retained state after reopening and shared tab touches passed on the maintained Craft host.

The device work found a separate host blocker: a labelled Modal hides its visible child button from accessibility queries. Filed with exported hierarchy and source evidence as /craft-native/craft/issues/402. The named-dialog test keeps its accessible name and assertion. No Craft source edits were made.

A subsequent dark-appearance run passed the Popover and TabList cases but lost the Tooltip Button action (Help taps: 0) against the same built app. This differs from the fixed deterministic STX double toggle. Filed the inconsistent nested tap delivery for host investigation at /craft-native/craft/issues/403; appearance alone is not established as the cause. The complete device suite remains red, and neither named-dialog accessibility nor Tooltip tap reliability is claimed complete.

The STX-owned fixture/Swift UI tests and read-only SDK runner are committed in a0f988650a; see the device validation guide for replay commands and boundaries. #2089 remains OPEN for the named-modal host fix, reliable nested taps, remaining native accessibility/focus checks and Android host validation. These device checks also provide scoped evidence for #2090/#2091, whose broader remaining requirements are still open. New main CI is queued, not yet confirmed green.

4 days ago

STX-only follow-up landed as 5d96e34: shared Dialog/Drawer sheetDetents arrays are now serialized as JSON inside their native binding expression. The previous interpolation emitted medium,large, which JavaScript evaluated as identifiers; both defaults reached the host as an empty string and triggered unknown-binding diagnostics.

The maintained native-shared-sheets.test.ts now asserts the actual bridge-facing array values for default detents, explicit arrays and empty arrays on iOS/Android test hosts, normal/minified bundles, and repeated open/close/reopen with retained node identity. It also requires no unknown-binding warnings and keeps native-only attributes out of web markup. Before the fix these enhanced tests had 10 failures; after it, 13 pass. The broader overlay/focus/presenter/nested-close/device-fixture protocol group passes all 41 tests. Component visual snapshots: 54 pass. Typecheck, lint, formatting and build/archive release preflight pass (324 preflight tests).

Version status: unreleased source after v0.2.743. No release cut here, and no physical-device behavior is newly claimed. Craft code was untouched. #2089 remains open for the existing Craft/device acceptance blockers.

4 days ago

Release follow-up: the sheet-detent fix in 5d96e34839 is 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. Its compiled/minified native screen receives the default Dialog/Drawer detents as arrays with no unknown-binding warnings. Both consumer tests passed (16 assertions).

All five release-commit CI jobs passed: https://github.com/stacksjs/stx/actions/runs/40876235685 . The release source gate also 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.

This establishes 0.2.744 as the package floor for this fix. Keeping #2089 open for its existing Craft/device acceptance blockers; Craft was untouched and no new physical-device result is claimed.

4 days ago

Completed the shared-overlay acceptance gate on main. The final STX fix is 575fc90bea, with acceptance documentation in d321e4fd8d; integrated tip 77d8cd6702.

Rerunning against clean Craft b49aa1e894198d1b8b5fe7b07a6fa43d2d6d34f6 confirmed Craft #402/#403 are closed but exposed a different failure: the opening callback ran (Dialog taps: 1; open: true) while the shared sheet had no accessible inner action. A raw-Modal control displayed flow content correctly. DialogPanel's absolute-only content wrapper was the difference. It now uses a full-size normal-flow wrapper, retaining bottom/center alignment and existing web markup.

The new structural regressions failed in all four iOS/Android normal/minified cases before the fix; afterward the sheet/device-fixture/nested-close group passes all 27 tests. Most importantly, the unchanged maintained iOS XCTest suite now passes 4/4 in light and 4/4 in dark, zero failures/skips: nested dialog dismissal and Drawer interaction, Popover clipping/actions/retained state, actual tab touches and Tooltip touch/outside dismissal. No test assertion was removed to obtain that result. Shared-control keyboard checks additionally pass 2/2 in each appearance. Craft code was untouched.

Version: unreleased source after v0.2.746; that release does not contain the final DialogPanel fix. Full integrated-tip CI is queued: https://github.com/stacksjs/stx/actions/runs/40915422118 . Closing the implemented shared-overlay contract with maintained iOS evidence. Android protocol tests are not Android device or assistive-technology certification; broader portability acceptance remains recorded in #2091 and the device validation guide.

4 days ago

Release follow-up: the DialogPanel flow-wrapper fix is now published in STX / @stacksjs/components v0.2.747, with bun-plugin-stx at the same version. Release commit: 239d660dd833bf3ecefe89836a0252e16c8568fe; all three are npm latest.

Verified the original public archives by integrity checksum. The components tarball contains the full-width/full-height native flow wrapper. A fresh registry install passes all 7 consumer checks (48 assertions), including opening/dismissing the actual shared Dialog and verifying its panel stays in flow on normal/minified iOS/Android protocol hosts. The same flow-layout assertions fail against published 0.2.745. The maintained iOS light/dark UI results recorded above remain the physical-host acceptance evidence; this publication check adds installed-package verification.

All five release-commit CI jobs are green: https://github.com/stacksjs/stx/actions/runs/40918690528 . Release source gate: 17,437 passed, zero failures, 48 existing skips: https://github.com/stacksjs/stx/actions/runs/40918689961 . Other package publication is still running. 0.2.747 is the published version floor for the final #2089 fix. Craft and the unrelated Map test edit were untouched.

Sign in to comment on this issue.