Skip to content

chore(deps): update dependency octane to ^0.8.0 - #8625

Merged
chenjiahan merged 1 commit into
mainfrom
renovate/octane-0.x
Oct 5, 2026
Merged

chenjiahan merged 1 commit into
mainfrom
renovate/octane-0.x

Conversation

@renovate

@renovate renovate Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
octane (source) ^0.6.0 → ^0.8.0 age confidence

Release Notes

octanejs/octane (octane)

v0.8.0

Compare Source

Minor Changes
  • 53db436: 'use dom bindings' views can now drive whole islands without the renderer:

    • Imported signal reads (count$.get(), count$.latest(fallback)) in projections, branch tests, lists and setup or block const declarations subscribe instead of raising an activation diagnostic. A pending read keeps the last DOM until its source changes.
    • @try/@pending/@catch compile into binding programs, select their arm like the renderer, adopt whichever arm the server rendered, and claim a boundary that is still streaming.
    • useLayoutEffect(callback, []) and useEffect(callback, []) run once after a view activates and clean up when it leaves.
    • An independent <Hydrate> whose only child is a prop-less, zero-argument binding view imported from a .tsrx module activates through that view's program, so its chunk no longer loads the renderer. Other islands keep the renderer activator.
    • A live binding over signals whose owner retires, as a document's signals do when the page is left, now stops quietly with its last DOM instead of throwing.
  • 3e3749d: Let interaction({ events }) opt into pointermove and pointercancel, so a deferred boundary woken by a press can replay whether that press moved, was released, or was cancelled by the browser before hydration finished.

    Both events only extend an interaction that another selected event already captured: they never start hydration or prefetch on their own, so selecting pointermove does not make a boundary hydrate on hover. Octane registers their document listeners only after a boundary that selects one captures intent, so pages that do not opt in never listen for pointer movement. The default interaction() events are unchanged.

    Capture no longer cancels the native default of a captured pointerup or pointermove, matching how it already treats pointerdown.

  • 950ef0b: Development server renders now report the client work an islands-only shell actually renders. The Vite dev server renders a hydrate: 'islands' route with the new shellWitness render option and warns once per site when the shell, outside its independent <Hydrate> islands, renders an event handler or function form action, a ref, an effect or store-subscription hook, a controlled value or checked the user can edit, or a live signal-handle binding. None of these would run, because the shell's modules never load in the browser.

    The report follows the render rather than the source, so it also catches what the build's source check cannot follow: components passed by reference, local aliases, handlers and refs passed through spreads, and elements a plain helper creates with createElement. It covers only the branches, rows, and streamed boundaries a request reaches. The route keeps serving either way, and the production build check is unchanged.

    renderToString, renderToReadableStream, and the other server renderers accept shellWitness in development. Production renders ignore it at no cost, and the production compiler output is unchanged.

  • dc4800b: Strong mode now requires state updaters and reducers to be pure
    (OCTANE_STRONG_IMPURE_UPDATER). Octane evaluates queued updaters and reducers
    while their owner renders and can call them more than once: a transition
    update is evaluated when it is staged and again when the transition renders,
    and an urgent update made while a transition is held is rebased onto the held
    value. A useState or useLinkedState updater, a useReducer reducer, or a
    useOptimistic reducer that calls fetch, schedules a timer, microtask, or
    promise callback, updates state, calls a state getter or Effect Event, touches
    useRef.current, reads a browser global or reassigned module variable, or
    calls Date.now(), Math.random(), performance.now(), or new Date() is a
    compile error. Inline functions, local and same-module declarations, and
    synchronous helpers are followed. Mutating the state an updater or reducer
    receives reports OCTANE_STRONG_SNAPSHOT_MUTATION. Do the work in the event
    handler and pass the result in. Compatibility mode is unchanged.

  • dc4800b: Strong mode now rejects mutating a state value outside render
    (OCTANE_STRONG_SNAPSHOT_MUTATION): in event handlers, effects, cleanup,
    timer and promise callbacks, and local helpers that receive the value. Passing
    a mutated array back to its setter does not re-render, and copying only the
    outer object leaves identity-based consumers stale and rewrites the value that
    transitions and useOptimistic revert to. Pass a new value instead, such as
    setItems([...items, item]), or keep mutable objects in useRef. Array
    mutators are now also recognized on nested literal properties and on state
    initialized lazily or through useReducer, Map and Set mutators on state
    created with new Map() or new Set(), and Object.assign-style targets;
    render-time mutations keep OCTANE_STRONG_RENDER_SNAPSHOT_MUTATION.
    Compatibility mode is unchanged.

  • dc4800b: Strong mode now rejects a deferred state update computed from the same state's
    render snapshot (OCTANE_STRONG_STALE_STATE_UPDATE). After an await, or in a
    setTimeout, setInterval, requestAnimationFrame, requestIdleCallback,
    queueMicrotask, or promise callback, setCount(count + 1) can overwrite an
    update that happened in between. Locals computed from the snapshot before the
    await, copied aliases, and closures are followed. Use the updater form,
    setCount((current) => current + 1), or read the latest value with the state
    getter. Synchronous handlers such as onClick={() => setCount(count + 1)}
    remain valid. Compatibility mode is unchanged.

  • 752028d: Strong mode now rejects a useSyncExternalStore getSnapshot or
    getServerSnapshot that provably returns a new object or array on every call
    (OCTANE_STRONG_UNCACHED_STORE_SNAPSHOT), such as a literal, a spread copy,
    .map(), .filter(), Object.keys(), a standard constructor, or a
    same-module function or local constant that allocates. Snapshots are compared
    with Object.is, so such a component warns in development and then renders
    until the update-depth limit throws. Return a value the store keeps, or read
    each field with its own useSyncExternalStore call. Compatibility mode is
    unchanged.

  • dc4800b: Strong mode now rejects write-only state (OCTANE_STRONG_WRITE_ONLY_STATE): a
    state tuple whose value is elided, unused, or read only to compute its own next
    value, whose getter is unused, and whose setter is called or passed on. This is
    the force-update pattern, including useReducer((x) => x + 1, 0) and
    useState(0)[1]. It reads the external source during render and subscribes
    afterwards, so a change in between is never rendered. Subscribe with
    useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) instead.
    Compatibility mode is unchanged.

  • 7422815: Universal renderers now ship host-binding and template-program code only when they use it. A Three scene's production bundle is about 5.2 KB gzip smaller.

    • universalHostBinding() installs the subscription, flush, and binding-only transaction code on its first call, so roots in an app that never creates a binding no longer carry it. Binding behavior is unchanged.
    • Template programs are now opt-in per driver. A driver's templateProgramMount, templateProgramRuns, and collapsedTemplateMount capabilities take effect only when the driver also sets templates: universalHostTemplates, a new export from octane/universal and octane/universal/native. A driver that declares these capabilities without it mounts and updates those trees through ordinary host commands. The Lynx background driver passes it.
Patch Changes
  • fbf6e40: Remove the server content left after a component's content during hydration. When the server rendered more in a component's range than the client renders there, the client adopted the matching prefix and kept the rest on screen without a report. This happened for a component whose identity differs on the client, for a hookless component in a renderable hole, and for a component that returns less on the client, including one rendered as a list item. Hydration now discards the stale remainder and reports it once through onRecoverableError, plus a located development warning. The nodes the client adopted keep their identity. To find where a multi-root template's roots end without parsing it in production, the compiler now passes the template's root count to template().

  • a50b846: When hydration adopts an element whose children are all component calls but
    the server rendered other content inside it, the first call now reports the
    server node it found in place of its range once, the element's server children
    are discarded, and the components are built inside the adopted element. A
    single-root component used to compare its template with the element itself,
    reporting that element, keeping the stale server children, and appending its
    output after them; a lite component removed the adopted element entirely. Later
    sibling calls in the same element no longer discard the rebuilt content of an
    earlier one, and an element the server left empty reports "nothing".

  • 9ef7385: Fix a crash when a pending <Hydrate> boundary resumes a component that
    suspended inside content its parent opened in a server-rendered row.

    A deferred boundary's retry resumes the component that suspended before it
    renders the rest of the boundary. When a hookless sibling component followed
    that component, for example a later row of the same list, the retry picked the
    sibling instead and threw TypeError: Cannot read properties of undefined (reading 'clear') once the promise settled. The retry now resumes the
    component that suspended.

  • 6ea582e: Hydrate a component call at a hole in an @if or @switch arm when the server rendered another arm with the same static roots, for a call that cannot render in place of one server node. The server node at the call's position is an element of the other arm, not the component's range. A keyed or dynamic call, or a component that renders several roots, a fragment, or nothing, left that element on screen. A call after one of the arm's adopted roots also deleted that root. A component with several roots in a development build lost the adopted node after the hole. The call now replaces exactly the server node at its position, the arm's adopted nodes keep their identity and keep updating, and hydration reports the mismatch once through onRecoverableError.

  • 52c9d33: Discard the server content left after a hydrated @if or @switch arm that ends with a @try whose body throws on the client. The boundary builds its catch arm and leaves the hydration cursor on its own close marker, so the server components the server's longer arm rendered after it stayed on screen with no report. Hydration now looks past that marker, removes them, and reports one structural onRecoverableError.

  • 8f8349b: Compile a bare-left @for (item of items) or @for ({ id } of items) header instead of crashing. Each row binds its own item, as a let header does, and shadows any outer name, on the DOM client, the server, hydration, split <Hydrate> boundaries, and the universal and Valdi renderers. A header that would assign an existing target, such as @for (obj.x of items), now reports a located compile error.

  • f355acd: Keep a binding island's server content and input while its client data is still loading at activation:

    • A @try in a 'use dom bindings' view no longer replaces server-rendered content with @pending when the client's own read is still pending. Like the renderer's resolved boundary, the server content stays and hydrates in place when the read settles; its handlers and mount-only effects start with that commit. After the first commit, a later pending read shows @pending as before.
    • An island whose binding view suspends outside @try on its first render no longer loses input. Clicks captured before activation, and input that reaches the island before its data arrives, now wait for the first commit and then replay in order. Input held by an island that is disposed before it commits is dropped.
  • 3ffd8cc: A 'use dom bindings' view whose text leaf casts a signal handle to number ({count$ as number}, or a handle passed through props) now server-renders the handle's text, so adoptBindings can adopt its own SSR output instead of throwing a mismatched-topology error. The ordinary renderer hydrates the same output in place.

  • 9350268: A DOM binding program, such as an island view, no longer ends silently on any error named ScopeDisposedError. It ends quietly and keeps its last DOM only when the owner of a source it observes retires. Observed sources are a tracked .get() read, a bound signal handle (concrete, declared, optimistic, or accepted by a transition), a whole-style or projected binding, and a bound control. A read of a different, already retired scope, or any error that only shares the name, is now reported through reportError. Like any other failure of a committed program, it keeps the last DOM and later updates still apply.

  • 4212874: Keep the hydration range-marker validator out of apps that never call
    hydrateRoot. A dynamic {x} text hole that may hold a signal handle
    previously retained the binding-marker protocol in every client bundle, about
    600 B gzip. Adopting a binding view's server text range is now a hydration
    method, so client-only apps drop it. Hydration behavior is unchanged.

  • f03d812: 'use dom bindings' views may now declare no props parameter. A view that reads only module-scope signal handles, such as an island's state, compiles for adoptBindings, mountBindings and server rendering like a view with props. Views with more than one parameter are still rejected.

  • 3315f74: Keep hydration aligned after a @try, <ErrorBoundary>, @for, or <Activity>. A @try or <ErrorBoundary> left the hydration cursor inside its own server range, so a following component with several roots in a body without a template, such as <>@try {…}<Pair /></>, did not find its server range: it was rendered a second time beside the server's copy and reported a false mismatch, even when the server and client rendered the same props. Every boundary, list, and activity now steps past its server range as components and branches do. This also lets an @if or @switch arm that ends with one of them find where its content ends, so elements or text that the server's longer arm rendered after it are removed and reported once instead of staying on screen without a report.

  • f5896c6: Discard the server content that a hydrating @if or @switch arm leaves after its last component or nested branch. When the server rendered a longer arm, such as one with a trailing element after the same components, that trailing content used to stay on the page with nothing reported. Hydration now removes it, keeps every node the client adopted, and reports the mismatch once to onRecoverableError, with one development diagnostic at the branch.

  • 3770c1e: Discard the server elements and text that an @if or @switch arm leaves unclaimed during hydration. When the server rendered a longer arm, the client arm adopts the start of the server's range. Elements or text after the roots of the client arm's own template, or after the single root that a component in the arm adopts in place, stayed on screen with no report. Hydration now steps past those roots, removes the server content after them, and reports one structural onRecoverableError, with a development diagnostic at the directive. Every compiled multi-root template now carries its root count, so production hydration finds that end without parsing the template, including for a branch in a helper passed as a render prop.

  • 3994e1e: Discard the server content an @if or @switch arm leaves unclaimed during hydration. When the server rendered a different arm, the client arm adopts the server's range from its start. Server components or ranges left after the client arm's content stayed on screen until the next arm swap. Hydration now removes them and reports one structural onRecoverableError, with a development diagnostic at the directive. A dormant <Hydrate> boundary whose captures changed before activation discards them without a report.

  • 6f019c5: Hydrate an @if or @switch at a hole in an @if/@switch arm when the server rendered another arm with the same static roots. The hole's position then holds an element or text of the other arm, not the slot's range. The slot used to treat that node as the node after it: a branch with several roots adopted it and the arm's next static root as well, so a node was lost silently, and later updates left the server node on screen or duplicated content. An empty branch kept the server node, a branch after one of the arm's adopted roots deleted that root, and a single-root branch that did not match threw NotFoundError from hydrateRoot. The slot now takes the place of exactly that server node. It adopts the node when its branch is that node, otherwise it rebuilds over the node or removes it and reports the mismatch once through onRecoverableError. The arm's adopted nodes keep their identity and keep updating.

  • 6c50c70: Read a component's signal and query declarations from its own @try, @pending,
    @catch, @if and @switch arms and keyed @for rows without creating a second
    cell. A query declared in setup and read again from one of those frames, directly
    or through a local derived$, now starts one request on the client and resumes
    the server's request during hydration, instead of starting another request per
    arm or row. A component's own signal displayed in an arm also keeps its value
    when that arm remounts.

    An arm or row still owns the declarations it evaluates itself and resets them
    when it is removed, and a child component that receives a handle as a prop still
    resolves it in its own instance.

  • de31710: Cache a const that only another cached calculation reads. In const labels = formatRows(rows); const view = wrapRows(labels); with only view in the template, labels used to recompute on every render, so view's cache never hit and wrapRows ran on every render too. Client builds now cache every link of such a chain, keyed on the chain's own inputs, in @{} and return <jsx> components alike. That includes a const read only by a Strong inline render expression. Declarations that are never cached (hook calls, live member calls, let, values only an event handler reads) still never are.

    Also fix a render crash: a cached calculation whose callback read a const declared after it, as in const reader = make(() => labels); const labels = formatRows(rows);, threw Cannot access 'labels' before initialization because its dependency check read labels early. Such a calculation is no longer cached.

  • fbaf501: Release the remaining host ref array owners when a callback cleanup throws, including nested arrays, and preserve the first error for existing error delivery.

  • 70ee6a3: When hydration finds other server content in a range where the client renders
    several siblings, such as server text in a {hole} whose client value is a
    component that renders two components, the development console now logs one
    hydration mismatch diagnostic for that recovery instead of one per sibling.
    Once a recovery reaches the end of the range, by discarding the server content
    before it or by rebuilding a single-root clone over the last server node,
    later sibling components, fragment clones, and single-root clones that find
    that end build on the client without a second "the server rendered the end of
    the parent block" diagnostic. A mismatch in a separate range still reports on
    its own.

  • ab2798e: Mount and update every component without allocating a closure context. The hydration path for a component inside a client-rebuilt subtree now calls a separate helper, so ordinary component calls no longer capture their arguments up front. Rendering behavior is unchanged.

  • 95421bb: A signal$, derived$, or query$ declared inside a custom hook now belongs to each call of that hook. Calling useUser$(a) and useUser$(b) in one component previously shared one cell, so the second call showed the first call's data and never ran its own loader. Each call now owns its cells, a call skipped by a condition keeps its own, and server and browser builds key them identically for SSR seeds and streamed results. Call sites are keyed in modules with a runtime signals import and for hooks named with $.

  • 9168bf6: Fix a runtime host element (from createElement) duplicating its children when
    hydration retries after suspending. When the server rendered other children than
    the client's, the first attempt built the client's children inside the adopted
    host; the retry then built a second copy beside them. The retry now replaces the
    earlier attempt's children, and a root that retries restores the server's.
    A child host element that replaces server content now calls onRecoverableError
    once across retries, and in development warns once at the hole that renders the
    runtime host.

  • 11a9da9: Adopt the server's nodes when a list item suspends while a deferred <Hydrate> boundary hydrates. A component that returns JSX passes the boundary its children as descriptors, so a host's children there hydrate as a list. When the first item's first render suspended, the boundary's retry filled that list again from wherever the resumed render had left the cursor. It reported a hydration mismatch, rebuilt the item on the client, and duplicated server nodes such as the item's first root. The retry now adopts the list from its first server item, as a compiled @for does. An item whose render suspends inside a hydrating @if or @switch arm the server rendered no range for also leaves the server's nodes as they were, so the retry adopts that arm instead of rebuilding it.

  • 92495b9: A @catch parameter written as an object or array pattern, such as @catch ({ message }: Error, retry), now binds its names on the client. Previously the client error arm threw a ReferenceError or read a component local with the same name, while server rendering showed the error, so client render and hydration disagreed with SSR. Pattern defaults may read component locals, as in @catch ({ message = fallback }).

  • bfed959: On an ordinary page exit, installSignalDocumentLifecycle now unmounts independent islands before it retires the document's signal owner. Island cleanups can read their document signals instead of hitting ScopeDisposedError, and live islands are no longer notified of the retirement. The owner still retires if an island's unmount throws.

  • c016c2d: Discard and report the server's @if/@switch arm when the client's arm renders nothing during hydration. An @if with no client arm already discarded the server content and reported it as an empty branch, but a client arm with an empty body, such as @else { <></> }, left the server's other arm on screen with no report. Hydration now removes that content and reports one structural onRecoverableError, with a development diagnostic at the directive. When the server arm starts with an element or text node rather than a range, it is still kept.

  • 21c3bfe: When hydration rebuilds an @for that the server rendered with no items because
    the client has items, the development diagnostic says the server rendered
    an empty list (@empty) only when the server rendered that arm. A list without
    an @empty arm now reports an empty list.

  • 320f9e3: Mount rows with event handlers without per-row weak-map bookkeeping. A module that can receive signal handles recorded each native handler's signal authority in two weak maps, so every mounted row paid two weak-map insertions even when no signals were in use, which made prepending 100 rows to a 1,000-row keyed list about 1.6 times slower. The authority now lives on the host beside its handler, and a scope's published-authority mark is a field on the scope. Updating a text hole with a plain value in such a module also writes the text directly instead of going through the signal binding path.

  • da9b1e4: Preserve the first script or stylesheet preload's options when equivalent calls are deduplicated, so a later preinit inherits the original integrity and connection metadata. Explicit preinit options continue to take precedence.

  • 4ab33b6: The compiler now rejects for…in and C-style @for headers on every target.
    @for (const k in object) used to compile as for…of over the object on the
    DOM client, server, and universal renderers, rendering its values or nothing
    instead of its keys. @for (let i = 0; i < n; i++) crashed the DOM compiler
    with an internal TypeError. Each now reports a diagnostic at the directive
    that suggests the for…of spelling, such as
    @for (const name of Object.keys(object); key name).

  • 8e90048: Keep every root of a hydrating @if or @switch arm that suspends inside a deferred <Hydrate> boundary while the server rendered no range for it. When the arm's last root was static, after the component that suspended or after a use() in the arm itself, the boundary's retry still bounded the arm after its first roots. Hydration looked right, but switching to another case left the arm's later roots on screen, and a case change while the boundary was pending did the same. The arm now ends after every root its template adopted, both when it first suspends and when the boundary retries it.

  • 6bb20ac: Keep the server nodes that a @switch or @if arm adopted when the arm
    suspends inside a deferred <Hydrate> boundary before it has a range of its
    own.

    When the server rendered no range for a @switch or @if, the client's arm
    takes the place of the server node at that position and adopts it. If that arm
    suspended inside a <Hydrate split={false}> boundary, the boundary's retry
    published the arm's range after the sibling that followed it, so that sibling
    reported a false mismatch and built a second copy of its server node. A case
    change while the boundary was still pending removed the wrong server nodes, with
    the same result for the next sibling. The arm now marks where its content starts
    with a comment of its own and keeps track of how far that content reached. Its
    retry finalizes the range from there, and another case replaces exactly that
    content and renders on the client without reporting a mismatch. This works for
    arms whose content is text, several roots, or a root the arm had not yet cloned.

  • 6959fa0: Rebuild a hydrated fragment that starts with a hole when the server rendered something else there, such as another @if arm. A leading component call, text hole, or nested block matches any server node, so hydration adopted the other arm's nodes as the fragment's roots: its static roots were never built, and a component in the hole took one of the server nodes as its position. Hydration now compares the first static element root after the leading holes with the server node it would adopt. On a mismatch it rebuilds the fragment on the client, as it already did for a fragment that starts with a static root, and reports one onRecoverableError. The development warning names that static root and the server node found in its place. Matching fragments adopt their server nodes as before. A HYDRATION_RANGE_BOUNDARY passthrough root, whose owner may adopt one range level off, keeps its existing behavior.

  • 105e0f0: Run a hook with an omitted dependency array on every render when its callback reads a binding declared after the hook call. That covers a later const, let, class or var, and the const that receives the hook's own result, such as a useCallback that calls itself. The compiler read the binding where the hook is called to fill the inferred array. A let, const or class threw ReferenceError: Cannot access … before initialization there. A var was still undefined, so the hook never re-ran when it changed. React runs a hook with an omitted array on every render, and Octane now does the same in this case. Strong mode reports OCTANE_STRONG_UNTRACKED_EFFECT instead, asking for the declaration to come before the hook.

  • 0d91b93: Give a component rendered inside an @if or @for arm of a hookless component
    the same signal instance on the server and in the browser. A hookless
    component's arms, rows and value children no longer lose that component's level
    from their signal and query identity, and no longer gain a stray list-item
    segment. Hydration now resumes the server's cells for them instead of starting
    fresh ones. Hookless siblings also keep their own owners when hydration retries a
    suspended @try body.

  • 7698907: Adopt a server-rendered @catch arm (or JSX ErrorBoundary fallback) during hydration when the client's try body throws again, instead of reporting a hydration mismatch and rebuilding it. This covers a boundary that is a component's only output and a try body that renders a host element before it throws. The server now marks a caught arm, so the client replays the try body without claiming the catch arm's DOM. A body that renders or waits on the client replaces the server's catch arm without a mismatch report. The replay also reads the use() values of Suspense boundaries and Hydrate islands the server completed inside the abandoned try body, including streamed ones, so a body with a nested boundary adopts the catch arm immediately instead of waiting for client data. When a boundary at the top level of a root rebuilds during hydration, the root's next sibling component now hydrates in place instead of being rebuilt with a mismatch report.

  • 3baa492: Hydrate a server-rendered @catch arm when a template-less component's use() rejected on the server. The component now reads its rejection seed, so the boundary adopts the server's catch arm and reports the caught error instead of discarding that arm, reporting a hydration mismatch, and staying suspended. A component that renders where the server rendered something else still reports the mismatch.

  • 1fa3853: Hydration no longer reports a mismatch for a @try body or <ErrorBoundary> child that throws to its catch arm after adopting server content. A component that cloned its element and then threw from a hole was compared against whatever the server rendered there, often the server's own catch arm, and logged a development mismatch warning and called onRecoverableError in development and production. The catch arm replaces that content, so these reports are now dropped. A body that completes or suspends still reports its mismatches, and nested boundaries pass them to the enclosing boundary.

  • 62e76bf: Load the browser's own query selection when hydration finds a server result for a
    different request. When a component's props or state select a query request
    other than the one the server resolved under the same owner, hydration no longer
    fails with "The presented query definition does not match". The query loads its
    current selection as if the server had not seeded it, and adoption keeps the
    server's nodes while reporting changed text as a recoverable hydration mismatch.

  • a1f136c: Deferred hydration no longer lets a live interaction overtake one captured
    before hydration. A <Hydrate> boundary or independent island replays its
    captured events after its hydration commits, which can be a frame later. An
    event that arrived in between reached the hydrated handlers immediately, so
    clicking option A before hydration and option B during it ran B's handler first
    and left A selected. The boundary now keeps capturing until its replay runs, so
    handlers see input in the order the user produced it. An island whose content
    is still suspended after activation also keeps those events instead of
    dropping them on the inert server markup.

  • 39f3e97: Deferred hydration now replays captured interaction events with the original
    event's timeStamp. Each replay used to be a newly constructed event stamped
    with the replay time, so the time between a captured pointerdown and
    pointerup, or how long an input had been held, collapsed to the hydration
    delay. A boundary nested inside another boundary reset the clock a second time.
    Replays from <Hydrate> boundaries, nested boundaries, and independent islands
    now keep the captured value and remain untrusted (isTrusted is false).

  • 11aebc7: Hydrate a single-root component whose body is an @if or @switch, called where the server rendered another branch's markup. Hydration renders such a call in place of the server node it finds there, but the branch inside the component bounded its content against that node. When the branch's root did not match the node, recovery rebuilt the root and removed the node, and hydrateRoot threw NotFoundError and left the container empty. This happened in development and production builds, for an element or component arm, including when the call was its host's last child or the last root of an arm's fragment. When the root matched, hydration adopted it outside the branch's range, so switching the branch to another arm left the server's node on screen next to the new arm. The call now renders before the node after the server's node, so the branch bounds exactly its own root. A mismatch is still reported once through onRecoverableError.

  • 416882f: Hydrate the component after one that adopted a server element in place. When the server rendered a different @if/@switch arm, a component that found that arm's element where its own server range belonged adopted it, but the next component adopted the same element again, and the server's range for the next component stayed on the page. When the first component's own holes left the cursor inside the element, the next component claimed the enclosing block's range instead, which discarded the arm. Hydration now continues after the adopted element. A returned single-root component that hydration rebuilt at the end of its parent's server range no longer takes that range's end marker as its own, so a later render that hides it removes it, and showing it again no longer throws.

  • 3ec43da: When a component call finds no server range of its own, because the server rendered another @if or @switch arm there, and hydration adopts the server markup in place, a component call inside that component's template now adopts the server node at its own position. Previously, when the server had rendered that inner call's markup inline, the inner call rendered against the first server node of the outer template instead: it reported a mismatch and replaced that node, which the outer component had already adopted.

  • 0b833bb: Adopt the server's text at a renderable hole in a fragment that a component adopts in place during hydration. When a component call finds no server range of its own, because the server rendered another arm or component inline there, its fragment template adopts those server nodes in place, and the server framed none of that markup's holes. A {value} hole then took the server's text node as its own end marker and inserted a second copy before it, so the page showed the text twice. The hole now adopts that text, or the element an element value renders, and updates it in place. A {null} hole at the end of such a fragment no longer claims the server node after it as the fragment's content, so hydration removes that stale node and reports it once instead of keeping it on screen.

  • 11aebc7: Hydrate a component call that has no server range of its own when a component inside the callee's branch adopts the server node at the call. This happens when the server rendered another branch's markup there, and an arm of the callee's @if or @switch renders a component whose root matches that node. Hydration rendered the inner component in place of the node, but the call itself did not record that it had adopted the node. The page looked right after hydration, but two things went wrong. Swapping the component that the call renders, for example through an imported binding, left the old component's content on screen next to the new component. When the call was the whole arm of an adopted branch, the server nodes after the adopted node in that arm were never removed. The call now adopts the node with the component inside it, in development and production builds. A swap replaces the call's content, and the leftover server nodes are removed and reported once through onRecoverableError.

  • bc30a18: Remove the server markup left after a component's last call when that call adopted its server node in place. When the server rendered another component's markup in a component's range, and the client's content there is a sequence of calls without server ranges of their own, each call adopts its server node in place. Whatever the server rendered after the last call's nodes stayed on screen, and hydration reported nothing. Hydration now removes it and reports the mismatch once through onRecoverableError, while the nodes the calls adopted keep their identity. This covers single roots and fragments, in lite and full component slots, in development and production builds.

  • b319c16: Production builds emit less code for inlined useMemo and useCallback calls. When the compiler supplied the memo's slot, the hook's name is no longer passed to the runtime, since only an authored slot can be missing. In plain .ts and .js modules, a dependency that is a never-reassigned local binding is now read directly instead of being copied to a temporary first. Across the repository's binding modules, this saves about 5.6 KB gzip, summed per module.

  • bc6761f: The hydrate: 'islands' shell check now rejects more interactive shells and also runs in dev:

    • A hook is recognized through an import alias (useEffect as onMount) and through a namespace or member call (O.useState()), and so is an aliased signal declaration.
    • Every local function the shell references is checked, not only JSX tags: a component passed as a prop (render={Item}) and a helper called to render output. A relative import passed into JSX is checked when it can render (a function, or a component-named value), including a namespace member such as UI.Button and a module-level alias of a local function. Strings, asset URLs and other plain values, including any export of a non-Octane module, are not shell output. A package component or a module-level wrapper such as memo(...) passed as a value cannot be checked.
    • A signal handle bound through a member ({state.count$}), an import alias (count$ as live) or a module-level alias (const live = count$) is reported.
    • In dev, an islands-only route whose shell needs client work now logs a warning naming the module, line and problem, once per problem, while the page keeps serving. The production build still fails on it.
  • f5644f7: Stop a query below a JSX <Suspense> from reloading without end during hydration, and resume the server value that a .tsx component reads in its returned JSX. A JSX <Suspense> or <ErrorBoundary> gave its try body a new identity on every render. A boundary retrying its own suspended first attempt therefore looked like it had received new children: it retired the attempt's queries and started fresh ones. When the browser had to load a query, for example one without a server seed, each load resolved into another retry and another load, and the page re-rendered indefinitely. The boundary now keeps one body and carries its children like a compiled @try, so only new children restart the attempt. On the server, a component's returned value rendered outside that component's signal owner, while the client renders it inside. A query$ that a .tsx component reads with .get() in its returned JSX was therefore seeded under the parent's identity and loaded twice on the server, and the browser loaded it again with a hydration mismatch. The server now renders a returned value inside its component's owner.

  • 5ef4f9a: Render private static JSX components through their compiled fragment again.

    A private .tsx/.jsx component whose body returns only static JSX, and
    whose every use is an attribute-free child of a returned host element, again
    renders through that parent's template as a lite component call. Since async
    signals, component descriptors carry their invocation site, and the proof
    recognized only the older descriptor call. Every such component therefore fell
    back to a full component slot plus a descriptor child slot. On the
    signal-favoring chain, the JSX twin's shallow bump went from 10 full component
    slots to 100.

  • 18c1b77: Reorder keyed lists without journaling every row. During a root render that can still be rolled back, a keyed reorder recorded each surviving row's previous position, four journal slots per row. Rotating a 1,000-row list was about 1.4 times slower than before root renders became undoable. The list's own shape record now restores each row's position from its original order on rollback. Recording the key order for that record also uses a single bulk copy. Rows that render their index still show the right position after a held render retries.

  • 2b69387: Discard a mismatched server node inside a hookless component's host during hydration. A hookless component renders into the element its call sits in, which can differ from the parent of the enclosing block's range, for example a <section> inside an @if arm. When its template did not match the server node there, hydration kept the stale server node on screen and inserted the client's element beside it. Hydration now builds the client's element in that node's place, including when the node was the host's last child, and reports the mismatch once.

  • 07cc1d2: Keep a component-local derived$ or query$ when its captured values are unchanged. Since local declarations started following new props, every render ran the declaration's closure again, even with equal props. A derived$ that returned an object produced a new object on each render, so an effect or memoized child that depended on it ran on every parent render. The compiler now lists the render values a local declaration captures, as it does for a hook with an omitted dependency list. A render whose captured values are all unchanged keeps the accepted definition and its value without running the closure, and skips the per-render staging work. A changed value still reruns the declaration as before. A handle from a local signal$ with no key or a literal key is not a captured value, so a derived$ that reads one keeps its value until the signal changes. When two declarations name one cell, such as a repeated explicit key, the first declaration in a render presents it on the server as well as in the browser. Server rendering previously threw because the second declaration replaced the cell's computation during the render.

    Declarations reached through a custom hook also no longer share a cell with an explicit key that spells out the hook's call path, such as 'user/h:…' declared directly in the component, and their path is no longer rebuilt for each declaration on every render.

  • 3d0e5e2: Re-select a component-local query$ when a later render's selector captures new props or state, and recompute a synchronous component-local derived$ from its captured values. Previously both kept the closure from their first render. An equal selection keeps its request and adopts the new loader without refetching. A changed selection aborts the obsolete request and starts the new one when the render is accepted. A held transition or suspended attempt keeps committed readers on the accepted selection and reuses its pending request when it retries.

  • f647cef: Keep destructured .map() callback parameters writable on the client. A
    template xs.map(({ label }) => <li … />) compiles to a keyed row loop, and its
    destructured fields were re-declared with const, so a row handler that
    reassigned one, such as onClick={() => { label = label + '!' }}, threw
    TypeError: Assignment to constant variable after mount or hydration. Server
    rendering already treated them as ordinary parameters. Identifier parameters
    were unaffected.

    The same row prologue now keeps the kind of an authored @for header, so the
    fields of @for (let { label } of xs) can be reassigned too. Output for
    const headers and identifier parameters is unchanged.

  • 8e90048: Keep every root of a hydrating @if or @switch branch that the server rendered no range for. Such a branch adopts the server nodes at the cursor in place, and hydration bounded its content after the first root it adopted. When the branch was the whole content of another arm or of a component the client adopted, the rules that remove the server's leftover content then deleted the branch's later roots, which the client still renders, and reported a mismatch for them. A branch that was not deleted from still owned only its first root, so switching it off left the others on screen. The branch now ends after all the roots its template adopted. The server content after them is still removed and reported once, and a branch whose roots outnumber what the server rendered there is built on the client and reported, instead of hydrating without its later roots. This also covers a branch inside a component that was adopted in place.

  • d496796: Compare a memo component's plain props with fewer ownership checks. The memo bail looked up own-property membership for every prop, so that an inherited Object.prototype value cannot stand in for a removed prop. That lookup is now made only for values such a read can produce: undefined, functions and objects. A primitive prop that compares equal is already the object's own. Rows that pass mostly strings and numbers bail with one lookup instead of one per prop. An own __proto__ prop holding Object.prototype still counts as a change when it is replaced.

  • fdf54fe: A plain .ts or .js module that calls a hook-named method while it initializes, such as export const initial = store.useValue(), now evaluates instead of throwing ReferenceError: Cannot access '_h$0' before initialization. That covers top-level statements, optional chains, and a module-level class's static fields, static blocks, and computed keys. These calls run outside every render, so they keep their authored form with no hook slot. A hook call in an instance field initializer runs with each construction, possibly during a render, so a hook method or module-declared custom hook called there now keeps its own slot, as it would in a constructor.

  • ce97a96: A plain .ts or .js module that calls a hook-named method or a custom hook inside a function it invokes in place, such as export const value = (() => store.useValue())(), now evaluates instead of throwing ReferenceError: Cannot access '_h$0' before initialization. That covers arrow and function expressions called directly or through .call and .apply, including async ones. A generator body waits for .next(), which a render may call, so its hook calls still get their own slot, as do those in a function invoked in place inside a component or hook.

  • dc3e180: Drop the native array snapshots from client and server bundles that never map a
    list. Both runtimes record Array.prototype.map and the Array[Symbol.species]
    getter when they load, so a mapped list can tell the native map from one that
    user code installs later. Bundlers could not prove those two reads free of side
    effects. Every bundle that never reached the mapped-list code therefore kept
    them as dead top-level statements, including a server bundle that only escapes
    HTML.

    The reads are now marked pure, so bundlers remove them when nothing uses them.
    A bundle that maps a list still takes both snapshots at load, before user code
    can replace either intrinsic. A minimal client bundle shrinks by 89 bytes raw,
    and the smallest server bundle shrinks from 654 to 571 bytes raw.

  • e219d88: Set host refs before layout effects when a nested Suspense boundary reveals
    after an update replaced its parent's pending retry. The nested boundary first
    suspended inside that retry; once the update discarded it, the boundary stayed
    hidden without its reveal bookkeeping, so refs mounted in it (including hosts
    preserved from before the suspension) were never attached and a layout effect
    reading them saw null. A nested boundary that suspends while its parent is
    already hidden also no longer calls a callback ref with null a second time,
    matching React.

  • ed188d7: Leave a signal-capable only-child text hole alone when its value is unchanged.

    A {props.value} hole that is its element's only child compiles to
    bindSignalChild whenever the value could carry a signal. That binding kept
    the Text node as its token, so every render read the node's nodeValue to tell
    an unchanged value from a changed one, and an unchanged value overwrote any
    outside edit to the text. The binding now keeps the last written primitive, as
    compiled text holes did before signal-capable bindings: an unchanged primitive
    does nothing and leaves the DOM alone, and a changed one rewrites the same Text
    node.

  • afbad9f: Discard and report server content that an only-child text hole cannot adopt during hydration.

    A text hole that is its element's only child adopts the server's leading Text
    node. When the server rendered something else there, such as an element or a
    comment, hydration kept that content and appended the client's text after it.
    It reported nothing, and every later update wrote the text beside the stale
    server nodes. Hydration now removes that content, so the element holds only the
    client's text. It reports the mismatch through onRecoverableError and, in
    development, a located warning. suppressHydrationWarning silences the report,
    here and when the client renders nothing over such content. An empty server
    frame is not reported, the same as an empty element.

  • 09cf476: Preserve the submitter's document order in function form actions and manual transition form status data. Include its entry before the native formdata event fires.

  • 0887a68: A delegated event listener that the browser runs synchronously during a render's
    DOM writes, such as onBlur when a render disables or removes a focused input,
    can now write signals. Before, it inherited the render's signal write guard and
    threw SignalWriteError. Its reads no longer become dependencies of that render.
    Subscribers notified by its writes also run outside the render, so they can
    write signals and their updates schedule like any other event's. The rest of the
    render stays write-guarded, and a listener called from inside a pure computat

❗ Important

✂ PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • Between 12:00 AM and 03:59 AM, only on Monday (* 0-3 * * 1)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@chenjiahan chenjiahan changed the title fix(deps): update dependency octane to ^0.8.0 chore(deps): update dependency octane to ^0.8.0 Oct 5, 2026
@chenjiahan
chenjiahan merged commit 69e8038 into main Oct 5, 2026
8 checks passed
@chenjiahan
chenjiahan deleted the renovate/octane-0.x branch October 5, 2026 02:23
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