Skip to content

Version Packages - #17

Merged
amritk merged 1 commit into
mainfrom
changeset-release/main
Jul 28, 2026
Merged

Version Packages#17
amritk merged 1 commit into
mainfrom
changeset-release/main

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

@amritk/[email protected]

Minor Changes

  • d165e3e: Close the four Known gaps: <list> recycling, UI methods, gesture composition, and reduced motion.

    The first three share a shape, and it is why they were the ones left. Everything else the engine can do is an attribute or an event, so the runtime binds a signal to it and the ceiling is the engine's. These are the places where the engine calls you, or where a capability is an action rather than a piece of state — so there is nothing for a signal to bind to and they needed real code here.

    • /recyclerecycle() drives __CreateList's recycling callbacks, so a long collection realises a bounded number of elements instead of all of them. Ten thousand rows, a dozen cells. This is the piece the README called the largest outstanding, and it fits a signals runtime unusually well: every other framework has to rebuild a recycled cell — ReactLynx hydrates the pooled element tree against the new row's virtual output, a diff per reuse per scroll frame — whereas here a cell owns an item signal and its original bindings are still attached, so reusing it for row 5,000 is one signal write that mutates exactly the attributes that changed. cell therefore receives getters, not values: a cell outlives the row it was built for.
    • /elementsquerySelector, querySelectorAll and invoke. scrollTo, boundingClientRect, scrollIntoView and setFocus are invoked rather than set. invoke turns the engine's { code } convention (zero means success) into a promise, once, instead of at every call site.
    • /gesturessetGestureDetector, and the waitFor / simultaneous / continueWith relations. Ordinary events already recognise a gesture; what they cannot express is arbitration — a tap that must lose to a pan already under way. Callback names pass straight through to the engine rather than being enumerated here, on the same reasoning as attributes and events.
    • reducedMotion() / setReducedMotion() on the . entry. RouteStack consults the preference above the transition seam and skips the animation, so a transition never checks and the behaviour covers transitions this package never saw. The runtime cannot read the value itself — there is no reduced-motion field on SystemInfo, and Lynx has no media queries, so no prefers-reduced-motion either — so the host app passes it in the way it passes colour scheme. That is one line at startup rather than a prop threaded through every animated component, which was the whole of what the Lynx rewrite lost.

    The engine boundary grows to match: __CreateList, __UpdateListCallbacks, __QuerySelector, __QuerySelectorAll, __InvokeUIMethod, __SetGestureDetector, __RemoveGestureDetector, __GetTag and __GetAttributeByName. All are optional, so a partial engine still satisfies the type and the runtime degrades with a stated warning rather than silently.

    createFakeEngine implements every one of them, because it is the reference implementation the whole suite runs against. It gains enterListItemAtIndex and leaveListItem — deliberately named after the pair in Lynx's own testing-library — so a recycler is driven through the same sequence a device uses; onInvoke for stubbing a UI method, since a fake that lays nothing out cannot honestly answer boundingClientRect; and dispatchGesture, which goes through the real runWorklet indirection. Its selector matcher covers tag, id, class, compounds and the descendant combinator and throws on anything else, because "nothing matched" and "not implemented" are indistinguishable in exactly the way that lets a test assert the opposite of the device. __FlushElementTree now records its arguments, so the { triggerLayout, operationID, elementID, listID } a list cell is committed with is asserted rather than assumed.

    A third thing to prototype on a device. /recycle's protocol is mirrored from ReactLynx's implementation and exercised off-device through the fake, but what a fake cannot tell you is whether the engine agrees about layout. It now sits alongside the worklet event tokens in the README's Before you ship — and /gestures callbacks go through that same worklet path, so one device session settles all three.

  • 9760bfd: Close the gaps between "the tests pass" and "an app can ship this": errors after
    construction, the one unverified engine assumption, and the platform values an
    app cannot reach.

    The three share a shape, and it is the shape of everything left on the list —
    none of them is a missing feature. Each is a place where this runtime is the
    outermost JavaScript frame
    , with native code or the job queue above it, so
    there is no caller to hand a problem to and nothing above to notice one.

    • setErrorHandler. ErrorBoundary covers construction, which is all a
      component can throw during; it runs once and is finished. Everything after
      that ran unguarded into a frame with no contract — a handler throwing inside
      the engine's own dispatch is undefined behaviour that ranges from the rest of
      the frame's listeners being skipped to the app going down, and a commit
      throwing on the promise job queue is an unhandled rejection Lynx's main-thread
      context has nobody listening for. Both are now caught and reported, with the
      source ('render' | 'event' | 'gesture' | 'commit' | 'lifecycle') attached,
      because "something threw" and "the commit threw" lead to different
      investigations. Handlers on one (type, name) are isolated from each other,
      since a component and a ref did not choose to share a dispatcher; a failed
      commit does not wedge the scheduler, so the next mutation recovers the screen;
      and /recycle's componentAtIndex answers the engine's own -1 rather than
      unwinding into list layout mid-scroll. With no handler installed it warns, so
      a mistake is visible in development with no setup. It does not rethrow, which
      is the deliberate part: there is nowhere useful to throw to.
    • setEventTransport, and /bridge. Event delivery is the one assumption
      in this package a device could still disprove — the engine hands back a token
      this framework made, and that it does so untouched is read from the engine's
      source rather than confirmed on hardware. The failure would be total and
      silent: the tree renders, nothing responds to touch. So the listener form is
      now a seam with the worklet transport as its default, and @amritk/mini-lynx/bridge
      ships the fallback the design note has always named — string handler names,
      with dispatchNamedEvent for the app's forwarder to call once the event has
      crossed back from the background thread. A device disagreeing is a startup
      line rather than a fork. The cost is stated where it is chosen: a thread hop,
      and with it the property that a handler runs in the gesture's own frame.
    • globalProps(). Colour scheme, locale, flags and the reduced-motion
      preference arrive from native twice — as lynx.__globalProps at startup, and
      thereafter through the engine calling a global updateGlobalProps. An app can
      read the first on its own but cannot usefully own the second: the engine calls
      exactly one function, so a component reacting to a theme change would be
      hoping it was the one that got there. renderPage claims the slot and turns
      it into a signal, which also makes the reduced-motion line a one-off rather
      than a subscription the app maintains.

    renderPage also emits firstScreen even when the root component throws. That
    event is the platform's cue to stop waiting rather than a report of success, and
    a blank screen with a crash report is strictly better than a splash screen that
    never dismisses and says nothing.

    The core's gzipped budget moves 5064 → 5414 to cover all of it. None of it is
    addable from outside the package, and none of it runs on a day when nothing goes
    wrong.

@amritk/[email protected]

Patch Changes

@github-actions
github-actions Bot force-pushed the changeset-release/main branch from b382904 to aa200e8 Compare July 28, 2026 15:25
@github-actions
github-actions Bot force-pushed the changeset-release/main branch from aa200e8 to 8ce0751 Compare July 28, 2026 18:58
@amritk
amritk merged commit 665c77f into main Jul 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant