Skip to content

fix(core): serial onLoadError block/advance for failed loadFn#27

Merged
SutuSebastian merged 7 commits into
mainfrom
fix/serial-onload-error
Jul 19, 2026
Merged

fix(core): serial onLoadError block/advance for failed loadFn#27
SutuSebastian merged 7 commits into
mainfrom
fix/serial-onload-error

Conversation

@SutuSebastian

@SutuSebastian SutuSebastian commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Serial stacks treat a failed loadFn as occupying the lane by default (onLoadError: "block"), fixing leapfrog where a later open could mount while an error layer stayed up.
  • Opt into onLoadError: "advance" to remove the failed layer, fire onLayerDismiss (child teardown), and drain the next queued open.
  • Docs, skill, and a patch changeset cover the new option and migration note.

Test plan

  • bun test packages/core/src/layerStack.test.ts (block, advance, empty queue, onLayerDismiss, dismiss-after-advance, parallel ignores)
  • Core package typecheck + full core unit suite via pre-commit / harden
  • bun run docs:check -- --isolated + docs:build
  • CI green on this PR

Summary by CodeRabbit

  • New Features

    • Added configurable handling for load failures in serial stacks.
    • By default, failed layers remain in an error state and block queued layers until dismissed.
    • Added an onLoadError: "advance" option to remove failed layers and continue with queued layers.
  • Documentation

    • Updated lifecycle, queue, error-handling, API, and configuration documentation with the new behavior and examples.

Default block keeps error occupying the serial lane (no leapfrog);
advance removes the failed layer and drains the queue.
Align serial/error/scope/glossary/core-api copy with guide headers,
occupancy wording, and AutoTypeTable for SerialOnLoadError.
Pre-1.0 bumps stay patch until v1.
Call onLayerDismiss before removing a failed serial advance layer;
expand coverage; align apps/docs and maintainer docs with occupancy
semantics; document leapfrog fix in the patch changeset.
Split the serial-queue lead, slim the glossary pointer, and align
lifecycle mermaid with occupant leave.
@SutuSebastian SutuSebastian added the docs Merged PRs with this label deploy the /layers docs site label Jul 19, 2026
@changeset-bot

changeset-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b35fce9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@stainless-code/layers Patch
@stainless-code/alpine-layers Patch
@stainless-code/angular-layers Patch
@stainless-code/layers-devtools Patch
@stainless-code/lit-layers Patch
@stainless-code/preact-layers Patch
@stainless-code/react-layers-devtools Patch
@stainless-code/react-layers Patch
@stainless-code/solid-layers Patch
@stainless-code/svelte-layers Patch
@stainless-code/vue-layers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@SutuSebastian, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ac611e7-970c-494f-aafd-cdf2374562dd

📥 Commits

Reviewing files that changed from the base of the PR and between a7b63f3 and b35fce9.

📒 Files selected for processing (2)
  • packages/core/src/layerStack.test.ts
  • packages/core/src/layerStack.ts
📝 Walkthrough

Walkthrough

Serial stacks now support configurable handling for rejected loadFn calls. The default "block" mode keeps the failed layer occupying the lane, while "advance" dismisses it and activates queued work. Types, tests, and documentation were updated accordingly.

Changes

Serial load error handling

Layer / File(s) Summary
Define serial load error options
packages/core/src/types.ts, apps/docs/content/reference/core-api.mdx
Adds the exported SerialOnLoadError type and optional scope.onLoadError configuration.
Handle failed serial loads
packages/core/src/layerStack.ts, packages/core/src/layerStack.test.ts
Error-phase layers retain serial occupancy by default; "advance" dismisses and removes failed layers, activates queued layers, and remains ineffective for parallel stacks.
Document serial occupancy and errors
.changeset/*, apps/docs/content/**/*.mdx, docs/*.md, packages/core/skills/layers/SKILL.md
Documents occupying-layer terminology, queue behavior, and "block" versus "advance" load-error handling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant loadFn
  participant LayerStack
  participant QueuedLayer
  loadFn-->>LayerStack: reject during layer loading
  alt onLoadError is "block"
    LayerStack->>LayerStack: retain error layer as occupant
  else onLoadError is "advance"
    LayerStack->>LayerStack: dismiss and remove failed layer
    LayerStack->>QueuedLayer: activate next queued layer
  end
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the core change: serial onLoadError handling for failed loadFn calls with block/advance behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/serial-onload-error

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/core/src/layerStack.ts (1)

230-246: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate state updates and align onLayerDismiss timing.

Currently, the "advance" flow first sets phase: "error", then immediately overrides it with phase: "dismissed". This double update emits an unnecessary fleeting error state to layer subscribers. Additionally, this.onLayerDismiss?.(layer) is called before the layer's phase is updated to "dismissed", which slightly deviates from #commitDismiss where the layer is marked "dismissed" before the teardown hook fires.

You can bypass the intermediate error phase entirely and strictly mirror #commitDismiss by isolating the "advance" branch.

♻️ Proposed refactor
-      layer.setPartial({ error: error as E, phase: "error" });
-      layer.reject(error as E);
-      if (
-        this.#serial &&
-        (this.options.scope?.onLoadError ?? "block") === "advance"
-      ) {
-        // Drop the failed occupant and drain — no lasting error UI on this stack.
-        // Mirror `#commitDismiss`: notify before remove so child stacks tear down.
-        this.onLayerDismiss?.(layer);
-        layer.setPartial({
-          phase: "dismissed",
-          transition: "settled",
-          ended: true,
-        });
-        this.#remove(layer);
-        return;
-      }
+      const isAdvance =
+        this.#serial &&
+        (this.options.scope?.onLoadError ?? "block") === "advance";
+
+      if (isAdvance) {
+        // Drop the failed occupant and drain — no lasting error UI on this stack.
+        layer.reject(error as E);
+        layer.setPartial({
+          error: error as E,
+          phase: "dismissed",
+          transition: "settled",
+          ended: true,
+        });
+        // Mirror `#commitDismiss`: notify before remove so child stacks tear down.
+        this.onLayerDismiss?.(layer);
+        this.#remove(layer);
+        return;
+      }
+
+      layer.setPartial({ error: error as E, phase: "error" });
+      layer.reject(error as E);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/layerStack.ts` around lines 230 - 246, Isolate the serial
`"advance"` branch before the initial error-state update in the surrounding
load-error handling, so it does not emit `phase: "error"` before dismissal. In
that branch, update the layer to its settled `"dismissed"` state first, then
invoke `onLayerDismiss`, remove the layer, and return, matching the ordering
used by `#commitDismiss`; keep the existing error state and rejection flow for
other cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/layerStack.ts`:
- Around line 230-246: Isolate the serial `"advance"` branch before the initial
error-state update in the surrounding load-error handling, so it does not emit
`phase: "error"` before dismissal. In that branch, update the layer to its
settled `"dismissed"` state first, then invoke `onLayerDismiss`, remove the
layer, and return, matching the ordering used by `#commitDismiss`; keep the
existing error state and rejection flow for other cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa032112-1b49-4723-950b-8aaf4a4f1790

📥 Commits

Reviewing files that changed from the base of the PR and between 9ccb2ae and a7b63f3.

📒 Files selected for processing (13)
  • .changeset/serial-onload-error.md
  • apps/docs/content/concepts/glossary.mdx
  • apps/docs/content/concepts/lifecycle.mdx
  • apps/docs/content/concepts/stacks-scope-gc.mdx
  • apps/docs/content/guides/error-handling.mdx
  • apps/docs/content/guides/serial-queues.mdx
  • apps/docs/content/reference/core-api.mdx
  • docs/architecture.md
  • docs/glossary.md
  • packages/core/skills/layers/SKILL.md
  • packages/core/src/layerStack.test.ts
  • packages/core/src/layerStack.ts
  • packages/core/src/types.ts

Skip intermediate phase error on onLoadError advance; mark dismissed
before onLayerDismiss, matching #commitDismiss.
@SutuSebastian

Copy link
Copy Markdown
Contributor Author

Fact-check: advance path ordering nit

Verdict: ⚠️ Partial → applied salvageable part in b35fce9.

Claim Verdict
Intermediate phase: "error" then dismissed is messy / should skip error paint ✅ Correct intent — applied: advance branch sets dismissed (+ error) in one setPartial
onLayerDismiss should run after marked dismissed (like #commitDismiss) ✅ Correct — reordered; test asserts phaseAtHook === "dismissed"
Fleeting error “emitted to layer subscribers” ❌ Overstated — advance path never #flush()’d between the two setPartials; only sync layer.state readers during the old hook order saw error

Thanks — ordering now mirrors #commitDismiss.

@SutuSebastian
SutuSebastian merged commit 323be82 into main Jul 19, 2026
17 checks passed
@SutuSebastian
SutuSebastian deleted the fix/serial-onload-error branch July 19, 2026 09:37
@github-actions github-actions Bot mentioned this pull request Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Merged PRs with this label deploy the /layers docs site

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant