@daltonr/pathwrite-svelte 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,13 +72,16 @@ Peer dependencies: Svelte 5+.
72
72
  |---|---|---|
73
73
  | `snapshot` | `PathSnapshot \| null` | Reactive getter. `null` when no path is active or when `completionBehaviour: "dismiss"` is used. With the default `"stayOnFinal"`, a non-null snapshot with `status === "completed"` is returned after the path finishes. |
74
74
  | `start(definition, data?)` | `Promise<void>` | Start or restart a path. |
75
- | `restart(definition, data?)` | `Promise<void>` | Tear down any active path and start fresh. |
75
+ | `restart()` | `Promise<void>` | Tear down any active path (without firing hooks) and restart the root path with the `initialData` from the original `start()`. Takes no arguments; rejects if nothing has been started. |
76
76
  | `next()` | `Promise<void>` | Advance one step. Completes on the last step. |
77
77
  | `previous()` | `Promise<void>` | Go back one step. No-op on the first step of a top-level path. |
78
78
  | `cancel()` | `Promise<void>` | Cancel the active path (or sub-path). |
79
79
  | `goToStep(stepId)` | `Promise<void>` | Jump to a step by ID. Calls `onLeave`/`onEnter`; bypasses guards. |
80
80
  | `goToStepChecked(stepId)` | `Promise<void>` | Jump to a step by ID, checking the current step's guard first. |
81
81
  | `setData(key, value)` | `Promise<void>` | Update a single data field. Type-safe when `TData` is specified. |
82
+ | `resetStep()` | `Promise<void>` | Restore the current step's data to what it was when the step was entered. Emits `stateChanged` with cause `"resetStep"`; no hooks run. |
83
+ | `retry()` | `Promise<void>` | Re-run the operation that set `snapshot.error`. Increments `retryCount` on repeated failure. No-op when there is no pending error. |
84
+ | `suspend()` | `Promise<void>` | Pause the path with intent to return. Emits `suspended`; all state and data are preserved. |
82
85
  | `startSubPath(definition, data?, meta?)` | `Promise<void>` | Push a sub-path. `meta` is returned to `onSubPathComplete`/`onSubPathCancel`. |
83
86
  | `validate()` | `void` | Set `snapshot.hasValidated` without navigating. Triggers all inline field errors simultaneously. Used to validate all tabs in a nested shell at once. |
84
87
 
@@ -96,24 +99,32 @@ Step content is supplied as Svelte 5 snippets whose names match each step's `id`
96
99
  | Prop | Type | Default | Description |
97
100
  |---|---|---|---|
98
101
  | `path` | `PathDefinition` | — | Path to run. Mutually exclusive with `engine`. |
99
- | `engine` | `PathEngine` | — | Externally-managed engine (e.g. from `restoreOrStart()`). Mutually exclusive with `path`. |
100
- | `initialData` | `PathData` | `{}` | Initial data passed to `engine.start()`. |
102
+ | `engine` | `PathEngine` | — | Externally-managed engine (e.g. from `restoreOrStart()`). Mutually exclusive with `path`. May be provided after mount (e.g. once an async `restoreOrStart()` resolves): the shell adopts it, re-subscribing and re-seeding from the new engine. Set `autoStart` to `false` if the shell should not start its own path in the meantime. |
103
+ | `initialData` | `PathData` | `{}` | Initial data passed to `engine.start()`. Overridden by the stored snapshot when `restoreKey` is set. |
101
104
  | `autoStart` | `boolean` | `true` | Start on mount. Ignored when `engine` is provided. |
102
105
  | `layout` | `"wizard" \| "form" \| "auto" \| "tabs"` | `"auto"` | `"wizard"`: Back on left, Cancel+Submit on right. `"form"`: Cancel on left, Submit on right, no Back. `"tabs"`: No progress header or footer — for tabbed interfaces. `"auto"` picks `"form"` for single-step paths. |
106
+ | `validationDisplay` | `"summary" \| "inline" \| "both"` | `"summary"` | Where `fieldErrors` are rendered. Use `"inline"` so step components render their own errors. |
107
+ | `progressLayout` | `"merged" \| "split" \| "rootOnly" \| "activeOnly"` | `"merged"` | How the root and sub-path progress bars are arranged while a sub-path is active. |
103
108
  | `hideProgress` | `boolean` | `false` | Hide the progress indicator. Also hidden automatically for single-step paths. |
109
+ | `hideFooter` | `boolean` | `false` | Hide the footer (navigation buttons). The error panel is still shown on async failure. |
104
110
  | `backLabel` | `string` | `"Previous"` | Previous button label. |
105
111
  | `nextLabel` | `string` | `"Next"` | Next button label. |
106
112
  | `completeLabel` | `string` | `"Complete"` | Complete button label (last step). |
113
+ | `loadingLabel` | `string` | `undefined` | Label for the Next/Complete button while an async operation is in progress. When unset, the button keeps its label and shows a CSS spinner. |
107
114
  | `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
108
115
  | `hideCancel` | `boolean` | `false` | Hide the Cancel button. |
109
- | `validateWhen` | `boolean` | `false` | When it becomes `true`, calls `validate()` on the engine. Bind to the outer snapshot's `hasAttemptedNext` when this shell is nested inside a step of an outer shell. |
110
- | `restoreKey` | `string` | — | When set, the shell automatically saves its full state (data + active step) into the nearest outer `PathShell`'s data under this key on every change, and restores from it on remount. No-op on a top-level shell. |
116
+ | `validateWhen` | `boolean` | `false` | When `true` (including already at mount), calls `validate()` on the engine so all steps show inline errors at once. Bind to the outer snapshot's `hasAttemptedNext` when this shell is nested inside a step of an outer shell. |
117
+ | `restoreKey` | `string` | — | When set, the shell automatically saves its full state (data + active step) into the nearest outer `PathShell`'s data under this key on every change, and restores from it on remount. No-op on a top-level shell. The stored value also carries the inner engine's serialized state, so a remount restores in place: no `onEnter` / `onLeave` re-run, attempted / visited state kept. |
111
118
  | `services` | `unknown` | `null` | Arbitrary services object available to step components via `usePathContext<TData, TServices>().services`. |
112
119
  | `oncomplete` | `(data: PathData) => void` | — | Called when the path finishes naturally. |
113
120
  | `oncancel` | `(data: PathData) => void` | — | Called when the path is cancelled. |
114
121
  | `onevent` | `(event: PathEvent) => void` | — | Called for every engine event. |
122
+ | `header` | `Snippet<[PathSnapshot]>` | — | Replaces the default progress header. A custom header is shown even for single-step paths, and hidden under `hideProgress` or `layout="tabs"`. |
123
+ | `footer` | `Snippet<[PathSnapshot, PathShellActions]>` | — | Replaces the default navigation footer. `actions` contains `next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`, `restart`, `retry`, `suspend`. |
115
124
  | `completion` | `Snippet<[PathSnapshot<any>]>` | — | Custom snippet rendered when `snapshot.status === "completed"` (`completionBehaviour: "stayOnFinal"`). Receives the completed snapshot. If omitted, a default "All done." panel is shown. |
116
125
 
126
+ The component instance also exposes `restart()` for `bind:this` refs, which restarts the path with its original `initialData` without remounting.
127
+
117
128
  > **Note:** Svelte requires event/callback props to be lowercase. Unlike React/Vue/Angular, passing `onComplete`, `onCancel`, or `onEvent` (camelCase) will be silently ignored. PathShell emits a `console.warn` in development if it detects one of these common mistakes.
118
129
 
119
130
  You can also replace the built-in header and footer with custom snippets:
@@ -137,7 +148,7 @@ You can also replace the built-in header and footer with custom snippets:
137
148
 
138
149
  ## usePathContext
139
150
 
140
- `usePathContext<TData>()` is the preferred way for step components rendered inside `<PathShell>` to access the path engine. `<PathShell>` calls `setContext()` internally with a private `Symbol` key; `usePathContext()` calls the matching `getContext()` and returns the same interface as `usePath`. It throws a clear error if called outside a `<PathShell>` — do not use Svelte's raw `getContext()` directly, as the key is a private `Symbol` and will silently return `undefined`.
151
+ `usePathContext<TData>()` is the preferred way for step components rendered inside `<PathShell>` to access the path engine. `<PathShell>` calls `setContext()` internally with a private `Symbol` key; `usePathContext()` calls the matching `getContext()` and returns `snapshot` (typed `PathSnapshot | null` — narrow with `{#if ctx.snapshot}`), the navigation actions (`next`, `previous`, `cancel`, `goToStep`, `goToStepChecked`, `setData`, `resetStep`, `restart`, `retry`, `suspend`) and `services`; it does not expose `start`, `startSubPath` or `validate`. It throws a clear error if called outside a `<PathShell>` — do not use Svelte's raw `getContext()` directly, as the key is a private `Symbol` and will silently return `undefined`. The context is the full `usePath()` return type (`PathContext` extends `UsePathReturn`), so `start`, `startSubPath` and `validate` are available to step components too — for example to launch a sub-path from a button inside a step.
141
152
 
142
153
  ```svelte
143
154
  <script lang="ts">
@@ -154,10 +165,22 @@ You can also replace the built-in header and footer with custom snippets:
154
165
  {/if}
155
166
  ```
156
167
 
168
+ ## Other exports
169
+
170
+ | Export | Description |
171
+ |---|---|
172
+ | `bindData(getSnapshot, setData, key)` | Two-way binding helper for inputs. Returns an object with a reactive `value` getter (reads `getSnapshot()?.data[key]`) and a `set(value)` method that calls `setData(key, value)`. Example: `const name = bindData(() => path.snapshot, path.setData, "name")`, then `<input value={name.value} oninput={(e) => name.set(e.currentTarget.value)} />`. |
173
+ | `stepIdToCamelCase(id)` | Converts a hyphenated step ID to camelCase (`"cover-letter"` → `"coverLetter"`) — the conversion `<PathShell>` uses to resolve snippets for hyphenated step IDs. |
174
+ | `setPathContext(ctx)` | Sets the `PathContext` that `usePathContext()` reads, under the adapter's private `Symbol` key. Used internally by `<PathShell>`; only needed when building your own shell component. |
175
+ | `getPathContextOrNull()` | Reads the nearest ancestor `PathContext`, or `undefined` when there is none. Used internally by `<PathShell>` to reach the outer shell for `restoreKey`; call it before `setPathContext()` so it reads the parent rather than self. |
176
+ | `formatFieldKey`, `errorPhaseMessage` | Re-exported from `@daltonr/pathwrite-core` for building custom summaries and error panels. |
177
+
178
+ `PathEngine` is re-exported as a **type only**. To construct an engine, import the class from `@daltonr/pathwrite-core`.
179
+
157
180
  ## Further reading
158
181
 
159
182
  - [Svelte getting started guide](../../docs/getting-started/frameworks/svelte.md)
160
- - [Navigation & guards](../../docs/guides/navigation.md)
183
+ - [Navigation & guards](../../docs/developer-guide/04-navigation.md)
161
184
  - [Full documentation](../../docs/README.md)
162
185
 
163
186
  ---
@@ -1,7 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte';
3
3
  import { usePath, setPathContext, getPathContextOrNull, formatFieldKey, errorPhaseMessage, stepIdToCamelCase } from './index.svelte.js';
4
- import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout } from './index.svelte.js';
4
+ import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout, PathShellActions } from './index.svelte.js';
5
+ import { PathEngine as PathEngineClass } from '@daltonr/pathwrite-core';
6
+ import type { SerializedPathState } from '@daltonr/pathwrite-core';
5
7
  import type { Snippet, Component } from 'svelte';
6
8
 
7
9
 
@@ -61,7 +63,7 @@
61
63
  onevent?: (event: any) => void;
62
64
  // Optional override snippets for header and footer
63
65
  header?: Snippet<[PathSnapshot<any>]>;
64
- footer?: Snippet<[PathSnapshot<any>, object]>;
66
+ footer?: Snippet<[PathSnapshot<any>, PathShellActions]>;
65
67
  /** Snippet rendered when `snapshot.status === "completed"`. Defaults to a simple "All done." panel with a restart button. */
66
68
  completion?: Snippet<[PathSnapshot<any>]>;
67
69
  // All other props treated as step components keyed by step ID
@@ -100,33 +102,56 @@
100
102
  // parent shell's snapshot and setData for restoreKey auto-wiring.
101
103
  const outerCtx = getPathContextOrNull();
102
104
 
105
+ // When remounting under restoreKey, rebuild the inner engine from the state the
106
+ // previous instance exported, instead of starting the path and jumping to the
107
+ // step (which re-ran onEnter/onLeave and lost attempted / visited state).
108
+ // svelte-ignore state_referenced_locally — read once at init on purpose: restore happens at mount only
109
+ const restoredEngine: PathEngine | null = (() => {
110
+ if (engineProp || !restoreKey || !outerCtx || !path) return null;
111
+ const stored = outerCtx.snapshot?.data[restoreKey] as { serializedState?: SerializedPathState } | undefined;
112
+ if (!stored || typeof stored !== 'object' || !stored.serializedState) return null;
113
+ try {
114
+ return PathEngineClass.fromState(stored.serializedState, { [path.id]: path });
115
+ } catch {
116
+ return null; // unusable state (e.g. the path definition changed): start fresh below
117
+ }
118
+ })();
119
+ // The shell's own engine is created once; an `engine` prop — present at
120
+ // mount or arriving later — always takes precedence and is adopted by usePath.
121
+ const ownEngine: PathEngine = restoredEngine ?? new PathEngineClass();
122
+ const currentEngine = (): PathEngine => engineProp ?? ownEngine;
123
+
103
124
  // Initialize path engine
104
125
  const pathReturn = usePath({
105
- get engine() { return engineProp; },
126
+ get engine() { return currentEngine(); },
106
127
  onEvent: (event) => {
107
128
  onevent?.(event);
108
129
  if (event.type === 'completed') oncomplete?.(event.data);
109
130
  if (event.type === 'cancelled') oncancel?.(event.data);
110
131
  if (restoreKey && outerCtx && event.type === 'stateChanged') {
111
132
  (outerCtx.setData as unknown as (key: string, value: unknown) => Promise<void>)(
112
- restoreKey, event.snapshot
133
+ restoreKey, { ...event.snapshot, serializedState: currentEngine().exportState() }
113
134
  );
114
135
  }
115
136
  }
116
137
  });
117
138
 
118
- const { start, next, previous, cancel, goToStep, goToStepChecked, setData, restart: restartFn, retry, suspend } = pathReturn;
139
+ const { start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData, resetStep, restart: restartFn, retry, suspend, validate } = pathReturn;
119
140
 
120
141
  // Provide context for child step components
121
142
  setPathContext({
122
143
  get snapshot() { return pathReturn.snapshot; },
144
+ start,
145
+ startSubPath,
146
+ validate,
123
147
  next,
124
148
  previous,
125
149
  cancel,
126
150
  goToStep,
127
151
  goToStepChecked,
128
152
  setData,
129
- restart: () => restartFn(path, initialData),
153
+ resetStep,
154
+ restart: () => restartFn(),
130
155
  retry,
131
156
  suspend,
132
157
  get services() { return services; },
@@ -134,8 +159,13 @@
134
159
 
135
160
  // Dev-mode warning: camelCase callback props are silently ignored in Svelte.
136
161
  // Warn if the user passed onComplete/onCancel/onEvent instead of the correct
137
- // lowercase forms oncomplete/oncancel/onevent.
138
- if (import.meta.env?.DEV !== false) {
162
+ // lowercase forms oncomplete/oncancel/onevent. Runs once, on mount (a closure
163
+ // reading the props at the top level would only capture their initial value).
164
+ // `import.meta.env` is a bundler convention (Vite); the cast keeps this
165
+ // package free of Vite's ambient types.
166
+ const isDev = (import.meta as { env?: { DEV?: boolean } }).env?.DEV !== false;
167
+ onMount(() => {
168
+ if (!isDev) return;
139
169
  const camelCallbacks = ['onComplete', 'onCancel', 'onEvent'] as const;
140
170
  for (const name of camelCallbacks) {
141
171
  if (name in stepSnippets) {
@@ -144,12 +174,13 @@
144
174
  );
145
175
  }
146
176
  }
147
- }
177
+ });
148
178
 
149
179
  // Auto-start the path when no external engine is provided
150
180
  let started = false;
151
181
  onMount(() => {
152
- if (autoStart && !started && !engineProp) {
182
+ if (autoStart && !started && !engineProp && !restoredEngine) {
183
+ if (!path) throw new Error('[PathShell] "path" is required when no "engine" is provided');
153
184
  started = true;
154
185
  let startData: PathData = initialData ?? {};
155
186
  let restoreStepId: string | undefined;
@@ -180,7 +211,11 @@
180
211
  }
181
212
 
182
213
  let snap = $derived(pathReturn.snapshot);
183
- let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData), retry, suspend });
214
+ let actions: PathShellActions = $derived({
215
+ next, previous, cancel, goToStep, goToStepChecked,
216
+ setData: (key, value) => setData(key as never, value as never),
217
+ restart: () => restartFn(), retry, suspend
218
+ });
184
219
 
185
220
  let effectiveHideProgress = $derived(hideProgress || layout === 'tabs');
186
221
  let effectiveHideFooter = $derived(hideFooter || layout === 'tabs');
@@ -202,7 +237,7 @@
202
237
  * ```
203
238
  */
204
239
  export function restart(): Promise<void> {
205
- return pathReturn.restart(path, initialData);
240
+ return pathReturn.restart();
206
241
  }
207
242
  </script>
208
243
 
@@ -211,7 +246,7 @@
211
246
  <div class="pw-shell__empty">
212
247
  <p>No active path.</p>
213
248
  {#if !autoStart}
214
- <button type="button" class="pw-shell__start-btn" onclick={() => start(path, initialData)}>
249
+ <button type="button" class="pw-shell__start-btn" onclick={() => path && start(path, initialData)}>
215
250
  Start
216
251
  </button>
217
252
  {/if}
@@ -239,7 +274,7 @@
239
274
  {:else}
240
275
  <div class="pw-shell__completion">
241
276
  <p class="pw-shell__completion-message">All done.</p>
242
- <button type="button" class="pw-shell__completion-restart" onclick={() => restartFn(path, initialData)}>
277
+ <button type="button" class="pw-shell__completion-restart" onclick={() => restartFn()}>
243
278
  Start over
244
279
  </button>
245
280
  </div>
@@ -1,4 +1,4 @@
1
- import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout } from './index.svelte.js';
1
+ import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout, PathShellActions } from './index.svelte.js';
2
2
  import type { Snippet, Component } from 'svelte';
3
3
  interface Props {
4
4
  path?: PathDefinition<any>;
@@ -54,7 +54,7 @@ interface Props {
54
54
  oncancel?: (data: PathData) => void;
55
55
  onevent?: (event: any) => void;
56
56
  header?: Snippet<[PathSnapshot<any>]>;
57
- footer?: Snippet<[PathSnapshot<any>, object]>;
57
+ footer?: Snippet<[PathSnapshot<any>, PathShellActions]>;
58
58
  /** Snippet rendered when `snapshot.status === "completed"`. Defaults to a simple "All done." panel with a restart button. */
59
59
  completion?: Snippet<[PathSnapshot<any>]>;
60
60
  [key: string]: Component<any> | any;
@@ -1 +1 @@
1
- {"version":3,"file":"PathShell.svelte.d.ts","sourceRoot":"","sources":["../src/PathShell.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC5G,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAI/C,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8HAA8H;IAC9H,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qNAAqN;IACrN,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC7C;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClD;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAE/B,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,6HAA6H;IAC7H,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAE1C,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;CACrC;AA0UH,QAAA,MAAM,SAAS;mBA7LQ,QAAQ,IAAI,CAAC;MA6LmB,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"PathShell.svelte.d.ts","sourceRoot":"","sources":["../src/PathShell.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG9H,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAI/C,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8HAA8H;IAC9H,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qNAAqN;IACrN,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC7C;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClD;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAE/B,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACxD,6HAA6H;IAC7H,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAE1C,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;CACrC;AA6WH,QAAA,MAAM,SAAS;mBA7LQ,QAAQ,IAAI,CAAC;MA6LmB,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
package/dist/index.css CHANGED
@@ -357,6 +357,23 @@
357
357
  content: ":";
358
358
  }
359
359
 
360
+ /* ------------------------------------------------------------------ */
361
+ /* Inline field-level errors and warnings (<FieldError /> component) */
362
+ /* ------------------------------------------------------------------ */
363
+ .pw-field-error {
364
+ display: block;
365
+ font-size: 12px;
366
+ color: var(--pw-color-error);
367
+ margin-top: 4px;
368
+ }
369
+
370
+ .pw-field-warning {
371
+ display: block;
372
+ font-size: 12px;
373
+ color: var(--pw-color-warning);
374
+ margin-top: 4px;
375
+ }
376
+
360
377
  /* ------------------------------------------------------------------ */
361
378
  /* Footer — navigation buttons */
362
379
  /* ------------------------------------------------------------------ */
@@ -12,6 +12,12 @@ export interface UsePathOptions {
12
12
  * - The engine lifecycle (start / cleanup) is the **caller's responsibility**.
13
13
  * - `PathShell` will skip its own `autoStart` call.
14
14
  */
15
+ /**
16
+ * An externally managed engine. Pass it as a getter (`get engine() { … }`)
17
+ * over a reactive prop to have the hook track it: an engine that arrives
18
+ * later (e.g. from an async `restoreOrStart()`) or is swapped is adopted —
19
+ * the hook re-subscribes and re-seeds its snapshot from the new engine.
20
+ */
15
21
  engine?: PathEngine;
16
22
  /** Called for every engine event (stateChanged, completed, cancelled, resumed). */
17
23
  onEvent?: (event: PathEvent) => void;
@@ -47,8 +53,9 @@ export interface UsePathReturn<TData extends PathData = PathData> {
47
53
  /** Reset the current step's data to what it was when the step was entered. Useful for "Clear" or "Reset" buttons. */
48
54
  resetStep: () => Promise<void>;
49
55
  /**
50
- * Tear down any active path (without firing hooks) and immediately start the
51
- * given path fresh. Safe to call whether or not a path is currently active.
56
+ * Tear down any active path (without firing hooks) and immediately restart
57
+ * the root path with the `initialData` from the original `start()` call.
58
+ * Takes no arguments; rejects if the engine has never been started.
52
59
  * Use for "Start over" / retry flows without remounting the component.
53
60
  */
54
61
  restart: () => Promise<void>;
@@ -114,8 +121,12 @@ export interface UsePathReturn<TData extends PathData = PathData> {
114
121
  * ```
115
122
  */
116
123
  export declare function usePath<TData extends PathData = PathData>(options?: UsePathOptions): UsePathReturn<TData>;
117
- export interface PathContext<TData extends PathData = PathData, TServices = unknown> {
118
- readonly snapshot: PathSnapshot<TData>;
124
+ /**
125
+ * Navigation actions handed to a custom `footer` snippet of `<PathShell>`
126
+ * (`{#snippet footer(snap, actions)}`). Same shape as the other adapters'
127
+ * `PathShellActions`.
128
+ */
129
+ export interface PathShellActions {
119
130
  next: () => Promise<void>;
120
131
  previous: () => Promise<void>;
121
132
  cancel: () => Promise<void>;
@@ -125,17 +136,20 @@ export interface PathContext<TData extends PathData = PathData, TServices = unkn
125
136
  goToStepChecked: (stepId: string, options?: {
126
137
  validateOnLeave?: boolean;
127
138
  }) => Promise<void>;
128
- setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
129
- resetStep: () => Promise<void>;
139
+ setData: (key: string, value: unknown) => Promise<void>;
140
+ /** Restart the shell's current path with its current `initialData`. */
130
141
  restart: () => Promise<void>;
131
142
  /** Re-run the operation that set `snapshot.error`. */
132
143
  retry: () => Promise<void>;
133
144
  /** Pause with intent to return, preserving all state. Emits `suspended`. */
134
145
  suspend: () => Promise<void>;
135
- /**
136
- * Services object passed through context from `PathShell`.
137
- * Typed as `TServices` when `usePathContext<TData, TServices>()` is used.
138
- */
146
+ }
147
+ /**
148
+ * What step components receive from `usePathContext()`: everything `usePath()`
149
+ * returns (derived from `UsePathReturn`, so the two cannot drift apart) plus
150
+ * the `services` object given to `<PathShell>`.
151
+ */
152
+ export interface PathContext<TData extends PathData = PathData, TServices = unknown> extends UsePathReturn<TData> {
139
153
  services: TServices;
140
154
  }
141
155
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../src/index.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.svelte.d.ts","sourceRoot":"","sources":["../src/index.svelte.ts"],"names":[],"mappings":"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"}
@@ -60,11 +60,11 @@ export { formatFieldKey, errorPhaseMessage } from "@daltonr/pathwrite-core";
60
60
  * ```
61
61
  */
62
62
  export function usePath(options) {
63
- const engine = options?.engine ?? new PathEngineClass();
64
- // Reactive snapshot via $state rune
63
+ let ownEngine = null;
64
+ const resolveEngine = () => options?.engine ?? (ownEngine ??= new PathEngineClass());
65
+ let engine = resolveEngine();
65
66
  let _snapshot = $state(engine.snapshot());
66
- // Subscribe to engine events
67
- const unsubscribe = engine.subscribe((event) => {
67
+ const onEngineEvent = (event) => {
68
68
  if (event.type === "stateChanged" || event.type === "resumed") {
69
69
  _snapshot = event.snapshot;
70
70
  }
@@ -72,9 +72,24 @@ export function usePath(options) {
72
72
  _snapshot = engine.snapshot();
73
73
  }
74
74
  options?.onEvent?.(event);
75
+ };
76
+ let unsubscribe = engine.subscribe(onEngineEvent);
77
+ // Adopt a late or swapped engine (the `engine` option is a getter over a
78
+ // reactive prop): re-subscribe and re-seed the snapshot. Created in its own
79
+ // effect root so usePath() also works outside a component.
80
+ const stopWatching = $effect.root(() => {
81
+ $effect(() => {
82
+ const next = resolveEngine();
83
+ if (next === engine)
84
+ return;
85
+ unsubscribe();
86
+ engine = next;
87
+ _snapshot = engine.snapshot();
88
+ unsubscribe = engine.subscribe(onEngineEvent);
89
+ });
75
90
  });
76
91
  // Auto-cleanup when component is destroyed
77
- onDestroy(unsubscribe);
92
+ onDestroy(() => { unsubscribe(); stopWatching(); });
78
93
  const start = (path, initialData = {}) => engine.start(path, initialData);
79
94
  const startSubPath = (path, initialData = {}, meta) => engine.startSubPath(path, initialData, meta);
80
95
  const next = () => engine.next();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daltonr/pathwrite-svelte",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Svelte 5 adapter for @daltonr/pathwrite-core — runes-based reactive bindings and optional PathShell component.",
@@ -28,6 +28,7 @@
28
28
  "import": "./dist/index.svelte.js"
29
29
  },
30
30
  "./PathShell.svelte": {
31
+ "types": "./dist/PathShell.svelte.d.ts",
31
32
  "svelte": "./dist/PathShell.svelte",
32
33
  "import": "./dist/PathShell.svelte"
33
34
  },
@@ -44,7 +45,8 @@
44
45
  "LICENSE"
45
46
  ],
46
47
  "scripts": {
47
- "build": "svelte-package -i src -o dist && cp ../shell.css dist/index.css",
48
+ "check": "svelte-check --tsconfig ./tsconfig.check.json --fail-on-warnings",
49
+ "build": "npm run check && svelte-package -i src -o dist && cp ../shell.css dist/index.css",
48
50
  "clean": "rm -rf dist tsconfig.tsbuildinfo",
49
51
  "prepublishOnly": "test -d dist && echo 'dist already built, skipping' || (npm run clean && npm run build)"
50
52
  },
@@ -52,12 +54,15 @@
52
54
  "svelte": ">=5.0.0"
53
55
  },
54
56
  "dependencies": {
55
- "@daltonr/pathwrite-core": "^0.12.0"
57
+ "@daltonr/pathwrite-core": "^0.13.0"
56
58
  },
57
59
  "devDependencies": {
58
60
  "@sveltejs/package": "^2.5.7",
59
61
  "@sveltejs/vite-plugin-svelte": "^4.0.0",
60
62
  "@testing-library/svelte": "^5.0.0",
61
63
  "svelte": "^5.0.0"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public"
62
67
  }
63
68
  }
@@ -1,7 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte';
3
3
  import { usePath, setPathContext, getPathContextOrNull, formatFieldKey, errorPhaseMessage, stepIdToCamelCase } from './index.svelte.js';
4
- import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout } from './index.svelte.js';
4
+ import type { PathDefinition, PathData, PathEngine, PathSnapshot, ProgressLayout, PathShellActions } from './index.svelte.js';
5
+ import { PathEngine as PathEngineClass } from '@daltonr/pathwrite-core';
6
+ import type { SerializedPathState } from '@daltonr/pathwrite-core';
5
7
  import type { Snippet, Component } from 'svelte';
6
8
 
7
9
 
@@ -61,7 +63,7 @@
61
63
  onevent?: (event: any) => void;
62
64
  // Optional override snippets for header and footer
63
65
  header?: Snippet<[PathSnapshot<any>]>;
64
- footer?: Snippet<[PathSnapshot<any>, object]>;
66
+ footer?: Snippet<[PathSnapshot<any>, PathShellActions]>;
65
67
  /** Snippet rendered when `snapshot.status === "completed"`. Defaults to a simple "All done." panel with a restart button. */
66
68
  completion?: Snippet<[PathSnapshot<any>]>;
67
69
  // All other props treated as step components keyed by step ID
@@ -100,33 +102,56 @@
100
102
  // parent shell's snapshot and setData for restoreKey auto-wiring.
101
103
  const outerCtx = getPathContextOrNull();
102
104
 
105
+ // When remounting under restoreKey, rebuild the inner engine from the state the
106
+ // previous instance exported, instead of starting the path and jumping to the
107
+ // step (which re-ran onEnter/onLeave and lost attempted / visited state).
108
+ // svelte-ignore state_referenced_locally — read once at init on purpose: restore happens at mount only
109
+ const restoredEngine: PathEngine | null = (() => {
110
+ if (engineProp || !restoreKey || !outerCtx || !path) return null;
111
+ const stored = outerCtx.snapshot?.data[restoreKey] as { serializedState?: SerializedPathState } | undefined;
112
+ if (!stored || typeof stored !== 'object' || !stored.serializedState) return null;
113
+ try {
114
+ return PathEngineClass.fromState(stored.serializedState, { [path.id]: path });
115
+ } catch {
116
+ return null; // unusable state (e.g. the path definition changed): start fresh below
117
+ }
118
+ })();
119
+ // The shell's own engine is created once; an `engine` prop — present at
120
+ // mount or arriving later — always takes precedence and is adopted by usePath.
121
+ const ownEngine: PathEngine = restoredEngine ?? new PathEngineClass();
122
+ const currentEngine = (): PathEngine => engineProp ?? ownEngine;
123
+
103
124
  // Initialize path engine
104
125
  const pathReturn = usePath({
105
- get engine() { return engineProp; },
126
+ get engine() { return currentEngine(); },
106
127
  onEvent: (event) => {
107
128
  onevent?.(event);
108
129
  if (event.type === 'completed') oncomplete?.(event.data);
109
130
  if (event.type === 'cancelled') oncancel?.(event.data);
110
131
  if (restoreKey && outerCtx && event.type === 'stateChanged') {
111
132
  (outerCtx.setData as unknown as (key: string, value: unknown) => Promise<void>)(
112
- restoreKey, event.snapshot
133
+ restoreKey, { ...event.snapshot, serializedState: currentEngine().exportState() }
113
134
  );
114
135
  }
115
136
  }
116
137
  });
117
138
 
118
- const { start, next, previous, cancel, goToStep, goToStepChecked, setData, restart: restartFn, retry, suspend } = pathReturn;
139
+ const { start, startSubPath, next, previous, cancel, goToStep, goToStepChecked, setData, resetStep, restart: restartFn, retry, suspend, validate } = pathReturn;
119
140
 
120
141
  // Provide context for child step components
121
142
  setPathContext({
122
143
  get snapshot() { return pathReturn.snapshot; },
144
+ start,
145
+ startSubPath,
146
+ validate,
123
147
  next,
124
148
  previous,
125
149
  cancel,
126
150
  goToStep,
127
151
  goToStepChecked,
128
152
  setData,
129
- restart: () => restartFn(path, initialData),
153
+ resetStep,
154
+ restart: () => restartFn(),
130
155
  retry,
131
156
  suspend,
132
157
  get services() { return services; },
@@ -134,8 +159,13 @@
134
159
 
135
160
  // Dev-mode warning: camelCase callback props are silently ignored in Svelte.
136
161
  // Warn if the user passed onComplete/onCancel/onEvent instead of the correct
137
- // lowercase forms oncomplete/oncancel/onevent.
138
- if (import.meta.env?.DEV !== false) {
162
+ // lowercase forms oncomplete/oncancel/onevent. Runs once, on mount (a closure
163
+ // reading the props at the top level would only capture their initial value).
164
+ // `import.meta.env` is a bundler convention (Vite); the cast keeps this
165
+ // package free of Vite's ambient types.
166
+ const isDev = (import.meta as { env?: { DEV?: boolean } }).env?.DEV !== false;
167
+ onMount(() => {
168
+ if (!isDev) return;
139
169
  const camelCallbacks = ['onComplete', 'onCancel', 'onEvent'] as const;
140
170
  for (const name of camelCallbacks) {
141
171
  if (name in stepSnippets) {
@@ -144,12 +174,13 @@
144
174
  );
145
175
  }
146
176
  }
147
- }
177
+ });
148
178
 
149
179
  // Auto-start the path when no external engine is provided
150
180
  let started = false;
151
181
  onMount(() => {
152
- if (autoStart && !started && !engineProp) {
182
+ if (autoStart && !started && !engineProp && !restoredEngine) {
183
+ if (!path) throw new Error('[PathShell] "path" is required when no "engine" is provided');
153
184
  started = true;
154
185
  let startData: PathData = initialData ?? {};
155
186
  let restoreStepId: string | undefined;
@@ -180,7 +211,11 @@
180
211
  }
181
212
 
182
213
  let snap = $derived(pathReturn.snapshot);
183
- let actions = $derived({ next, previous, cancel, goToStep, goToStepChecked, setData, restart: () => restartFn(path, initialData), retry, suspend });
214
+ let actions: PathShellActions = $derived({
215
+ next, previous, cancel, goToStep, goToStepChecked,
216
+ setData: (key, value) => setData(key as never, value as never),
217
+ restart: () => restartFn(), retry, suspend
218
+ });
184
219
 
185
220
  let effectiveHideProgress = $derived(hideProgress || layout === 'tabs');
186
221
  let effectiveHideFooter = $derived(hideFooter || layout === 'tabs');
@@ -202,7 +237,7 @@
202
237
  * ```
203
238
  */
204
239
  export function restart(): Promise<void> {
205
- return pathReturn.restart(path, initialData);
240
+ return pathReturn.restart();
206
241
  }
207
242
  </script>
208
243
 
@@ -211,7 +246,7 @@
211
246
  <div class="pw-shell__empty">
212
247
  <p>No active path.</p>
213
248
  {#if !autoStart}
214
- <button type="button" class="pw-shell__start-btn" onclick={() => start(path, initialData)}>
249
+ <button type="button" class="pw-shell__start-btn" onclick={() => path && start(path, initialData)}>
215
250
  Start
216
251
  </button>
217
252
  {/if}
@@ -239,7 +274,7 @@
239
274
  {:else}
240
275
  <div class="pw-shell__completion">
241
276
  <p class="pw-shell__completion-message">All done.</p>
242
- <button type="button" class="pw-shell__completion-restart" onclick={() => restartFn(path, initialData)}>
277
+ <button type="button" class="pw-shell__completion-restart" onclick={() => restartFn()}>
243
278
  Start over
244
279
  </button>
245
280
  </div>
@@ -39,6 +39,12 @@ export interface UsePathOptions {
39
39
  * - The engine lifecycle (start / cleanup) is the **caller's responsibility**.
40
40
  * - `PathShell` will skip its own `autoStart` call.
41
41
  */
42
+ /**
43
+ * An externally managed engine. Pass it as a getter (`get engine() { … }`)
44
+ * over a reactive prop to have the hook track it: an engine that arrives
45
+ * later (e.g. from an async `restoreOrStart()`) or is swapped is adopted —
46
+ * the hook re-subscribes and re-seeds its snapshot from the new engine.
47
+ */
42
48
  engine?: PathEngine;
43
49
  /** Called for every engine event (stateChanged, completed, cancelled, resumed). */
44
50
  onEvent?: (event: PathEvent) => void;
@@ -71,8 +77,9 @@ export interface UsePathReturn<TData extends PathData = PathData> {
71
77
  /** Reset the current step's data to what it was when the step was entered. Useful for "Clear" or "Reset" buttons. */
72
78
  resetStep: () => Promise<void>;
73
79
  /**
74
- * Tear down any active path (without firing hooks) and immediately start the
75
- * given path fresh. Safe to call whether or not a path is currently active.
80
+ * Tear down any active path (without firing hooks) and immediately restart
81
+ * the root path with the `initialData` from the original `start()` call.
82
+ * Takes no arguments; rejects if the engine has never been started.
76
83
  * Use for "Start over" / retry flows without remounting the component.
77
84
  */
78
85
  restart: () => Promise<void>;
@@ -145,25 +152,40 @@ export interface UsePathReturn<TData extends PathData = PathData> {
145
152
  export function usePath<TData extends PathData = PathData>(
146
153
  options?: UsePathOptions
147
154
  ): UsePathReturn<TData> {
148
- const engine = options?.engine ?? new PathEngineClass();
155
+ let ownEngine: PathEngine | null = null;
156
+ const resolveEngine = (): PathEngine => options?.engine ?? (ownEngine ??= new PathEngineClass());
157
+ let engine = resolveEngine();
149
158
 
150
- // Reactive snapshot via $state rune
151
159
  let _snapshot: PathSnapshot<TData> | null = $state(
152
160
  engine.snapshot() as PathSnapshot<TData> | null
153
161
  );
154
162
 
155
- // Subscribe to engine events
156
- const unsubscribe = engine.subscribe((event: PathEvent) => {
163
+ const onEngineEvent = (event: PathEvent): void => {
157
164
  if (event.type === "stateChanged" || event.type === "resumed") {
158
165
  _snapshot = event.snapshot as PathSnapshot<TData>;
159
166
  } else if (event.type === "completed" || event.type === "cancelled") {
160
167
  _snapshot = engine.snapshot() as PathSnapshot<TData> | null;
161
168
  }
162
169
  options?.onEvent?.(event);
170
+ };
171
+ let unsubscribe = engine.subscribe(onEngineEvent);
172
+
173
+ // Adopt a late or swapped engine (the `engine` option is a getter over a
174
+ // reactive prop): re-subscribe and re-seed the snapshot. Created in its own
175
+ // effect root so usePath() also works outside a component.
176
+ const stopWatching = $effect.root(() => {
177
+ $effect(() => {
178
+ const next = resolveEngine();
179
+ if (next === engine) return;
180
+ unsubscribe();
181
+ engine = next;
182
+ _snapshot = engine.snapshot() as PathSnapshot<TData> | null;
183
+ unsubscribe = engine.subscribe(onEngineEvent);
184
+ });
163
185
  });
164
186
 
165
187
  // Auto-cleanup when component is destroyed
166
- onDestroy(unsubscribe);
188
+ onDestroy(() => { unsubscribe(); stopWatching(); });
167
189
 
168
190
  const start = (path: PathDefinition<any>, initialData: PathData = {}): Promise<void> =>
169
191
  engine.start(path, initialData);
@@ -210,30 +232,38 @@ export function usePath<TData extends PathData = PathData>(
210
232
  };
211
233
  }
212
234
 
213
- // ---------------------------------------------------------------------------
214
- // Context API for PathShell
215
- // ---------------------------------------------------------------------------
216
-
217
- const PATH_CONTEXT_KEY = Symbol("pathwrite-context");
218
-
219
- export interface PathContext<TData extends PathData = PathData, TServices = unknown> {
220
- readonly snapshot: PathSnapshot<TData>;
235
+ /**
236
+ * Navigation actions handed to a custom `footer` snippet of `<PathShell>`
237
+ * (`{#snippet footer(snap, actions)}`). Same shape as the other adapters'
238
+ * `PathShellActions`.
239
+ */
240
+ export interface PathShellActions {
221
241
  next: () => Promise<void>;
222
242
  previous: () => Promise<void>;
223
243
  cancel: () => Promise<void>;
224
244
  goToStep: (stepId: string, options?: { validateOnLeave?: boolean }) => Promise<void>;
225
245
  goToStepChecked: (stepId: string, options?: { validateOnLeave?: boolean }) => Promise<void>;
226
- setData: <K extends string & keyof TData>(key: K, value: TData[K]) => Promise<void>;
227
- resetStep: () => Promise<void>;
246
+ setData: (key: string, value: unknown) => Promise<void>;
247
+ /** Restart the shell's current path with its current `initialData`. */
228
248
  restart: () => Promise<void>;
229
249
  /** Re-run the operation that set `snapshot.error`. */
230
250
  retry: () => Promise<void>;
231
251
  /** Pause with intent to return, preserving all state. Emits `suspended`. */
232
252
  suspend: () => Promise<void>;
233
- /**
234
- * Services object passed through context from `PathShell`.
235
- * Typed as `TServices` when `usePathContext<TData, TServices>()` is used.
236
- */
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Context API for PathShell
257
+ // ---------------------------------------------------------------------------
258
+
259
+ const PATH_CONTEXT_KEY = Symbol("pathwrite-context");
260
+
261
+ /**
262
+ * What step components receive from `usePathContext()`: everything `usePath()`
263
+ * returns (derived from `UsePathReturn`, so the two cannot drift apart) plus
264
+ * the `services` object given to `<PathShell>`.
265
+ */
266
+ export interface PathContext<TData extends PathData = PathData, TServices = unknown> extends UsePathReturn<TData> {
237
267
  services: TServices;
238
268
  }
239
269