@rangojs/router 0.29.0 → 0.30.1

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/dist/bin/rango.js CHANGED
@@ -1644,7 +1644,7 @@ var init_package = __esm({
1644
1644
  "package.json"() {
1645
1645
  package_default = {
1646
1646
  name: "@rangojs/router",
1647
- version: "0.29.0",
1647
+ version: "0.30.1",
1648
1648
  description: "Django-inspired RSC router with composable URL patterns",
1649
1649
  keywords: [
1650
1650
  "react",
@@ -13,11 +13,12 @@ export type ServerActionFunction = ((...args: any[]) => Promise<any>) & {
13
13
  * tracks the state of individual server action invocations.
14
14
  *
15
15
  * Uses the event controller for reactive state management.
16
- * State is derived from the inflight actions tracked by the controller.
16
+ * State is derived from the calls the controller tracks: those in flight,
17
+ * and each action's last settled call.
17
18
  *
18
19
  * Features:
19
20
  * - Tracks action lifecycle: idle → loading → streaming → idle
20
- * - Captures result/error locally (React handles cleanup)
21
+ * - Keeps the settled call's state until the action is called again.
21
22
  * - If the same action fires several times, reports the latest call to
22
23
  * start. An earlier call that settles later changes nothing it reports;
23
24
  * a latest call that was aborted reports idle with nothing. One call at a
@@ -39,6 +39,7 @@ export declare function useHandle<T, A, S>(handle: Handle<T, A>, selector: (data
39
39
  * location commit with their page.
40
40
  *
41
41
  * A value shallow-equal to the last one sets nothing, so a reader renders only
42
- * when its value moves.
42
+ * when its value moves. A release is compared with what React committed, as in
43
+ * useRouteState.
43
44
  */
44
45
  export declare function useCommitAnchoredState<T>(init: () => T, waitForRelease?: boolean): [T, (value: T, signal?: RouteStateSignal) => void];
@@ -0,0 +1,45 @@
1
+ import type { RouteStateSignal } from "../event-controller.js";
2
+ /**
3
+ * The value a reader rendered in React's last commit of it, written by its
4
+ * layout effect. A page's release runs in the provider's layout effect, after
5
+ * every reader's (children first), so it reads the commit that brings the
6
+ * page.
7
+ */
8
+ export declare function useCommitted<T>(value: T): {
9
+ readonly current: T;
10
+ };
11
+ /**
12
+ * Sets a route-state reader's value. `next` maps the value React ends on once
13
+ * every set so far commits (`latest`) to the reader's value; `rebase` sets
14
+ * `next` itself, so React recomputes it on whatever base it renders. The value
15
+ * must not be a function.
16
+ */
17
+ export type RouteStateSetter<T> = (next: (base: T) => T, signal?: RouteStateSignal, rebase?: boolean) => void;
18
+ /**
19
+ * Route state a reader sets as the router store notifies it, in the lane of
20
+ * the notification that carries it. A notification equal to `latest` sets
21
+ * nothing, so a reader renders only when its value moves.
22
+ *
23
+ * A release is compared with what React committed, not with `latest`: while
24
+ * React holds a page, its set is not on screen, and an urgent page that ends
25
+ * the hold brings an equal value. A release that finds every set committed
26
+ * with its value sets nothing.
27
+ */
28
+ export declare function useRouteState<T>(init: () => T, same: (a: T, b: T) => boolean): [T, RouteStateSetter<T>];
29
+ /** The route a URL hook reads. */
30
+ export interface UrlRoute {
31
+ location: URL;
32
+ params: Record<string, string>;
33
+ }
34
+ /**
35
+ * A URL hook's value (usePathname, useParams, useSearchParams): `read` of the
36
+ * route, seeded from the hydration snapshot while the document hydrates, then
37
+ * set as the store notifies it and at a page's release (useRouteState). The
38
+ * release rides the handle channel. `read` applies when the store changes,
39
+ * never in a later render: a new one takes effect from the next change.
40
+ *
41
+ * A read equal to the value it is set over keeps that value, so an object
42
+ * reader changes identity once per value: a release that ends a hold sets the
43
+ * held page's object, which React's render of the held lane keeps.
44
+ */
45
+ export declare function useUrlValue<T>(read: (route: UrlRoute) => T, same: (a: T, b: T) => boolean): T;
@@ -231,7 +231,11 @@ export type ActionLifecycleState = "idle" | "loading" | "streaming";
231
231
  * @internal This type is an implementation detail and may change without notice.
232
232
  */
233
233
  export interface TrackedActionState {
234
- /** Current lifecycle state of the action */
234
+ /**
235
+ * Current lifecycle state of the action. Once the call settles it is
236
+ * "idle" and the rest of the call's state is kept until the action's next
237
+ * call.
238
+ */
235
239
  state: ActionLifecycleState;
236
240
  /** Server action function ID (e.g., "addToCart") */
237
241
  actionId: string | null;
@@ -239,12 +243,12 @@ export interface TrackedActionState {
239
243
  payload: unknown[] | FormData | null;
240
244
  /** Error if action failed */
241
245
  error: unknown | null;
242
- /** Result data from the action (preserved after completion) */
246
+ /** Result data from the action */
243
247
  result: unknown | null;
244
248
  /**
245
249
  * The error of a loader that threw while it re-ran after this successful
246
- * action (the first, when several did). The loader also throws at its
247
- * useLoader read, where the nearest error boundary catches it.
250
+ * action (the first, when several did). The loader also throws at its useLoader read, where the nearest
251
+ * error boundary catches it.
248
252
  */
249
253
  revalidationError: unknown | null;
250
254
  }
@@ -68,6 +68,19 @@ export interface BoundaryAudit {
68
68
  /** The fallback's effect was cleaned up in this task (auditFallback). */
69
69
  fallbackGone: boolean;
70
70
  }
71
+ /**
72
+ * One loaded copy of this module. A test that re-imports the module after
73
+ * vi.resetModules() runs two, and the one its static imports use is not the
74
+ * one on `__rangoSuspenseAudit`. The router's unit-test lock resets and
75
+ * checks each (src/__tests__/kit/suspense-audit-lock.ts).
76
+ */
77
+ export interface SuspenseAuditInstance {
78
+ /** The violation counters above zero and the last events, if any. */
79
+ violations(): string | undefined;
80
+ forget(): void;
81
+ /** Resolves once the judgments queued so far have run. */
82
+ settle(): Promise<void>;
83
+ }
71
84
  export declare function createBoundaryAudit(boundary: string, segmentId: string): BoundaryAudit;
72
85
  /**
73
86
  * Render phase: the value this render hands the boundary.
@@ -153,5 +166,10 @@ export declare function auditTreeCause(cause: TreeUpdateCause): void;
153
166
  * readers subscribe to, or through a pending promise read with use().
154
167
  */
155
168
  export declare function auditTreeUpdate(root?: unknown): void;
156
- /** Forget every tree and mount seen so far. For tests of the audit itself. */
169
+ /**
170
+ * Forget every tree, mount, hand-over and count seen so far: the root that
171
+ * held them is gone. A test unmounts its root, production never does
172
+ * (testing/render-route.tsx, src/__tests__/kit/suspense-audit-lock.ts).
173
+ * Creates no report: with the audit off there is none.
174
+ */
157
175
  export declare function forgetSuspenseAudit(): void;
@@ -385,15 +385,15 @@ export interface TestRouterHandle {
385
385
  * await act(async () => tail.resolve());
386
386
  * // useNavigation(): idle, isStreaming false
387
387
  *
388
- * A navigation started while React holds a page goes inside
389
- * `await act(async () => ...)`. Its first update makes React retry the held
390
- * page, which suspends again; a synchronous act() scope drops that retry
391
- * and React renders no later update of the same priority.
388
+ * A navigation started while React holds a page shows its page once the
389
+ * call resolves, with or without act(). A `<Link>` click over a held page
390
+ * still goes inside `await act(async () => ...)` (docs/testing.md).
392
391
  *
393
392
  * @example
394
- * await act(async () => {
395
- * await router.navigate("/products/3", { transition: false });
396
- * });
393
+ * await router.navigate("/products/2", { loaders: [[ProductLoader, next]] });
394
+ * // React holds /products/2
395
+ * await router.navigate("/products/3", { transition: false });
396
+ * // /products/3 is on screen
397
397
  */
398
398
  navigate(url: string, options?: Pick<RenderRouteOptions, "loaders" | "handles"> & Pick<NavigateOptions, "state" | "replace" | "transition"> & {
399
399
  streamUntil?: Promise<unknown>;
@@ -194,22 +194,26 @@ export declare function isSourceMap(fileName: string): boolean;
194
194
  * hashed by the routers that reach it (resolveRouterMembers), so nothing the
195
195
  * map says is lost by skipping it.
196
196
  *
197
- * Works on the region comments, so a minified server build (which has none)
198
- * keeps the map in every router's hash: more clearing, never less.
197
+ * Works on the module markers, so a minified server build (which has none),
198
+ * or a map no marker follows, keeps the map in every router's hash: more
199
+ * clearing, never less.
199
200
  */
200
201
  export declare function stripServerReferenceMap(source: string): string;
201
202
  /**
202
- * Take the module paths out of a chunk's region comments.
203
+ * Take the module paths out of a chunk's module markers.
203
204
  *
204
- * The bundler prints one before each module's code, and the path says where
205
- * the build ran, not what it built: it is relative to the directory `vite
206
- * build` was started from (`vite build apps/web` from a repository root
207
- * prints other paths than `vite build` in the app), it holds the pnpm store
208
- * directory with its peer suffix, and a virtual id is printed verbatim,
209
- * absolute paths in its query included. The code under the comment is hashed
210
- * either way.
205
+ * The path says where the build ran, not what it built: it is relative to the
206
+ * directory `vite build` was started from (`vite build apps/web` from a
207
+ * repository root prints other paths than `vite build` in the app), it holds
208
+ * the pnpm store directory with its peer suffix, and a virtual id is printed
209
+ * verbatim, absolute paths in its query included. The code after the marker
210
+ * is hashed either way.
211
+ *
212
+ * A `//#region` line is always a marker. A `// ` line is one only when it
213
+ * names a module of the chunk (`moduleIds`): a kept comment or a line of a
214
+ * multi-line string can start the same way, and is hashed.
211
215
  */
212
- export declare function stripRegionPaths(source: string): string;
216
+ export declare function stripModulePaths(source: string, moduleIds: readonly string[]): string;
213
217
  /** One thing a version is a hash of: its name and the digest of its bytes. */
214
218
  export type VersionInput = readonly [name: string, digest: string];
215
219
  /** One bundle file, digested once for every router. */
@@ -237,8 +241,8 @@ export interface DigestedFile {
237
241
  *
238
242
  * Three things are taken out of a chunk's text first, each because it says
239
243
  * where or how the build ran: the server-reference map's body
240
- * ({@link stripServerReferenceMap}), the paths in region comments
241
- * ({@link stripRegionPaths}), and the hashed names of bundle files. A name
244
+ * ({@link stripServerReferenceMap}), the paths in module markers
245
+ * ({@link stripModulePaths}), and the hashed names of bundle files. A name
242
246
  * carries the content hash of its file, and every chunk importing the file
243
247
  * has the name in its bytes, so one router's change would otherwise move the
244
248
  * digest of every chunk naming its chunk. Only names with a content hash are
@@ -41,7 +41,7 @@ export declare function canonicalAssetsManifestBinding(code: string): string | u
41
41
  * (`ServerReferencesManager.claimMap`), which varies with the concurrent
42
42
  * scan builds (e2e/test-app: 2 of 2 consecutive builds differed). Sorted
43
43
  * here so a minified server build, where stripServerReferenceMap finds no
44
- * region comments, still hashes the same bytes.
44
+ * module markers, still hashes the same bytes.
45
45
  * 2. Local name of the assets-manifest import. plugin-rsc imports the manifest
46
46
  * under three names; the bundler merges them into one import per chunk and
47
47
  * keeps a different one from build to build (the cache-versions fixture:
@@ -4491,13 +4491,13 @@ import {
4491
4491
  Component as Component3,
4492
4492
  createElement as createElement4,
4493
4493
  isValidElement as isValidElement2,
4494
- useContext as useContext17,
4494
+ useContext as useContext18,
4495
4495
  useMemo as useMemo9,
4496
4496
  Suspense as Suspense2
4497
4497
  } from "react";
4498
4498
 
4499
4499
  // src/browser/react/use-handle.ts
4500
- import { useContext, useState, useEffect, useRef } from "react";
4500
+ import { useContext as useContext2, useState as useState2, useEffect as useEffect2, useRef as useRef2 } from "react";
4501
4501
 
4502
4502
  // src/missing-id-error.ts
4503
4503
  var SELF_FILES = /* @__PURE__ */ new Set([
@@ -4575,6 +4575,19 @@ var NavigationStoreContext = createContext(null);
4575
4575
  var LocationStateContext = createContext(void 0);
4576
4576
  var HydrationBarrierContext = createContext(0);
4577
4577
 
4578
+ // src/browser/react/use-route-state.ts
4579
+ import {
4580
+ useContext,
4581
+ useEffect,
4582
+ useLayoutEffect,
4583
+ useRef,
4584
+ useState
4585
+ } from "react";
4586
+ var NO_ROUTE = {
4587
+ location: new URL("/", "http://localhost"),
4588
+ params: Object.freeze({})
4589
+ };
4590
+
4578
4591
  // src/handles/meta.ts
4579
4592
  function isUnsetDescriptor(descriptor) {
4580
4593
  return typeof descriptor === "object" && descriptor !== null && "unset" in descriptor && typeof descriptor.unset === "string";
@@ -4704,18 +4717,18 @@ function collectMeta(segments) {
4704
4717
  var Meta = createHandle(collectMeta, "__rsc_router_meta__");
4705
4718
 
4706
4719
  // src/theme/theme-context.ts
4707
- import { createContext as createContext2, useContext as useContext2 } from "react";
4720
+ import { createContext as createContext2, useContext as useContext3 } from "react";
4708
4721
  var ThemeContext = createContext2(null);
4709
4722
 
4710
4723
  // src/browser/react/nonce-context.ts
4711
- import { createContext as createContext3, useContext as useContext3 } from "react";
4724
+ import { createContext as createContext3, useContext as useContext4 } from "react";
4712
4725
  var NonceContext = createContext3(void 0);
4713
4726
 
4714
4727
  // src/handles/MetaTags.tsx
4715
4728
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4716
4729
 
4717
4730
  // src/handles/Scripts.tsx
4718
- import { useState as useState2 } from "react";
4731
+ import { useState as useState3 } from "react";
4719
4732
 
4720
4733
  // src/handles/script.ts
4721
4734
  function validateConfigDev(config) {
@@ -4769,7 +4782,7 @@ var Script = createHandle(collectScripts, "__rsc_router_script__");
4769
4782
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
4770
4783
 
4771
4784
  // src/browser/react/ScrollRestoration.tsx
4772
- import { useEffect as useEffect2 } from "react";
4785
+ import { useEffect as useEffect3 } from "react";
4773
4786
 
4774
4787
  // src/browser/scroll-restoration.ts
4775
4788
  init_logging();
@@ -4783,12 +4796,12 @@ import { Component, Suspense, use as use2, useMemo as useMemo3 } from "react";
4783
4796
 
4784
4797
  // src/outlet-provider.tsx
4785
4798
  import {
4786
- useContext as useContext4,
4799
+ useContext as useContext5,
4787
4800
  useMemo
4788
4801
  } from "react";
4789
4802
 
4790
4803
  // src/suspense-audit-react.tsx
4791
- import { useEffect as useEffect3, useLayoutEffect, useRef as useRef2 } from "react";
4804
+ import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
4792
4805
 
4793
4806
  // src/suspense-audit.ts
4794
4807
  init_internal_debug();
@@ -4803,13 +4816,13 @@ import { jsx as jsx3 } from "react/jsx-runtime";
4803
4816
  import { createElement as createElement2 } from "react";
4804
4817
 
4805
4818
  // src/loader-redirect.tsx
4806
- import { useEffect as useEffect4 } from "react";
4819
+ import { useEffect as useEffect5 } from "react";
4807
4820
 
4808
4821
  // src/browser/react/use-router.ts
4809
- import { useContext as useContext6, useMemo as useMemo2 } from "react";
4822
+ import { useContext as useContext7, useMemo as useMemo2 } from "react";
4810
4823
 
4811
4824
  // src/browser/react/use-mount.ts
4812
- import { useContext as useContext5 } from "react";
4825
+ import { useContext as useContext6 } from "react";
4813
4826
 
4814
4827
  // src/browser/react/mount-context.ts
4815
4828
  import {
@@ -5002,12 +5015,12 @@ import {
5002
5015
  startTransition,
5003
5016
  use as use3,
5004
5017
  useCallback,
5005
- useContext as useContext7,
5006
- useEffect as useEffect5,
5018
+ useContext as useContext8,
5019
+ useEffect as useEffect6,
5007
5020
  useMemo as useMemo4,
5008
5021
  useOptimistic,
5009
- useRef as useRef3,
5010
- useState as useState3
5022
+ useRef as useRef4,
5023
+ useState as useState4
5011
5024
  } from "react";
5012
5025
 
5013
5026
  // src/loader-store.ts
@@ -5169,37 +5182,23 @@ function insertSegments(node, segments, index, leafBase, paramNames) {
5169
5182
  }
5170
5183
 
5171
5184
  // src/browser/react/use-navigation.ts
5172
- import {
5173
- useContext as useContext8,
5174
- useState as useState4,
5175
- useEffect as useEffect6,
5176
- useLayoutEffect as useLayoutEffect2,
5177
- useRef as useRef4
5178
- } from "react";
5185
+ import { useContext as useContext9, useEffect as useEffect7, useRef as useRef5 } from "react";
5179
5186
 
5180
5187
  // src/browser/react/use-pathname.ts
5181
- import { useContext as useContext9, useState as useState5, useEffect as useEffect7, useRef as useRef5 } from "react";
5188
+ import { useContext as useContext10 } from "react";
5182
5189
 
5183
5190
  // src/client-urls/optimistic-location.ts
5184
5191
  import { createContext as createContext6 } from "react";
5185
5192
  var OptimisticLocationContext = createContext6(null);
5186
5193
 
5187
5194
  // src/browser/react/use-search-params.ts
5188
- import {
5189
- useCallback as useCallback2,
5190
- useContext as useContext10,
5191
- useEffect as useEffect8,
5192
- useMemo as useMemo5,
5193
- useRef as useRef6,
5194
- useState as useState6
5195
- } from "react";
5195
+ import { useCallback as useCallback2, useContext as useContext11, useMemo as useMemo5 } from "react";
5196
5196
 
5197
5197
  // src/browser/react/use-params.ts
5198
- import { useContext as useContext11, useState as useState7, useEffect as useEffect9, useRef as useRef7 } from "react";
5199
- var EMPTY_PARAMS = Object.freeze({});
5198
+ import { useContext as useContext12 } from "react";
5200
5199
 
5201
5200
  // src/browser/react/use-action.ts
5202
- import { useContext as useContext12, useState as useState8, useEffect as useEffect10, useRef as useRef8 } from "react";
5201
+ import { useContext as useContext13, useState as useState5, useEffect as useEffect8, useRef as useRef6 } from "react";
5203
5202
 
5204
5203
  // src/browser/event-controller.ts
5205
5204
  init_notify_listeners();
@@ -5262,31 +5261,31 @@ function subscribeToLocationChange(eventController, listener) {
5262
5261
  }
5263
5262
 
5264
5263
  // src/browser/react/use-segments.ts
5265
- import { useContext as useContext13, useEffect as useEffect11, useRef as useRef9 } from "react";
5264
+ import { useContext as useContext14, useEffect as useEffect9, useRef as useRef7 } from "react";
5266
5265
 
5267
5266
  // src/browser/react/NavigationProvider.tsx
5268
5267
  import {
5269
- useState as useState11,
5270
- useEffect as useEffect13,
5268
+ useState as useState8,
5269
+ useEffect as useEffect11,
5271
5270
  useLayoutEffect as useLayoutEffect4,
5272
5271
  useCallback as useCallback4,
5273
5272
  useMemo as useMemo7,
5274
- useRef as useRef11,
5273
+ useRef as useRef9,
5275
5274
  use as use4,
5276
5275
  startTransition as startTransition2
5277
5276
  } from "react";
5278
5277
 
5279
5278
  // src/root-error-boundary.tsx
5280
- import { Component as Component2, useState as useState9 } from "react";
5279
+ import { Component as Component2, useState as useState6 } from "react";
5281
5280
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
5282
5281
 
5283
5282
  // src/theme/ThemeProvider.tsx
5284
5283
  import {
5285
5284
  useCallback as useCallback3,
5286
- useEffect as useEffect12,
5285
+ useEffect as useEffect10,
5287
5286
  useLayoutEffect as useLayoutEffect3,
5288
5287
  useMemo as useMemo6,
5289
- useState as useState10
5288
+ useState as useState7
5290
5289
  } from "react";
5291
5290
 
5292
5291
  // src/theme/constants.ts
@@ -5320,18 +5319,18 @@ import { jsx as jsx7 } from "react/jsx-runtime";
5320
5319
  import {
5321
5320
  forwardRef,
5322
5321
  useCallback as useCallback5,
5323
- useContext as useContext15,
5324
- useEffect as useEffect15,
5322
+ useContext as useContext16,
5323
+ useEffect as useEffect13,
5325
5324
  useMemo as useMemo8,
5326
- useRef as useRef12
5325
+ useRef as useRef10
5327
5326
  } from "react";
5328
5327
 
5329
5328
  // src/browser/react/use-link-status.ts
5330
5329
  import {
5331
5330
  createContext as createContext7,
5332
- useContext as useContext14,
5333
- useState as useState12,
5334
- useEffect as useEffect14
5331
+ useContext as useContext15,
5332
+ useState as useState9,
5333
+ useEffect as useEffect12
5335
5334
  } from "react";
5336
5335
  var LinkContext = createContext7(
5337
5336
  null
@@ -5532,14 +5531,14 @@ var Link = forwardRef(function Link2({
5532
5531
  onClick,
5533
5532
  ...props
5534
5533
  }, ref) {
5535
- const ctx = useContext15(NavigationStoreContext);
5534
+ const ctx = useContext16(NavigationStoreContext);
5536
5535
  const isExternal = isExternalUrl(to, ctx?.origin);
5537
5536
  const resolvedTo = useMemo8(
5538
5537
  () => isExternal ? to : prefixBasename(to, ctx?.basename),
5539
5538
  [to, isExternal, ctx?.basename]
5540
5539
  );
5541
5540
  const configuredStrategy = prefetch ?? ctx?.defaultPrefetch ?? ENVIRONMENT_DEFAULT_PREFETCH;
5542
- const internalRef = useRef12(null);
5541
+ const internalRef = useRef10(null);
5543
5542
  const setRef = useCallback5(
5544
5543
  (node) => {
5545
5544
  internalRef.current = node;
@@ -5551,7 +5550,7 @@ var Link = forwardRef(function Link2({
5551
5550
  },
5552
5551
  [ref]
5553
5552
  );
5554
- const stateRef = useRef12(state);
5553
+ const stateRef = useRef10(state);
5555
5554
  stateRef.current = state;
5556
5555
  const handleClick = useCallback5(
5557
5556
  (e) => {
@@ -5608,7 +5607,7 @@ var Link = forwardRef(function Link2({
5608
5607
  );
5609
5608
  }
5610
5609
  }, [configuredStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
5611
- useEffect15(() => {
5610
+ useEffect13(() => {
5612
5611
  if (isExternal || !ctx?.store) return;
5613
5612
  if (configuredStrategy !== "viewport" && configuredStrategy !== "render" && configuredStrategy !== "adaptive") {
5614
5613
  return;
@@ -5728,7 +5727,7 @@ var Breadcrumbs = createHandle(
5728
5727
  );
5729
5728
 
5730
5729
  // src/browser/react/location-state.ts
5731
- import { useContext as useContext16, useEffect as useEffect16 } from "react";
5730
+ import { useContext as useContext17, useEffect as useEffect14 } from "react";
5732
5731
 
5733
5732
  // src/browser/react/use-href.tsx
5734
5733
  import { useCallback as useCallback6 } from "react";
@@ -5944,7 +5943,7 @@ import {
5944
5943
  useDeferredValue,
5945
5944
  useLayoutEffect as useLayoutEffect5,
5946
5945
  useMemo as useMemo10,
5947
- useState as useState13
5946
+ useState as useState10
5948
5947
  } from "react";
5949
5948
 
5950
5949
  // src/client-urls/navigation.ts
@@ -7142,7 +7141,7 @@ function resolveVersionsFrom(table, routerId2) {
7142
7141
  // package.json
7143
7142
  var package_default = {
7144
7143
  name: "@rangojs/router",
7145
- version: "0.29.0",
7144
+ version: "0.30.1",
7146
7145
  description: "Django-inspired RSC router with composable URL patterns",
7147
7146
  keywords: [
7148
7147
  "react",
@@ -8054,20 +8053,39 @@ var SERVER_CSS_MODULE = /^virtual:vite-rsc\/css\?type=rsc&id=([^&]+)/;
8054
8053
  function isSourceMap(fileName) {
8055
8054
  return fileName.endsWith(".map");
8056
8055
  }
8057
- var SERVER_REFERENCES_REGION_START = `//#region \\0${SERVER_REFERENCES_MODULE_ID.slice(1)}
8058
- `;
8059
- var REGION_END = "//#endregion";
8056
+ var MODULE_MARKER = /^\/\/(#region)? (.+)$/gm;
8057
+ var SERVER_REFERENCE_MAP = new RegExp(
8058
+ `(^//(?:#region)? ${escapeRegExp(`\\0${SERVER_REFERENCES_MODULE_ID.slice(1)}`)}
8059
+ )[\\s\\S]*?(?=^//(?: |#endregion))`,
8060
+ "m"
8061
+ );
8060
8062
  function stripServerReferenceMap(source) {
8061
- const start = source.indexOf(SERVER_REFERENCES_REGION_START);
8062
- if (start === -1) return source;
8063
- const bodyStart = start + SERVER_REFERENCES_REGION_START.length;
8064
- const end = source.indexOf(REGION_END, bodyStart);
8065
- if (end === -1) return source;
8066
- return source.slice(0, bodyStart) + source.slice(end);
8063
+ return source.replace(SERVER_REFERENCE_MAP, "$1");
8067
8064
  }
8068
- var REGION_LINE = /^\/\/#region .*$/gm;
8069
- function stripRegionPaths(source) {
8070
- return source.replace(REGION_LINE, "//#region");
8065
+ function printedModuleIds(moduleIds) {
8066
+ const printed = /* @__PURE__ */ new Set();
8067
+ for (const id of moduleIds) {
8068
+ if (!isAbsolutePath(id)) {
8069
+ printed.add(id.replaceAll("\0", "\\0"));
8070
+ continue;
8071
+ }
8072
+ const segments = id.replaceAll("\\", "/").split("/");
8073
+ for (let index = 0; index < segments.length; index++) {
8074
+ printed.add(segments.slice(index).join("/"));
8075
+ }
8076
+ }
8077
+ return printed;
8078
+ }
8079
+ function stripModulePaths(source, moduleIds) {
8080
+ let printed;
8081
+ return source.replace(
8082
+ MODULE_MARKER,
8083
+ (line, region, name) => {
8084
+ if (region) return "//#region";
8085
+ printed ??= printedModuleIds(moduleIds);
8086
+ return printed.has(name.replace(/^(?:\.\.\/)+/, "")) ? "//" : line;
8087
+ }
8088
+ );
8071
8089
  }
8072
8090
  var FILE_REFERENCE = "\0";
8073
8091
  var FOREIGN_FILE = "~";
@@ -8119,8 +8137,9 @@ function digestBundle(bundle, projectRoot, read, label) {
8119
8137
  };
8120
8138
  }
8121
8139
  const references = [];
8122
- const text = stripRegionPaths(
8123
- stripServerReferenceMap(decoder3.decode(bytes))
8140
+ const text = stripModulePaths(
8141
+ stripServerReferenceMap(decoder3.decode(bytes)),
8142
+ chunk.moduleIds
8124
8143
  );
8125
8144
  const blanked = reference ? text.replace(reference, (name) => {
8126
8145
  references.push(fileOfBaseName.get(name));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.29.0",
3
+ "version": "0.30.1",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -450,16 +450,12 @@ function StockPanel() {
450
450
  }
451
451
 
452
452
  function SaveStock({ sku }: { sku: string }) {
453
+ // Kept until the next save starts.
453
454
  const { revalidationError } = useAction(saveStock);
454
- const [stale, setStale] = useState(false);
455
- // useAction resets to idle once the action has settled: keep the notice.
456
- useEffect(() => {
457
- if (revalidationError) setStale(true);
458
- }, [revalidationError]);
459
455
  return (
460
456
  <form action={saveStock.bind(null, sku)}>
461
457
  <button>Save</button>
462
- {stale && (
458
+ {revalidationError != null && (
463
459
  <p role="status">Saved. The stock count could not be refreshed.</p>
464
460
  )}
465
461
  </form>
@@ -196,6 +196,14 @@ Track the lifecycle of a server action, wherever on the page it was invoked.
196
196
  If the same action fires several times, the hook reports the latest call: the
197
197
  last one to start.
198
198
 
199
+ - Once the call settles, `state` is `idle` and the rest of its state
200
+ (`result`, `error`, `revalidationError`, `payload`, `actionId`) stays until
201
+ the same action is called again: through navigations and Back/Forward, and
202
+ in a component that mounts after the call settled. Every reader of the
203
+ same action (a function or a full id) reads the same call; a string reader
204
+ follows the latest call of any action with that export name. The next
205
+ call reads `loading` with none of them, then its own. A document load
206
+ starts with nothing.
199
207
  - When the latest call returns before an earlier one, the hook shows its
200
208
  result. The earlier call settling afterwards changes nothing the hook
201
209
  reports, a result or an error alike. That call still runs to its end: its
@@ -203,8 +211,7 @@ last one to start.
203
211
  page, and an error-boundary response replaces it.
204
212
  - A latest call that was aborted reports nothing: `idle`, with no result or
205
213
  error. The hook does not fall back to an earlier call. The router aborts
206
- calls in flight when another call's response is an error boundary, and on
207
- Back/Forward into or out of an intercept.
214
+ the other calls in flight when a call's response is an error boundary.
208
215
  - `useNavigation()` stays `loading` while any call runs.
209
216
 
210
217
  Which calls count as the same action depends on what you pass:
@@ -90,9 +90,9 @@ reads (a selector, the full state): work that starts shows at once
90
90
  when the response stream ended earlier, and agrees with every other
91
91
  `useNavigation()` reader on the screen. A page that ends the hold urgently (a
92
92
  Back to a page without `transition()`, a link with `transition={false}`)
93
- shows with its own values in every `useNavigation()` reader. `usePathname()`
94
- can read the path of the page being left on that page's first screen, until
95
- React finishes the render of the page it held.
93
+ shows with its own values in every `useNavigation()` reader, and with its URL
94
+ in `usePathname()`, `useParams()`, `useSearchParams()` and `useSegments()`,
95
+ also when it is the URL React was holding.
96
96
 
97
97
  The rule holds for readers mounted before the page was handed to React. A
98
98
  reader that mounts while the page is held (a menu opened during the wait)
@@ -123,6 +123,7 @@ click or `router.push()`.
123
123
  | Back/Forward, entry fetched again | when the fetched page commits; until then the page being left is on screen |
124
124
  | a Back/Forward whose fetch fails | with the error boundary: they report the entry the browser is on |
125
125
  | `router.push(url, { revalidate: false })` | at once: nothing is fetched and the page stays |
126
+ | `transition={false}` over a held page | with that page's first screen, also on the URL React was holding |
126
127
  | inside an optimistic `clientUrls()` destination | at the click, for that branch only (see `/client-urls`) |
127
128
 
128
129
  **A newer click that fails while a page is held.** Click a link, and while