@jsenv/navi 0.29.436 → 0.29.437

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.
@@ -84,8 +84,9 @@ start when unsure which export solves a problem.
84
84
  action and a command on one press. Before running an action from a component.
85
85
  - `resource.md` — `resource()` and its relations (never an `op` discriminator),
86
86
  `GET_RANGE`, a search as the same `GET_MANY`, `withParams()` scopes and
87
- `dependencies`, `persist`, a function versus the verb's instance. Before
88
- writing a resource, and before caching a response yourself.
87
+ `dependencies`, `persist` (and `keepPageOnScreen` for the page on screen), a
88
+ function versus the verb's instance. Before writing a resource, and before
89
+ caching a response yourself.
89
90
  - `data_states.md` — `data`, `loading` and `error` are three questions;
90
91
  `loading: true` never suspends; a skeleton is told whether it is loading.
91
92
  Before drawing a skeleton on `!data` or hiding content because `error` is set.
@@ -79,7 +79,9 @@ action, a `bindParams` holding one) included, when the signal moves to other
79
79
  params. A row known from the previous page load counts too, when the resource
80
80
  asks for it:
81
81
  [`persist`](./resource.md#persist-the-last-answer-drawn-again-after-a-reload)
82
- puts a reload on the second line as well.
82
+ puts a reload on the second line as well, and so does
83
+ [`keepPageOnScreen`](./resource.md#keeppageonscreen-the-page-on-screen-drawn-again-by-the-next-document) for
84
+ what the page on screen reads — a `GET_MANY` and a `GET_RANGE` included.
83
85
 
84
86
  **The emptiness test is `data === undefined`, never `loading`.** Reading
85
87
  `loading` as "there is nothing to display" blanks the screen on every refresh —
@@ -127,7 +127,10 @@ A composition is about the **values** its params hold, not the reader instance:
127
127
  writes the same one. A verb in `rerunOn.GET_RANGE`, or `reader.invalidate()`,
128
128
  drops the compositions — they stand for an order that is gone — and
129
129
  `memoryBudget` (1000 ranks by default) trims the ranks far from any window,
130
- which are asked for again if the user goes back to them.
130
+ which are asked for again if the user goes back to them. A composition read by a
131
+ list on screen also outlives the document when the page is kept
132
+ ([`keepPageOnScreen`](./resource.md#keeppageonscreen-the-page-on-screen-drawn-again-by-the-next-document)): the ranks around the window it draws are written with
133
+ their rows, and a reload is the same return after a longer absence.
131
134
 
132
135
  ### Who decides the re-read — and who does not
133
136
 
@@ -67,9 +67,13 @@ was asked:
67
67
  what it holds instead of falling into its error boundary. A route opening a
68
68
  user by id **or** by slug gives navi both keys to look by:
69
69
  `resource("user", { uniqueKeys: ["slug"], … })`.
70
- - **A completed read stays completed.** `GET_MANY` (and every other read) has
71
- nowhere to answer from: the store holds items, not queries, and only the
72
- action's own value knows which ids answered `/users?scope=shareable`. So a
70
+ - **A read drawing a kept answer completes with it.** A root `GET` or
71
+ `GET_MANY` whose first run drew what the page kept from the previous document
72
+ ([`keepPageOnScreen`](./resource.md#keeppageonscreen-the-page-on-screen-drawn-again-by-the-next-document)) answers with those rows: a reload with no network reopens
73
+ on the page.
74
+ - **A completed read stays completed.** Otherwise `GET_MANY` (and every other
75
+ read) has nowhere to answer from: the store holds items, not queries, and only
76
+ the action's own value knows which ids answered `/users?scope=shareable`. So a
73
77
  rerun asked of a completed read under the policy is held — the action keeps
74
78
  its state, its value and its data, exactly as a `run()` on a completed action
75
79
  would.
@@ -185,7 +189,8 @@ has nothing to decide which one says why.
185
189
  which signal, for which session: a resource declaring
186
190
  [`persist`](./resource.md#persist-the-last-answer-drawn-again-after-a-reload)
187
191
  has its kept row answer the `GET` under the policy, so a reload offline
188
- reopens on it.
192
+ reopens on it; [`keepPageOnScreen`](./resource.md#keeppageonscreen-the-page-on-screen-drawn-again-by-the-next-document) does the same for the page
193
+ on screen.
189
194
  - **A queue of writes to replay** once the network is back — not navi's: what a
190
195
  replayed write means (a score entered twice? a seat taken since?) is the app's
191
196
  business.
package/docs/resource.md CHANGED
@@ -34,6 +34,7 @@ Actions are read in components through the action system (`useAsyncData`,
34
34
 
35
35
  - [`store.upsert()` is not how data enters the store](#storeupsert-is-not-how-data-enters-the-store)
36
36
  - [`persist`: the last answer, drawn again after a reload](#persist-the-last-answer-drawn-again-after-a-reload)
37
+ - [`keepPageOnScreen`: the page on screen, drawn again by the next document](#keeppageonscreen-the-page-on-screen-drawn-again-by-the-next-document)
37
38
  - [`GET_RANGE`: feeding a list that loads as it scrolls](#get_range-feeding-a-list-that-loads-as-it-scrolls)
38
39
  - [Searching the same collection](#searching-the-same-collection)
39
40
  - [Relations: pick one of the four methods](#relations-pick-one-of-the-four-methods)
@@ -129,11 +130,102 @@ What the copy is not:
129
130
  - **an answer.** The `GET` goes out every time; a screen must not read `data` as
130
131
  "the server confirmed" — that is `loading`, and the two are independent.
131
132
  - **a store on disk.** One row per params key, `GET` only: meant for a singleton
132
- or a handful of rows, not a resource whose `GET` is asked for every id.
133
+ or a handful of rows, not a resource whose `GET` is asked for every id. What
134
+ the page on screen reads, lists included, is
135
+ [`keepPageOnScreen`](#keeppageonscreen-the-page-on-screen-drawn-again-by-the-next-document),
136
+ below.
133
137
  - **seeded at declaration.** The row enters the store at the first run of the
134
138
  `GET`, once the relations are declared, so its relation values are normalized
135
139
  as a real answer's are. `ME.store` read before that first run is empty.
136
140
 
141
+ ## `keepPageOnScreen`: the page on screen, drawn again by the next document
142
+
143
+ A reload, a pull-to-refresh, a tab the system discarded in the background: a
144
+ new document opens at the same address, on the page that was left, and what
145
+ that page reads almost always answers what it answered a moment ago. `persist`
146
+ keeps one row of one resource. A page reads several things, lists included,
147
+ and which ones depends on the page. `keepPageOnScreen` keeps that set, one
148
+ slot, replaced when the user moves:
149
+
150
+ ```js
151
+ const pageKeptSignal = stateSignal(undefined, {
152
+ id: `page@${APP_VERSION}`,
153
+ persists: true,
154
+ type: "object",
155
+ });
156
+ // Before the routes start: the first runs are the ones that look.
157
+ keepPageOnScreen({
158
+ signal: pageKeptSignal,
159
+ when: () => !viewAsSignal.value,
160
+ always: [MY_GAMES_PAGE],
161
+ });
162
+ ```
163
+
164
+ **The unit is the page, not the resource.** One resource is read by several
165
+ screens: `GAME.GET` for any game opened, `USER.GET_MANY` for a tab and for
166
+ every search. Kept per resource, that is every game ever opened. The page's
167
+ reads are what navi already runs for it: the route actions asking something for
168
+ the page (`activeRouteActionsSignal`, which the app also reads to rerun the page
169
+ when it comes back to the foreground), and the compositions the `<List.Items>`
170
+ on screen read. A navigation replaces the slot with the next page's reads.
171
+
172
+ What is kept, for the next document's first run of the same read with the same
173
+ params:
174
+
175
+ | read | kept |
176
+ | --------------------------------------- | ------------------------------------------------------------ |
177
+ | a route action on a resource `GET` | the row, its relations inline |
178
+ | a route action on a resource `GET_MANY` | the rows in order, written the same way |
179
+ | a `<List.Items>` reading a `GET_RANGE` | the count and the ranks around the window drawn, rows inline |
180
+
181
+ The `GET` and the `GET_MANY` draw it as their provisional value: `data` set
182
+ while `loading` is `true`, the answer replacing it. The list finds it as a
183
+ composition it left
184
+ ([list_refresh.md](./list_refresh.md#leaving-the-screen-and-coming-back)): every
185
+ rank stale, one request for the window. Under a network policy answering reads
186
+ from the store, the kept answer answers the read
187
+ ([network_policy.md](./network_policy.md)).
188
+
189
+ What is not kept, and why:
190
+
191
+ - **a relationship read** (`.one()`, `.many()`, `.scopedOne()`,
192
+ `.scopedMany()`). Its answer enters the store through its owner: the parent
193
+ row it is nested in, or the store of the owner it is scoped to. It is not rows
194
+ of its own that could be written back alone.
195
+ - **a plain `createAction`** (a summary, a count the server computes). Its value
196
+ is whatever the callback returns, with no store to give it a shape to write
197
+ and to read back. A page's code is a route action too, and it is a function.
198
+ - **a read that is not a route action**: a search typed on the page, a popup's
199
+ own load.
200
+
201
+ A read writes what it shows once it has answered in this document. Until then
202
+ the slot keeps what the previous document kept for it, never what the read
203
+ shows meanwhile: an answer handed over from other params (a list standing in
204
+ for the next search) is not about these. The write waits for the page to settle
205
+ and is flushed when the document is hidden, which is when the system may discard
206
+ it. Between those moments a navigation goes through half-built pages: the route
207
+ matched but its list not mounted, or the page left still drawn under a
208
+ transition.
209
+
210
+ **The start page.** An installed app launched after being killed opens at its
211
+ start address, not on the page that was left. The reads made while a route of
212
+ `always` matches stay kept once that page is left, until the next visit
213
+ replaces them.
214
+
215
+ What stays the app's, as for `persist`: **the key** (the deployed version in the
216
+ signal's `id`), **`when`** (`false` reads nothing, writes nothing, and empties
217
+ the signal), and **the moment**: sign-out writes `undefined` into the signal,
218
+ whatever page it holds, and nothing is written again until a read lands. Also
219
+ **what the reads feed beside their data**: a count the app sets from an answer
220
+ is its own signal to keep. When a list's opening position comes from such a
221
+ count, `defaultScrolled` follows the fresh value as long as nobody has moved the
222
+ list ([scroll.md](./scroll.md#where-the-list-opens-and-where-it-is)).
223
+
224
+ What it costs: the copy follows the store, so a `PUT` on a row rewrites it, and
225
+ every change in the stores the page reads serializes the page's reads again.
226
+ Only the write to the signal is coalesced. A list's window with its rows inline
227
+ is tens of kilobytes.
228
+
137
229
  ## `GET_RANGE`: feeding a list that loads as it scrolls
138
230
 
139
231
  A `<List.Items>` asks for the rows it is about to draw and keeps what it gets.
package/docs/scroll.md CHANGED
@@ -121,7 +121,9 @@ say so with `"document"` or the element itself.
121
121
  by MEASURING it: it lands where it was even if rows were inserted before it,
122
122
  whatever the screen it was saved on. "Reopen a thread where I left it" is
123
123
  already provided; keep the position whole, its `visibleCount` sizes the first
124
- paint. A list with an `id` also **comes back where it was** when its screen is
124
+ paint. A `defaultScrolled` that changes before anyone has moved the list is
125
+ followed — a place read from an answer that refreshes (yesterday's "today", then
126
+ today's) — and it is compared by value, so it can be computed at every render. A list with an `id` also **comes back where it was** when its screen is
125
127
  left and come back to, the way the page does (its `id` must name one list of the
126
128
  app, see [below](#a-list-that-comes-back-one-id-one-list));
127
129
  **`scrollResetOnNavigation`** opts out. A list scrolling the document that
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.436",
3
+ "version": "0.29.437",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {