Version Packages - #17
Merged
Merged
Conversation
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
July 28, 2026 15:25
b382904 to
aa200e8
Compare
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
July 28, 2026 18:58
aa200e8 to
8ce0751
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
/recycle—recycle()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 anitemsignal 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.celltherefore receives getters, not values: a cell outlives the row it was built for./elements—querySelector,querySelectorAllandinvoke.scrollTo,boundingClientRect,scrollIntoViewandsetFocusare invoked rather than set.invoketurns the engine's{ code }convention (zero means success) into a promise, once, instead of at every call site./gestures—setGestureDetector, and thewaitFor/simultaneous/continueWithrelations. 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.RouteStackconsults 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 onSystemInfo, and Lynx has no media queries, so noprefers-reduced-motioneither — 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,__GetTagand__GetAttributeByName. All are optional, so a partial engine still satisfies the type and the runtime degrades with a stated warning rather than silently.createFakeEngineimplements every one of them, because it is the reference implementation the whole suite runs against. It gainsenterListItemAtIndexandleaveListItem— deliberately named after the pair in Lynx's own testing-library — so a recycler is driven through the same sequence a device uses;onInvokefor stubbing a UI method, since a fake that lays nothing out cannot honestly answerboundingClientRect; anddispatchGesture, which goes through the realrunWorkletindirection. 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.__FlushElementTreenow records its arguments, so the{ triggerLayout, operationID, elementID, listID }a list cell is committed with is asserted rather than assumed.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.ErrorBoundarycovers construction, which is all acomponent 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
refdid not choose to share a dispatcher; a failedcommit does not wedge the scheduler, so the next mutation recovers the screen;
and
/recycle'scomponentAtIndexanswers the engine's own-1rather thanunwinding 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 assumptionin 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/bridgeships the fallback the design note has always named — string handler names,
with
dispatchNamedEventfor the app's forwarder to call once the event hascrossed 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-motionpreference arrive from native twice — as
lynx.__globalPropsat startup, andthereafter through the engine calling a global
updateGlobalProps. An app canread 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.
renderPageclaims the slot and turnsit into a signal, which also makes the reduced-motion line a one-off rather
than a subscription the app maintains.
renderPagealso emitsfirstScreeneven when the root component throws. Thatevent 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