@loadbare/app 0.12.0 → 0.14.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 (56) hide show
  1. package/dist/build/assemble.d.ts.map +1 -1
  2. package/dist/build/assemble.js +87 -1
  3. package/dist/build/assemble.js.map +1 -1
  4. package/dist/build/cli.js +2 -1
  5. package/dist/build/cli.js.map +1 -1
  6. package/dist/build/elements.d.ts +9 -1
  7. package/dist/build/elements.d.ts.map +1 -1
  8. package/dist/build/elements.js +50 -0
  9. package/dist/build/elements.js.map +1 -1
  10. package/dist/build/origins.d.ts +2 -0
  11. package/dist/build/origins.d.ts.map +1 -1
  12. package/dist/build/origins.js +1 -1
  13. package/dist/build/origins.js.map +1 -1
  14. package/dist/core/lb-constants.d.ts +34 -1
  15. package/dist/core/lb-constants.d.ts.map +1 -1
  16. package/dist/core/lb-constants.js +105 -0
  17. package/dist/core/lb-constants.js.map +1 -1
  18. package/dist/core/lb-types.d.ts +74 -18
  19. package/dist/core/lb-types.d.ts.map +1 -1
  20. package/dist/core/lb-types.js +74 -11
  21. package/dist/core/lb-types.js.map +1 -1
  22. package/dist/hub/lb-apply.d.ts +57 -18
  23. package/dist/hub/lb-apply.d.ts.map +1 -1
  24. package/dist/hub/lb-apply.js +661 -63
  25. package/dist/hub/lb-apply.js.map +1 -1
  26. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  27. package/dist/hub/lb-hub.browser.js +400 -33
  28. package/dist/hub/lb-hub.browser.js.map +1 -1
  29. package/dist/server/lb-express.d.ts +19 -7
  30. package/dist/server/lb-express.d.ts.map +1 -1
  31. package/dist/server/lb-express.js +62 -26
  32. package/dist/server/lb-express.js.map +1 -1
  33. package/dist/server/lb-server.d.ts +80 -11
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/server/lb-server.js +185 -33
  36. package/dist/server/lb-server.js.map +1 -1
  37. package/docs/TECHREF-1.0.md +584 -111
  38. package/docs/comparison.md +17 -13
  39. package/docs/possible-ideas.md +308 -0
  40. package/docs/reference/chrome.md +7 -1
  41. package/docs/reference/custom-elements.md +134 -39
  42. package/docs/reference/data-binding.md +405 -17
  43. package/docs/reference/page-files.md +101 -19
  44. package/docs/reference/server.md +24 -0
  45. package/docs/reference/widgets.md +118 -25
  46. package/docs/roadmap.md +17 -6
  47. package/docs/testing.md +21 -10
  48. package/package.json +2 -3
  49. package/skills/loadbare-app/SKILL.md +92 -30
  50. package/skills/loadbare-app/references/TECHREF-1.0.md +584 -111
  51. package/skills/loadbare-app/references/chrome.md +7 -1
  52. package/skills/loadbare-app/references/custom-elements.md +134 -39
  53. package/skills/loadbare-app/references/data-binding.md +405 -17
  54. package/skills/loadbare-app/references/page-files.md +101 -19
  55. package/skills/loadbare-app/references/server.md +24 -0
  56. package/skills/loadbare-app/references/widgets.md +118 -25
@@ -320,9 +320,9 @@ without `.browser` is not bundled, and the build error names it.
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
322
  - A custom element carrying `lb-query` and a row template may implement
323
- `lbPlaceRow(el, row, template)` to decide where a row goes, and
324
- `lbRowsLanded()` to build scaffolding such as group headings. The hub does
325
- the cloning, matching, removal and counting.
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.
326
326
  - Method names beginning with `lb` on a custom element are reserved.
327
327
 
328
328
  Widget libraries are npm packages listed in `imports.ts`. The builder scans
@@ -396,7 +396,7 @@ query, the kind and key the server declared, and one of these:
396
396
  A column holds one value. A set of values is a query of its own. A row
397
397
  never contains rows. Master-detail is a row and rows answered under two
398
398
  names. Many masters with their details is one `rows` query of joined rows,
399
- grouped for display by a custom element's `lbPlaceRow`.
399
+ shown in groups by a group template.
400
400
 
401
401
  The application designs no endpoints, routes or serialization format. The
402
402
  names in the markup are the keys in the page's `.queries.ts` and
@@ -467,11 +467,13 @@ The hub stamps the element that committed:
467
467
  |----------------------|------------------------------------------------------------|
468
468
  | `lb-request-pending` | The round trip is in flight; `aria-busy="true"` is set with it |
469
469
  | `lb-request-error` | The last round trip from this element failed |
470
+ | `lb-request-refused` | The handler refused it, carrying its message |
470
471
 
471
472
  A commit on an element carrying `lb-request-pending` is ignored. A round
472
473
  trip has a ten-second deadline, after which it is aborted and treated as a
473
474
  failure. `lb-request-error` records that a round trip failed, and carries
474
- no message.
475
+ no message. A handler's `refusal()` carries words for the user, shown on the
476
+ element and on each control it names.
475
477
 
476
478
  After a successful `lb-row-insert` gathered from a form, the hub resets the
477
479
  form. A failed insert resets nothing.
@@ -785,11 +787,12 @@ TECHREF-1.0 lists every name Loadbare/app owns in one cross-reference.
785
787
 
786
788
  | Owned | Count | Names |
787
789
  |-------------------------------------|-------|---------------------------------------------------------------|
788
- | `lb-*` attributes a developer writes | 7 | `lb-query`, `lb-column`, `lb-show`, `lb-request`, `lb-url-link`, `lb-url-push`, `lb-url-unknown` |
789
- | `lb-*` stamps | 8 | `lb-column-value`, `lb-key-value`, `lb-row-live`, `lb-query-row-count`, `lb-request-pending`, `lb-request-error`, `lb-page`, `lb-page-title` |
790
+ | `lb-*` attributes a developer writes | 13 | `lb-query`, `lb-column`, `lb-show`, `lb-group`, `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max`, `lb-request`, `lb-url-link`, `lb-url-push`, `lb-url-unknown` |
791
+ | `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` |
790
792
  | `lb-*` build-time attributes | 2 | `lb-exp-slot`, `lb-exp-template` |
791
793
  | Requests Loadbare provides | 3 | `lb-row-insert`, `lb-row-update`, `lb-row-delete` |
792
- | `lb*` methods on custom elements | 2 | `lbPlaceRow`, `lbRowsLanded` |
794
+ | `lb*` methods on custom elements | 1 | `lbRowsLanded` |
795
+ | Query parms Loadbare reads | 1 | `lb-order-<query>` |
793
796
  | Attribute namespace for expansion | 1 | `exp-*` |
794
797
  | Reserved tags | 1 | `<lb-hub>` |
795
798
  | Reserved events | 1 | `lb-request` |
@@ -821,7 +824,7 @@ cross-reference, so no count is given for them here.
821
824
  - A widget's markup is in the document and in view-source.
822
825
  - On navigation the hub sets `document.title` from the page's `<title>`. It
823
826
  does not move focus or announce the new page.
824
- - Checkboxes and radio buttons are not implemented for landing or gathering.
827
+ - Radio buttons are not implemented for landing or gathering.
825
828
 
826
829
  ### Elsewhere
827
830
 
@@ -916,9 +919,9 @@ Roadmap.
916
919
  | Nested data | A row never holds rows. Trees are sent as joined rows. |
917
920
  | Record URLs and view parameters | A path names a page. A record or a filter is a query parm, `/accounts?acct=23`. |
918
921
  | Query arguments from the browser | None. Query parms reach queries through `contextFor` and `ctx`. |
919
- | Checkboxes, radio buttons, file inputs | Not landed and not gathered. Open blocker. |
922
+ | Radio buttons, file inputs | Not landed and not gathered. Radio buttons are an open blocker. Checkboxes land and gather as `true` and `false`. |
920
923
  | Data types and formatting | No conversion. Queries format values. Open blocker. |
921
- | Error messages | `lb-request-error` carries no message. |
924
+ | Error messages | A handler's `refusal()` reaches the user; any other failure carries no message. |
922
925
  | Field validation | No mechanism. Per-keystroke validation is a roadmap question. |
923
926
  | Optimistic updates | None. |
924
927
  | Concurrent writers | No version or conflict check. Roadmap question. |
@@ -926,7 +929,7 @@ Roadmap.
926
929
  | Offline or local-first use | None. |
927
930
  | Server-side rendering for crawlers | None. Every path answers the same document with status 200. |
928
931
  | Subpath hosting | Not possible. The hub uses absolute paths. |
929
- | Chrome-level queries | A widget in the chrome bound to a query requires every page to declare it. Open blocker. |
932
+ | Chrome-level queries | A widget in the chrome bound to a query requires every page to declare it. On the roadmap. |
930
933
  | A widget receiving a whole row | Not available. Open blocker. |
931
934
  | A nested `rows` query per outer row | A nested query receives the same rows in every outer row. A new outer row's nested query stays empty until its name lands again. Open blocker. |
932
935
  | Configurable request deadline | Fixed at ten seconds. |
@@ -1181,7 +1184,8 @@ Each equivalent is approximate.
1181
1184
  | Expansion | Server-side include; partial; build-time component render (Astro) |
1182
1185
  | `exp-` parameter | A prop fixed at build time |
1183
1186
  | `lb-exp-slot`, `lb-exp-template` | Default slot and named slots; `ng-content` with `select` |
1184
- | `lbPlaceRow`, `lbRowsLanded` | Custom list rendering; a render prop |
1187
+ | `lbRowsLanded` | An effect after a list renders |
1188
+ | `order`, `lb-group` | `ORDER BY` with report-writer control breaks (`BREAK ON`, group bands) |
1185
1189
  | `ctx` / `HubContext` | `event.locals` (SvelteKit); loader `context` (React Router 7); request-scoped dependency injection |
1186
1190
  | `imports.ts` | Installing and registering a component library |
1187
1191
  | `pages.ts` | Generated route manifest |
@@ -0,0 +1,308 @@
1
+ # Possible Ideas
2
+
3
+ > **LLM-authored, not yet revised by a person.** Drafted by Claude on
4
+ > 2026-10-02 against `@loadbare/app` 0.12.0, from a session in ef. Nothing
5
+ > here is decided or planned.
6
+
7
+ ## Why these are being considered
8
+
9
+ A custom element is the platform's one way to attach code to markup, and
10
+ widget work keeps running into what that costs.
11
+
12
+ - No decomposition. Code that concerns one row has nowhere to live but a
13
+ wrapper around the whole table, so `lb-table` owns everything inside it.
14
+ That is how page-specific behavior ends up in a shared widget: the COA
15
+ page's ghost row per section grew because row-level code could attach
16
+ only to the table. It is the overfit the README's test guards against.
17
+ - The browser's lifecycle is not the hub's. A moved row is disconnected and
18
+ connected, the copy a move leaves behind is constructed, and an `lb-show`
19
+ toggle adopts twice, so `connectedCallback` must be written to run again.
20
+ - Clutter. The page's source is the widget's dialect of named templates
21
+ rather than HTML, and the shipped page keeps a wrapper tag around what it
22
+ expanded.
23
+ - Plumbing through the wrapper. The tag carrying the code is not the
24
+ control: `WrappedControl` forwards `value`, fires `change` again, and needs
25
+ `ElementInternals` to join a form. Focus is searched for inside the
26
+ wrapper, and `autofocus` on a wrapped `<input>` is lost.
27
+ - Content models reach forms too. A `<form>` cannot wrap a `<tr>`, so the
28
+ ghost row joins its controls to a hidden form outside the table by the
29
+ `form` attribute. A form cannot hold a form, so `lb-insert-dialog` is
30
+ written once outside every row instead of beside the control it serves.
31
+ - Ids repeat in a row template. `lb-confirm` finds its dialog by position,
32
+ because `commandfor`, HTML's own way to open one with no script, works
33
+ only by id.
34
+ - Upgrade timing is the hub's problem. An element shipped inside an
35
+ `lb-show` template, or whose definition loads late, has not upgraded, so
36
+ the hub keeps `waiting()`, lands kept answers again once it upgrades, and
37
+ stamps `lb-column-value` for a control to take up on first connect.
38
+ - A few lines cost a wrapper. `lb-options` has a script mostly to copy
39
+ `lb-group-value` into an `<optgroup>`'s `label` and each key into an
40
+ option's `value`, and for that the whole `<select>` sits inside a custom
41
+ element.
42
+ - Parameters cannot carry structure. Expansion parameters are words, so
43
+ when groups became a template, `lb-picker` lost `exp-group` and a grouped
44
+ picker went back to a hand-written `lb-options`.
45
+ - CSS and accessibility see an element HTML does not define. A wrapper tag
46
+ has no role, shifts child and `:nth-child` selectors, and has no place in
47
+ a table. The usual remedy, `display: contents`, has a history of
48
+ accessibility bugs.
49
+ - One element, one class. A ghost row that should also save as it is typed
50
+ in needs a widget combining the two.
51
+
52
+ ## Code on elements the parser constrains
53
+
54
+ Loadbare's data vocabulary is attributes, and an attribute goes on any
55
+ element: `<tr lb-query>`, `<option lb-column>`, `<tbody lb-show>`. Behavior
56
+ is different. The platform attaches code to markup through a custom element
57
+ tag, and a tag must survive the parser. In a table, a `<select>`, or a `<p>`
58
+ it does not: an unexpected tag is dropped, moved out of the table, or closes
59
+ the paragraph.
60
+
61
+ So code that concerns one row lives on a wrapper around the whole table.
62
+ `lb-table` owns the `<table>`, and the page writes its contents as named
63
+ templates (`head`, `foot`, `ghost`) that the builder joins back into a
64
+ table. Each new need is another named destination, and the page's source
65
+ reads as `lb-table`'s shape rather than a table's. The shipped HTML is
66
+ honest; the source is a dialect.
67
+
68
+ Two ways to put code on the element itself follow, and only the second
69
+ survives.
70
+
71
+ ### Customized built-ins
72
+
73
+ ```html
74
+ <tr is="lb-ghost-row">…</tr>
75
+ ```
76
+
77
+ The platform's own answer: a class extending `HTMLTableRowElement`, which
78
+ the parser treats as a `<tr>`. WebKit has declined to ship it, and every
79
+ browser on iOS and iPadOS is WebKit, so a polyfill, usually a
80
+ `MutationObserver`, would be the code path on every iPhone and iPad for
81
+ good. That rules it out.
82
+
83
+ ### Behavior by attribute
84
+
85
+ ```html
86
+ <tr lb-behavior="ghost-row">…</tr>
87
+ ```
88
+
89
+ Code bound to any element by an attribute, as Stimulus does with
90
+ `data-controller`. It fits the parser everywhere and needs no polyfill.
91
+
92
+ - What usually drives it is a `MutationObserver`, which is incidental: the
93
+ code runs because something noticed. The hub, though, already knows every
94
+ time it puts an element in the page: it shows a page, clones a row, builds
95
+ a group, and moves an element in and out of an `lb-show` template. A
96
+ behavior attached and detached at those moments would be explicit.
97
+ - Elements the hub does not add (a widget's own scaffolding, anything a
98
+ script creates) would get no behavior, which may be the right rule rather
99
+ than a limitation.
100
+ - No form association of its own, and mostly none needed: a behavior on a
101
+ native control leaves it a native control, which joins its form, takes
102
+ focus and honors `autofocus` as it always did. Only a control whose UI is
103
+ built from scratch needs `ElementInternals`, and stays an autonomous
104
+ custom element.
105
+ - The platform has discussed a registry for custom attributes, which would
106
+ be this idea built in. It has not shipped. Without it the browser has no
107
+ part in a behavior: everything below is the hub's.
108
+
109
+ #### A lifecycle in the hub's terms
110
+
111
+ A custom element's lifecycle is the browser's, and it speaks of insertion:
112
+ connected, disconnected, adopted. The hub's work rarely lines up with it. A
113
+ moved row is disconnected and connected, the copy a move leaves behind is
114
+ constructed, an `lb-show` toggle adopts twice, which is why a custom element
115
+ must write `connectedCallback` to run again.
116
+
117
+ A behavior's lifecycle would be calls the hub makes from its own code, at
118
+ moments it already knows, named for what the hub did:
119
+
120
+ | The hub | A behavior hears |
121
+ | -------------------------------------------------------------- | ----------------------------------------------------------- |
122
+ | Shows a page, places a cloned row, builds a group, first shows an element shipped absent | `attached()`, once, after the element is placed and filled |
123
+ | Lands new values on the element's row | `landed(row)` |
124
+ | Moves a row the order put elsewhere | `moved()`; the instance is kept |
125
+ | Leaves a copy of a moved row behind | Nothing; the copy gets no behavior |
126
+ | Turns `lb-show` off, then on | `hidden()`, then `shown()`; the instance is kept |
127
+ | Removes a row, once its animations end | `detached()`, once |
128
+ | Finishes a request issued from inside the element | `requestDone(detail)` |
129
+
130
+ - `attached` and `detached` pair exactly, so setup and teardown need no
131
+ guard against running twice.
132
+ - The calls are the code-side twin of the stamps a stylesheet already sees
133
+ (`lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-leaving`):
134
+ one vocabulary for CSS and code, in the hub's relational terms.
135
+ - The hub keeps one instance per element and behavior name. An element may
136
+ carry several, `lb-behavior="ghost-row autosave"`, which a tag cannot.
137
+ - A behavior is a class in a file the builder finds by name, as it finds
138
+ `<tag>.browser.ts`, and a name with no file is a build error.
139
+
140
+ What the hub would have to define and keep: whether `attached` comes before
141
+ or after the rows inside a host land, and the order of calls among nested
142
+ behaviors. A custom element gets its ordering from the platform; here it is
143
+ loadbare's to state.
144
+
145
+ A rule for choosing: a custom element makes a new kind of element (its own
146
+ markup, expansion, or a control built from scratch); a behavior adds code to
147
+ an element HTML already has.
148
+
149
+ ### Where it would land
150
+
151
+ Behavior by attribute lets a page write a real `<table lb-query>` with code
152
+ on the rows that need it, and leaves `lb-table` to supply only what is truly
153
+ the table's. It keeps the parser and the browser matrix out of it, at the
154
+ cost of a lifecycle the hub would define.
155
+
156
+ ## Expansion that replaces the tag
157
+
158
+ Today a custom element's tag does two jobs: at build time it is expanded,
159
+ and at run time it carries code. Because the code needs the tag, the tag
160
+ ships as a wrapper around what it expanded, and the wrapper is the source of
161
+ most widget friction: focus searched for inside it, values forwarded through
162
+ it, `autofocus` lost, and no place in a table.
163
+
164
+ With behaviors carrying the code, expansion is free to stop shipping the
165
+ tag.
166
+
167
+ ```html
168
+ <!-- page -->
169
+ <lb-field exp-label="Name" lb-column="name"></lb-field>
170
+
171
+ <!-- what ships -->
172
+ <!-- lb-field exp-label="Name" lb-column="name" -->
173
+ <label lb-behavior="lb-field">Name <input lb-column="name" /></label>
174
+ ```
175
+
176
+ - **The tag becomes a comment.** The shipped DOM keeps a trail back to the
177
+ source at no run-time cost. A comment is legal anywhere, tables included,
178
+ and is invisible to CSS, `:nth-child` and the hub's counting.
179
+ - **The builder puts the behavior on the root.** When
180
+ `<name>.behavior.ts` exists, the expanded root gets `lb-behavior="<name>"`;
181
+ the author never writes it. `[lb-behavior~="lb-field"]` then serves CSS
182
+ and `querySelector` as the tag selector `lb-field` does now.
183
+ - **Every widget inherits one base class.** A `Behavior` holds `this.el`,
184
+ has empty methods for every call in the hub's lifecycle, and types
185
+ `landed(row)` and `requestDone(detail)`. A widget overrides only what it
186
+ uses, as it extends `HTMLElement` or `WrappedControl` today.
187
+
188
+ The file convention decides which path an element takes:
189
+
190
+ | The element has | The builder | At run time |
191
+ | ------------------------------------------ | -------------------------------------------------- | ---------------------- |
192
+ | An element file, no script | Replaces the tag, leaves a comment | Plain HTML |
193
+ | An element file and `<name>.behavior.ts` | Replaces the tag, puts `lb-behavior` on the root | The hub's lifecycle |
194
+ | `<name>.browser.ts` | Keeps the tag, as today | The browser's lifecycle |
195
+
196
+ The third row is for controls built from scratch, the only elements that
197
+ still need `ElementInternals`.
198
+
199
+ ### A flag to keep the tag
200
+
201
+ Considered and set aside. It gives every widget two DOM shapes, so its CSS
202
+ and code must work in both. It cannot apply where replacing pays off most,
203
+ since a kept tag still has no place in a table. And what people would keep
204
+ the tag for, a name to find and style the widget by and a trail to its
205
+ source, the `lb-behavior` attribute and the comment already give.
206
+
207
+ ### To settle
208
+
209
+ 1. **One root.** A behavior attaches to one element, so an element file with
210
+ a behavior has exactly one top-level element, and anything more is a
211
+ build error.
212
+ 2. **Where the tag's attributes go.** The wrapper used to keep them; once
213
+ the tag is gone each needs a destination. The natural default is the
214
+ root, and `lb-field` breaks it: its root is the `<label>`, but
215
+ `lb-column` belongs on the `<input>` inside while `lb-show` belongs on the
216
+ label. Either the element file marks a target for data attributes, or
217
+ the page passes them as parameters (`exp-column`, written into the file as
218
+ `lb-column="{{column}}"`), which works today but reads less naturally
219
+ than `lb-column`. This wants worked examples before a rule.
220
+
221
+ ## The case for behaviors
222
+
223
+ Today's model is simple to state: a widget is a name, and a name is one or
224
+ both of a `.html` and a `.browser.ts`. One name is one element the
225
+ developer writes, so what a widget does is easy to predict. Most of what
226
+ that costs is paid inside Loadbare, in service of the simple model. The one
227
+ place it leaks to the developer is the lifecycle, where a widget author must
228
+ follow strict rules to get the behavior they expect.
229
+
230
+ Behaviors would simplify the hub, but they seem to spread code that a
231
+ custom element keeps in one file. The containment comes from the element
232
+ file, though, not from the tag.
233
+
234
+ ### What one file bundles today
235
+
236
+ `lb-table`'s script does three jobs, joined by where they sit rather than by
237
+ any data they share:
238
+
239
+ | Job | Reads and writes |
240
+ | -------------------------- | -------------------------------------------------- |
241
+ | Table scaffolding | Caption, head and foot destinations; expansion only |
242
+ | The ghost row | The ghost row's controls, its form, and focus |
243
+ | Scrolling to the saved row | The list's rows and their stamps |
244
+
245
+ It is one file, and three concerns in one class, none reusable without the
246
+ others. A `<ul>` cannot have a ghost row without becoming an `lb-table`.
247
+
248
+ ### Two units
249
+
250
+ - A widget is markup. Its element file decides what elements exist, at
251
+ build time, as now.
252
+ - A behavior is what one element does. Its code attaches to the element
253
+ that carries it, and runs on the hub's lifecycle.
254
+
255
+ An element file may write `lb-behavior` on its own inner elements:
256
+
257
+ ```html
258
+ <!-- lb-table.html -->
259
+ <table lb-behavior="scroll-to-saved">
260
+ <thead lb-exp-template="head"></thead>
261
+ <tbody lb-exp-slot></tbody>
262
+ <tbody lb-exp-template="ghost" lb-behavior="ghost-row"></tbody>
263
+ </table>
264
+ ```
265
+
266
+ `lb-table` stays the one place to read, and its element file says which code
267
+ runs on which element. The code is split into files by job, and the widget
268
+ assembles them. A page that wants a ghost row in a `<ul>` writes
269
+ `lb-behavior="ghost-row"` on it.
270
+
271
+ ### Where code goes
272
+
273
+ Code goes on the element it reads and writes.
274
+
275
+ - The ghost row's code touches the ghost row's controls and form, so it is a
276
+ behavior on the ghost row.
277
+ - Scrolling touches the list's rows, so it is a behavior on the list.
278
+ - Code that seems to need two elements is the sign to stop. Behaviors never
279
+ call each other. They speak only through what widgets use today:
280
+ attributes the hub stamps, and bubbling events such as `lb-request-done`.
281
+ The scrolling behavior knows nothing of the ghost row; it reads
282
+ `lb-row-requested`.
283
+
284
+ The rule also refuses the overfit that started this: a ghost row per
285
+ section would be a behavior on the table reaching into every section.
286
+
287
+ ### Where the leak moves
288
+
289
+ Today the leak is the browser's lifecycle: `connectedCallback` written to
290
+ run again, and a value stamp adopted on first connect. With behaviors it
291
+ becomes three rules Loadbare states:
292
+
293
+ 1. Only elements the hub places get behaviors.
294
+ 2. A behavior is attached and detached exactly once.
295
+ 3. Behaviors speak only through stamps and events.
296
+
297
+ They are fewer, and in the hub's terms rather than the browser's. The cost
298
+ moves to Loadbare's design: the ordering the platform gives custom elements
299
+ must be stated, such as whether a host's behavior attaches before or after
300
+ its rows land, and the order among nested behaviors.
301
+
302
+ ### Both, not one
303
+
304
+ Custom elements stay. A control built from scratch still needs
305
+ `ElementInternals`, and a tag with no code is expansion alone. Behaviors
306
+ cover what a tag does badly: code on an element HTML already has, inside
307
+ content models that forbid a tag. The developer's question becomes whether
308
+ they are making a new kind of element, or giving an existing element a job.
@@ -72,6 +72,10 @@ Put everything the user interacts with inside `<lb-hub>`. The hub normally
72
72
  sits directly inside `<body>`, with banner, nav, footer and `<main>` inside
73
73
  it, so Loadbare can act on all of them.
74
74
 
75
+ Write exactly one `<lb-hub>` and exactly one `<main>`, with the `<main>`
76
+ inside the hub and empty but for whitespace. The builder rejects a chrome
77
+ that does otherwise.
78
+
75
79
  The chrome's `<title>` is the document title until the first page shows.
76
80
  From then on the hub sets the document title to the page's label; see
77
81
  [The URL](#the-url).
@@ -165,7 +169,9 @@ pushes one instead when the element carrying `lb-request` also carries
165
169
 
166
170
  The hub answers the request itself, with no round trip, then reloads the
167
171
  page's queries at the new URL. The page stays in place, and rows that come
168
- back keep their place.
172
+ back keep their place. A request that changes only `lb-order-<query>`, the
173
+ user's [order](./data-binding.md#order) for a query the hub sorts itself,
174
+ re-sorts the rows already on the page and loads nothing.
169
175
 
170
176
  A control whose column the URL does not carry lands empty, so every control
171
177
  inside `lb-query="lb-url"` shows what the address bar says, after a reload