Skip to content

fix(plugin-history-sync): only call fallbackActivity when no route matches#707

Merged
orionmiz merged 3 commits intomainfrom
edward_karrot/check-slack-bug
Apr 30, 2026
Merged

fix(plugin-history-sync): only call fallbackActivity when no route matches#707
orionmiz merged 3 commits intomainfrom
edward_karrot/check-slack-bug

Conversation

@orionmiz
Copy link
Copy Markdown
Collaborator

Summary

  • fallbackActivity callback was being invoked on every initialization regardless of whether currentPath matched a registered route, due to a refactor in feat(react, plugin-history-sync): Default history setup option for rich deep link experiences #610 that hoisted the call out of the no-match branch in order to unify the matched/fallback targetActivityRoute for defaultHistory.
  • This regressed the pre-1.8.0 contract: apps performing side effects in this callback (e.g. logging unknown deep links) fired on every successful match too. The fix lazy-evaluates the fallback inside ??, restoring the original behavior while keeping defaultHistory support intact.
  • Added two regression tests asserting the callback is not called on a successful match and is called when no route matches.

Test plan

  • yarn test historySyncPlugin.spec — 29/29 passing
  • Verified the new test catches the regression by temporarily reverting the fix

…tches

Move the fallbackActivity callback invocation back inside the no-match
branch so it is no longer called on every initialization. This restores
the pre-1.8.0 contract where the callback is only invoked when
currentPath does not match any registered route.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 30, 2026

🦋 Changeset detected

Latest commit: eb2ef69

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

This PR includes changesets to release 1 package
Name Type
@stackflow/plugin-history-sync 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

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 30, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
stackflow-docs eb2ef69 Commit Preview URL Apr 30 2026, 09:39 AM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 30, 2026

Deploying stackflow-demo with  Cloudflare Pages  Cloudflare Pages

Latest commit: eb2ef69
Status:⚡️  Build in progress...

View logs

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf850cfb-0d5c-465c-981f-16e8ee3abfc7

📥 Commits

Reviewing files that changed from the base of the PR and between 59e721c and eb2ef69.

📒 Files selected for processing (2)
  • extensions/plugin-history-sync/src/historySyncPlugin.spec.ts
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Restored behavior so the fallback callback runs only when no route matches the current path, avoiding unintended side effects (e.g., spurious deep-link logging) during initialization.
  • Tests

    • Added tests confirming the fallback is called once when no initial route matches and not called when the initial location matches an available route.

Walkthrough

The changes ensure fallbackActivity in @stackflow/plugin-history-sync is invoked only when the current path does not match any configured route during initialization, preventing the fallback callback from running on successful route matches.

Changes

Cohort / File(s) Summary
Changeset Release Metadata
.changeset/fix-fallback-activity-called-on-match.md
Adds a patch changeset documenting that fallbackActivity is called only when no route matches currentPath.
Plugin Initialization Logic
extensions/plugin-history-sync/src/historySyncPlugin.tsx
Refactors overrideInitialEvents to first determine a matchedActivityRoute from currentPath and only compute/use the fallback activity when no match exists, avoiding precomputed fallback invocation for matched routes.
Test Coverage
extensions/plugin-history-sync/src/historySyncPlugin.spec.ts
Adds two Jest tests: one asserting fallbackActivity is not called when initial history location matches a route; another asserting it is called once when no route matches the initial location.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing the fallbackActivity callback to only be called when no route matches, which aligns with the changeset and test additions.
Description check ✅ Passed The description is directly related to the changeset, explaining the regression from #610, the behavioral fix, and the test coverage added.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch edward_karrot/check-slack-bug

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@extensions/plugin-history-sync/src/historySyncPlugin.spec.ts`:
- Around line 173-219: The tests use a fallbackActivity mock that must match the
typed signature (args: { initialContext: any }) => K; update both jest.fn mocks
in the failing tests to accept the callback arg (e.g. jest.fn((_args) => "Home")
or jest.fn(({ initialContext }) => "Home")) so the mock is assignable to the
fallbackActivity parameter passed into historySyncPlugin when calling stackflow;
ensure both occurrences in the tests that define fallbackActivity are changed
accordingly.

In `@extensions/plugin-history-sync/src/historySyncPlugin.tsx`:
- Around line 240-246: The code calls options.fallbackActivity({ initialContext
}) inside the activityRoutes.find predicate causing repeated evaluation; compute
the fallback once into a const (e.g., const fallbackName =
options.fallbackActivity({ initialContext })) before computing
targetActivityRoute and then use fallbackName in the find call instead of
invoking options.fallbackActivity again; update the targetActivityRoute
expression that references matchedActivityRoute, activityRoutes.find(...), and
options.fallbackActivity to use the cached fallbackName.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 06ecf61d-1de3-42ff-b276-b37c308c79f9

📥 Commits

Reviewing files that changed from the base of the PR and between 163e864 and 6c4ebbb.

📒 Files selected for processing (3)
  • .changeset/fix-fallback-activity-called-on-match.md
  • extensions/plugin-history-sync/src/historySyncPlugin.spec.ts
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx

Comment on lines +173 to +219
test("historySyncPlugin - 초기에 매칭하는 라우트가 있으면 fallbackActivity 콜백을 호출하지 않습니다", async () => {
history = createMemoryHistory({
initialEntries: ["/articles/123"],
});

const fallbackActivity = jest.fn(() => "Home");

stackflow({
activityNames: ["Home", "Article"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: "/home",
Article: "/articles/:articleId",
},
fallbackActivity,
}),
],
});

expect(fallbackActivity).not.toHaveBeenCalled();
});

test("historySyncPlugin - 초기에 매칭하는 라우트가 없으면 fallbackActivity 콜백을 호출합니다", async () => {
history = createMemoryHistory({
initialEntries: ["/non-existent-path"],
});

const fallbackActivity = jest.fn(() => "Home");

stackflow({
activityNames: ["Home", "Article"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: "/home",
Article: "/articles/:articleId",
},
fallbackActivity,
}),
],
});

expect(fallbackActivity).toHaveBeenCalled();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Make the fallback mocks accept the callback argument.

fallbackActivity is typed as (args: { initialContext: any }) => K, but both new tests use jest.fn(() => "Home"). That no-arg mock is not assignable here, which matches the TS2322 failure in CI. Please change both mocks to accept the parameter, e.g. jest.fn((_args) => "Home") or jest.fn(({ initialContext }) => "Home").

🛠️ Suggested fix
-    const fallbackActivity = jest.fn(() => "Home");
+    const fallbackActivity = jest.fn((_args) => "Home");
@@
-    const fallbackActivity = jest.fn(() => "Home");
+    const fallbackActivity = jest.fn((_args) => "Home");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("historySyncPlugin - 초기에 매칭하는 라우트가 있으면 fallbackActivity 콜백을 호출하지 않습니다", async () => {
history = createMemoryHistory({
initialEntries: ["/articles/123"],
});
const fallbackActivity = jest.fn(() => "Home");
stackflow({
activityNames: ["Home", "Article"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: "/home",
Article: "/articles/:articleId",
},
fallbackActivity,
}),
],
});
expect(fallbackActivity).not.toHaveBeenCalled();
});
test("historySyncPlugin - 초기에 매칭하는 라우트가 없으면 fallbackActivity 콜백을 호출합니다", async () => {
history = createMemoryHistory({
initialEntries: ["/non-existent-path"],
});
const fallbackActivity = jest.fn(() => "Home");
stackflow({
activityNames: ["Home", "Article"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: "/home",
Article: "/articles/:articleId",
},
fallbackActivity,
}),
],
});
expect(fallbackActivity).toHaveBeenCalled();
});
test("historySyncPlugin - 초기에 매칭하는 라우트가 있으면 fallbackActivity 콜백을 호출하지 않습니다", async () => {
history = createMemoryHistory({
initialEntries: ["/articles/123"],
});
const fallbackActivity = jest.fn((_args) => "Home");
stackflow({
activityNames: ["Home", "Article"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: "/home",
Article: "/articles/:articleId",
},
fallbackActivity,
}),
],
});
expect(fallbackActivity).not.toHaveBeenCalled();
});
test("historySyncPlugin - 초기에 매칭하는 라우트가 없으면 fallbackActivity 콜백을 호출합니다", async () => {
history = createMemoryHistory({
initialEntries: ["/non-existent-path"],
});
const fallbackActivity = jest.fn((_args) => "Home");
stackflow({
activityNames: ["Home", "Article"],
plugins: [
historySyncPlugin({
history,
routes: {
Home: "/home",
Article: "/articles/:articleId",
},
fallbackActivity,
}),
],
});
expect(fallbackActivity).toHaveBeenCalled();
});
🧰 Tools
🪛 GitHub Actions: Build

[error] 189-189: TypeScript declaration build failed (tsc). TS2322: Type 'Mock<string, [], any>' is not assignable to type '(args: { initialContext: any; }) => "Home" | "Article"'.

🪛 GitHub Actions: Integration

[error] 189-189: tsc (build:dts) failed with TypeScript error TS2322: Type 'Mock<string, [], any>' is not assignable to type '(args: { initialContext: any; }) => "Home" | "Article"'.

🪛 GitHub Actions: Release

[error] 189-189: TS2322: Type 'Mock<string, [], any>' is not assignable to type '(args: { initialContext: any; }) => "Home" | "Article"'. (tsc --emitDeclarationOnly, step @stackflow/plugin-history-sync::tsc)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extensions/plugin-history-sync/src/historySyncPlugin.spec.ts` around lines
173 - 219, The tests use a fallbackActivity mock that must match the typed
signature (args: { initialContext: any }) => K; update both jest.fn mocks in the
failing tests to accept the callback arg (e.g. jest.fn((_args) => "Home") or
jest.fn(({ initialContext }) => "Home")) so the mock is assignable to the
fallbackActivity parameter passed into historySyncPlugin when calling stackflow;
ensure both occurrences in the tests that define fallbackActivity are changed
accordingly.

Comment thread extensions/plugin-history-sync/src/historySyncPlugin.tsx Outdated
…ivity

The K type parameter on historySyncPlugin is constrained to a string
literal union of activity names. Using jest.fn(() => "Home") infers
Mock<string, ...>, widening the return to string and failing the
assignability check. Annotate the return type so the literal narrows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 30, 2026

  • @stackflow/demo

    yarn add https://pkg.pr.new/@stackflow/link@707.tgz
    
    yarn add https://pkg.pr.new/@stackflow/plugin-history-sync@707.tgz
    
    yarn add https://pkg.pr.new/@stackflow/plugin-preload@707.tgz
    

commit: eb2ef69

…predicate

Calling options.fallbackActivity inside the activityRoutes.find predicate
caused it to fire once per route iteration. Hoist the call into an IIFE
so it runs at most once per overrideInitialEvents invocation when no
route matches.

Tightened the regression test to assert exactly one invocation per
plugin instance instead of the looser "any call" assertion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@orionmiz orionmiz merged commit 2c5786a into main Apr 30, 2026
6 of 9 checks passed
@orionmiz orionmiz deleted the edward_karrot/check-slack-bug branch April 30, 2026 09:38
@coderabbitai coderabbitai Bot mentioned this pull request Apr 30, 2026
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