@loadbare/app 0.14.0 → 0.15.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 +2 -0
  13. package/dist/hub/lb-apply.d.ts.map +1 -1
  14. package/dist/hub/lb-apply.js +18 -29
  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 +106 -87
  27. package/docs/reference/data-binding.md +7 -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 +106 -87
  34. package/skills/loadbare-app/references/data-binding.md +7 -7
  35. package/skills/loadbare-app/references/widgets.md +2 -2
@@ -0,0 +1,140 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * The hub's lifecycle — see docs/reference/custom-elements.md, When its code
4
+ * runs.
5
+ *
6
+ * A custom element hears what the hub did to it, in the hub's terms, rather
7
+ * than the browser's insertions. The hub knows every moment it places an
8
+ * element and every moment it removes one, so it says so: `lbAttached` once
9
+ * the element is placed and filled, `lbDetached` once it is gone. A move, or
10
+ * `lb-show` parking an element and bringing it back, is neither, and the
11
+ * element hears nothing.
12
+ *
13
+ * Placing is noted as it happens and told at the end of the landing that
14
+ * follows, so an element is filled before it hears it, and an outer element
15
+ * hears it before the ones inside it.
16
+ *
17
+ * Every custom element the hub places has upgraded before it is filled: a
18
+ * live row and a group are imported into the document, and an absent branch
19
+ * is imported again as it arrives — see upgradeAbsent().
20
+ */
21
+ import { ATTR_GROUP_LEAVING, ATTR_ROW_LEAVING, ATTR_SHOW, } from "../core/lb-constants.js";
22
+ /** The template holding an absent branch — see ATTR_SHOW. */
23
+ export const PARKED = `template[${ATTR_SHOW}]`;
24
+ /** A row or a group on its way out. */
25
+ export const LEAVING = `[${ATTR_ROW_LEAVING}], [${ATTR_GROUP_LEAVING}]`;
26
+ /** An Element's nodeType. Tier 3 has no `Node` global to read it from. */
27
+ const ELEMENT_NODE = 1;
28
+ /** Every custom element the hub has attached and not yet detached. */
29
+ const attached = new WeakSet();
30
+ /** What the hub has placed since the last landing ended. */
31
+ let placedSince = [];
32
+ /** A custom element whose class has run on it. */
33
+ function upgraded(el) {
34
+ if (!el.localName.includes("-"))
35
+ return false;
36
+ const registry = globalThis
37
+ .customElements;
38
+ const defined = registry?.get(el.localName);
39
+ return defined !== undefined && el instanceof defined;
40
+ }
41
+ /**
42
+ * Every upgraded custom element at and under `root`, in tree order, outer
43
+ * before inner. An absent branch is searched where its template stands.
44
+ */
45
+ function customIn(root, found) {
46
+ if (root.nodeType === ELEMENT_NODE && upgraded(root)) {
47
+ found.push(root);
48
+ }
49
+ for (const child of root.children) {
50
+ customIn(child.matches(PARKED) ? child.content : child, found);
51
+ }
52
+ }
53
+ /** Call one of the hub's methods, if the element has it. */
54
+ function tell(el, method, ...args) {
55
+ const fn = el[method];
56
+ try {
57
+ fn?.apply(el, args);
58
+ }
59
+ catch (err) {
60
+ console.error(`lb-hub: <${el.localName}> ${method} threw`, err);
61
+ }
62
+ }
63
+ /**
64
+ * Make every absent branch under `root` from elements that have upgraded.
65
+ *
66
+ * The builder ships each `lb-show` element inside its template, and a
67
+ * template's content belongs to a document with no definitions, so nothing
68
+ * shipped there has upgraded. Importing the branch into `doc` upgrades it as
69
+ * it is made, and it goes back into its template already the class it will
70
+ * be when it shows: the hub sets a control's `value` while it is away, and
71
+ * calls its methods, as on any other.
72
+ *
73
+ * Called on what the hub places before anything lands on it, while every
74
+ * branch is as the builder shipped it.
75
+ */
76
+ export function upgradeAbsent(root, doc) {
77
+ for (const parked of root.querySelectorAll(PARKED)) {
78
+ const content = parked.content;
79
+ const branch = content.firstElementChild;
80
+ if (!branch)
81
+ continue;
82
+ const made = doc.importNode(branch, true);
83
+ branch.replaceWith(made);
84
+ upgradeAbsent(made, doc);
85
+ }
86
+ }
87
+ /** Note that the hub has placed `root`, to attach at the end of landing. */
88
+ export function placed(root) {
89
+ placedSince.push(root);
90
+ }
91
+ /**
92
+ * Attach every custom element in what the hub has placed since the last
93
+ * landing ended. A root inside another is reached from the outer one, so an
94
+ * outer element is attached before the ones inside it. An element on its way
95
+ * out is passed over.
96
+ */
97
+ export function attachPlaced() {
98
+ const roots = placedSince;
99
+ placedSince = [];
100
+ const outer = roots.filter((root) => !roots.some((other) => other !== root && other.contains(root)));
101
+ for (const root of outer) {
102
+ if (root.closest(LEAVING))
103
+ continue;
104
+ const found = [];
105
+ customIn(root, found);
106
+ for (const el of found) {
107
+ if (attached.has(el) || el.closest(LEAVING))
108
+ continue;
109
+ attached.add(el);
110
+ tell(el, "lbAttached");
111
+ }
112
+ }
113
+ }
114
+ /**
115
+ * Detach every attached custom element in nodes the hub has removed, inner
116
+ * before outer. An element the hub never attached, such as the copy a moved
117
+ * row leaves behind, hears nothing.
118
+ */
119
+ export function detachRemoved(nodes) {
120
+ const found = [];
121
+ for (const node of nodes) {
122
+ if (node.nodeType === ELEMENT_NODE)
123
+ customIn(node, found);
124
+ }
125
+ for (const el of found.reverse()) {
126
+ if (attached.delete(el))
127
+ tell(el, "lbDetached");
128
+ }
129
+ }
130
+ /**
131
+ * Tell the attached custom elements from `origin` outward how its request
132
+ * turned out, innermost first.
133
+ */
134
+ export function requestDone(origin, detail) {
135
+ for (let el = origin; el; el = el.parentElement) {
136
+ if (attached.has(el))
137
+ tell(el, "lbRequestDone", detail, origin);
138
+ }
139
+ }
140
+ //# sourceMappingURL=lb-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lb-lifecycle.js","sourceRoot":"","sources":["../../hub/lb-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n\n/**\n * The hub's lifecycle — see docs/reference/custom-elements.md, When its code\n * runs.\n *\n * A custom element hears what the hub did to it, in the hub's terms, rather\n * than the browser's insertions. The hub knows every moment it places an\n * element and every moment it removes one, so it says so: `lbAttached` once\n * the element is placed and filled, `lbDetached` once it is gone. A move, or\n * `lb-show` parking an element and bringing it back, is neither, and the\n * element hears nothing.\n *\n * Placing is noted as it happens and told at the end of the landing that\n * follows, so an element is filled before it hears it, and an outer element\n * hears it before the ones inside it.\n *\n * Every custom element the hub places has upgraded before it is filled: a\n * live row and a group are imported into the document, and an absent branch\n * is imported again as it arrives — see upgradeAbsent().\n */\n\nimport {\n ATTR_GROUP_LEAVING,\n ATTR_ROW_LEAVING,\n ATTR_SHOW,\n} from \"../core/lb-constants.js\";\nimport type { HubLifecycle, HubRequestDone } from \"../core/lb-types.js\";\n\n/** The template holding an absent branch — see ATTR_SHOW. */\nexport const PARKED = `template[${ATTR_SHOW}]`;\n\n/** A row or a group on its way out. */\nexport const LEAVING = `[${ATTR_ROW_LEAVING}], [${ATTR_GROUP_LEAVING}]`;\n\n/** An Element's nodeType. Tier 3 has no `Node` global to read it from. */\nconst ELEMENT_NODE = 1;\n\n/** Every custom element the hub has attached and not yet detached. */\nconst attached = new WeakSet<Element>();\n\n/** What the hub has placed since the last landing ended. */\nlet placedSince: Element[] = [];\n\n/** A custom element whose class has run on it. */\nfunction upgraded(el: Element): boolean {\n if (!el.localName.includes(\"-\")) return false;\n const registry = (globalThis as { customElements?: CustomElementRegistry })\n .customElements;\n const defined = registry?.get(el.localName);\n return defined !== undefined && el instanceof defined;\n}\n\n/**\n * Every upgraded custom element at and under `root`, in tree order, outer\n * before inner. An absent branch is searched where its template stands.\n */\nfunction customIn(root: Element | DocumentFragment, found: Element[]): void {\n if (root.nodeType === ELEMENT_NODE && upgraded(root as Element)) {\n found.push(root as Element);\n }\n for (const child of root.children) {\n customIn(\n child.matches(PARKED) ? (child as HTMLTemplateElement).content : child,\n found,\n );\n }\n}\n\n/** Call one of the hub's methods, if the element has it. */\nfunction tell<K extends keyof HubLifecycle>(\n el: Element,\n method: K,\n ...args: Parameters<NonNullable<HubLifecycle[K]>>\n): void {\n const fn = (el as Element & HubLifecycle)[method] as\n ((...a: typeof args) => void) | undefined;\n try {\n fn?.apply(el, args);\n } catch (err) {\n console.error(`lb-hub: <${el.localName}> ${method} threw`, err);\n }\n}\n\n/**\n * Make every absent branch under `root` from elements that have upgraded.\n *\n * The builder ships each `lb-show` element inside its template, and a\n * template's content belongs to a document with no definitions, so nothing\n * shipped there has upgraded. Importing the branch into `doc` upgrades it as\n * it is made, and it goes back into its template already the class it will\n * be when it shows: the hub sets a control's `value` while it is away, and\n * calls its methods, as on any other.\n *\n * Called on what the hub places before anything lands on it, while every\n * branch is as the builder shipped it.\n */\nexport function upgradeAbsent(\n root: Element | DocumentFragment,\n doc: Document,\n): void {\n for (const parked of root.querySelectorAll(PARKED)) {\n const content = (parked as HTMLTemplateElement).content;\n const branch = content.firstElementChild;\n if (!branch) continue;\n const made = doc.importNode(branch, true);\n branch.replaceWith(made);\n upgradeAbsent(made, doc);\n }\n}\n\n/** Note that the hub has placed `root`, to attach at the end of landing. */\nexport function placed(root: Element): void {\n placedSince.push(root);\n}\n\n/**\n * Attach every custom element in what the hub has placed since the last\n * landing ended. A root inside another is reached from the outer one, so an\n * outer element is attached before the ones inside it. An element on its way\n * out is passed over.\n */\nexport function attachPlaced(): void {\n const roots = placedSince;\n placedSince = [];\n const outer = roots.filter(\n (root) => !roots.some((other) => other !== root && other.contains(root)),\n );\n for (const root of outer) {\n if (root.closest(LEAVING)) continue;\n const found: Element[] = [];\n customIn(root, found);\n for (const el of found) {\n if (attached.has(el) || el.closest(LEAVING)) continue;\n attached.add(el);\n tell(el, \"lbAttached\");\n }\n }\n}\n\n/**\n * Detach every attached custom element in nodes the hub has removed, inner\n * before outer. An element the hub never attached, such as the copy a moved\n * row leaves behind, hears nothing.\n */\nexport function detachRemoved(nodes: Iterable<ChildNode>): void {\n const found: Element[] = [];\n for (const node of nodes) {\n if (node.nodeType === ELEMENT_NODE) customIn(node as Element, found);\n }\n for (const el of found.reverse()) {\n if (attached.delete(el)) tell(el, \"lbDetached\");\n }\n}\n\n/**\n * Tell the attached custom elements from `origin` outward how its request\n * turned out, innermost first.\n */\nexport function requestDone(origin: Element, detail: HubRequestDone): void {\n for (let el: Element | null = origin; el; el = el.parentElement) {\n if (attached.has(el)) tell(el, \"lbRequestDone\", detail, origin);\n }\n}\n"]}
@@ -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