Skip to content

Conversation

@ENvironmentSet
Copy link
Collaborator

@ENvironmentSet ENvironmentSet commented Sep 19, 2025

Changes

plugin-history-sync

  • An interface NavigationProcess modeling a navigation process is added
  • useInitialSetupProcessStatus() hook is added to provide users easy way of accessing status of the stack initialization navigation process
  • useIsRenderInTransition() hook is added to allow users to figure out whether current render is for a navigation transition process.

core

  • Stack exposes events used to construct itself by .events property
    • events are ordered in evaluation order

@changeset-bot
Copy link

changeset-bot bot commented Sep 19, 2025

🦋 Changeset detected

Latest commit: 6a24370

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

This PR includes changesets to release 2 packages
Name Type
@stackflow/core Minor
@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 Sep 19, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Stack now exposes a chronological events log.
    • Plugin provides initialization status, enabling apps to defer logging/fetching during initial navigation.
    • Added activation count tracking and context; introduced useIsActivatedActivity hook to detect if the current activity has been activated.
  • Improvements

    • More predictable initial history setup and sequential navigation handling.
  • Chores

    • Added react18-use as a dependency.

Walkthrough

Adds a required events: DomainEvent[] to core Stack and records DomainEvents throughout reducers and aggregation. Introduces navigation-process abstractions (NavigationProcess, SerialNavigationProcess), mutex/publisher primitives, activity-activation monitoring and publishing, a useIsActivatedActivity hook, refactors history-sync plugin to use the new pipeline, updates tests, adds deps and changesets.

Changes

Cohort / File(s) Summary
Core: Stack + reducers + aggregate
core/src/Stack.ts, core/src/activity-utils/makeStackReducer.ts, core/src/aggregate.ts
Adds required events: DomainEvent[] to Stack. Reducers and noop now append events into stack.events. Aggregate seeds events: [], sorts input events by eventDate, and returns stacks carrying events.
Core: Tests
core/src/aggregate.spec.ts, core/src/produceEffects.spec.ts
Tests updated to include events in fixtures/expected outputs; use deterministic ordering/uniq helpers and adjust assertions for the new events field.
Plugin: Navigation event types & process API
extensions/plugin-history-sync/src/NavigationEvent.ts, extensions/plugin-history-sync/src/NavigationProcess/NavigationProcess.ts
Adds navigation event unions and type guards, NavigationProcess interface, NavigationProcessStatus constants, and isTerminated helper.
Plugin: Serial navigation executor
extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts
Implements SerialNavigationProcess to run queued navigations sequentially, validate dispatched events against history/base events, manage status, and emit Pushed/StepPushed events per opportunity.
Plugin: Concurrency & pub/sub primitives
extensions/plugin-history-sync/src/Mutex.ts, extensions/plugin-history-sync/src/Publisher.ts
Adds an async Mutex (acquire / runExclusively) and a Publisher<T> with subscribe/unsubscribe and serialized publish that awaits all subscriber results.
Plugin: Activation monitoring and context
extensions/plugin-history-sync/src/ActivityActivationCountsContext.ts, .../ActivityActivationMonitor/ActivityActivationMonitor.ts, .../CountPublishingActivityActivationMonitor.ts, .../DefaultHistoryActivityActivationMonitor.ts, extensions/plugin-history-sync/src/useIsActivatedActivity.ts
Introduces ActivityActivationCountsContext, ActivityActivationMonitor interface, CountPublishingActivityActivationMonitor decorator, DefaultHistoryActivityActivationMonitor implementation, and useIsActivatedActivity hook.
Plugin: History sync integration & exports
extensions/plugin-history-sync/src/historySyncPlugin.tsx, extensions/plugin-history-sync/src/index.ts
Refactors plugin to use navigation-process pipeline for initial setup, exposes actions (getStack, dispatchEvent, push, stepPush), publishes initial-setup status and activation counts via Publisher and contexts, and re-exports useIsActivatedActivity.
Plugin: Dependencies & PnP state
extensions/plugin-history-sync/package.json, .pnp.cjs
Adds runtime dependency react18-use and wires it into Yarn PnP runtime/virtual state.
Release metadata / changesets
.changeset/crazy-rats-accept.md, .changeset/sad-adults-prove.md
Adds changesets documenting minor releases for core (exposes Stack.events) and plugin (exposes initialization status).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant Plugin as historySyncPlugin
  participant Proc as SerialNavigationProcess
  participant Core as @stackflow/core
  participant Mon as ActivationMonitors
  participant Pub as Publisher

  App->>Plugin: onInit()
  Plugin->>Proc: captureNavigationOpportunity(stack=null, t0)
  alt pending navigations exist
    Proc-->>Plugin: [Pushed/StepPushed events]
    Plugin->>Core: dispatchEvent(events)
    Core-->>Plugin: updated Stack (includes events[])
    Plugin->>Mon: captureStackChange(stack)
    Plugin->>Pub: publish(status/progress)
  else none
    Proc-->>Plugin: []
    Plugin->>Pub: publish(status/succeeded)
  end
Loading
sequenceDiagram
  autonumber
  participant UI as UI Subscriber
  participant Pub as Publisher
  participant Mx as Mutex
  participant Sub1 as Subscriber A
  participant Sub2 as Subscriber B

  UI->>Pub: subscribe(handler)
  note over Pub: stores handler snapshot
  UI->>Pub: publish(value)
  Pub->>Mx: runExclusively(...)
  activate Mx
  Mx-->>Pub: lock acquired
  par notify all subscribers
    Pub->>Sub1: handler(value)
    Sub1-->>Pub: resolved/rejected
    Pub->>Sub2: handler(value)
    Sub2-->>Pub: resolved/rejected
  end
  Pub-->>UI: Promise.allSettled results
  deactivate Mx
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ 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 clearly indicates that this feature adds support in both core and plugin-history-sync for exposing the initial stack setup navigation process, succinctly summarizing the primary change without unnecessary detail or generic wording.
Description Check ✅ Passed The description succinctly outlines the new plugin hooks and core Stack change, directly matching the changes in the diff and remaining focused on relevant additions without veering off-topic.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch default-history-setup-transition-state

📜 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 c7ee042 and 6a24370.

📒 Files selected for processing (1)
  • extensions/plugin-history-sync/src/useIsActivatedActivity.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • extensions/plugin-history-sync/src/useIsActivatedActivity.ts
⏰ 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

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.

@ENvironmentSet
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Sep 19, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Sep 19, 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 6a24370 Commit Preview URL Oct 02 2025, 02:38 AM

@ENvironmentSet ENvironmentSet changed the title [WIP]: Initial stack setup process exposure [WIP] feat(core, plugin-history-sync): Initial stack setup process exposure Sep 19, 2025
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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/src/activity-utils/makeStackReducer.ts (1)

132-144: Resumed event not logged when replaying pausedEvents

When resuming from a paused state, the Resumed event itself is omitted from stack.events, breaking the “append each processed event” guarantee.

-        const { pausedEvents, ...rest } = stack;
-        return pausedEvents.reduce(reducer, {
-          ...rest,
-          globalTransitionState: "idle",
-        });
+        const { pausedEvents, ...rest } = stack;
+        return pausedEvents.reduce(reducer, {
+          ...rest,
+          globalTransitionState: "idle",
+          // Log the Resumed event in processing order
+          events: [...rest.events, event],
+        });
🧹 Nitpick comments (18)
extensions/plugin-history-sync/src/useIsEntryActivity.ts (1)

3-6: Ensure the hook always returns a boolean

The current return can be undefined. Coerce to boolean (and avoid leaking any behavior).

 export function useIsEntryActivity(): boolean {
   const { context } = useActivity();
-  return (context as any)?.isEntryActivity;
+  return Boolean((context as any)?.isEntryActivity);
 }
demo/src/stackflow/stackflow.config.ts (1)

25-31: Default history now pre-includes an Article; verify duplication is intended

When landing on an Article route, this will likely produce two Article activities (one from defaultHistory + the target route). If that’s not desired, drop the hard-coded Article or make it conditional.

       defaultHistory: () => [
         {
           activityName: "Main",
           activityParams: {},
         },
-        {
-          activityName: "Article",
-          activityParams: {
-            articleId: 60547101,
-            title: "울랄라",
-          },
-        },
       ],

Also, decode accesses params.title but the path doesn’t include :title; confirm you rely on query params for it.

extensions/plugin-history-sync/src/withResolvers.ts (1)

1-15: Match Promise resolver types to lib definitions

Widen resolver signatures to align with TS Promise constructor (T | PromiseLike<T> and any), avoiding assignability edge cases.

 export function withResolvers<T>(): {
   promise: Promise<T>;
-  resolve: (value: T) => void;
-  reject: (reason?: unknown) => void;
+  resolve: (value: T | PromiseLike<T>) => void;
+  reject: (reason?: any) => void;
 } {
-  let resolve!: (value: T) => void;
-  let reject!: (reason?: unknown) => void;
+  let resolve!: (value: T | PromiseLike<T>) => void;
+  let reject!: (reason?: any) => void;
 
   const promise = new Promise<T>((_resolve, _reject) => {
     resolve = _resolve;
     reject = _reject;
   });
extensions/plugin-history-sync/src/NavigationEvent.ts (1)

19-29: Minor cleanup: avoid repetitive string comparisons

Use a Set for membership; easier to maintain if events grow.

-export function isNavigationEvent(
-  event: DomainEvent,
-): event is NavigationEvent {
-  return (
-    event.name === "Pushed" ||
-    event.name === "Popped" ||
-    event.name === "Replaced" ||
-    event.name === "StepPushed" ||
-    event.name === "StepPopped" ||
-    event.name === "StepReplaced"
-  );
-}
+const NAV_EVENT_NAMES = new Set([
+  "Pushed",
+  "Popped",
+  "Replaced",
+  "StepPushed",
+  "StepPopped",
+  "StepReplaced",
+] as const);
+
+export function isNavigationEvent(event: DomainEvent): event is NavigationEvent {
+  return NAV_EVENT_NAMES.has(event.name as (typeof NAV_EVENT_NAMES extends Set<infer U> ? U : never));
+}
extensions/plugin-history-sync/src/Mutex.ts (2)

2-7: Naming nit: clarify queue tail field

“latestlyBookedSession” is hard to parse. Consider a clearer name like “tail” or “lastBookedSession”. No behavior change.

 export class Mutex {
-  private latestlyBookedSession: Promise<void> = Promise.resolve();
+  private lastBookedSession: Promise<void> = Promise.resolve();
 
   acquire(): Promise<{ release: () => void }> {
     return new Promise((resolveSessionHandle) => {
-      this.latestlyBookedSession = this.latestlyBookedSession.then(
+      this.lastBookedSession = this.lastBookedSession.then(
         () =>
           new Promise((resolveSession) =>
             resolveSessionHandle({ release: () => resolveSession() }),
           ),
       );
     });

15-23: Optional: accept sync thunks

Broaden the thunk type so callers can pass sync or async work; your await already handles both.

-  async runExclusively<T>(thunk: () => Promise<T>): Promise<Awaited<T>> {
+  async runExclusively<T>(thunk: () => T | Promise<T>): Promise<Awaited<T>> {
     const { release } = await this.acquire();
     try {
       return await thunk();
     } finally {
       release();
     }
   }
extensions/plugin-history-sync/src/InitialSetupProcessStatusContext.ts (1)

10-12: Use explicit null check to avoid false negatives

If NavigationProcessStatus ever becomes a falsy value, !status would throw incorrectly.

-  if (!status) {
+  if (status === null) {
     throw new Error("InitialSetupProcessStatusContext is not found");
   }
extensions/plugin-history-sync/src/NavigationProcess/StatusPublishingNavigationProcess.ts (2)

45-49: Status equality check may republish unnecessarily

!== compares by reference. If NavigationProcessStatus is an object (not a string/enum), identical statuses with new object identities will trigger publishes.

If status is an object, either:

  • make NavigationProcessStatus a stable string/enum, or
  • compare by a stable key (e.g., status.kind), or
  • add a shallow/deep compare utility.

Example (if kind exists):

-  private refreshCurrentStatus(nextStatus: NavigationProcessStatus) {
-    if (nextStatus !== this.currentStatus) {
+  private refreshCurrentStatus(nextStatus: NavigationProcessStatus) {
+    if (nextStatus.kind !== this.currentStatus.kind) {
       this.currentStatus = nextStatus;
       this.publisher.publish(nextStatus);
     }
   }

8-12: Minor: mark fields readonly

These dependencies aren’t reassigned; mark as readonly for intent.

 export class StatusPublishingNavigationProcess implements NavigationProcess {
-  private process: NavigationProcess;
-  private publisher: Publisher<NavigationProcessStatus>;
+  private readonly process: NavigationProcess;
+  private readonly publisher: Publisher<NavigationProcessStatus>;
   private currentStatus: NavigationProcessStatus;
core/src/activity-utils/makeStackReducer.ts (1)

83-85: Name noop is misleading now that it logs events

It no longer “does nothing.” Consider renaming for clarity.

-function noop(stack: Stack, event: DomainEvent) {
+function logOnly(stack: Stack, event: DomainEvent) {
   return { ...stack, events: [...stack.events, event] };
 }

And update call sites:

-    Pushed: withPauseReducer(withActivitiesReducer(noop, context)),
+    Pushed: withPauseReducer(withActivitiesReducer(logOnly, context)),
extensions/plugin-history-sync/src/NavigationProcess/NavigationProcess.ts (1)

12-27: Consider using an enum instead of const object for better type safety

While the current implementation works, using a TypeScript enum would provide better type safety and intellisense support.

-export const NavigationProcessStatus = {
-  IDLE: "idle",
-  PROGRESS: "progress",
-  SUCCEEDED: "succeeded",
-  FAILED: "failed",
-} as const;
-
-export type NavigationProcessStatus =
-  (typeof NavigationProcessStatus)[keyof typeof NavigationProcessStatus];
+export enum NavigationProcessStatus {
+  IDLE = "idle",
+  PROGRESS = "progress",
+  SUCCEEDED = "succeeded",
+  FAILED = "failed",
+}
extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts (2)

32-37: Add null check before accessing stack.globalTransitionState

The condition checks stack !== null but then accesses stack.globalTransitionState in a combined condition. This could be clearer and safer.

-    if (isTerminated(this.status)) return [];
-    if (stack !== null && stack.globalTransitionState !== "idle") return [];
+    if (isTerminated(this.status)) return [];
+    if (stack?.globalTransitionState !== "idle") return [];

62-65: Inefficient use of splice and flatMap

Using splice(0, 1) returns an array with a single element, then using flatMap on it is unnecessary. Consider simplifying.

-    const nextNavigation = this.pendingNavigations.splice(0, 1);
-    const nextNavigationEvents = nextNavigation.flatMap((navigation) =>
-      navigation(navigationTime),
-    );
+    const nextNavigation = this.pendingNavigations.shift();
+    if (!nextNavigation) return [];
+    const nextNavigationEvents = nextNavigation(navigationTime);
extensions/plugin-history-sync/src/NavigationProcess/CompositNavigationProcess.ts (1)

51-68: getStatus creates derived process as side effect

The getStatus method modifies state by creating the derived process. This violates the principle that getter methods should be side-effect free.

Consider creating the derived process in captureNavigationOpportunity only, or rename the method to indicate it has side effects (e.g., ensureDerivedAndGetStatus).

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

443-447: Null-safe navigation operator but no null return handling

The code uses initialSetupProcess?.captureNavigationOpportunity but doesn't handle the case where initialSetupProcess might be null and thus return undefined.

     initialSetupProcess
       ?.captureNavigationOpportunity(stack, Date.now())
-      .forEach((event) =>
+      ?.forEach((event) =>
         event.name === "Pushed" ? push(event) : stepPush(event),
       );

658-664: Duplicate code in onChanged handler

The onChanged handler has identical logic to onInit for handling navigation events. Consider extracting to a shared function.

+    const processNavigationEvents = (
+      stack: any,
+      push: any,
+      stepPush: any
+    ) => {
+      initialSetupProcess
+        ?.captureNavigationOpportunity(stack, Date.now())
+        ?.forEach((event) =>
+          event.name === "Pushed" ? push(event) : stepPush(event),
+        );
+    };

     // In onInit:
-    initialSetupProcess
-      ?.captureNavigationOpportunity(stack, Date.now())
-      .forEach((event) =>
-        event.name === "Pushed" ? push(event) : stepPush(event),
-      );
+    processNavigationEvents(stack, push, stepPush);

     // In onChanged:
-    initialSetupProcess
-      ?.captureNavigationOpportunity(getStack(), Date.now())
-      .forEach((event) =>
-        event.name === "Pushed" ? push(event) : stepPush(event),
-      );
+    processNavigationEvents(getStack(), push, stepPush);

108-123: Publisher wrapping could cause subscription timing issues

The subscribe function wraps the subscriber in an async function, but the original subscriber might not be async. This could affect error handling and timing.

     const subscribeInitialSetupProcessStatus = (
       subscriber: (status: NavigationProcessStatus) => void,
     ) => {
-      return initialSetupProcessPublisher.subscribe(async (status) =>
-        subscriber(status),
-      );
+      return initialSetupProcessPublisher.subscribe(async (status) => {
+        try {
+          await subscriber(status);
+        } catch (error) {
+          console.error('Initial setup process status subscriber error:', error);
+        }
+      });
     };

277-283: Optional callback receives complex subscription mechanism

The onInitialSetupProcessCreated callback receives a complex subscription wrapper. Document this API clearly for consumers.

Consider simplifying the API by directly passing the publisher or providing a cleaner subscription interface.

📜 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 f298988 and fb702d2.

📒 Files selected for processing (17)
  • core/src/Stack.ts (1 hunks)
  • core/src/activity-utils/makeStackReducer.ts (5 hunks)
  • core/src/aggregate.ts (1 hunks)
  • core/src/interfaces/StackflowActions.ts (1 hunks)
  • demo/src/stackflow/stackflow.config.ts (1 hunks)
  • extensions/plugin-history-sync/src/InitialSetupProcessStatusContext.ts (1 hunks)
  • extensions/plugin-history-sync/src/Mutex.ts (1 hunks)
  • extensions/plugin-history-sync/src/NavigationEvent.ts (1 hunks)
  • extensions/plugin-history-sync/src/NavigationProcess/CompositNavigationProcess.ts (1 hunks)
  • extensions/plugin-history-sync/src/NavigationProcess/NavigationProcess.ts (1 hunks)
  • extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts (1 hunks)
  • extensions/plugin-history-sync/src/NavigationProcess/StatusPublishingNavigationProcess.ts (1 hunks)
  • extensions/plugin-history-sync/src/Publisher.ts (1 hunks)
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx (6 hunks)
  • extensions/plugin-history-sync/src/index.ts (1 hunks)
  • extensions/plugin-history-sync/src/useIsEntryActivity.ts (1 hunks)
  • extensions/plugin-history-sync/src/withResolvers.ts (1 hunks)
⏰ 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). (1)
  • GitHub Check: Workers Builds: stackflow-docs
🔇 Additional comments (12)
core/src/interfaces/StackflowActions.ts (1)

2-2: LGTM — import-only change

Type-only import addition is safe and doesn’t affect the public API.

core/src/aggregate.ts (1)

30-31: LGTM: safe initialization of the new events log

Initializing events to [] ensures reducers can append without guards.

extensions/plugin-history-sync/src/index.ts (2)

3-3: LGTM: exposing initial setup process status hook

Re-export looks good and matches the new context/hook.


12-12: LGTM: useIsEntryActivity re-export

No issues.

core/src/activity-utils/makeStackReducer.ts (3)

94-95: LGTM: event logging on Initialized

Correctly appends the event; consistent with new events log.


114-115: LGTM: event logging on ActivityRegistered

Consistent with the logging strategy.


125-126: LGTM: event logging on Paused

Pause event is logged prior to transition; consistent.

extensions/plugin-history-sync/src/NavigationProcess/StatusPublishingNavigationProcess.ts (1)

1-1: Incorrect — bare import 'Publisher' is valid in this package

extensions/plugin-history-sync/tsconfig.json sets "baseUrl": "./src" and extensions/plugin-history-sync/src/Publisher.ts exists, so import type { Publisher } from "Publisher" resolves; switching to "../Publisher" is optional for explicitness/consistency.

Likely an incorrect or invalid review comment.

extensions/plugin-history-sync/src/Publisher.ts (2)

15-23: LGTM! Robust concurrent publishing implementation

The publish method correctly uses a mutex to serialize publish operations and handles all subscribers via Promise.allSettled, ensuring that a failed subscriber doesn't affect others. The snapshot of subscribers via slice() prevents concurrent modification issues.


7-13: Subscriber management OK — no leak in current usage

Publisher is only instantiated as initialSetupProcessPublisher in extensions/plugin-history-sync/src/historySyncPlugin.tsx; subscribe() returns an unsubscribe that removes the exact function reference, and useSyncExternalStore (wrapStack) will unsubscribe on unmount. If onInitialSetupProcessCreated hands the subscribe function to external callers, callers must call the returned unsubscribe to avoid leaks (document if needed).

extensions/plugin-history-sync/src/NavigationProcess/NavigationProcess.ts (1)

3-10: LGTM! Well-designed navigation process interface

The interface is clean and follows good TypeScript practices with proper return types for navigation event handling.

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

290-291: Incomplete actions destructuring in onInit

The function signature shows getStack, dispatchEvent, push, stepPush but the AI summary indicates these are the only actions now available. Verify this doesn't break existing functionality.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Sep 22, 2025

Deploying stackflow-demo with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6a24370
Status: ✅  Deploy successful!
Preview URL: https://55bd3247.stackflow-demo.pages.dev
Branch Preview URL: https://default-history-setup-transi.stackflow-demo.pages.dev

View logs

@ENvironmentSet
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Sep 28, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/src/activity-utils/makeStackReducer.ts (1)

131-149: Resumed without queued events keeps the stack stuck in paused.

Line 131: when a Resumed event arrives while globalTransitionState === "paused" but pausedEvents is empty, we now append the event to the log yet still return the stack with globalTransitionState: "paused". Any subsequent activity event will be buffered again instead of being reduced, so the stack never actually resumes unless an extra Resumed is dispatched. Please clear the pause before returning in that branch.

-        if (stack.globalTransitionState !== "paused" || !stack.pausedEvents) {
-          return { ...stack, events: [...stack.events, event] };
-        }
+        if (stack.globalTransitionState !== "paused") {
+          return { ...stack, events: [...stack.events, event] };
+        }
+
+        if (!stack.pausedEvents) {
+          return {
+            ...stack,
+            globalTransitionState: "idle",
+            events: [...stack.events, event],
+          };
+        }
🧹 Nitpick comments (8)
extensions/plugin-history-sync/src/ActivityActivationMonitor/CountPublishingActivityActivationMonitor.ts (1)

19-31: Avoid floating promise and align naming with domain (“activation” vs “focus”).

Publish returns a Promise; explicitly ignore it and use consistent variable names.

   captureActivitiesNavigation(stack: Stack): void {
-    const previousFocusCount =
+    const previousActivationCount =
       this.activityActivationMonitor.getActivationCount();

     this.activityActivationMonitor.captureActivitiesNavigation(stack);

-    const currentFocusCount =
+    const currentActivationCount =
       this.activityActivationMonitor.getActivationCount();

-    if (currentFocusCount !== previousFocusCount) {
-      this.publisher.publish(currentFocusCount);
+    if (currentActivationCount !== previousActivationCount) {
+      void this.publisher.publish(currentActivationCount);
     }
   }
extensions/plugin-history-sync/src/Publisher.ts (1)

4-4: Broaden subscriber type (sync or async), fix typo, and keep snapshot naming consistent.

This makes the API friendlier while retaining behavior; also corrects targetSubcriberstargetSubscribers.

 export class Publisher<T> {
-  private subscribers: ((value: T) => Promise<void>)[] = [];
+  private subscribers: ((value: T) => void | Promise<void>)[] = [];
   private publishLock: Mutex = new Mutex();

-  subscribe(subscriber: (value: T) => Promise<void>): () => void {
+  subscribe(subscriber: (value: T) => void | Promise<void>): () => void {
     this.subscribers.push(subscriber);

     return () => {
       this.subscribers = this.subscribers.filter((s) => s !== subscriber);
     };
   }

   publish(value: T): Promise<PromiseSettledResult<void>[]> {
-    const targetSubcribers = this.subscribers.slice();
+    const targetSubscribers = this.subscribers.slice();

     return this.publishLock.runExclusively(() =>
       Promise.allSettled(
-        targetSubcribers.map((subscriber) => subscriber(value)),
+        targetSubscribers.map((subscriber) =>
+          Promise.resolve(subscriber(value)),
+        ),
       ),
     );
   }
 }

Also applies to: 7-7, 15-22

extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts (2)

62-65: Simplify queue consumption with shift().

Clearer than splice+flatMap for a single element.

-    const nextNavigation = this.pendingNavigations.splice(0, 1);
-    const nextNavigationEvents = nextNavigation.flatMap((navigation) =>
-      navigation(navigationTime),
-    );
+    const nextNavigation = this.pendingNavigations.shift()!;
+    const nextNavigationEvents = nextNavigation(navigationTime);

85-93: Optional: use a Set for membership checks.

Building a Set of allowed ids reduces O(n²) scans in verifyNoUnknownNavigationEvents.

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

188-196: SSR safety: provide getServerSnapshot to useSyncExternalStore.

Prevents hydration mismatches during SSR. Suggest passing a stable initial value (e.g., IDLE and [] respectively).


506-513: Order of operations: dispatch follow-up events before running monitors.

Run monitors against the post-dispatch stack for immediate consistency (you also run them in onPushed/onReplaced/onPopped; this just tightens init).

-        runActivityActivationMonitors(stack);
-
-        followUpEvents?.forEach((event) =>
-          event.name === "Pushed" ? push(event) : stepPush(event),
-        );
+        followUpEvents?.forEach((event) =>
+          event.name === "Pushed" ? push(event) : stepPush(event),
+        );
+        runActivityActivationMonitors(getStack());

325-344: Do we also want to monitor the entry activity?

Monitors are added for defaultHistory Pushed events but not for the final target entry Pushed. If useIsEntryActivity() relies on counts, consider monitoring the entry as well.

-            (navigationTime) => [
-              makeEvent("Pushed", {
-                activityId: id(),
-                activityName: targetActivityRoute.activityName,
-                activityParams:
-                  makeTemplate(
-                    targetActivityRoute,
-                    options.urlPatternOptions,
-                  ).parse(currentPath) ??
-                  urlSearchParamsToMap(pathToUrl(currentPath).searchParams),
-                eventDate: navigationTime,
-                activityContext: {
-                  path: currentPath,
-                  lazyActivityComponentRenderContext: {
-                    shouldRenderImmediately: true,
-                  },
-                },
-              }),
-            ],
+            (navigationTime) => {
+              const event = makeEvent("Pushed", {
+                activityId: id(),
+                activityName: targetActivityRoute.activityName,
+                activityParams:
+                  makeTemplate(
+                    targetActivityRoute,
+                    options.urlPatternOptions,
+                  ).parse(currentPath) ??
+                  urlSearchParamsToMap(pathToUrl(currentPath).searchParams),
+                eventDate: navigationTime,
+                activityContext: {
+                  path: currentPath,
+                  lazyActivityComponentRenderContext: {
+                    shouldRenderImmediately: true,
+                  },
+                },
+              });
+              activityActivationMonitors.push(
+                new DefaultHistoryActivityActivationMonitor(
+                  event.activityId,
+                  initialSetupProcess!,
+                ),
+              );
+              return [event];
+            },

103-129: Alternative: leverage CountPublishingActivityActivationMonitor to push changes, drop manual scan/caching.

Wrapping monitors with the publishing monitor allows you to subscribe directly, avoiding manual diffing and caching logic here.

Also applies to: 163-184

📜 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 fb702d2 and 4f7bf48.

⛔ Files ignored due to path filters (2)
  • .yarn/cache/react18-use-npm-0.4.1-3dd4e3b3bc-e8d61ca4ae.zip is excluded by !**/.yarn/**, !**/*.zip
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (16)
  • .changeset/big-dodos-grow.md (1 hunks)
  • .pnp.cjs (3 hunks)
  • core/src/activity-utils/makeStackReducer.ts (6 hunks)
  • core/src/aggregate.spec.ts (54 hunks)
  • core/src/produceEffects.spec.ts (32 hunks)
  • extensions/plugin-history-sync/package.json (1 hunks)
  • extensions/plugin-history-sync/src/ActivityActivationCountsContext.ts (1 hunks)
  • extensions/plugin-history-sync/src/ActivityActivationMonitor/ActivityActivationMonitor.ts (1 hunks)
  • extensions/plugin-history-sync/src/ActivityActivationMonitor/CountPublishingActivityActivationMonitor.ts (1 hunks)
  • extensions/plugin-history-sync/src/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts (1 hunks)
  • extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts (1 hunks)
  • extensions/plugin-history-sync/src/Publisher.ts (1 hunks)
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx (7 hunks)
  • extensions/plugin-history-sync/src/index.ts (1 hunks)
  • extensions/plugin-history-sync/src/useDelayTransitionRender.ts (1 hunks)
  • extensions/plugin-history-sync/src/useIsRenderInTransition.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .changeset/big-dodos-grow.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • extensions/plugin-history-sync/src/index.ts
⏰ 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 (2)
.pnp.cjs (2)

6381-6381: Dependency entry matches runtime usage

The added react18-use mapping slots cleanly into the workspace dependency list alongside its peers. This aligns with the new hook usage and ensures PnP resolution stays consistent.


15317-15337: Virtual package wiring looks correct

The generated block properly records the physical cache location plus peer expectations ([email protected], @types/[email protected]). This should satisfy the shim’s requirements without additional tweaks.

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

📜 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 4f7bf48 and 9006a17.

📒 Files selected for processing (8)
  • extensions/plugin-history-sync/src/ActivityActivationMonitor/ActivityActivationMonitor.ts (1 hunks)
  • extensions/plugin-history-sync/src/ActivityActivationMonitor/CountPublishingActivityActivationMonitor.ts (1 hunks)
  • extensions/plugin-history-sync/src/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts (1 hunks)
  • extensions/plugin-history-sync/src/NavigationEvent.ts (1 hunks)
  • extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts (1 hunks)
  • extensions/plugin-history-sync/src/Publisher.ts (1 hunks)
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx (5 hunks)
  • extensions/plugin-history-sync/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • extensions/plugin-history-sync/src/ActivityActivationMonitor/ActivityActivationMonitor.ts
  • extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts
🧰 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/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts
  • extensions/plugin-history-sync/src/Publisher.ts
  • extensions/plugin-history-sync/src/index.ts
  • extensions/plugin-history-sync/src/ActivityActivationMonitor/CountPublishingActivityActivationMonitor.ts
  • extensions/plugin-history-sync/src/NavigationEvent.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/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts
  • extensions/plugin-history-sync/src/Publisher.ts
  • extensions/plugin-history-sync/src/index.ts
  • extensions/plugin-history-sync/src/ActivityActivationMonitor/CountPublishingActivityActivationMonitor.ts
  • extensions/plugin-history-sync/src/NavigationEvent.ts
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
🧬 Code graph analysis (5)
extensions/plugin-history-sync/src/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts (2)
extensions/plugin-history-sync/src/ActivityActivationMonitor/ActivityActivationMonitor.ts (1)
  • ActivityActivationMonitor (3-7)
extensions/plugin-history-sync/src/NavigationEvent.ts (2)
  • ActivityNavigationEvent (11-11)
  • isActivityNavigationEvent (20-28)
extensions/plugin-history-sync/src/Publisher.ts (1)
extensions/plugin-history-sync/src/Mutex.ts (1)
  • Mutex (1-24)
extensions/plugin-history-sync/src/ActivityActivationMonitor/CountPublishingActivityActivationMonitor.ts (2)
extensions/plugin-history-sync/src/ActivityActivationMonitor/ActivityActivationMonitor.ts (1)
  • ActivityActivationMonitor (3-7)
extensions/plugin-history-sync/src/Publisher.ts (1)
  • Publisher (3-24)
extensions/plugin-history-sync/src/NavigationEvent.ts (7)
core/src/event-types/PushedEvent.ts (1)
  • PushedEvent (3-14)
core/src/event-types/PoppedEvent.ts (1)
  • PoppedEvent (3-9)
core/src/event-types/ReplacedEvent.ts (1)
  • ReplacedEvent (3-14)
core/src/event-types/StepPushedEvent.ts (1)
  • StepPushedEvent (3-13)
core/src/event-types/StepPoppedEvent.ts (1)
  • StepPoppedEvent (3-8)
core/src/event-types/StepReplacedEvent.ts (1)
  • StepReplacedEvent (3-13)
core/src/event-types/index.ts (1)
  • DomainEvent (12-22)
extensions/plugin-history-sync/src/historySyncPlugin.tsx (10)
extensions/plugin-history-sync/src/Publisher.ts (1)
  • Publisher (3-24)
extensions/plugin-history-sync/src/NavigationProcess/NavigationProcess.ts (3)
  • NavigationProcessStatus (12-17)
  • NavigationProcessStatus (19-20)
  • NavigationProcess (3-10)
extensions/plugin-history-sync/src/ActivityActivationMonitor/ActivityActivationMonitor.ts (1)
  • ActivityActivationMonitor (3-7)
core/src/Stack.ts (1)
  • Stack (50-57)
integrations/react/src/__internal__/shims/useSyncExternalStore.ts (1)
  • useSyncExternalStore (3-17)
extensions/plugin-history-sync/src/InitialSetupProcessStatusContext.ts (1)
  • InitialSetupProcessStatusContext (4-5)
extensions/plugin-history-sync/src/ActivityActivationCountsContext.ts (1)
  • ActivityActivationCountsContext (3-5)
extensions/plugin-history-sync/src/NavigationProcess/StatusPublishingNavigationProcess.ts (1)
  • StatusPublishingNavigationProcess (8-50)
extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts (1)
  • SerialNavigationProcess (9-101)
extensions/plugin-history-sync/src/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts (1)
  • DefaultHistoryActivityActivationMonitor (12-61)
⏰ 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 (1)
extensions/plugin-history-sync/src/historySyncPlugin.tsx (1)

181-182: Pass undefined when publishing a void payload

Publisher<void>.publish still requires its single argument; omitting it causes a compile-time error (“Expected 1 arguments, but got 0”). Provide undefined explicitly (and optionally discard the returned Promise) so the code type-checks.

-      if (changeOccurred) {
-        activityActivationCountsChangeNotifier.publish();
-      }
+      if (changeOccurred) {
+        void activityActivationCountsChangeNotifier.publish(undefined);
+      }

@ENvironmentSet ENvironmentSet changed the title [WIP] feat(core, plugin-history-sync): Initial stack setup process exposure feat(core, plugin-history-sync): Initial stack setup process exposure Sep 29, 2025
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

📜 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 83bd8e4 and 94f74a4.

📒 Files selected for processing (2)
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx (5 hunks)
  • extensions/plugin-history-sync/src/index.ts (1 hunks)
🧰 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/index.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/index.ts
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
🧬 Code graph analysis (1)
extensions/plugin-history-sync/src/historySyncPlugin.tsx (10)
extensions/plugin-history-sync/src/Publisher.ts (1)
  • Publisher (3-24)
extensions/plugin-history-sync/src/NavigationProcess/NavigationProcess.ts (3)
  • NavigationProcessStatus (12-17)
  • NavigationProcessStatus (19-20)
  • NavigationProcess (3-10)
extensions/plugin-history-sync/src/ActivityActivationMonitor/ActivityActivationMonitor.ts (1)
  • ActivityActivationMonitor (3-7)
extensions/plugin-history-sync/src/makeHistoryTaskQueue.ts (1)
  • makeHistoryTaskQueue (7-30)
core/src/Stack.ts (1)
  • Stack (50-57)
extensions/plugin-history-sync/src/InitialSetupProcessStatusContext.ts (1)
  • InitialSetupProcessStatusContext (4-5)
extensions/plugin-history-sync/src/ActivityActivationCountsContext.ts (1)
  • ActivityActivationCountsContext (3-5)
extensions/plugin-history-sync/src/NavigationProcess/StatusPublishingNavigationProcess.ts (1)
  • StatusPublishingNavigationProcess (8-50)
extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts (1)
  • SerialNavigationProcess (9-101)
extensions/plugin-history-sync/src/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts (1)
  • DefaultHistoryActivityActivationMonitor (12-61)
⏰ 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). (1)
  • GitHub Check: Workers Builds: stackflow-docs
🔇 Additional comments (1)
extensions/plugin-history-sync/src/historySyncPlugin.tsx (1)

181-182: Pass undefined when publishing void notifications.

Publisher<void>.publish still requires an explicit argument; calling it without one (publish()) triggers “Expected 1 arguments, but got 0” under strict TS settings. Please pass undefined (and optionally discard the Promise) to satisfy the signature.

-        activityActivationCountsChangeNotifier.publish();
+        void activityActivationCountsChangeNotifier.publish(undefined);

Based on learnings.

Copy link
Collaborator

@orionmiz orionmiz left a comment

Choose a reason for hiding this comment

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

  • useInitialSetupProcessStatus() hook is added to provide users easy way of accessing status of the stack initialization navigation process
  • useIsRenderInTransition() hook is added to allow users to figure out whether current render is for a navigation transition process.

These hooks can be consolidated into a single useIsActivatedActivity() hook, which can handle both use cases requiring this hook.

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

♻️ Duplicate comments (1)
extensions/plugin-history-sync/src/historySyncPlugin.tsx (1)

167-169: Pass undefined to publish() to satisfy TypeScript.

Even though activityActivationCountsChangeNotifier is typed Publisher<void>, the publish(value: T) method signature requires an argument. When T is void, you must pass undefined.

Apply this diff:

       if (changeOccurred) {
-        activityActivationCountsChangeNotifier.publish();
+        activityActivationCountsChangeNotifier.publish(undefined);
       }

Based on learnings.

📜 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 64720cb and 3cf81c5.

📒 Files selected for processing (3)
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx (5 hunks)
  • extensions/plugin-history-sync/src/index.ts (1 hunks)
  • extensions/plugin-history-sync/src/useIsActivatedActivity.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • extensions/plugin-history-sync/src/index.ts
🧰 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/useIsActivatedActivity.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/useIsActivatedActivity.ts
  • extensions/plugin-history-sync/src/historySyncPlugin.tsx
🧬 Code graph analysis (2)
extensions/plugin-history-sync/src/useIsActivatedActivity.ts (2)
integrations/react/src/__internal__/activity/useActivity.ts (1)
  • useActivity (8-8)
extensions/plugin-history-sync/src/ActivityActivationCountsContext.ts (1)
  • ActivityActivationCountsContext (3-5)
extensions/plugin-history-sync/src/historySyncPlugin.tsx (10)
extensions/plugin-history-sync/src/NavigationProcess/NavigationProcess.ts (1)
  • NavigationProcess (3-9)
extensions/plugin-history-sync/src/ActivityActivationMonitor/ActivityActivationMonitor.ts (1)
  • ActivityActivationMonitor (3-7)
extensions/plugin-history-sync/src/Publisher.ts (1)
  • Publisher (3-24)
core/src/Stack.ts (1)
  • Stack (50-57)
integrations/react/src/__internal__/shims/useSyncExternalStore.ts (1)
  • useSyncExternalStore (3-17)
extensions/plugin-history-sync/src/ActivityActivationCountsContext.ts (1)
  • ActivityActivationCountsContext (3-5)
extensions/plugin-history-sync/src/NavigationProcess/SerialNavigationProcess.ts (1)
  • SerialNavigationProcess (9-105)
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/ActivityActivationMonitor/DefaultHistoryActivityActivationMonitor.ts (1)
  • DefaultHistoryActivityActivationMonitor (12-61)
⏰ 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: Check the status of Changesets
  • GitHub Check: Workers Builds: stackflow-docs
🔇 Additional comments (9)
extensions/plugin-history-sync/src/useIsActivatedActivity.ts (1)

5-13: LGTM with a clarification note.

The hook correctly retrieves the current activity ID, finds its activation count from context, and returns true when the count is undefined (not yet tracked) or greater than zero (activated). The logic is sound for the initial setup scenario.

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

128-148: Cache comparison logic is correct given the invariant.

The developer confirmed that activityActivationMonitors entries are never removed, only added. Given this invariant, the current cache comparison correctly detects changes: it checks length mismatch and whether any existing cached item has a different count in the current array. Since IDs are stable and never removed, the one-directional comparison is sufficient.

Note: If the invariant changes in the future (e.g., monitors can be removed), this logic will need updating to a bidirectional comparison.


260-338: LGTM: SerialNavigationProcess integration is well-structured.

The refactor correctly constructs a SerialNavigationProcess with navigation factories for defaultHistory and the target activity. Each factory returns an array of events (Pushed and StepPushed), and monitors are registered for each pushed activity. The final captureNavigationOpportunity(null) call retrieves the initial events with adjusted timestamps.

The logic properly handles:

  • Mapping defaultHistory entries to event factories
  • Creating DefaultHistoryActivityActivationMonitor instances for each Pushed event
  • Calling captureNavigationOpportunity to retrieve the first batch of events

493-499: LGTM: Initial setup process continuation in onInit.

After setting up the history listener, the code correctly:

  • Calls captureNavigationOpportunity(stack) to retrieve the next batch of events
  • Dispatches each event via push or stepPush based on event name
  • Runs activity activation monitors on the updated stack

This properly integrates the SerialNavigationProcess into the plugin lifecycle.


710-720: LGTM: onChanged correctly continues setup and monitors activation.

The onChanged hook:

  • Retrieves the current stack
  • Calls captureNavigationOpportunity(stack) to dispatch any remaining setup events
  • Dispatches events via push or stepPush
  • Runs activity activation monitors

This ensures the initial setup process progresses on each state change and activation counts are kept up-to-date.


172-721: Plugin lifecycle hooks comply with documented API.

The plugin implements only the documented lifecycle hooks:

  • wrapStack
  • overrideInitialEvents
  • onInit
  • onPushed
  • onStepPushed
  • onReplaced
  • onStepReplaced
  • onBeforePush
  • onBeforeReplace
  • onBeforeStepPop
  • onBeforePop
  • onChanged

All hooks match the documented plugin API for @stackflow/react.

As per coding guidelines.


18-18: Fix the import path for ActivityActivationCountsContext.

The import uses a bare module specifier without the relative path prefix, which will cause a module resolution error.

Apply this diff:

-import { ActivityActivationCountsContext } from "./ActivityActivationCountsContext";
+import { ActivityActivationCountsContext } from "./ActivityActivationCountsContext";

Wait—reviewing line 18 again, I see it already uses "./ActivityActivationCountsContext" with the correct relative prefix. The past review comment referenced a different import that has since been fixed. No action needed here.


175-178: Verify useSyncExternalStore usage for SSR safety.

The hook is called unconditionally in wrapStack, which is correct. However, ensure that getActivityActivationCounts() does not throw or return inconsistent values between server and client renders, as useSyncExternalStore requires stable snapshots.

Run the following script to check if getActivityActivationCounts can be called safely during SSR (when window is undefined):


340-500: Verify push and stepPush are defined on the actions object.

The onInit hook destructures { getStack, dispatchEvent, push, stepPush } from actions. Ensure that the @stackflow/react plugin API exposes these methods on the actions object passed to onInit.

Run the following script to verify the actions shape in the plugin API:

anakin_karrot and others added 2 commits October 2, 2025 11:23
@orionmiz orionmiz merged commit 83ee5ed into main Oct 2, 2025
9 checks passed
@orionmiz orionmiz deleted the default-history-setup-transition-state branch October 2, 2025 02:40
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.

3 participants