Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/remove-core-step-context-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@stackflow/core": patch
---

Remove the internal optional `stepContext` event fields and `ActivityStep.context`
storage that were added for plugin-history-sync URL preservation.
7 changes: 7 additions & 0 deletions .changeset/withdraw-history-sync-param-coercion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@stackflow/plugin-history-sync": patch
---

Withdraw the activity and step param string coercion introduced in `1.11.0`.
Internal navigation now preserves non-string param values at runtime again, while
URL arrivals continue to use decoded URL params as before.
2 changes: 1 addition & 1 deletion core/src/Stack.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { BaseDomainEvent } from "event-types/_base";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if BaseDomainEvent is referenced in Stack.ts beyond the import line

# Search for BaseDomainEvent usage in Stack.ts, excluding the import line itself
rg -n 'BaseDomainEvent' core/src/Stack.ts | rg -v '^1:import'

Repository: daangn/stackflow

Length of output: 42


Remove the unused BaseDomainEvent type-only import from core/src/Stack.ts
BaseDomainEvent is only present in the import statement and has no other references in core/src/Stack.ts, so the import can be removed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/src/Stack.ts` at line 1, The file imports the type BaseDomainEvent but
never uses it; remove the unused type-only import to clean up the module by
deleting BaseDomainEvent from the import statement in core/src/Stack.ts (i.e.,
remove the reference to BaseDomainEvent so the import becomes unused-free).

import type {
DomainEvent,
PoppedEvent,
Expand All @@ -18,7 +19,6 @@ export type ActivityStep = {
params: {
[key: string]: string | undefined;
};
context?: {};
enteredBy: PushedEvent | ReplacedEvent | StepPushedEvent | StepReplacedEvent;
exitedBy?: ReplacedEvent | PoppedEvent | StepReplacedEvent | StepPoppedEvent;
zIndex: number;
Expand Down
4 changes: 1 addition & 3 deletions core/src/activity-utils/makeActivityReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export function makeActivityReducer(context: {
const newRoute = {
id: event.stepId,
params: event.stepParams,
...(event.stepContext ? { context: event.stepContext } : null),
enteredBy: event,
zIndex: activity.zIndex,
hasZIndex: event.hasZIndex ?? false,
Expand All @@ -89,7 +88,6 @@ export function makeActivityReducer(context: {
const newRoute = {
id: event.stepId,
params: event.stepParams,
...(event.stepContext ? { context: event.stepContext } : null),
enteredBy: event,
zIndex: activity.zIndex,
hasZIndex: event.hasZIndex ?? false,
Expand All @@ -109,7 +107,7 @@ export function makeActivityReducer(context: {
* Pop the last step
* If there are params in the previous step, set them as the new params
*/
StepPopped: (activity: Activity, _event: StepPoppedEvent): Activity => {
StepPopped: (activity: Activity, event: StepPoppedEvent): Activity => {
activity.steps.pop();

const beforeActivityParams = last(activity.steps)?.params;
Expand Down
1 change: 0 additions & 1 deletion core/src/aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export function aggregate(inputEvents: DomainEvent[], now: number): Stack {
{
...step,
zIndex: lastStepZIndex + (step.hasZIndex ? 1 : 0),
...(step.context ? { context: step.context } : null),
},
];
}, []);
Expand Down
1 change: 0 additions & 1 deletion core/src/event-types/StepPushedEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export type StepPushedEvent = BaseDomainEvent<
stepParams: {
[key: string]: string | undefined;
};
stepContext?: {};
targetActivityId?: string;
hasZIndex?: boolean;
}
Expand Down
1 change: 0 additions & 1 deletion core/src/event-types/StepReplacedEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export type StepReplacedEvent = BaseDomainEvent<
stepParams: {
[key: string]: string | undefined;
};
stepContext?: {};
targetActivityId?: string;
hasZIndex?: boolean;
}
Expand Down
16 changes: 0 additions & 16 deletions docs/pages/docs/advanced/history-sync.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,6 @@ The `historySyncPlugin` accepts two options: `config` and `fallbackActivity`.
**Warning** - When mapping activity parameters to path parameters, ensure that the parameter values are URL-safe. If special characters that are not URL-safe are used, query parameters may appear duplicated.
</Callout>

## Runtime Coercion of Activity Params

Regardless of how an activity is entered, including `push()`, `replace()`, `pushStep()`, `replaceStep()`, or URL arrival with or without a `decode` hook, the params you receive from `useActivityParams()` are always `string | undefined` at runtime.

```tsx
// These two paths used to produce different runtime types. They don't anymore.
push("ArticleActivity", { visible: true }) // store: { visible: "true" }
// URL arrival: /articles/1?visible=true // store: { visible: "true" }
```

The `encode` hook on a route still receives the original typed params, so you can use `encode: ({ visible }) => ({ visible: visible ? "y" : "n" })` exactly as before. Coercion happens at the `@stackflow/plugin-history-sync` boundary, after `encode` has consumed the typed values to build the URL.

<Callout emoji="⚠️">
**Migration note for `decode` users**: if you previously relied on `decode` to inject typed values, such as `decode: (p) => ({ count: Number(p.count) })`, and read them back via `useActivityParams().count` as a number, that value is now a string in the store. Perform the type coercion at the usage site instead: `Number(params.count)`.
</Callout>

<Callout emoji="⚡️">
In a server-side rendering environment, the `window.location` value is not available, so the initial activity cannot be determined. To set the initial activity, add the path value to the `req.path` field in the `initialContext` of the Stack as follows:

Expand Down
16 changes: 0 additions & 16 deletions docs/pages/docs/advanced/history-sync.ko.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,6 @@ const { Stack } = stackflow({
특수문자를 사용하는 경우 query parameter가 중복해서 나타날 수 있어요.
</Callout>

## 액티비티 파라미터의 런타임 강제 변환

액티비티에 어떻게 진입했는지와 관계없이, `push()`, `replace()`, `pushStep()`, `replaceStep()`, 또는 `decode` 훅 유무와 무관한 URL 직접 진입 모두에서 `useActivityParams()`로 받는 파라미터는 런타임에 항상 `string | undefined`예요.

```tsx
// 이전에는 두 경로가 런타임에 서로 다른 타입을 반환했지만, 이제는 동일해요.
push("ArticleActivity", { visible: true }) // 스토어: { visible: "true" }
// URL 진입: /articles/1?visible=true // 스토어: { visible: "true" }
```

라우트의 `encode` 훅은 여전히 원본의 타입이 적용된 파라미터를 받아요. 예를 들어 `encode: ({ visible }) => ({ visible: visible ? "y" : "n" })`는 이전과 동일하게 동작해요. 문자열화는 `encode`가 URL 생성을 위해 타입이 적용된 값을 소비한 이후에, `@stackflow/plugin-history-sync` 경계에서 이뤄져요.

<Callout emoji="⚠️">
**`decode` 사용자를 위한 마이그레이션 안내**: 이전에 `decode`로 타입이 적용된 값을 주입해서, 예를 들어 `decode: (p) => ({ count: Number(p.count) })`, `useActivityParams().count`를 숫자로 사용하셨다면, 이제 해당 값은 스토어에서 문자열이에요. 사용 지점에서 타입 변환을 해주세요: `Number(params.count)`.
</Callout>

<Callout emoji="⚡️">
서버사이드 렌더링 환경에서는 `window.location` 값이 없으므로 초기 액티비티를 결정할 수 없어요.
초기 액티비티를 결정하려면 다음과 같이 Stack의 `initialContext`에 `req.path` 필드에 path 값을 넣어주세요.
Expand Down
71 changes: 0 additions & 71 deletions extensions/plugin-history-sync/INTENT.md

This file was deleted.

Loading
Loading