From d38423f26616102a8f0d3288618baae929584210 Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Tue, 23 Dec 2025 17:18:52 +0900 Subject: [PATCH 01/16] naive direct landing impl (without history adjustment) --- .../src/historySyncPlugin.tsx | 208 ++++++++++++------ 1 file changed, 145 insertions(+), 63 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 507473121..6a5e50ce7 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -64,6 +64,7 @@ type HistorySyncPluginOptions> = ( useHash?: boolean; history?: History; urlPatternOptions?: UrlPatternOptions; + disableDefaultHistorySetupTransition?: boolean; }; export function historySyncPlugin< @@ -258,78 +259,159 @@ export function historySyncPlugin< const defaultHistory = targetActivityRoute.defaultHistory?.(params) ?? []; - initialSetupProcess = new SerialNavigationProcess([ - ...defaultHistory.map( - ({ activityName, activityParams, additionalSteps = [] }) => - () => { - const events: ( - | Omit - | Omit - )[] = [ - { - name: "Pushed", - id: id(), - activityId: id(), - activityName, - activityParams: { - ...activityParams, - }, - activityContext: { - path: currentPath, - lazyActivityComponentRenderContext: { - shouldRenderImmediately: true, - }, - }, - }, - ...additionalSteps.map( - ({ - stepParams, - hasZIndex, - }): Omit => ({ - name: "StepPushed", - id: id(), - stepId: id(), - stepParams, - hasZIndex, - }), - ), - ]; - - for (const event of events) { - if (event.name === "Pushed") { + if (options.disableDefaultHistorySetupTransition) { + initialSetupProcess = new SerialNavigationProcess([ + () => { + const events: ( + | Omit + | Omit + )[] = [ + ...defaultHistory.flatMap( + ({ activityName, activityParams, additionalSteps = [] }) => { + const activityId = id(); + activityActivationMonitors.push( new DefaultHistoryActivityActivationMonitor( - event.activityId, + activityId, initialSetupProcess!, ), ); + + const events: ( + | Omit + | Omit + )[] = [ + { + name: "Pushed", + id: id(), + activityId, + activityName, + activityParams: { + ...activityParams, + }, + activityContext: { + path: currentPath, + lazyActivityComponentRenderContext: { + shouldRenderImmediately: true, + }, + }, + skipEnterActiveState: true, + }, + ...additionalSteps.map( + ({ + stepParams, + hasZIndex, + }): Omit => ({ + name: "StepPushed", + id: id(), + stepId: id(), + stepParams, + hasZIndex, + }), + ), + ]; + + return events; + }, + ), + { + name: "Pushed", + id: id(), + activityId: id(), + activityName: targetActivityRoute.activityName, + activityParams: + makeTemplate( + targetActivityRoute, + options.urlPatternOptions, + ).parse(currentPath) ?? + urlSearchParamsToMap(pathToUrl(currentPath).searchParams), + activityContext: { + path: currentPath, + lazyActivityComponentRenderContext: { + shouldRenderImmediately: true, + }, + }, + skipEnterActiveState: true, + }, + ]; + + return events; + }, + ]); + } else { + initialSetupProcess = new SerialNavigationProcess([ + ...defaultHistory.map( + ({ activityName, activityParams, additionalSteps = [] }) => + () => { + const events: ( + | Omit + | Omit + )[] = [ + { + name: "Pushed", + id: id(), + activityId: id(), + activityName, + activityParams: { + ...activityParams, + }, + activityContext: { + path: currentPath, + lazyActivityComponentRenderContext: { + shouldRenderImmediately: true, + }, + }, + }, + ...additionalSteps.map( + ({ + stepParams, + hasZIndex, + }): Omit => ({ + name: "StepPushed", + id: id(), + stepId: id(), + stepParams, + hasZIndex, + }), + ), + ]; + + for (const event of events) { + if (event.name === "Pushed") { + activityActivationMonitors.push( + new DefaultHistoryActivityActivationMonitor( + event.activityId, + initialSetupProcess!, + ), + ); + } } - } - return events; - }, - ), - () => [ - { - name: "Pushed", - id: id(), - activityId: id(), - activityName: targetActivityRoute.activityName, - activityParams: - makeTemplate( - targetActivityRoute, - options.urlPatternOptions, - ).parse(currentPath) ?? - urlSearchParamsToMap(pathToUrl(currentPath).searchParams), - activityContext: { - path: currentPath, - lazyActivityComponentRenderContext: { - shouldRenderImmediately: true, + return events; + }, + ), + () => [ + { + name: "Pushed", + id: id(), + activityId: id(), + activityName: targetActivityRoute.activityName, + activityParams: + makeTemplate( + targetActivityRoute, + options.urlPatternOptions, + ).parse(currentPath) ?? + urlSearchParamsToMap(pathToUrl(currentPath).searchParams), + activityContext: { + path: currentPath, + lazyActivityComponentRenderContext: { + shouldRenderImmediately: true, + }, }, }, - }, - ], - ]); + ], + ]); + } return initialSetupProcess .captureNavigationOpportunity(null) From 8ac735a133255ce377e05872e395852cf90f08b4 Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Tue, 23 Dec 2025 17:58:39 +0900 Subject: [PATCH 02/16] adjust browsing history after initialization --- .../src/historySyncPlugin.tsx | 92 +++++++++++++++---- 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 6a5e50ce7..13c2f8a58 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -415,36 +415,80 @@ export function historySyncPlugin< return initialSetupProcess .captureNavigationOpportunity(null) - .map((event) => ({ + .map((event, index) => ({ ...event, - eventDate: Date.now() - MINUTE, + eventDate: Date.now() - MINUTE + index, })); }, onInit({ actions: { getStack, dispatchEvent, push, stepPush } }) { const stack = getStack(); - const rootActivity = stack.activities[0]; - const match = activityRoutes.find( - (r) => r.activityName === rootActivity.name, - )!; - const template = makeTemplate(match, options.urlPatternOptions); + for (const activity of stack.activities) { + if ( + activity.transitionState === "enter-done" || + activity.transitionState === "enter-active" + ) { + const match = activityRoutes.find( + (r) => r.activityName === activity.name, + )!; + const template = makeTemplate(match, options.urlPatternOptions); - const lastStep = last(rootActivity.steps); + if (activity.isRoot) { + requestHistoryTick(() => { + silentFlag = true; + console.log("replaceActivity", activity); + replaceState({ + history, + pathname: template.fill(activity.params), + state: { + activity: activity, + step: activity.steps[0], + }, + useHash: options.useHash, + }); + console.log("silentFlag - after", silentFlag); + }); + } else { + requestHistoryTick(() => { + silentFlag = true; + console.log("pushActivity", activity); + pushState({ + history, + pathname: template.fill(activity.params), + state: { + activity: activity, + step: activity.steps[0], + }, + useHash: options.useHash, + }); + console.log("silentFlag - after", silentFlag); + }); + } - requestHistoryTick(() => { - silentFlag = true; - replaceState({ - history, - pathname: template.fill(rootActivity.params), - state: { - activity: rootActivity, - step: lastStep, - }, - useHash: options.useHash, - }); - }); + for (const step of activity.steps) { + if (!step.exitedBy && step.enteredBy.name !== "Pushed") { + requestHistoryTick(() => { + console.log("pushStep", step); + silentFlag = true; + pushState({ + history, + pathname: template.fill(activity.params), + state: { + activity: activity, + step: step, + }, + useHash: options.useHash, + }); + console.log("silentFlag - after", silentFlag); + }); + } + } + } + } const onPopState: Listener = (e) => { + console.log("onPopState", e); + console.log("silentFlag", silentFlag); if (silentFlag) { silentFlag = false; return; @@ -603,6 +647,7 @@ export function historySyncPlugin< }, useHash: options.useHash, }); + console.log("silentFlag - after / onPushed", silentFlag); }); }, onStepPushed({ effect: { activity, step } }) { @@ -628,6 +673,7 @@ export function historySyncPlugin< }, useHash: options.useHash, }); + console.log("silentFlag - after / onStepPushed", silentFlag); }); }, onReplaced({ effect: { activity } }) { @@ -651,6 +697,7 @@ export function historySyncPlugin< }, useHash: options.useHash, }); + console.log("silentFlag - after / onReplaced", silentFlag); }); }, onStepReplaced({ effect: { activity, step } }) { @@ -675,6 +722,7 @@ export function historySyncPlugin< }, useHash: options.useHash, }); + console.log("silentFlag - after / onStepReplaced", silentFlag); }); }, onBeforePush({ actionParams, actions: { overrideActionParams } }) { @@ -758,6 +806,7 @@ export function historySyncPlugin< if ((currentActivity?.steps.length ?? 0) > 1) { requestHistoryTick(() => { silentFlag = true; + console.log("silentFlag - before / onBeforeStepPop", silentFlag); history.back(); }); } @@ -786,6 +835,7 @@ export function historySyncPlugin< requestHistoryTick(() => { silentFlag = true; history.back(); + console.log("silentFlag - before / onBeforePop", silentFlag); }); } } @@ -793,6 +843,8 @@ export function historySyncPlugin< onChanged({ actions: { getStack, push, stepPush } }) { const stack = getStack(); + console.log("stack", stack); + initialSetupProcess ?.captureNavigationOpportunity(stack) .forEach((event) => From 7ae6c10a3079eaa9a1475de9c7ad51ff4c64b1da Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Tue, 23 Dec 2025 18:03:45 +0900 Subject: [PATCH 03/16] cleanup console.log --- .../src/historySyncPlugin.tsx | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 13c2f8a58..65ca4f306 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -436,7 +436,6 @@ export function historySyncPlugin< if (activity.isRoot) { requestHistoryTick(() => { silentFlag = true; - console.log("replaceActivity", activity); replaceState({ history, pathname: template.fill(activity.params), @@ -446,12 +445,10 @@ export function historySyncPlugin< }, useHash: options.useHash, }); - console.log("silentFlag - after", silentFlag); }); } else { requestHistoryTick(() => { silentFlag = true; - console.log("pushActivity", activity); pushState({ history, pathname: template.fill(activity.params), @@ -461,14 +458,12 @@ export function historySyncPlugin< }, useHash: options.useHash, }); - console.log("silentFlag - after", silentFlag); }); } for (const step of activity.steps) { if (!step.exitedBy && step.enteredBy.name !== "Pushed") { requestHistoryTick(() => { - console.log("pushStep", step); silentFlag = true; pushState({ history, @@ -479,7 +474,6 @@ export function historySyncPlugin< }, useHash: options.useHash, }); - console.log("silentFlag - after", silentFlag); }); } } @@ -487,8 +481,6 @@ export function historySyncPlugin< } const onPopState: Listener = (e) => { - console.log("onPopState", e); - console.log("silentFlag", silentFlag); if (silentFlag) { silentFlag = false; return; @@ -647,7 +639,6 @@ export function historySyncPlugin< }, useHash: options.useHash, }); - console.log("silentFlag - after / onPushed", silentFlag); }); }, onStepPushed({ effect: { activity, step } }) { @@ -673,7 +664,6 @@ export function historySyncPlugin< }, useHash: options.useHash, }); - console.log("silentFlag - after / onStepPushed", silentFlag); }); }, onReplaced({ effect: { activity } }) { @@ -697,7 +687,6 @@ export function historySyncPlugin< }, useHash: options.useHash, }); - console.log("silentFlag - after / onReplaced", silentFlag); }); }, onStepReplaced({ effect: { activity, step } }) { @@ -722,7 +711,6 @@ export function historySyncPlugin< }, useHash: options.useHash, }); - console.log("silentFlag - after / onStepReplaced", silentFlag); }); }, onBeforePush({ actionParams, actions: { overrideActionParams } }) { @@ -806,7 +794,6 @@ export function historySyncPlugin< if ((currentActivity?.steps.length ?? 0) > 1) { requestHistoryTick(() => { silentFlag = true; - console.log("silentFlag - before / onBeforeStepPop", silentFlag); history.back(); }); } @@ -835,7 +822,6 @@ export function historySyncPlugin< requestHistoryTick(() => { silentFlag = true; history.back(); - console.log("silentFlag - before / onBeforePop", silentFlag); }); } } @@ -843,8 +829,6 @@ export function historySyncPlugin< onChanged({ actions: { getStack, push, stepPush } }) { const stack = getStack(); - console.log("stack", stack); - initialSetupProcess ?.captureNavigationOpportunity(stack) .forEach((event) => From 0ef20094142a4a74c29a3d65c85f0467c11f4db9 Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Fri, 26 Dec 2025 12:55:25 +0900 Subject: [PATCH 04/16] rename option --- extensions/plugin-history-sync/src/historySyncPlugin.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 65ca4f306..6fc2463a2 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -64,7 +64,7 @@ type HistorySyncPluginOptions> = ( useHash?: boolean; history?: History; urlPatternOptions?: UrlPatternOptions; - disableDefaultHistorySetupTransition?: boolean; + skipDefaultHistorySetupTransition?: boolean; }; export function historySyncPlugin< @@ -259,7 +259,7 @@ export function historySyncPlugin< const defaultHistory = targetActivityRoute.defaultHistory?.(params) ?? []; - if (options.disableDefaultHistorySetupTransition) { + if (options.skipDefaultHistorySetupTransition) { initialSetupProcess = new SerialNavigationProcess([ () => { const events: ( From 91277f199b4390a3b9cbc06cd665c32d0b69539c Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Fri, 26 Dec 2025 16:28:51 +0900 Subject: [PATCH 05/16] reformat --- .../src/historySyncPlugin.tsx | 140 +++++++++--------- 1 file changed, 69 insertions(+), 71 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 6fc2463a2..068d0e48a 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -261,82 +261,80 @@ export function historySyncPlugin< if (options.skipDefaultHistorySetupTransition) { initialSetupProcess = new SerialNavigationProcess([ - () => { - const events: ( - | Omit - | Omit - )[] = [ - ...defaultHistory.flatMap( - ({ activityName, activityParams, additionalSteps = [] }) => { - const activityId = id(); - - activityActivationMonitors.push( - new DefaultHistoryActivityActivationMonitor( - activityId, - initialSetupProcess!, - ), - ); - - const events: ( - | Omit - | Omit - )[] = [ - { - name: "Pushed", - id: id(), - activityId, - activityName, - activityParams: { - ...activityParams, - }, - activityContext: { - path: currentPath, - lazyActivityComponentRenderContext: { - shouldRenderImmediately: true, - }, + (): ( + | Omit + | Omit + )[] => [ + ...defaultHistory.flatMap( + ({ + activityName, + activityParams, + additionalSteps = [], + }): ( + | Omit + | Omit + )[] => { + const activityId = id(); + + activityActivationMonitors.push( + new DefaultHistoryActivityActivationMonitor( + activityId, + initialSetupProcess!, + ), + ); + + return [ + { + name: "Pushed", + id: id(), + activityId, + activityName, + activityParams: { + ...activityParams, + }, + activityContext: { + path: currentPath, + lazyActivityComponentRenderContext: { + shouldRenderImmediately: true, }, - skipEnterActiveState: true, }, - ...additionalSteps.map( - ({ - stepParams, - hasZIndex, - }): Omit => ({ - name: "StepPushed", - id: id(), - stepId: id(), - stepParams, - hasZIndex, - }), - ), - ]; - - return events; - }, - ), - { - name: "Pushed", - id: id(), - activityId: id(), - activityName: targetActivityRoute.activityName, - activityParams: - makeTemplate( - targetActivityRoute, - options.urlPatternOptions, - ).parse(currentPath) ?? - urlSearchParamsToMap(pathToUrl(currentPath).searchParams), - activityContext: { - path: currentPath, - lazyActivityComponentRenderContext: { - shouldRenderImmediately: true, + skipEnterActiveState: true, }, + ...additionalSteps.map( + ({ + stepParams, + hasZIndex, + }): Omit => ({ + name: "StepPushed", + id: id(), + stepId: id(), + stepParams, + hasZIndex, + }), + ), + ]; + }, + ), + { + name: "Pushed", + id: id(), + activityId: id(), + activityName: targetActivityRoute.activityName, + activityParams: + makeTemplate( + targetActivityRoute, + options.urlPatternOptions, + ).parse(currentPath) ?? + urlSearchParamsToMap(pathToUrl(currentPath).searchParams), + activityContext: { + path: currentPath, + lazyActivityComponentRenderContext: { + shouldRenderImmediately: true, }, - skipEnterActiveState: true, }, - ]; - - return events; - }, + skipEnterActiveState: true, + }, + ], ]); } else { initialSetupProcess = new SerialNavigationProcess([ From 9d0f3a3ced43b96862f5fd49a3f4910aded0b3df Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Fri, 26 Dec 2025 16:45:10 +0900 Subject: [PATCH 06/16] simplify --- .../src/historySyncPlugin.tsx | 215 +++++++----------- 1 file changed, 83 insertions(+), 132 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 068d0e48a..11f177833 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -28,7 +28,7 @@ import type { NavigationProcess } from "./NavigationProcess/NavigationProcess"; import { SerialNavigationProcess } from "./NavigationProcess/SerialNavigationProcess"; import { normalizeActivityRouteMap } from "./normalizeActivityRouteMap"; import { Publisher } from "./Publisher"; -import type { RouteLike } from "./RouteLike"; +import type { HistoryEntry, RouteLike } from "./RouteLike"; import { RoutesProvider } from "./RoutesContext"; import { sortActivityRoutes } from "./sortActivityRoutes"; @@ -258,156 +258,107 @@ export function historySyncPlugin< }; const defaultHistory = targetActivityRoute.defaultHistory?.(params) ?? []; + const historyEntryToEvents = ({ + activityName, + activityParams, + additionalSteps = [], + }: HistoryEntry): ( + | Omit + | Omit + )[] => [ + { + name: "Pushed", + id: id(), + activityId: id(), + activityName, + activityParams: { + ...activityParams, + }, + activityContext: { + path: currentPath, + lazyActivityComponentRenderContext: { + shouldRenderImmediately: true, + }, + }, + }, + ...additionalSteps.map( + ({ + stepParams, + hasZIndex, + }): Omit => ({ + name: "StepPushed", + id: id(), + stepId: id(), + stepParams, + hasZIndex, + }), + ), + ]; + const createTargetActivityPushEvent = (): Omit< + PushedEvent, + "eventDate" + > => ({ + name: "Pushed", + id: id(), + activityId: id(), + activityName: targetActivityRoute.activityName, + activityParams: + makeTemplate(targetActivityRoute, options.urlPatternOptions).parse( + currentPath, + ) ?? urlSearchParamsToMap(pathToUrl(currentPath).searchParams), + activityContext: { + path: currentPath, + lazyActivityComponentRenderContext: { + shouldRenderImmediately: true, + }, + }, + }); if (options.skipDefaultHistorySetupTransition) { initialSetupProcess = new SerialNavigationProcess([ - (): ( - | Omit - | Omit - )[] => [ - ...defaultHistory.flatMap( - ({ - activityName, - activityParams, - additionalSteps = [], - }): ( - | Omit - | Omit - )[] => { - const activityId = id(); + () => [ + ...defaultHistory.flatMap((historyEntry) => + historyEntryToEvents(historyEntry).map((event) => { + if (event.name !== "Pushed") return event; activityActivationMonitors.push( new DefaultHistoryActivityActivationMonitor( - activityId, + event.activityId, initialSetupProcess!, ), ); - return [ - { - name: "Pushed", - id: id(), - activityId, - activityName, - activityParams: { - ...activityParams, - }, - activityContext: { - path: currentPath, - lazyActivityComponentRenderContext: { - shouldRenderImmediately: true, - }, - }, - skipEnterActiveState: true, - }, - ...additionalSteps.map( - ({ - stepParams, - hasZIndex, - }): Omit => ({ - name: "StepPushed", - id: id(), - stepId: id(), - stepParams, - hasZIndex, - }), - ), - ]; - }, + return { + ...event, + skipEnterActiveState: true, + }; + }), ), { - name: "Pushed", - id: id(), - activityId: id(), - activityName: targetActivityRoute.activityName, - activityParams: - makeTemplate( - targetActivityRoute, - options.urlPatternOptions, - ).parse(currentPath) ?? - urlSearchParamsToMap(pathToUrl(currentPath).searchParams), - activityContext: { - path: currentPath, - lazyActivityComponentRenderContext: { - shouldRenderImmediately: true, - }, - }, + ...createTargetActivityPushEvent(), skipEnterActiveState: true, }, ], ]); } else { initialSetupProcess = new SerialNavigationProcess([ - ...defaultHistory.map( - ({ activityName, activityParams, additionalSteps = [] }) => - () => { - const events: ( - | Omit - | Omit - )[] = [ - { - name: "Pushed", - id: id(), - activityId: id(), - activityName, - activityParams: { - ...activityParams, - }, - activityContext: { - path: currentPath, - lazyActivityComponentRenderContext: { - shouldRenderImmediately: true, - }, - }, - }, - ...additionalSteps.map( - ({ - stepParams, - hasZIndex, - }): Omit => ({ - name: "StepPushed", - id: id(), - stepId: id(), - stepParams, - hasZIndex, - }), + ...defaultHistory.map((historyEntry) => () => { + const events = historyEntryToEvents(historyEntry); + + for (const event of events) { + if (event.name === "Pushed") { + activityActivationMonitors.push( + new DefaultHistoryActivityActivationMonitor( + event.activityId, + initialSetupProcess!, ), - ]; - - for (const event of events) { - if (event.name === "Pushed") { - activityActivationMonitors.push( - new DefaultHistoryActivityActivationMonitor( - event.activityId, - initialSetupProcess!, - ), - ); - } - } - - return events; - }, - ), - () => [ - { - name: "Pushed", - id: id(), - activityId: id(), - activityName: targetActivityRoute.activityName, - activityParams: - makeTemplate( - targetActivityRoute, - options.urlPatternOptions, - ).parse(currentPath) ?? - urlSearchParamsToMap(pathToUrl(currentPath).searchParams), - activityContext: { - path: currentPath, - lazyActivityComponentRenderContext: { - shouldRenderImmediately: true, - }, - }, - }, - ], + ); + } + } + + return events; + }), + () => [createTargetActivityPushEvent()], ]); } From a147c9edc6405cc60262166ab827b19b718c3e5a Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Fri, 26 Dec 2025 17:03:09 +0900 Subject: [PATCH 07/16] correct --- .../src/historySyncPlugin.tsx | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 11f177833..72c2fa6b8 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -342,31 +342,38 @@ export function historySyncPlugin< ]); } else { initialSetupProcess = new SerialNavigationProcess([ - ...defaultHistory.map((historyEntry) => () => { - const events = historyEntryToEvents(historyEntry); - - for (const event of events) { - if (event.name === "Pushed") { - activityActivationMonitors.push( - new DefaultHistoryActivityActivationMonitor( - event.activityId, - initialSetupProcess!, - ), - ); - } - } - - return events; + ...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; + activityActivationMonitors.push( + new DefaultHistoryActivityActivationMonitor( + event.activityId, + initialSetupProcess!, + ), + ); + + return { + ...event, + skipEnterActiveState, + }; + }); }), () => [createTargetActivityPushEvent()], ]); } + const now = Date.now(); + return initialSetupProcess .captureNavigationOpportunity(null) - .map((event, index) => ({ + .map((event, index, array) => ({ ...event, - eventDate: Date.now() - MINUTE + index, + eventDate: now - (array.length - index), })); }, onInit({ actions: { getStack, dispatchEvent, push, stepPush } }) { From 29ec5d2dd85ab966db3a612ad8ed0d7b2c36b1f1 Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Fri, 26 Dec 2025 17:35:52 +0900 Subject: [PATCH 08/16] fix --- .../src/historySyncPlugin.tsx | 59 ++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 72c2fa6b8..618849e1c 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -381,8 +381,8 @@ export function historySyncPlugin< for (const activity of stack.activities) { if ( - activity.transitionState === "enter-done" || - activity.transitionState === "enter-active" + activity.transitionState === "enter-active" || + activity.transitionState === "enter-done" ) { const match = activityRoutes.find( (r) => r.activityName === activity.name, @@ -390,47 +390,38 @@ export function historySyncPlugin< const template = makeTemplate(match, options.urlPatternOptions); if (activity.isRoot) { - requestHistoryTick(() => { - silentFlag = true; - replaceState({ - history, - pathname: template.fill(activity.params), - state: { - activity: activity, - step: activity.steps[0], - }, - useHash: options.useHash, - }); + replaceState({ + history, + pathname: template.fill(activity.params), + state: { + activity: activity, + step: activity.steps[0], + }, + useHash: options.useHash, }); } else { - requestHistoryTick(() => { - silentFlag = true; + pushState({ + history, + pathname: template.fill(activity.params), + state: { + activity: activity, + step: activity.steps[0], + }, + useHash: options.useHash, + }); + } + + for (const step of activity.steps) { + if (!step.exitedBy && step.enteredBy.name !== "Pushed") { pushState({ history, - pathname: template.fill(activity.params), + pathname: template.fill(step.params), state: { activity: activity, - step: activity.steps[0], + step: step, }, useHash: options.useHash, }); - }); - } - - for (const step of activity.steps) { - if (!step.exitedBy && step.enteredBy.name !== "Pushed") { - requestHistoryTick(() => { - silentFlag = true; - pushState({ - history, - pathname: template.fill(activity.params), - state: { - activity: activity, - step: step, - }, - useHash: options.useHash, - }); - }); } } } From c7ca5b2b84db8853fa618d171df5c7ed3f4b32c3 Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Fri, 26 Dec 2025 17:45:01 +0900 Subject: [PATCH 09/16] omit step --- extensions/plugin-history-sync/src/historySyncPlugin.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 618849e1c..c80811464 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -395,7 +395,6 @@ export function historySyncPlugin< pathname: template.fill(activity.params), state: { activity: activity, - step: activity.steps[0], }, useHash: options.useHash, }); @@ -405,7 +404,6 @@ export function historySyncPlugin< pathname: template.fill(activity.params), state: { activity: activity, - step: activity.steps[0], }, useHash: options.useHash, }); From 22f4a66b7036f06eecc19ac7dc2911234b385ee8 Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Fri, 26 Dec 2025 17:47:00 +0900 Subject: [PATCH 10/16] skip sync when session was restored --- .../src/historySyncPlugin.tsx | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index c80811464..2698d447d 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -379,48 +379,50 @@ export function historySyncPlugin< onInit({ actions: { getStack, dispatchEvent, push, stepPush } }) { const stack = getStack(); - for (const activity of stack.activities) { - if ( - activity.transitionState === "enter-active" || - activity.transitionState === "enter-done" - ) { - const match = activityRoutes.find( - (r) => r.activityName === activity.name, - )!; - const template = makeTemplate(match, options.urlPatternOptions); - - if (activity.isRoot) { - replaceState({ - history, - pathname: template.fill(activity.params), - state: { - activity: activity, - }, - useHash: options.useHash, - }); - } else { - pushState({ - history, - pathname: template.fill(activity.params), - state: { - activity: activity, - }, - useHash: options.useHash, - }); - } + if (parseState(history.location.state) === null) { + for (const activity of stack.activities) { + if ( + activity.transitionState === "enter-active" || + activity.transitionState === "enter-done" + ) { + const match = activityRoutes.find( + (r) => r.activityName === activity.name, + )!; + const template = makeTemplate(match, options.urlPatternOptions); - for (const step of activity.steps) { - if (!step.exitedBy && step.enteredBy.name !== "Pushed") { + if (activity.isRoot) { + replaceState({ + history, + pathname: template.fill(activity.params), + state: { + activity: activity, + }, + useHash: options.useHash, + }); + } else { pushState({ history, - pathname: template.fill(step.params), + pathname: template.fill(activity.params), state: { activity: activity, - step: step, }, useHash: options.useHash, }); } + + 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, + }); + } + } } } } From a210e287bd26c0a6227c2af8ecdd546d9168b757 Mon Sep 17 00:00:00 2001 From: anakin_karrot Date: Fri, 26 Dec 2025 22:26:02 +0900 Subject: [PATCH 11/16] cs --- .changeset/shiny-ears-draw.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shiny-ears-draw.md diff --git a/.changeset/shiny-ears-draw.md b/.changeset/shiny-ears-draw.md new file mode 100644 index 000000000..d0a42c799 --- /dev/null +++ b/.changeset/shiny-ears-draw.md @@ -0,0 +1,5 @@ +--- +"@stackflow/plugin-history-sync": minor +--- + +Add an option to skip default history setup transition From 322581732dfae65940c9356e19cf77a66f6a3296 Mon Sep 17 00:00:00 2001 From: ENvironmentSet Date: Wed, 7 Jan 2026 17:36:06 +0900 Subject: [PATCH 12/16] delete unused constants --- extensions/plugin-history-sync/src/historySyncPlugin.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 2698d447d..3a2b4a2aa 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -32,9 +32,6 @@ import type { HistoryEntry, RouteLike } from "./RouteLike"; import { RoutesProvider } from "./RoutesContext"; import { sortActivityRoutes } from "./sortActivityRoutes"; -const SECOND = 1000; -const MINUTE = 60 * SECOND; - type ConfigHistorySync = { makeTemplate: typeof makeTemplate; urlPatternOptions?: UrlPatternOptions; From 609d4e1b8a2831713f5a1a03c10e350b304a50d5 Mon Sep 17 00:00:00 2001 From: ENvironmentSet Date: Wed, 7 Jan 2026 17:50:00 +0900 Subject: [PATCH 13/16] Make skipDefaultHistorySetupTransition per-route flag --- extensions/plugin-history-sync/src/RouteLike.ts | 1 + extensions/plugin-history-sync/src/historySyncPlugin.tsx | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/plugin-history-sync/src/RouteLike.ts b/extensions/plugin-history-sync/src/RouteLike.ts index 104fd3a8a..6ba8da6bf 100644 --- a/extensions/plugin-history-sync/src/RouteLike.ts +++ b/extensions/plugin-history-sync/src/RouteLike.ts @@ -10,6 +10,7 @@ export type Route = { params: Record, ) => ComponentType extends ActivityComponentType ? U : {}; defaultHistory?: (params: Record) => HistoryEntry[]; + skipDefaultHistorySetupTransition?: boolean; }; export type HistoryEntry = { diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 3a2b4a2aa..0e4a0f5fb 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -61,7 +61,6 @@ type HistorySyncPluginOptions> = ( useHash?: boolean; history?: History; urlPatternOptions?: UrlPatternOptions; - skipDefaultHistorySetupTransition?: boolean; }; export function historySyncPlugin< @@ -311,7 +310,7 @@ export function historySyncPlugin< }, }); - if (options.skipDefaultHistorySetupTransition) { + if (targetActivityRoute.skipDefaultHistorySetupTransition) { initialSetupProcess = new SerialNavigationProcess([ () => [ ...defaultHistory.flatMap((historyEntry) => From 41202b2ac3a6d779cd523e6076accc32bb827051 Mon Sep 17 00:00:00 2001 From: ENvironmentSet Date: Wed, 7 Jan 2026 18:07:28 +0900 Subject: [PATCH 14/16] refactor --- .../src/historySyncPlugin.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 0e4a0f5fb..7489dd019 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -337,15 +337,12 @@ export function historySyncPlugin< ], ]); } else { + //@TODO: Initial push must be marked as skipEnterActiveState: true initialSetupProcess = new SerialNavigationProcess([ - ...defaultHistory.map((historyEntry, index) => () => { - let isFirstPush = true; - + ...defaultHistory.map((historyEntry) => () => { return historyEntryToEvents(historyEntry).map((event) => { if (event.name !== "Pushed") return event; - const skipEnterActiveState = index === 0 && isFirstPush; - isFirstPush = false; activityActivationMonitors.push( new DefaultHistoryActivityActivationMonitor( event.activityId, @@ -355,7 +352,6 @@ export function historySyncPlugin< return { ...event, - skipEnterActiveState, }; }); }), @@ -364,13 +360,24 @@ export function historySyncPlugin< } const now = Date.now(); - - return initialSetupProcess + const initialEvents = initialSetupProcess .captureNavigationOpportunity(null) .map((event, index, array) => ({ ...event, eventDate: now - (array.length - index), })); + const firstPushEvent = initialEvents.find( + (event) => event.name === "Pushed", + ); + + return initialEvents.map((event) => { + if (event.id !== firstPushEvent?.id) return event; + + return { + ...event, + skipEnterActiveState: true, + }; + }); }, onInit({ actions: { getStack, dispatchEvent, push, stepPush } }) { const stack = getStack(); From dc6b7c103df4d0e8d96beb48e29fe404301deee0 Mon Sep 17 00:00:00 2001 From: ENvironmentSet Date: Wed, 7 Jan 2026 18:24:53 +0900 Subject: [PATCH 15/16] refactor --- .../plugin-history-sync/src/RouteLike.ts | 27 ++++++++++++++++++- .../src/historySyncPlugin.tsx | 19 ++++++++----- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/extensions/plugin-history-sync/src/RouteLike.ts b/extensions/plugin-history-sync/src/RouteLike.ts index 6ba8da6bf..aae5ef77b 100644 --- a/extensions/plugin-history-sync/src/RouteLike.ts +++ b/extensions/plugin-history-sync/src/RouteLike.ts @@ -9,7 +9,13 @@ export type Route = { decode?: ( params: Record, ) => ComponentType extends ActivityComponentType ? U : {}; - defaultHistory?: (params: Record) => HistoryEntry[]; + defaultHistory?: ( + params: Record, + ) => HistoryEntry[] | DefaultHistoryDescriptor; +}; + +export type DefaultHistoryDescriptor = { + entries: HistoryEntry[]; skipDefaultHistorySetupTransition?: boolean; }; @@ -29,3 +35,22 @@ export type RouteLike = | string[] | Route | Route[]; + +export function interpretDefaultHistoryOption( + option: + | (( + params: Record, + ) => HistoryEntry[] | DefaultHistoryDescriptor) + | undefined, + params: Record, +): DefaultHistoryDescriptor { + if (!option) return { entries: [] }; + + const entiresOrDescriptor = option(params); + + if (Array.isArray(entiresOrDescriptor)) { + return { entries: entiresOrDescriptor }; + } + + return entiresOrDescriptor; +} diff --git a/extensions/plugin-history-sync/src/historySyncPlugin.tsx b/extensions/plugin-history-sync/src/historySyncPlugin.tsx index 7489dd019..2428d0407 100644 --- a/extensions/plugin-history-sync/src/historySyncPlugin.tsx +++ b/extensions/plugin-history-sync/src/historySyncPlugin.tsx @@ -28,7 +28,11 @@ import type { NavigationProcess } from "./NavigationProcess/NavigationProcess"; import { SerialNavigationProcess } from "./NavigationProcess/SerialNavigationProcess"; import { normalizeActivityRouteMap } from "./normalizeActivityRouteMap"; import { Publisher } from "./Publisher"; -import type { HistoryEntry, RouteLike } from "./RouteLike"; +import { + type HistoryEntry, + interpretDefaultHistoryOption, + type RouteLike, +} from "./RouteLike"; import { RoutesProvider } from "./RoutesContext"; import { sortActivityRoutes } from "./sortActivityRoutes"; @@ -252,8 +256,10 @@ export function historySyncPlugin< ...searchParams, ...pathParams, }; - const defaultHistory = - targetActivityRoute.defaultHistory?.(params) ?? []; + const defaultHistory = interpretDefaultHistoryOption( + targetActivityRoute.defaultHistory, + params, + ); const historyEntryToEvents = ({ activityName, activityParams, @@ -310,10 +316,10 @@ export function historySyncPlugin< }, }); - if (targetActivityRoute.skipDefaultHistorySetupTransition) { + if (defaultHistory.skipDefaultHistorySetupTransition) { initialSetupProcess = new SerialNavigationProcess([ () => [ - ...defaultHistory.flatMap((historyEntry) => + ...defaultHistory.entries.flatMap((historyEntry) => historyEntryToEvents(historyEntry).map((event) => { if (event.name !== "Pushed") return event; @@ -337,9 +343,8 @@ export function historySyncPlugin< ], ]); } else { - //@TODO: Initial push must be marked as skipEnterActiveState: true initialSetupProcess = new SerialNavigationProcess([ - ...defaultHistory.map((historyEntry) => () => { + ...defaultHistory.entries.map((historyEntry) => () => { return historyEntryToEvents(historyEntry).map((event) => { if (event.name !== "Pushed") return event; From b7dd300e77e20edf4a3fdb3b2e16da94518472a7 Mon Sep 17 00:00:00 2001 From: ENvironmentSet Date: Wed, 7 Jan 2026 18:45:14 +0900 Subject: [PATCH 16/16] fix typo --- extensions/plugin-history-sync/src/RouteLike.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/plugin-history-sync/src/RouteLike.ts b/extensions/plugin-history-sync/src/RouteLike.ts index aae5ef77b..617908f83 100644 --- a/extensions/plugin-history-sync/src/RouteLike.ts +++ b/extensions/plugin-history-sync/src/RouteLike.ts @@ -46,11 +46,11 @@ export function interpretDefaultHistoryOption( ): DefaultHistoryDescriptor { if (!option) return { entries: [] }; - 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; }