@jsenv/navi 0.29.68 → 0.29.69

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.
@@ -165,9 +165,11 @@ consistency across the app, not from any single call site.
165
165
  - `docs/route_transitions.md` — how pages move against each other on
166
166
  navigation (`defineRouteTransition`): a transition states a relation the
167
167
  user reads as a map, which movement fits which relation, when a global
168
- default is right, marking the page area between fixed bars, and why a pair
169
- of routes is animated by `RouteTravel` or by a route transition but never
170
- both. Read it before animating any navigation.
168
+ default is right, how one link or one `navTo` overrides what the pair says
169
+ for the length of a single navigation (`<Link routeTransition>`), marking the page
170
+ area between fixed bars, and why a pair of routes is animated by
171
+ `RouteTravel` or by a route transition but never both. Read it before
172
+ animating any navigation.
171
173
  - `docs/navigation.md` — how to build navigation: declaring routes
172
174
  (`route()` / `setupRoutes()`), when a section is a route of its own rather
173
175
  than a param, search params bound to signals, rendering with `<Route>`,
@@ -170,11 +170,64 @@ list is walked through. A transition states a relation between two pages (see
170
170
  rendering hold for the one frame the browser needs to photograph it, and gives
171
171
  it back. It never decides what the page arriving is allowed to ask for. Held by
172
172
  `tests/route_transition_list_revisit/`, which mounts the same app twice — with
173
- and without a relation on the pair — and compares both counts.
173
+ and without a relation on the pair — and walks the way back ten times on each,
174
+ counting what goes out at every revisit.
174
175
 
175
- So a list that stopped refreshing after a transition was added has, in the
176
- overwhelming majority of cases, never refreshed on its own: it was reading
177
- through `GET_MANY`, and something else in the app was doing the re-read.
176
+ So the first thing to check, when a list stops refreshing after a transition was
177
+ added, is which row of the table above it is on: a `GET_MANY` list never
178
+ refreshed on its own, and what changed is whatever else in the app was doing the
179
+ re-read.
180
+
181
+ ### Watching what the run asks for
182
+
183
+ A run that decides **not** to ask is invisible from the application's side: it
184
+ sends nothing and changes no state, so the network is silent and
185
+ `onRequestStateChange` — which reports what a request is doing — has no request
186
+ to report. A run that declined and a run that was never mounted look identical.
187
+ `debugScroll` is where that difference is visible; it carries the render window
188
+ and the run's asking, which are one subject.
189
+
190
+ ```jsx
191
+ window.askLog = [];
192
+ <NaviDebug debugScroll={(...args) => window.askLog.push(args.join(" "))}>
193
+ <MyList />
194
+ </NaviDebug>;
195
+ ```
196
+
197
+ One line per pass of the run, whatever the outcome:
198
+
199
+ ```
200
+ ask 0-49: sent (revalidating=true holdPending=false count=412)
201
+ ask -1--1: nothing missing (revalidating=false holdPending=false count=412)
202
+ ask 0-49: held on a row not reached yet (revalidating=true holdPending=true count=412)
203
+ ```
204
+
205
+ | outcome | what it means |
206
+ | ------------------------------------ | ------------------------------------------------------------------------------------------------------- |
207
+ | `sent` | the range on the line went out |
208
+ | `nothing missing` | the run holds every row it draws and has nothing to revalidate |
209
+ | `held on a row not reached yet` | the list is on its way somewhere the window does not frame; only the row it is held on can be asked for |
210
+ | `already revalidating` | a revalidation for this window is in flight |
211
+ | `a request still covers this window` | what is in flight is still what the list would draw |
212
+ | `this range was asked for already` | asking again could only produce the same answer |
213
+
214
+ The state that decides is on the line rather than left to be inferred:
215
+
216
+ - **`revalidating`** — the run knows what it holds is from before. A revisit
217
+ showing `revalidating=false` never restored a composition, which is a
218
+ different problem from one showing `revalidating=true` and no `sent`.
219
+ - **`holdPending`** — the list is held somewhere it has not reached.
220
+ - **`count`** — how many rows the run stands for, `undefined` before its first
221
+ answer.
222
+
223
+ **Record, do not print.** The sink is called during rendering: push into an
224
+ array. Formatting an object in a console costs far more than what it measures,
225
+ and a timing-sensitive symptom moves under one — a list that fails to refresh on
226
+ the first revisit can start refreshing on the first two as soon as a
227
+ `console.log` is in the way, which makes the log a report about the log.
228
+
229
+ An absence in the trace is a fact too: no line for a revisit means the run never
230
+ rendered, which is about the list being mounted, not about what it asked for.
178
231
 
179
232
  ## `rerunOn`, verb by verb
180
233
 
@@ -310,11 +310,21 @@ Three cases, and they are not a policy to configure but three different facts:
310
310
  tall is clamped away.
311
311
  - **A reload** lands where one was, as it would have without navi.
312
312
 
313
- The consequence worth knowing when building a screen: **a "back" written as a
314
- `<Link>` is a push**, so it lands at the top like any arrival — no mechanism
315
- can guess that a push was morally a return. A back arrow that should feel like
316
- one is `navBack()`. Where there may be nothing to go back to (a shared link
317
- opened cold), decide what the arrow does from the history, not from the link.
313
+ One consequence is softened where the browser exposes its stack (the
314
+ Navigation API everywhere but Firefox today): **a `<Link>` whose destination
315
+ is the entry right next to the current one becomes a real traversal** instead
316
+ of a push. A "back" link to the page one just came from therefore behaves as a
317
+ back the stack stays what the reader thinks it is (no A, B, A, B… growth)
318
+ and the scroll comes back; one step forward too, so returning to the page one
319
+ just left resumes it where it was. Only towards entries of this document (a
320
+ traversal to another one would be a full page load no link asked for), and
321
+ never when the push carries explicit state.
322
+
323
+ Everywhere else a `<Link>` is an arrival and lands at the top. A back arrow
324
+ that must ALWAYS behave as a back — even far from the entry it targets, even
325
+ in a browser with no Navigation API — is `navBack()`. Where there may be
326
+ nothing to go back to (a shared link opened cold), decide what the arrow does
327
+ from the history, not from the link.
318
328
 
319
329
  What is not covered: a page whose height depends on something still loading is
320
330
  not tall enough at the moment its position is put back, so a deep position is
@@ -1,8 +1,9 @@
1
1
  # Route transitions
2
2
 
3
3
  How pages of an app move against each other when the user navigates —
4
- `defineRouteTransition`, `defineRouteDefaultTransition`, and the thinking that
5
- decides which movement (if any) a navigation deserves. The API grammar itself
4
+ `defineRouteTransition`, `defineRouteDefaultTransition`, what one link or one
5
+ `navTo` may ask for on top of them, and the thinking that decides which
6
+ movement (if any) a navigation deserves. The API grammar itself
6
7
  (accepted forms, shipped type names) lives in the JSDoc of
7
8
  `defineRouteTransition`; this file holds what a signature cannot say.
8
9
 
@@ -77,6 +78,77 @@ A default has no direction (nothing says which of two arbitrary pages is
77
78
  "before" the other), so only directionless movements make sense there. Written
78
79
  relations, and `"none"`, always win over it.
79
80
 
81
+ ## When one navigation knows better
82
+
83
+ A relation is written on a PAIR, so it holds for every way of reaching the
84
+ page — and some ways are walked against the map. Two pairs that are travelled
85
+ in both directions, with one direction common and one rare:
86
+
87
+ - a game ↔ a player's profile: the name of a player, tapped from the game, is
88
+ the common way in; a badge on a profile that leads back to the game it was
89
+ won in is the rare one;
90
+ - a profile ↔ the cards it describes: going down into the cards is the
91
+ structural descent; a card that leads up to the player it describes goes back
92
+ out.
93
+
94
+ Written for the common direction, the rare one plays backwards. And `"none"`
95
+ cannot fix it: navi does not tell a link from the back button, so silencing the
96
+ bad direction silences the good one too.
97
+
98
+ So the navigation itself may ask, and what it asks holds for **that navigation
99
+ and no other**:
100
+
101
+ ```jsx
102
+ // The rare way round: the pair's movement, turned round.
103
+ <Link
104
+ route={GAME_ROUTE}
105
+ routeParams={{ id }}
106
+ transition={{ direction: "back" }}
107
+ >
108
+ {badge.gameName}
109
+ </Link>
110
+ ```
111
+
112
+ ```js
113
+ // The same thing said by a call rather than by an element.
114
+ navTo(GAME_ROUTE.buildUrl({ id }), { routeTransition: { direction: "back" } });
115
+ GAME_ROUTE.navTo({ id }, { routeTransition: { direction: "back" } });
116
+ ```
117
+
118
+ The request is `"slide-x"`-style shorthand or `{ type, duration, direction }`,
119
+ the same forms `defineRouteTransition` takes, plus `direction`. It overrides
120
+ **field by field**: what it does not name, the relation (or the default) still
121
+ answers for. So:
122
+
123
+ - `{ direction: "back" }` keeps the pair's movement and only turns it round;
124
+ - `"zoom"` swaps the movement, keeping nothing else;
125
+ - `"none"` cuts, where the pair — or the default — would have played;
126
+ - `{ duration: 500 }` re-times what was already going to play.
127
+
128
+ A pair no relation was ever written for answers the same way: silence is what
129
+ the routes say, and a link that asks for a movement gets it, forward unless it
130
+ says otherwise. That is the whole shape of the control:
131
+ `defineRouteTransition` is what the app's map says and applies by default; a
132
+ link, or a programmatic `navTo`, overrides it for the length of one navigation.
133
+ Navigate again by any other means and the relation is back in charge — nothing
134
+ is remembered.
135
+
136
+ The link wears what it asks as an attribute, so a plain `<a>` says it too (a
137
+ type name, or the object as JSON):
138
+
139
+ ```html
140
+ <a href="/game/42" data-navi-route-transition-request='{"direction":"back"}'
141
+ >…</a
142
+ >
143
+ ```
144
+
145
+ **Not yet: a movement chosen by HOW one navigated.** Playing one movement for
146
+ the back button and another for a link would be written on the same request —
147
+ `{ back: "slide-x", forward: "slide-y" }` — but the History API does not say
148
+ which way a traversal went, and navi runs on it today (see
149
+ `browser_integration/via_history.js`). The Navigation API does; the notation is
150
+ kept in mind for the day navi navigates through it.
151
+
80
152
  ## Pages between fixed bars: the transition area
81
153
 
82
154
  By default the movement plays on the document itself — right when pages are the
@@ -151,6 +223,20 @@ movement against the view transition pseudo-elements — of the document, or of
151
223
  the marked area. See the JSDoc of `defineRouteTransition` for the selector
152
224
  shape, and the `spin` type in the demo for a working one.
153
225
 
226
+ What is left to write is the `animation-name`s and nothing else: navi attaches
227
+ to ANY named type what makes a movement look like one — each picture at the
228
+ size it was taken at (a page half the height of the one it crosses would
229
+ otherwise be seen inflating over the length of the movement), two solid pages
230
+ rather than two panes of glass, and the animation held where it ends. The
231
+ untyped cross-fade keeps the browser's defaults, since scaling one picture into
232
+ the other is the whole idea there.
233
+
234
+ The one knob a custom movement may want back: a movement that animates ONE of
235
+ its two sides leaves the other on the browser's fade, and a fade needs its two
236
+ half-transparent pictures to add up rather than cover each other
237
+ (`mix-blend-mode: plus-lighter`, as the shipped `zoom` and the demo's `spin`
238
+ both do). A movement where both pages move wants what navi poses.
239
+
154
240
  ## Route transitions and `RouteTravel` — one pair, one system
155
241
 
156
242
  `RouteTravel` and `defineRouteTransition` answer different questions:
@@ -198,11 +284,18 @@ browser needs to photograph the page being left (see `rendering_hold.js`), and
198
284
  it gives it back in the same callback. The hold is about a picture, not about
199
285
  data: nothing waits on it, nothing is skipped because of it.
200
286
 
201
- So "this list stopped refreshing since we animated the pair" is a claim worth
202
- checking twice before believing: it is held by
203
- `tests/route_transition_list_revisit/`, which mounts one app twice the two
204
- mounts differing by a single `defineRouteTransition` line and compares what
205
- each one sends to the network on the way back. See
287
+ That is the design, and it is held by `tests/route_transition_list_revisit/`:
288
+ one app mounted twice, the two mounts differing by a single
289
+ `defineRouteTransition` line, walked back and forth **ten times each** with what
290
+ goes to the network counted on every revisit. The loop is the point — a
291
+ difference that came and went would pass a single comparison often enough to
292
+ look like an invariant.
293
+
294
+ What the test holds is that shape: pages between fixed bars, a marked area, a
295
+ list virtualized against its scroller, held on a row the url names. It is
296
+ evidence about the mechanism, not a proof about every application; a page that
297
+ loses its revisit only under a movement is worth reporting with the loop above
298
+ run against it. See
206
299
  [list_refresh.md](./list_refresh.md#who-decides-the-re-read--and-who-does-not)
207
300
  for which source refreshes on a revisit and which does not.
208
301
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.68",
3
+ "version": "0.29.69",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {