@loadbare/app 0.14.0 → 0.16.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.
Files changed (35) hide show
  1. package/dist/build/elements.d.ts +12 -2
  2. package/dist/build/elements.d.ts.map +1 -1
  3. package/dist/build/elements.js +44 -10
  4. package/dist/build/elements.js.map +1 -1
  5. package/dist/core/lb-constants.d.ts +2 -1
  6. package/dist/core/lb-constants.d.ts.map +1 -1
  7. package/dist/core/lb-constants.js +16 -3
  8. package/dist/core/lb-constants.js.map +1 -1
  9. package/dist/core/lb-types.d.ts +25 -8
  10. package/dist/core/lb-types.d.ts.map +1 -1
  11. package/dist/core/lb-types.js.map +1 -1
  12. package/dist/hub/lb-apply.d.ts +9 -0
  13. package/dist/hub/lb-apply.d.ts.map +1 -1
  14. package/dist/hub/lb-apply.js +118 -35
  15. package/dist/hub/lb-apply.js.map +1 -1
  16. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  17. package/dist/hub/lb-hub.browser.js +41 -5
  18. package/dist/hub/lb-hub.browser.js.map +1 -1
  19. package/dist/hub/lb-lifecycle.d.ts +40 -0
  20. package/dist/hub/lb-lifecycle.d.ts.map +1 -0
  21. package/dist/hub/lb-lifecycle.js +140 -0
  22. package/dist/hub/lb-lifecycle.js.map +1 -0
  23. package/docs/TECHREF-1.0.md +49 -47
  24. package/docs/comparison.md +7 -5
  25. package/docs/possible-ideas.md +6 -0
  26. package/docs/reference/custom-elements.md +110 -89
  27. package/docs/reference/data-binding.md +12 -7
  28. package/docs/reference/widgets.md +2 -2
  29. package/docs/testing.md +2 -2
  30. package/package.json +1 -1
  31. package/skills/loadbare-app/SKILL.md +20 -16
  32. package/skills/loadbare-app/references/TECHREF-1.0.md +49 -47
  33. package/skills/loadbare-app/references/custom-elements.md +110 -89
  34. package/skills/loadbare-app/references/data-binding.md +12 -7
  35. package/skills/loadbare-app/references/widgets.md +2 -2
@@ -715,10 +715,9 @@ standing where it stood, and moved back out when the column turns on. It is
715
715
  not rendered, focused, clicked, announced or gathered. It is moved and
716
716
  never rebuilt, so a custom element keeps its instance and a control keeps
717
717
  what was typed into it. Landing reaches into that template, so the element
718
- and everything inside it return current. A custom element sees
719
- `disconnectedCallback` then `adoptedCallback` going in, and
720
- `adoptedCallback` then `connectedCallback` coming out, and still receives
721
- `lb-column-value` while it is away.
718
+ and everything inside it return current. A custom element hears nothing
719
+ going in or coming out, and still receives `lb-column-value` while it is
720
+ away.
722
721
 
723
722
  The builder ships every `lb-show` element already inside its template, so
724
723
  nothing conditional shows until its row lands. An absent element's template
@@ -1098,8 +1097,10 @@ makes a confirmation wrapper possible without the wrapped element knowing
1098
1097
  about it.
1099
1098
 
1100
1099
  Once what the request brought back has landed, or its round trip has failed,
1101
- the hub dispatches the bubbling `lb-request-done` event from the same
1102
- element. Its `detail`, `HubRequestDone` in `@loadbare/app/types`, holds
1100
+ the hub calls `lbRequestDone(detail, origin)` on the committing element and
1101
+ every custom element around it, innermost first (see
1102
+ [Lifecycle](#lifecycle)), then dispatches the bubbling `lb-request-done`
1103
+ event from the same element. Its `detail`, `HubRequestDone` in `@loadbare/app/types`, holds
1103
1104
  the request as the hub sent it and `items`, every response item that landed
1104
1105
  because of it, in order. An answer that moved the URL contributes its
1105
1106
  `lb-url` item and the page load. `error` is set instead when the round trip
@@ -1884,14 +1885,12 @@ Each direction has one mechanism for each kind of message:
1884
1885
  | -------------- | ------------------------------------------- | ---------------------------- | ------------------------------------------------ |
1885
1886
  | Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-group-*`, `lb-row-*`, `lb-request-pending`, `lb-request-error` |
1886
1887
  | Hub to element | State the platform already names | A property | A control's `value` |
1887
- | Hub to element | Work the hub needs done now, while landing | An optional `lb` method | `lbRowsLanded` |
1888
+ | Hub to element | What the hub did | An optional `lb` method | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
1888
1889
  | Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
1889
1890
  | Element to hub | A moment | A bubbling event | `lb-request`, and `change`, `click` and `submit` |
1890
1891
 
1891
- State is an attribute, because a stylesheet can select on it and an element
1892
- that upgrades late still finds it. A method is for work the hub cannot go
1893
- on without, since it acts on one element at one point in landing and nothing
1894
- else can do it. A moment is an event, because the element that cares is
1892
+ State is an attribute, because a stylesheet can select on it. A method is
1893
+ how the hub tells one element what it just did to that element. A moment is an event, because the element that cares is
1895
1894
  often an ancestor of the one it concerns, and an event reaches it with no
1896
1895
  knowledge of either.
1897
1896
 
@@ -1910,45 +1909,47 @@ class NoteField extends HTMLElement {
1910
1909
  }
1911
1910
  ```
1912
1911
 
1913
- A control that is not yet upgraded when its column lands receives
1914
- `lb-column-value` alone, since the hub sets `value` only on a control. That
1915
- happens to an element the builder shipped absent, and to one whose
1916
- definition loads after the hub lands. A control takes the stamp up the
1917
- first time it connects, and never again.
1918
-
1919
1912
  A custom element that is not a control receives `lb-column-value` and
1920
1913
  renders it; its content is never replaced.
1921
1914
 
1922
1915
  ### Lifecycle
1923
1916
 
1924
- | The hub | A custom element sees |
1925
- | ---------------------------------------- | -------------------------------------------- |
1926
- | Shows a page | `constructor`, `connectedCallback` |
1927
- | Creates a live row, before placing it | `constructor` |
1928
- | Places a live row the first time | `connectedCallback` |
1929
- | Moves a live row the order put elsewhere | `connectedMoveCallback` where the browser moves in place, otherwise `disconnectedCallback`, `connectedCallback` |
1930
- | Leaves a copy of a moved row behind | The copy's `constructor`, `connectedCallback`, and later `disconnectedCallback` |
1931
- | Removes a live row | `disconnectedCallback`, once its animations end |
1932
- | Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
1933
- | Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
1934
- | Turns on an element shipped absent | `constructor`, `connectedCallback` |
1917
+ A custom element hears the hub's lifecycle, never the browser's. The hub
1918
+ calls four optional methods, which the `HubLifecycle` interface in
1919
+ `@loadbare/app/types` declares:
1920
+
1921
+ | The hub | A custom element hears |
1922
+ | ------------------------------------------------ | ------------------------------- |
1923
+ | Places it: chrome at startup, a page, a new live row, a new group | `lbAttached()`, once, after every column has landed on it |
1924
+ | Lands rows on it | `lbRowsLanded()` |
1925
+ | Finishes a request committed by it or inside it | `lbRequestDone(detail, origin)` |
1926
+ | Removes it: a row or group done leaving, the page replaced | `lbDetached()`, once |
1927
+ | Moves a live row the order put elsewhere | Nothing |
1928
+ | Turns `lb-show` off or on | Nothing |
1929
+ | Leaves a copy of a moved row behind | Nothing, on the copy |
1930
+
1931
+ The chrome and a page are placed as the page's first answer lands, or once
1932
+ that round trip fails. An element under an `lb-show` that is off is placed
1933
+ inside its template, so `lbAttached` runs whether it shows or not. `lbAttached` reaches an outer
1934
+ custom element before the ones inside it, and `lbDetached` the inner ones
1935
+ first. `lbRequestDone` reaches the committing element and every custom
1936
+ element around it, innermost first, before the `lb-request-done` event.
1937
+ An element a script creates hears none of them.
1935
1938
 
1936
1939
  A listener on the element itself goes in the constructor, which reads no
1937
- attribute and no child. A child is looked up when it is needed.
1938
- `connectedCallback` runs on every move and is written to run again; work
1939
- done once per instance goes behind a flag.
1940
+ attribute and no child. A child is looked up when it is needed. Work done
1941
+ once per instance goes in `lbAttached`, and its undoing in `lbDetached`.
1940
1942
 
1941
- ### Row hooks
1943
+ The build refuses a script that declares `connectedCallback`,
1944
+ `disconnectedCallback`, `adoptedCallback` or `connectedMoveCallback`.
1942
1945
 
1943
- | Method | Implemented By | The hub calls it |
1944
- | ---------------- | -------------- | ------------------------------------------------------ |
1945
- | `lbRowsLanded()` | Developer | After the rows land, with every row placed and stamped |
1946
+ ### Row hooks
1946
1947
 
1947
- The hub calls it on a custom element with `lb-query` and a row template.
1948
- It is optional. The `RowsHost` interface in `@loadbare/app/types` declares
1949
- it. The hub places every row and builds every group itself, so a custom
1950
- element reacts to rows and never places them. Scrolling to the row the
1951
- page's request created is the case:
1948
+ The hub calls `lbRowsLanded()` on a custom element with `lb-query` and a row
1949
+ template, after the rows land, with every row placed and stamped. The hub
1950
+ places every row and builds every group itself, so a custom element reacts
1951
+ to rows and never places them. Scrolling to the row the page's request
1952
+ created is the case:
1952
1953
 
1953
1954
  ```ts
1954
1955
  lbRowsLanded() {
@@ -1957,10 +1958,8 @@ lbRowsLanded() {
1957
1958
  }
1958
1959
  ```
1959
1960
 
1960
- A custom element the builder shipped absent under `lb-show` has not
1961
- upgraded while its column is off. When the column first turns on and it
1962
- upgrades, the hub lands the query's last answer on it again, as all rows,
1963
- so the hook runs.
1961
+ A custom element under an `lb-show` that is off holds its rows all the
1962
+ same, and the hook runs as they land.
1964
1963
 
1965
1964
  `applyRow(root, row)`, exported by `@loadbare/app`, fills `root` from one
1966
1965
  row, the same operation that fills a live row.
@@ -2136,9 +2135,12 @@ Loadbare/app owns every DOM event in this table, both the name and what its
2136
2135
 
2137
2136
  ### Reserved methods
2138
2137
 
2139
- | Method | On | Defined in |
2140
- | -------------- | -------------------------------------------------- | ----------------------- |
2141
- | `lbRowsLanded` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
2138
+ | Method | On | Defined in |
2139
+ | --------------- | --------------------------------------------------- | ----------------------- |
2140
+ | `lbAttached` | Any custom element the hub places | [Lifecycle](#lifecycle) |
2141
+ | `lbRowsLanded` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
2142
+ | `lbRequestDone` | A custom element that committed, or holds one that did | [Lifecycle](#lifecycle) |
2143
+ | `lbDetached` | Any custom element the hub removes | [Lifecycle](#lifecycle) |
2142
2144
 
2143
2145
  ### Constants
2144
2146
 
@@ -319,10 +319,11 @@ without `.browser` is not bundled, and the build error names it.
319
319
  - A widget may send a request by dispatching a bubbling `lb-request`
320
320
  `CustomEvent`. The hub completes it with `query`, `key` and `values` from
321
321
  where the element sits. An ancestor may stop the event.
322
- - A custom element carrying `lb-query` and a row template may implement
323
- `lbRowsLanded()` to act on the rows once they land, such as scrolling to
324
- the one a request created. The hub does the cloning, matching, ordering,
325
- grouping, removal and counting.
322
+ - A custom element hears the hub's lifecycle rather than the browser's:
323
+ `lbAttached()` and `lbDetached()` once each, when the hub places and
324
+ removes it, `lbRowsLanded()` once rows land on it, and `lbRequestDone()`
325
+ when a request from inside it finishes. The hub does the cloning,
326
+ matching, ordering, grouping, removal and counting.
326
327
  - Method names beginning with `lb` on a custom element are reserved.
327
328
 
328
329
  Widget libraries are npm packages listed in `imports.ts`. The builder scans
@@ -791,7 +792,7 @@ TECHREF-1.0 lists every name Loadbare/app owns in one cross-reference.
791
792
  | `lb-*` stamps | 17 | `lb-column-value`, `lb-key-value`, `lb-row-live`, `lb-query-row-count`, `lb-group-live`, `lb-group-column`, `lb-group-value`, `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested`, `lb-row-leaving`, `lb-group-leaving`, `lb-request-pending`, `lb-request-error`, `lb-page`, `lb-page-title` |
792
793
  | `lb-*` build-time attributes | 2 | `lb-exp-slot`, `lb-exp-template` |
793
794
  | Requests Loadbare provides | 3 | `lb-row-insert`, `lb-row-update`, `lb-row-delete` |
794
- | `lb*` methods on custom elements | 1 | `lbRowsLanded` |
795
+ | `lb*` methods on custom elements | 4 | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
795
796
  | Query parms Loadbare reads | 1 | `lb-order-<query>` |
796
797
  | Attribute namespace for expansion | 1 | `exp-*` |
797
798
  | Reserved tags | 1 | `<lb-hub>` |
@@ -1185,6 +1186,7 @@ Each equivalent is approximate.
1185
1186
  | `exp-` parameter | A prop fixed at build time |
1186
1187
  | `lb-exp-slot`, `lb-exp-template` | Default slot and named slots; `ng-content` with `select` |
1187
1188
  | `lbRowsLanded` | An effect after a list renders |
1189
+ | `lbAttached`, `lbDetached` | Mount and unmount; `onMounted` and `onUnmounted` (Vue); `onMount` and `onDestroy` (Svelte) |
1188
1190
  | `order`, `lb-group` | `ORDER BY` with report-writer control breaks (`BREAK ON`, group bands) |
1189
1191
  | `ctx` / `HubContext` | `event.locals` (SvelteKit); loader `context` (React Router 7); request-scoped dependency injection |
1190
1192
  | `imports.ts` | Installing and registering a component library |
@@ -4,6 +4,12 @@
4
4
  > 2026-10-02 against `@loadbare/app` 0.12.0, from a session in ef. Nothing
5
5
  > here is decided or planned.
6
6
 
7
+ > **Status, 2026-10-04.** The lifecycle in the hub's terms was adopted, on
8
+ > custom elements rather than on behaviors: see
9
+ > [When its code runs](./reference/custom-elements.md#when-its-code-runs).
10
+ > Behavior by attribute, and expansion that replaces the tag, were set aside.
11
+ > What follows is kept as the record of how the decision was reached.
12
+
7
13
  ## Why these are being considered
8
14
 
9
15
  A custom element is the platform's one way to attach code to markup, and
@@ -278,10 +278,10 @@ A custom element reads `lb-` attributes and never assigns one. The developer
278
278
  writes them in markup, and the hub and the builder write their stamps.
279
279
 
280
280
  Loadbare reserves method names beginning with `lb` on a custom element, for
281
- the methods it calls; see [Holding rows](#holding-rows). The build refuses
282
- a script that declares a method, accessor or field whose name is `lb`, a
283
- capital, and anything else Loadbare does not call, so a misspelled
284
- `lbRowLanded` fails the build rather than never being called.
281
+ the methods it calls; see [When its code runs](#when-its-code-runs). The
282
+ build refuses a script that declares a method, accessor or field whose name
283
+ is `lb`, a capital, and anything else Loadbare does not call, so a
284
+ misspelled `lbRowLanded` fails the build rather than never being called.
285
285
 
286
286
  ### What the hub and an element say to each other
287
287
 
@@ -291,39 +291,80 @@ Each direction has one mechanism for each kind of message:
291
291
  |----------------|--------------------------------------------|-----------------------------|-------|
292
292
  | Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-group-*`, `lb-row-*`, `lb-request-pending`, `lb-request-error` |
293
293
  | Hub to element | State the platform already names | A property | A control's `value` |
294
- | Hub to element | Work the hub needs done now, while landing | An optional `lb` method | `lbRowsLanded` |
294
+ | Hub to element | What the hub did | An optional `lb` method | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
295
295
  | Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
296
296
  | Element to hub | A moment | A bubbling event | `lb-request`, and `change`, `click` and `submit` |
297
297
 
298
- State is an attribute: a stylesheet can select on it, and an element that
299
- upgrades late still finds it. A method is for work the hub cannot go on
300
- without, done by one element at one point in landing. A moment is an event,
301
- because the element that cares is often an ancestor of the one it concerns,
302
- and an event reaches it knowing neither.
298
+ State is an attribute, because a stylesheet can select on it. A method is
299
+ how the hub tells one element what it just did to that element. A moment is
300
+ an event, because the element that cares is often an ancestor of the one it
301
+ concerns, and an event reaches it knowing neither.
303
302
 
304
303
  ### When its code runs
305
304
 
306
- The hub creates, places and parks elements, and each of those reaches a
307
- custom element as the browser's lifecycle callbacks. It is the same instance
308
- from its constructor on, however often it moves:
305
+ The hub tells a custom element what it did through four optional methods,
306
+ named for the hub's own work. The `HubLifecycle` interface in
307
+ `@loadbare/app/types` declares them. Here is an element that closes its
308
+ popup on Escape, listening on `document` only while the hub has it on the
309
+ page:
309
310
 
310
- | What the hub does | Callbacks, in order |
311
- |----------------------------------------------------|---------------------------------------------|
312
- | Shows a page | `constructor`, `connectedCallback` |
313
- | Creates a live row, filled before it is placed | `constructor` |
314
- | Places a live row for the first time | `connectedCallback` |
315
- | Places it again, as it does on every set of all rows | `disconnectedCallback`, `connectedCallback` |
316
- | Removes a live row | `disconnectedCallback` |
317
- | Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
318
- | Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
319
- | Turns on an element the builder shipped absent | `constructor`, `connectedCallback` |
311
+ ```ts
312
+ // src/note-popup.browser.ts
313
+ import type { HubLifecycle } from "@loadbare/app/types";
314
+
315
+ class NotePopup extends HTMLElement implements HubLifecycle {
316
+ #onKey = (e: KeyboardEvent) => {
317
+ if (e.key === "Escape") this.querySelector("dialog")?.close();
318
+ };
320
319
 
321
- The builder ships every `lb-show` element inside its template, and nothing
322
- in a template is upgraded, so such an element has no instance until its
323
- column first turns on. See
324
- [Conditional rendering](./data-binding.md#conditional-rendering).
320
+ lbAttached() {
321
+ document.addEventListener("keydown", this.#onKey);
322
+ }
325
323
 
326
- So each kind of work has one place:
324
+ lbDetached() {
325
+ document.removeEventListener("keydown", this.#onKey);
326
+ }
327
+ }
328
+
329
+ customElements.define("note-popup", NotePopup);
330
+ ```
331
+
332
+ | Method | The hub calls it |
333
+ |---------------------------------|-------------------------------------------------------------------|
334
+ | `lbAttached()` | Once, after it places the element and every column has landed on it |
335
+ | `lbRowsLanded()` | After rows land on the element, with every row placed and stamped |
336
+ | `lbRequestDone(detail, origin)` | After a request committed by the element, or inside it, has finished |
337
+ | `lbDetached()` | Once, after it removes the element |
338
+
339
+ The hub places an element when it starts up the chrome, shows a page,
340
+ creates a live row, or builds a group. It places the chrome and a page as
341
+ the page's first answer lands, or once that round trip fails, so an element
342
+ in either hears `lbAttached` with the page's data in place. It removes an
343
+ element when a live row or a group has finished leaving, and when it shows
344
+ another page in place of the one holding it. Write setup in `lbAttached` and its undoing in
345
+ `lbDetached`; the two pair exactly, so neither guards against running
346
+ twice.
347
+
348
+ Moving a row and turning `lb-show` off and on are neither placing nor
349
+ removing. The element keeps its instance and hears nothing. An element
350
+ under an `lb-show` that is off is placed inside its template, so its
351
+ `lbAttached` runs whether or not it shows. Measure and focus when the user
352
+ acts, never in `lbAttached`, where the element may not be rendered.
353
+
354
+ The hub calls `lbAttached` on an outer custom element before the ones
355
+ inside it, and `lbDetached` on the inner ones first. A table that sets the
356
+ `form` attribute on its controls in `lbAttached` does so before each control
357
+ hears its own.
358
+
359
+ An element a script creates hears none of the four, and neither does the
360
+ copy of a moved row the hub leaves behind while it fades. The hub calls
361
+ them only on what it places.
362
+
363
+ Never write `connectedCallback`, `disconnectedCallback`, `adoptedCallback`
364
+ or `connectedMoveCallback`. The browser calls those on every move, and the
365
+ build refuses a script that declares one.
366
+
367
+ Each kind of work then has one place:
327
368
 
328
369
  1. **A listener on the element itself goes in the constructor.** It is
329
370
  attached once and survives every move, and a live row gets its own
@@ -331,15 +372,11 @@ So each kind of work has one place:
331
372
  element, so the listener needs no child to exist yet. The constructor
332
373
  reads no attribute and no child: an element made with
333
374
  `document.createElement` has neither when it runs.
334
- 2. **A child is looked up when it is needed**, in a handler, a getter,
335
- or `lbRowsLanded`, and never held from setup.
336
- 3. **`connectedCallback` runs on every move**, so it is written to run
337
- again. It rearranges the element's own children into a state it checks
338
- for first, or adds a listener to `document` or `window`, which
339
- `disconnectedCallback` removes. Work done once per instance that needs
340
- the element's attributes, such as a control taking up a value that landed
341
- before it upgraded, goes behind a flag there; see
342
- [Being a control](#being-a-control).
375
+ 2. **A child is looked up when it is needed**, in a handler, a getter, or
376
+ one of the four methods, and never held from setup.
377
+ 3. **Work done once per instance goes in `lbAttached`**, with its undoing
378
+ in `lbDetached`: rearranging the element's own children, or a listener on
379
+ `document` or `window`.
343
380
 
344
381
  ### Being a control
345
382
 
@@ -350,20 +387,10 @@ and commits its `lb-request` on `change`:
350
387
 
351
388
  ```ts
352
389
  // src/star-rating.browser.ts
353
- import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
354
-
355
390
  class StarRating extends HTMLElement {
356
391
  static formAssociated = true;
357
392
  #internals = this.attachInternals();
358
393
  #value = "";
359
- #connected = false;
360
-
361
- connectedCallback() {
362
- if (this.#connected) return;
363
- this.#connected = true;
364
- const stamped = this.getAttribute(ATTR_COLUMN_VALUE);
365
- if (stamped !== null) this.value = stamped;
366
- }
367
394
 
368
395
  get value(): string {
369
396
  return this.#value;
@@ -399,14 +426,6 @@ customElements.define("star-rating", StarRating);
399
426
  Dispatch `change` with `bubbles: true`, as a native control's does, when the
400
427
  user commits an edit. Setting `value` from the hub fires nothing.
401
428
 
402
- A control can receive its value before it is a control. An element the
403
- builder shipped absent, or one whose definition loads after the hub has
404
- landed, is not upgraded when its column lands, so the hub cannot set its
405
- `value` and the value arrives as `lb-column-value` alone. Take the stamp up
406
- the first time the control connects, as `connectedCallback` does above, and
407
- never again: after that the hub sets `value` itself, and a later move must
408
- not undo an edit.
409
-
410
429
  A form owns a form-associated custom element the way it owns an `<input>`,
411
430
  including through the HTML `form` attribute, so a form gathers it on submit.
412
431
  Write `formResetCallback` to restore the default value: the hub resets a form
@@ -538,7 +557,9 @@ customElements.define("visit-count", VisitCount);
538
557
  List `ATTR_COLUMN_VALUE` in `observedAttributes`. The browser calls
539
558
  `attributeChangedCallback` for an attribute already present when an element
540
559
  upgrades, as well as for one that changes afterward, so the first render and
541
- every later one go through the one callback.
560
+ every later one go through the one callback. A landing that brings the value
561
+ the element already carries leaves the attribute alone, and the callback does
562
+ not run.
542
563
 
543
564
  A custom element carrying `lb-show`, or inside an element that does, still
544
565
  receives its column while it is away, so it returns current; see
@@ -571,23 +592,28 @@ Let the event bubble, so an ancestor can stop it before the hub sends it.
571
592
  ### Hearing how it turned out
572
593
 
573
594
  Once what a request brought back has landed, or its round trip has failed,
574
- the hub dispatches `lb-request-done` from the element that committed. It
575
- bubbles, so the element that cares need not be the one that committed: a
576
- dialog hears it from the form inside it.
595
+ the hub calls `lbRequestDone(detail, origin)` on the custom element that
596
+ committed and on every custom element around it, innermost first. `origin`
597
+ is the element that committed, so the element that cares need not be the
598
+ one that committed: a dialog hears it from the form inside it.
577
599
 
578
600
  ```ts
579
- import { LB_DONE_EVENT_NAME } from "@loadbare/app/constants";
580
- import type { HubRequestDone } from "@loadbare/app/types";
581
-
582
- this.addEventListener(LB_DONE_EVENT_NAME, (e) => {
583
- const { request, items, error } = (e as CustomEvent<HubRequestDone>).detail;
584
- if (error) return;
585
- const item = items.find((i) => i.query === request.query);
586
- const created = item?.patch?.rows?.[0];
587
- if (created) this.choose(String(created[item!.key]));
588
- });
601
+ import type { HubLifecycle, HubRequestDone } from "@loadbare/app/types";
602
+
603
+ class NewRowDialog extends HTMLElement implements HubLifecycle {
604
+ lbRequestDone({ request, items, error }: HubRequestDone) {
605
+ if (error) return;
606
+ const item = items.find((i) => i.query === request.query);
607
+ const created = item?.patch?.rows?.[0];
608
+ if (created) this.choose(String(created[item!.key]));
609
+ }
610
+ }
589
611
  ```
590
612
 
613
+ The hub then dispatches `lb-request-done` from the element that committed,
614
+ carrying the same `detail` as a bubbling `CustomEvent`, for any other
615
+ listener. Import its name as `LB_DONE_EVENT_NAME`.
616
+
591
617
  `items` is every response item that landed because of the request, in
592
618
  order. `refusal` is set beside `error` when the handler refused the
593
619
  request, holding its `message` and `columns`. An answer that moved the URL contributes its `lb-url` item, which
@@ -596,11 +622,12 @@ the round trip failed, and then nothing landed. A new row's key is in the
596
622
  answer only when the handler returned it in a patch: a refresh sends all
597
623
  rows, and nothing marks which one is new.
598
624
 
599
- By the time the event is dispatched, `lb-request-pending` is gone and the
600
- page is as the answer left it. A request that was never sent, because the
601
- hub or an ancestor stopped it, gets no `lb-request-done`. An element the
602
- answer removed, such as the row a delete took away, dispatches the event
603
- outside the document, and no ancestor it had there hears it.
625
+ By the time either is called, `lb-request-pending` is gone and the page is
626
+ as the answer left it. A request that was never sent, because the hub or an
627
+ ancestor stopped it, gets neither. An element the answer removed, such as
628
+ the row a delete took away, is outside the document by then: no ancestor it
629
+ had there hears the event, and no custom element the hub has removed hears
630
+ `lbRequestDone`.
604
631
 
605
632
  ### Holding rows
606
633
 
@@ -611,32 +638,28 @@ plain element carrying `lb-query` with a row template inside it needs no
611
638
  code, so a custom element holds rows only to do something with them once
612
639
  they land.
613
640
 
614
- A custom element carrying `lb-query` and a row template may implement one
615
- optional method, which the hub calls:
641
+ A custom element carrying `lb-query` and a row template implements
642
+ `lbRowsLanded`, which the hub calls once the rows have landed, with every
643
+ row placed and stamped:
616
644
 
617
645
  ```ts
618
- import type { RowsHost } from "@loadbare/app/types";
646
+ import type { HubLifecycle } from "@loadbare/app/types";
619
647
  import { ATTR_ROW_REQUESTED, REQUESTED_CREATED } from "@loadbare/app/constants";
620
648
 
621
- class ScrollingList extends HTMLElement implements RowsHost {
649
+ class ScrollingList extends HTMLElement implements HubLifecycle {
622
650
  lbRowsLanded() {
623
- // Once, after the rows have landed, every row placed and stamped.
624
651
  this.querySelector(`[${ATTR_ROW_REQUESTED}="${REQUESTED_CREATED}"]`)
625
652
  ?.scrollIntoView({ block: "nearest" });
626
653
  }
627
654
  }
628
655
  ```
629
656
 
630
- | Method | The hub calls it |
631
- |----------------|-------------------------------------------------------|
632
- | `lbRowsLanded` | After the rows land, with every row placed and stamped |
633
-
634
657
  Everything else is the hub's:
635
658
 
636
659
  | Concern | The hub |
637
660
  |-------------|----------------------------------------------------------|
638
661
  | Cloning | Clones the row template once per new key |
639
- | Matching | Fills the live row already showing that key |
662
+ | Matching | Fills the live row already showing that key, when its values changed |
640
663
  | Placing | Places every live row by the query's order |
641
664
  | Grouping | Builds a group where the order breaks, and removes it with its last row |
642
665
  | Removing | Marks the live rows the response says are gone as leaving, and removes them |
@@ -649,11 +672,9 @@ gives each option and `<optgroup>` what landing does not, a value and a
649
672
  label, and the other scrolls to the row the page's request created or
650
673
  moved.
651
674
 
652
- A custom element the builder shipped absent under `lb-show` has not
653
- upgraded while its column is off, and the hub places the rows that land on
654
- it then as it places any. When the column first turns on and the element
655
- upgrades, the hub lands the query's last answer on it again, as all rows,
656
- so `lbRowsLanded` runs.
675
+ A custom element under an `lb-show` that is off still holds its rows. The
676
+ hub places the rows that land on it then as it places any, and calls its
677
+ `lbRowsLanded`, so it returns current.
657
678
 
658
679
  A custom element that walks its own rows finds them with `LIVE_ROW` from
659
680
  `@loadbare/app/constants`, never by `lb-key-value`: an element a `row` lands
@@ -172,6 +172,11 @@ membership: a live row whose key did not arrive leaves. A patch changes only
172
172
  the rows it names, and every other live row keeps its content. The hub
173
173
  places every live row by the query's order; see [Order](#order).
174
174
 
175
+ A live row whose values arrive as it already shows them is not filled
176
+ again. A control in it that the user has typed into and not committed keeps
177
+ what was typed, through a refresh and through a change of order. A row
178
+ whose values changed is filled whole, and its controls show what arrived.
179
+
175
180
  A key is unique within a query. Two rows with one key in the same answer,
176
181
  or two live rows showing one key, are reported on the console.
177
182
 
@@ -284,10 +289,9 @@ scaffolding around rows, such as a group's heading or a ghost row. So
284
289
  a query nested in another query's rows need not land again, or land after
285
290
  the outer query, for a new outer row to show its choices.
286
291
 
287
- A custom element the builder shipped absent has not upgraded while its
288
- `lb-show` column is off. When the column first turns on and the element
289
- upgrades, the hub lands the kept answer on it again, so its `lbRowsLanded`
290
- runs. See [Holding rows](./custom-elements.md#holding-rows).
292
+ A custom element under an `lb-show` that is off is filled like any other,
293
+ and its `lbRowsLanded` runs as its rows land. See
294
+ [Holding rows](./custom-elements.md#holding-rows).
291
295
 
292
296
  Nothing is answered from what the hub kept: a request always goes to the
293
297
  server, and a query re-runs only when a response names it. A patch that
@@ -877,9 +881,10 @@ An ancestor may stop the event, and the request is not sent. A custom
877
881
  element may dispatch the event itself; see
878
882
  [Sending a request](./custom-elements.md#sending-a-request).
879
883
 
880
- Once the answer has landed, or the round trip has failed, the hub dispatches
881
- a bubbling `lb-request-done` event from the same element, carrying the
882
- request and what landed because of it, or the error; see
884
+ Once the answer has landed, or the round trip has failed, the hub calls
885
+ `lbRequestDone` on the custom elements from the committing element outward,
886
+ then dispatches a bubbling `lb-request-done` event from the same element.
887
+ Both carry the request and what landed because of it, or the error; see
883
888
  [Hearing how it turned out](./custom-elements.md#hearing-how-it-turned-out).
884
889
 
885
890
  ## The URL
@@ -182,8 +182,8 @@ the hub until the user says yes:
182
182
  | `exp-request` | The confirm button's `lb-request` |
183
183
  | `exp-confirm` | The confirm button's text, `Confirm` when absent |
184
184
 
185
- The widget closes the dialog once `lb-request-done` says the request
186
- succeeded, and leaves it open on a refusal.
185
+ The widget closes the dialog once the request succeeds, and leaves it open
186
+ on a refusal.
187
187
 
188
188
  ## `lb-form`
189
189
 
package/docs/testing.md CHANGED
@@ -263,8 +263,8 @@ One window per file, which is what running each file in its own process gives
263
263
  for free — registration is global and permanent, so sharing a process across
264
264
  widget files would mean sharing a registry.
265
265
 
266
- What that buys is real upgrades: the constructor, `connectedCallback`, and
267
- `attributeChangedCallback` for attributes already present. That last one is
266
+ What that buys is real upgrades: the constructor, the hub's `lb` methods,
267
+ and `attributeChangedCallback` for attributes already present. That last one is
268
268
  the mechanism by which hydration and refresh are one operation, and it
269
269
  behaves the same in jsdom as in a browser. What it does not buy is layout,
270
270
  painting, or navigation.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loadbare/app",
3
3
  "description": "High performance web app framework for server-bound applications",
4
- "version": "0.14.0",
4
+ "version": "0.16.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",