Skip to content

Conversation

@ENvironmentSet
Copy link
Collaborator

@ENvironmentSet ENvironmentSet commented Dec 23, 2025

  • An option skipDefaultHistorySetupTransition is added to use in cases where sequential transition entrances are awkward

@changeset-bot
Copy link

changeset-bot bot commented Dec 23, 2025

🦋 Changeset detected

Latest commit: ec1c07d

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 Minor

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
Copy link

coderabbitai bot commented Dec 23, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a new option to skip the default history setup transition in the plugin history sync plugin, providing more control over initial navigation state handling.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds a public route descriptor option to skip the default-history setup transition and updates history-sync initialization to support a normalized defaultHistory descriptor, conditional flattened vs sequenced event construction, unified event timing, and expanded restoration/navigation handling for root and step activities.

Changes

Cohort / File(s) Summary
Changeset Entry
\.changeset/shiny-ears-draw.md
New changeset documenting a minor bump for @stackflow/plugin-history-sync and the skipDefaultHistorySetupTransition option.
Plugin: history sync
extensions/plugin-history-sync/src/historySyncPlugin.tsx
Reworked initial-history handling: replaced inline defaultHistory processing with interpretDefaultHistoryOption, added historyEntryToEvents and createTargetActivityPushEvent, unified event timestamp computation, and introduced conditional initialization paths when skipDefaultHistorySetupTransition is set. Expanded onInit, onBeforePush, and onBeforeReplace to better restore/resolve root vs non-root and per-step navigation; exported HistoryEntry and new plugin option.
Route type and utilities
extensions/plugin-history-sync/src/RouteLike.ts
defaultHistory now may return HistoryEntry[] or a DefaultHistoryDescriptor (entries + optional skipDefaultHistorySetupTransition). Added DefaultHistoryDescriptor type and interpretDefaultHistoryOption(option, params) helper and exported them.
Manifest / package context
package.json, changelog context via changeset
Package manifest/changelog updated via the new changeset entry.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant HistorySyncPlugin
  participant HistoryStore
  participant ActivityManager

  Note over Client,HistorySyncPlugin: Initialization request with route + defaultHistory
  Client->>HistorySyncPlugin: init(targetActivityRoute, defaultHistory)
  HistorySyncPlugin->>HistorySyncPlugin: interpretDefaultHistoryOption(...)
  alt descriptor.skipDefaultHistorySetupTransition == true
    Note right of HistorySyncPlugin: Flatten descriptor.entries\nmap via historyEntryToEvents\nmark events skipEnterActiveState
    HistorySyncPlugin->>HistoryStore: write SerialNavigationProcess (flattened events)
    HistorySyncPlugin->>ActivityManager: push target activity (skipEnterActiveState)
  else
    Note right of HistorySyncPlugin: Build per-entry sequences\nmap entries -> events\nmark first Pushed to skip initial activation
    HistorySyncPlugin->>HistoryStore: write sequenced navigation processes
    HistorySyncPlugin->>ActivityManager: push target activity (normal push)
  end
  Note over HistoryStore,ActivityManager: onInit/onBeforePush/onBeforeReplace resolve paths and restore step/root navigation
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding an option to skip default history setup transition in the plugin-history-sync plugin.
Description check ✅ Passed The description is directly related to the changeset, explaining the purpose of the new option for cases with awkward sequential transitions.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dc6b7c1 and ec1c07d.

📒 Files selected for processing (1)
  • extensions/plugin-history-sync/src/RouteLike.ts

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 and usage tips.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 23, 2025

Deploying stackflow-demo with  Cloudflare Pages  Cloudflare Pages

Latest commit: b7dd300
Status: ✅  Deploy successful!
Preview URL: https://b03e08ca.stackflow-demo.pages.dev
Branch Preview URL: https://default-history-transition-o.stackflow-demo.pages.dev

View logs

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 23, 2025

commit: b7dd300

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 23, 2025

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 ec1c07d Commit Preview URL Jan 07 2026, 09:53 AM

@ENvironmentSet ENvironmentSet changed the title feat(plugin-history-sync): Add option to disable default history setup transition feat(plugin-history-sync): Add an option to skip default history setup transition Dec 26, 2025
@ENvironmentSet ENvironmentSet marked this pull request as ready for review December 26, 2025 13:27
Copy link

@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: 1

🧹 Nitpick comments (2)
extensions/plugin-history-sync/src/historySyncPlugin.tsx (2)

346-352: Consider simplifying isFirstPush tracking.

The isFirstPush variable is used to track whether we're processing the first Pushed event, but the logic can be simplified since there's only one Pushed event per historyEntry (at the start of the array returned by historyEntryToEvents).

The condition index === 0 && isFirstPush could be replaced with just index === 0 combined with checking if the event is the first Pushed event in the entry (which it always is, given historyEntryToEvents structure).

🔎 Proposed simplification
-            ...defaultHistory.map((historyEntry, index) => () => {
-              let isFirstPush = true;
-
-              return historyEntryToEvents(historyEntry).map((event) => {
-                if (event.name !== "Pushed") return event;
-                const skipEnterActiveState = index === 0 && isFirstPush;
-
-                isFirstPush = false;
+            ...defaultHistory.map((historyEntry, index) => () => {
+              let pushedEventProcessed = false;
+
+              return historyEntryToEvents(historyEntry).map((event) => {
+                if (event.name !== "Pushed") return event;
+                const skipEnterActiveState = index === 0 && !pushedEventProcessed;
+
+                pushedEventProcessed = true;

413-425: Consider handling StepReplaced steps differently.

Steps entered via StepReplaced are currently pushed to history using pushState. This could result in additional history entries for what were semantically replace operations. Consider using replaceState for steps where step.enteredBy.name === "StepReplaced".

🔎 Proposed differentiation
               for (const step of activity.steps) {
                 if (!step.exitedBy && step.enteredBy.name !== "Pushed") {
-                  pushState({
-                    history,
-                    pathname: template.fill(step.params),
-                    state: {
-                      activity: activity,
-                      step: step,
-                    },
-                    useHash: options.useHash,
-                  });
+                  const statePayload = {
+                    history,
+                    pathname: template.fill(step.params),
+                    state: {
+                      activity: activity,
+                      step: step,
+                    },
+                    useHash: options.useHash,
+                  };
+                  if (step.enteredBy.name === "StepReplaced") {
+                    replaceState(statePayload);
+                  } else {
+                    pushState(statePayload);
+                  }
                 }
               }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 57fd2da and a210e28.

📒 Files selected for processing (2)
  • .changeset/shiny-ears-draw.md
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Write source in TypeScript with strict typing enabled across the codebase

Files:

  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
extensions/plugin-*/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Plugins must implement only the documented lifecycle hooks (onInit, onBeforePush/onPushed, onBeforePop/onPopped, onBeforeReplace/onReplaced, onBeforeStepPush/onStepPushed, onBeforeStepPop/onStepPopped, onBeforeStepReplace/onStepReplaced, onChanged)

Files:

  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
.changeset/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Include a Changeset entry for any user-facing package change

Files:

  • .changeset/shiny-ears-draw.md
🧬 Code graph analysis (1)
extensions/plugin-history-sync/src/historySyncPlugin.tsx (6)
extensions/plugin-history-sync/src/RouteLike.ts (1)
  • HistoryEntry (15-24)
core/src/event-types/PushedEvent.ts (1)
  • PushedEvent (3-14)
core/src/event-types/StepPushedEvent.ts (1)
  • StepPushedEvent (3-13)
extensions/plugin-history-sync/src/makeTemplate.ts (3)
  • makeTemplate (48-110)
  • urlSearchParamsToMap (9-17)
  • pathToUrl (5-7)
extensions/plugin-history-sync/src/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts (1)
  • DefaultHistoryActivityActivationMonitor (12-61)
extensions/plugin-history-sync/src/historyState.ts (3)
  • parseState (39-45)
  • replaceState (65-81)
  • pushState (47-63)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: stackflow-docs
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (6)
.changeset/shiny-ears-draw.md (1)

1-5: LGTM!

The changeset entry correctly documents a minor version bump for the new skipDefaultHistorySetupTransition option. As per coding guidelines, user-facing package changes include a changeset entry.

extensions/plugin-history-sync/src/historySyncPlugin.tsx (5)

31-31: LGTM!

The HistoryEntry type import is appropriately added to support the new helper function's type signature.


67-67: LGTM!

The new optional skipDefaultHistorySetupTransition property is well-named and properly typed.


297-315: LGTM!

The createTargetActivityPushEvent helper cleanly encapsulates target activity event creation with appropriate fallback handling for parameter parsing.


370-377: LGTM!

The event timing calculation correctly assigns ascending timestamps, ensuring proper event ordering with consistent relative offsets from a single captured timestamp.


284-295: Consider explicitly setting targetActivityId on StepPushed events to improve code clarity.

The StepPushed events generated for additionalSteps don't include targetActivityId. While optional per the type definition and handled by fallback to the latest active activity, explicitly setting this field would make the association clearer and more robust, especially in scenarios involving multiple simultaneous activity initializations. The Pushed event's activityId should be captured and reused for the corresponding StepPushed events.

Copy link

@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: 1

🤖 Fix all issues with AI agents
In @extensions/plugin-history-sync/src/historySyncPlugin.tsx:
- Around line 391-393: The code uses a non-null assertion when assigning const
match = activityRoutes.find((r) => r.activityName === activity.name)! which will
throw if no route is found; change this to handle undefined by removing the `!`,
checking if match is falsy, and then either skip initialization for that
activity or throw a clearer error/log (e.g., processLogger.warn or throw new
Error with activity.name) so misconfigured activities don’t crash
initialization; update any subsequent use of match to be inside the guarded
branch.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a210e28 and 41202b2.

📒 Files selected for processing (2)
  • extensions/plugin-history-sync/src/RouteLike.ts
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Write source in TypeScript with strict typing enabled across the codebase

Files:

  • extensions/plugin-history-sync/src/RouteLike.ts
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
extensions/plugin-*/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Plugins must implement only the documented lifecycle hooks (onInit, onBeforePush/onPushed, onBeforePop/onPopped, onBeforeReplace/onReplaced, onBeforeStepPush/onStepPushed, onBeforeStepPop/onStepPopped, onBeforeStepReplace/onStepReplaced, onChanged)

Files:

  • extensions/plugin-history-sync/src/RouteLike.ts
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: stackflow-docs
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (5)
extensions/plugin-history-sync/src/RouteLike.ts (1)

13-13: LGTM! Clean addition of the feature flag.

The optional boolean property is properly typed and clearly named.

extensions/plugin-history-sync/src/historySyncPlugin.tsx (4)

31-31: LGTM! Import correctly adds HistoryEntry type.

The import is properly used for typing the historyEntryToEvents helper function.


340-340: Address or remove the TODO comment.

The TODO states "Initial push must be marked as skipEnterActiveState: true", but lines 369-380 already handle marking the first push event with skipEnterActiveState. This TODO may be outdated.

Please verify whether:

  1. The TODO is still valid and needs addressing, or
  2. The TODO can be removed since lines 369-380 handle the requirement

362-380: LGTM! Event date calculation ensures proper chronological ordering.

The logic correctly:

  • Assigns timestamps in chronological order (earlier events get earlier timestamps)
  • Identifies and marks the first push event with skipEnterActiveState

257-292: Verify that each default history entry should compute its own path from its activityParams rather than sharing currentPath.

In historyEntryToEvents (line 274), all synthetic history entries are assigned the same path: currentPath, regardless of their individual activityName and activityParams.

The standard pattern in this codebase (lines 675, 698) is to compute paths dynamically: find the route matching the activityName, then use template.fill(activityParams) to generate the path. Since historyEntryToEvents pre-sets the path, the lifecycle hooks' path computation logic (lines 665–680) is skipped for these entries.

Each history entry represents a distinct activity with its own params—they should likely have distinct paths computed from their individual params using the appropriate route template, consistent with how normal activity pushes work.

Copy link

@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: 1

🤖 Fix all issues with AI agents
In @extensions/plugin-history-sync/src/historySyncPlugin.tsx:
- Around line 263-285: historyEntryToEvents incorrectly assigns path:
currentPath to all default history activities; instead derive each entry's own
path from its activityName and activityParams. Update historyEntryToEvents to
compute a per-entry path (e.g., call an existing helper like
buildPathForActivity(activityName, activityParams) or add a small helper
derivePathForActivity) and use that value for activityContext.path rather than
currentPath, ensuring each default history event reflects its own activity
route.
🧹 Nitpick comments (1)
extensions/plugin-history-sync/src/RouteLike.ts (1)

49-49: Typo in variable name: entiresOrDescriptorentriesOrDescriptor.

The variable is misspelled as "entires" instead of "entries".

✏️ Suggested fix
-  const entiresOrDescriptor = option(params);
+  const entriesOrDescriptor = option(params);

-  if (Array.isArray(entiresOrDescriptor)) {
-    return { entries: entiresOrDescriptor };
+  if (Array.isArray(entriesOrDescriptor)) {
+    return { entries: entriesOrDescriptor };
   }

-  return entiresOrDescriptor;
+  return entriesOrDescriptor;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 41202b2 and dc6b7c1.

📒 Files selected for processing (2)
  • extensions/plugin-history-sync/src/RouteLike.ts
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Write source in TypeScript with strict typing enabled across the codebase

Files:

  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
  • extensions/plugin-history-sync/src/RouteLike.ts
extensions/plugin-*/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Plugins must implement only the documented lifecycle hooks (onInit, onBeforePush/onPushed, onBeforePop/onPopped, onBeforeReplace/onReplaced, onBeforeStepPush/onStepPushed, onBeforeStepPop/onStepPopped, onBeforeStepReplace/onStepReplaced, onChanged)

Files:

  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
  • extensions/plugin-history-sync/src/RouteLike.ts
🧬 Code graph analysis (1)
extensions/plugin-history-sync/src/historySyncPlugin.tsx (9)
extensions/plugin-history-sync/src/RouteLike.ts (2)
  • interpretDefaultHistoryOption (39-56)
  • HistoryEntry (22-31)
core/src/event-types/PushedEvent.ts (1)
  • PushedEvent (3-14)
core/src/event-types/StepPushedEvent.ts (1)
  • StepPushedEvent (3-13)
extensions/plugin-history-sync/src/index.ts (1)
  • makeTemplate (3-3)
extensions/plugin-history-sync/src/makeTemplate.ts (1)
  • makeTemplate (48-110)
extensions/plugin-history-sync/src/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts (1)
  • DefaultHistoryActivityActivationMonitor (12-61)
integrations/react/src/stable/stackflow.tsx (1)
  • getStack (287-292)
core/src/makeCoreStore.ts (1)
  • getStack (88-90)
extensions/plugin-history-sync/src/historyState.ts (3)
  • parseState (39-45)
  • replaceState (65-81)
  • pushState (47-63)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: stackflow-docs
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (5)
extensions/plugin-history-sync/src/RouteLike.ts (1)

17-20: LGTM!

The DefaultHistoryDescriptor type and interpretDefaultHistoryOption function provide a clean abstraction for handling both array and descriptor forms of default history. The normalization logic correctly handles undefined, array, and descriptor cases.

Also applies to: 39-56

extensions/plugin-history-sync/src/historySyncPlugin.tsx (4)

319-365: LGTM!

The conditional logic correctly implements the skipDefaultHistorySetupTransition feature:

  • When true, all events are batched into a single process step with skipEnterActiveState: true
  • When false, each history entry creates a separate process step for sequential transitions

The self-referencing initialSetupProcess! is safe since captureNavigationOpportunity is called after the assignment completes.


367-385: LGTM!

The event date calculation correctly ensures chronological ordering by assigning earlier timestamps to earlier events. The first Pushed event is marked with skipEnterActiveState: true to prevent the initial activity from animating in.


390-436: LGTM!

The state initialization logic correctly handles the case when history state is absent:

  • Root activities use replaceState to avoid adding to browser history
  • Non-root activities use pushState to enable back navigation
  • Steps are pushed only if not exited and not the initial step (where enteredBy.name !== "Pushed")

31-35: LGTM!

The imports correctly bring in the new HistoryEntry type and interpretDefaultHistoryOption function from the updated RouteLike module.

@ENvironmentSet ENvironmentSet enabled auto-merge (squash) January 7, 2026 09:48
@ENvironmentSet ENvironmentSet merged commit a7d0c01 into main Jan 7, 2026
5 of 8 checks passed
@ENvironmentSet ENvironmentSet deleted the default-history-transition-opt-out branch January 7, 2026 09:49
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.

2 participants