@loadbare/app 0.13.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 (51) hide show
  1. package/dist/build/assemble.d.ts.map +1 -1
  2. package/dist/build/assemble.js +31 -1
  3. package/dist/build/assemble.js.map +1 -1
  4. package/dist/build/elements.d.ts +12 -2
  5. package/dist/build/elements.d.ts.map +1 -1
  6. package/dist/build/elements.js +44 -10
  7. package/dist/build/elements.js.map +1 -1
  8. package/dist/core/lb-constants.d.ts +13 -2
  9. package/dist/core/lb-constants.d.ts.map +1 -1
  10. package/dist/core/lb-constants.js +50 -3
  11. package/dist/core/lb-constants.js.map +1 -1
  12. package/dist/core/lb-types.d.ts +52 -8
  13. package/dist/core/lb-types.d.ts.map +1 -1
  14. package/dist/core/lb-types.js.map +1 -1
  15. package/dist/hub/lb-apply.d.ts +22 -3
  16. package/dist/hub/lb-apply.d.ts.map +1 -1
  17. package/dist/hub/lb-apply.js +82 -37
  18. package/dist/hub/lb-apply.js.map +1 -1
  19. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  20. package/dist/hub/lb-hub.browser.js +382 -26
  21. package/dist/hub/lb-hub.browser.js.map +1 -1
  22. package/dist/hub/lb-lifecycle.d.ts +40 -0
  23. package/dist/hub/lb-lifecycle.d.ts.map +1 -0
  24. package/dist/hub/lb-lifecycle.js +140 -0
  25. package/dist/hub/lb-lifecycle.js.map +1 -0
  26. package/dist/server/lb-express.d.ts +13 -3
  27. package/dist/server/lb-express.d.ts.map +1 -1
  28. package/dist/server/lb-express.js +30 -12
  29. package/dist/server/lb-express.js.map +1 -1
  30. package/dist/server/lb-server.d.ts +31 -1
  31. package/dist/server/lb-server.d.ts.map +1 -1
  32. package/dist/server/lb-server.js +112 -2
  33. package/dist/server/lb-server.js.map +1 -1
  34. package/docs/TECHREF-1.0.md +343 -68
  35. package/docs/comparison.md +13 -9
  36. package/docs/possible-ideas.md +152 -26
  37. package/docs/reference/custom-elements.md +203 -88
  38. package/docs/reference/data-binding.md +310 -11
  39. package/docs/reference/page-files.md +76 -3
  40. package/docs/reference/server.md +20 -0
  41. package/docs/reference/widgets.md +35 -12
  42. package/docs/roadmap.md +9 -2
  43. package/docs/testing.md +2 -2
  44. package/package.json +1 -1
  45. package/skills/loadbare-app/SKILL.md +70 -23
  46. package/skills/loadbare-app/references/TECHREF-1.0.md +343 -68
  47. package/skills/loadbare-app/references/custom-elements.md +203 -88
  48. package/skills/loadbare-app/references/data-binding.md +310 -11
  49. package/skills/loadbare-app/references/page-files.md +76 -3
  50. package/skills/loadbare-app/references/server.md +20 -0
  51. package/skills/loadbare-app/references/widgets.md +35 -12
@@ -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
@@ -467,11 +468,13 @@ The hub stamps the element that committed:
467
468
  |----------------------|------------------------------------------------------------|
468
469
  | `lb-request-pending` | The round trip is in flight; `aria-busy="true"` is set with it |
469
470
  | `lb-request-error` | The last round trip from this element failed |
471
+ | `lb-request-refused` | The handler refused it, carrying its message |
470
472
 
471
473
  A commit on an element carrying `lb-request-pending` is ignored. A round
472
474
  trip has a ten-second deadline, after which it is aborted and treated as a
473
475
  failure. `lb-request-error` records that a round trip failed, and carries
474
- no message.
476
+ no message. A handler's `refusal()` carries words for the user, shown on the
477
+ element and on each control it names.
475
478
 
476
479
  After a successful `lb-row-insert` gathered from a form, the hub resets the
477
480
  form. A failed insert resets nothing.
@@ -789,7 +792,7 @@ TECHREF-1.0 lists every name Loadbare/app owns in one cross-reference.
789
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` |
790
793
  | `lb-*` build-time attributes | 2 | `lb-exp-slot`, `lb-exp-template` |
791
794
  | Requests Loadbare provides | 3 | `lb-row-insert`, `lb-row-update`, `lb-row-delete` |
792
- | `lb*` methods on custom elements | 1 | `lbRowsLanded` |
795
+ | `lb*` methods on custom elements | 4 | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
793
796
  | Query parms Loadbare reads | 1 | `lb-order-<query>` |
794
797
  | Attribute namespace for expansion | 1 | `exp-*` |
795
798
  | Reserved tags | 1 | `<lb-hub>` |
@@ -822,7 +825,7 @@ cross-reference, so no count is given for them here.
822
825
  - A widget's markup is in the document and in view-source.
823
826
  - On navigation the hub sets `document.title` from the page's `<title>`. It
824
827
  does not move focus or announce the new page.
825
- - Checkboxes and radio buttons are not implemented for landing or gathering.
828
+ - Radio buttons are not implemented for landing or gathering.
826
829
 
827
830
  ### Elsewhere
828
831
 
@@ -917,9 +920,9 @@ Roadmap.
917
920
  | Nested data | A row never holds rows. Trees are sent as joined rows. |
918
921
  | Record URLs and view parameters | A path names a page. A record or a filter is a query parm, `/accounts?acct=23`. |
919
922
  | Query arguments from the browser | None. Query parms reach queries through `contextFor` and `ctx`. |
920
- | Checkboxes, radio buttons, file inputs | Not landed and not gathered. Open blocker. |
923
+ | Radio buttons, file inputs | Not landed and not gathered. Radio buttons are an open blocker. Checkboxes land and gather as `true` and `false`. |
921
924
  | Data types and formatting | No conversion. Queries format values. Open blocker. |
922
- | Error messages | `lb-request-error` carries no message. |
925
+ | Error messages | A handler's `refusal()` reaches the user; any other failure carries no message. |
923
926
  | Field validation | No mechanism. Per-keystroke validation is a roadmap question. |
924
927
  | Optimistic updates | None. |
925
928
  | Concurrent writers | No version or conflict check. Roadmap question. |
@@ -1183,6 +1186,7 @@ Each equivalent is approximate.
1183
1186
  | `exp-` parameter | A prop fixed at build time |
1184
1187
  | `lb-exp-slot`, `lb-exp-template` | Default slot and named slots; `ng-content` with `select` |
1185
1188
  | `lbRowsLanded` | An effect after a list renders |
1189
+ | `lbAttached`, `lbDetached` | Mount and unmount; `onMounted` and `onUnmounted` (Vue); `onMount` and `onDestroy` (Svelte) |
1186
1190
  | `order`, `lb-group` | `ORDER BY` with report-writer control breaks (`BREAK ON`, group bands) |
1187
1191
  | `ctx` / `HubContext` | `event.locals` (SvelteKit); loader `context` (React Router 7); request-scoped dependency injection |
1188
1192
  | `imports.ts` | Installing and registering a component library |
@@ -4,6 +4,57 @@
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
+
13
+ ## Why these are being considered
14
+
15
+ A custom element is the platform's one way to attach code to markup, and
16
+ widget work keeps running into what that costs.
17
+
18
+ - No decomposition. Code that concerns one row has nowhere to live but a
19
+ wrapper around the whole table, so `lb-table` owns everything inside it.
20
+ That is how page-specific behavior ends up in a shared widget: the COA
21
+ page's ghost row per section grew because row-level code could attach
22
+ only to the table. It is the overfit the README's test guards against.
23
+ - The browser's lifecycle is not the hub's. A moved row is disconnected and
24
+ connected, the copy a move leaves behind is constructed, and an `lb-show`
25
+ toggle adopts twice, so `connectedCallback` must be written to run again.
26
+ - Clutter. The page's source is the widget's dialect of named templates
27
+ rather than HTML, and the shipped page keeps a wrapper tag around what it
28
+ expanded.
29
+ - Plumbing through the wrapper. The tag carrying the code is not the
30
+ control: `WrappedControl` forwards `value`, fires `change` again, and needs
31
+ `ElementInternals` to join a form. Focus is searched for inside the
32
+ wrapper, and `autofocus` on a wrapped `<input>` is lost.
33
+ - Content models reach forms too. A `<form>` cannot wrap a `<tr>`, so the
34
+ ghost row joins its controls to a hidden form outside the table by the
35
+ `form` attribute. A form cannot hold a form, so `lb-insert-dialog` is
36
+ written once outside every row instead of beside the control it serves.
37
+ - Ids repeat in a row template. `lb-confirm` finds its dialog by position,
38
+ because `commandfor`, HTML's own way to open one with no script, works
39
+ only by id.
40
+ - Upgrade timing is the hub's problem. An element shipped inside an
41
+ `lb-show` template, or whose definition loads late, has not upgraded, so
42
+ the hub keeps `waiting()`, lands kept answers again once it upgrades, and
43
+ stamps `lb-column-value` for a control to take up on first connect.
44
+ - A few lines cost a wrapper. `lb-options` has a script mostly to copy
45
+ `lb-group-value` into an `<optgroup>`'s `label` and each key into an
46
+ option's `value`, and for that the whole `<select>` sits inside a custom
47
+ element.
48
+ - Parameters cannot carry structure. Expansion parameters are words, so
49
+ when groups became a template, `lb-picker` lost `exp-group` and a grouped
50
+ picker went back to a hand-written `lb-options`.
51
+ - CSS and accessibility see an element HTML does not define. A wrapper tag
52
+ has no role, shifts child and `:nth-child` selectors, and has no place in
53
+ a table. The usual remedy, `display: contents`, has a history of
54
+ accessibility bugs.
55
+ - One element, one class. A ghost row that should also save as it is typed
56
+ in needs a widget combining the two.
57
+
7
58
  ## Code on elements the parser constrains
8
59
 
9
60
  Loadbare's data vocabulary is attributes, and an attribute goes on any
@@ -20,7 +71,8 @@ table. Each new need is another named destination, and the page's source
20
71
  reads as `lb-table`'s shape rather than a table's. The shipped HTML is
21
72
  honest; the source is a dialect.
22
73
 
23
- Two ways to put code on the element itself follow.
74
+ Two ways to put code on the element itself follow, and only the second
75
+ survives.
24
76
 
25
77
  ### Customized built-ins
26
78
 
@@ -28,25 +80,11 @@ Two ways to put code on the element itself follow.
28
80
  <tr is="lb-ghost-row">…</tr>
29
81
  ```
30
82
 
31
- The platform's own answer: a class extending `HTMLTableRowElement`,
32
- registered with `customElements.define(name, cls, { extends: "tr" })`. The
33
- parser sees a `<tr>`, so the element sits wherever a `<tr>` may, keeps every
34
- native behavior of one, and gets the custom element lifecycle.
35
-
36
- - Chrome and Firefox ship it. WebKit has declined to, on principle
37
- (subclassing a built-in breaks when its internals change), so Safari needs
38
- a polyfill for as long as the feature exists.
39
- - Every browser on iOS and iPadOS is WebKit, Chrome included. The polyfill
40
- is therefore not a Safari fallback but the code path on every iPhone and
41
- iPad, permanently. The usual one, `@ungap/custom-elements`, watches the
42
- document with a `MutationObserver`, so on those devices the lifecycle runs
43
- because something noticed.
44
- - `is` must be present when the element is created; setting it later does
45
- nothing. Cloning a template preserves it, so a row template carrying
46
- `is` yields upgraded rows.
47
- - A customized built-in cannot be form-associated (`ElementInternals` is for
48
- autonomous custom elements), which matters little: the built-in elements
49
- that controls are already take part in forms.
83
+ The platform's own answer: a class extending `HTMLTableRowElement`, which
84
+ the parser treats as a `<tr>`. WebKit has declined to ship it, and every
85
+ browser on iOS and iPadOS is WebKit, so a polyfill, usually a
86
+ `MutationObserver`, would be the code path on every iPhone and iPad for
87
+ good. That rules it out.
50
88
 
51
89
  ### Behavior by attribute
52
90
 
@@ -114,13 +152,12 @@ A rule for choosing: a custom element makes a new kind of element (its own
114
152
  markup, expansion, or a control built from scratch); a behavior adds code to
115
153
  an element HTML already has.
116
154
 
117
- ### Where each would land
155
+ ### Where it would land
118
156
 
119
- Either one lets a page write a real `<table lb-query>` with code on the rows
120
- that need it, and leaves `lb-table` to supply only what is truly the
121
- table's. Customized built-ins borrow the platform's lifecycle at the cost of
122
- a permanent polyfill; behavior by attribute keeps the parser and the browser
123
- matrix out of it, at the cost of a lifecycle the hub would define.
157
+ Behavior by attribute lets a page write a real `<table lb-query>` with code
158
+ on the rows that need it, and leaves `lb-table` to supply only what is truly
159
+ the table's. It keeps the parser and the browser matrix out of it, at the
160
+ cost of a lifecycle the hub would define.
124
161
 
125
162
  ## Expansion that replaces the tag
126
163
 
@@ -186,3 +223,92 @@ source, the `lb-behavior` attribute and the comment already give.
186
223
  the page passes them as parameters (`exp-column`, written into the file as
187
224
  `lb-column="{{column}}"`), which works today but reads less naturally
188
225
  than `lb-column`. This wants worked examples before a rule.
226
+
227
+ ## The case for behaviors
228
+
229
+ Today's model is simple to state: a widget is a name, and a name is one or
230
+ both of a `.html` and a `.browser.ts`. One name is one element the
231
+ developer writes, so what a widget does is easy to predict. Most of what
232
+ that costs is paid inside Loadbare, in service of the simple model. The one
233
+ place it leaks to the developer is the lifecycle, where a widget author must
234
+ follow strict rules to get the behavior they expect.
235
+
236
+ Behaviors would simplify the hub, but they seem to spread code that a
237
+ custom element keeps in one file. The containment comes from the element
238
+ file, though, not from the tag.
239
+
240
+ ### What one file bundles today
241
+
242
+ `lb-table`'s script does three jobs, joined by where they sit rather than by
243
+ any data they share:
244
+
245
+ | Job | Reads and writes |
246
+ | -------------------------- | -------------------------------------------------- |
247
+ | Table scaffolding | Caption, head and foot destinations; expansion only |
248
+ | The ghost row | The ghost row's controls, its form, and focus |
249
+ | Scrolling to the saved row | The list's rows and their stamps |
250
+
251
+ It is one file, and three concerns in one class, none reusable without the
252
+ others. A `<ul>` cannot have a ghost row without becoming an `lb-table`.
253
+
254
+ ### Two units
255
+
256
+ - A widget is markup. Its element file decides what elements exist, at
257
+ build time, as now.
258
+ - A behavior is what one element does. Its code attaches to the element
259
+ that carries it, and runs on the hub's lifecycle.
260
+
261
+ An element file may write `lb-behavior` on its own inner elements:
262
+
263
+ ```html
264
+ <!-- lb-table.html -->
265
+ <table lb-behavior="scroll-to-saved">
266
+ <thead lb-exp-template="head"></thead>
267
+ <tbody lb-exp-slot></tbody>
268
+ <tbody lb-exp-template="ghost" lb-behavior="ghost-row"></tbody>
269
+ </table>
270
+ ```
271
+
272
+ `lb-table` stays the one place to read, and its element file says which code
273
+ runs on which element. The code is split into files by job, and the widget
274
+ assembles them. A page that wants a ghost row in a `<ul>` writes
275
+ `lb-behavior="ghost-row"` on it.
276
+
277
+ ### Where code goes
278
+
279
+ Code goes on the element it reads and writes.
280
+
281
+ - The ghost row's code touches the ghost row's controls and form, so it is a
282
+ behavior on the ghost row.
283
+ - Scrolling touches the list's rows, so it is a behavior on the list.
284
+ - Code that seems to need two elements is the sign to stop. Behaviors never
285
+ call each other. They speak only through what widgets use today:
286
+ attributes the hub stamps, and bubbling events such as `lb-request-done`.
287
+ The scrolling behavior knows nothing of the ghost row; it reads
288
+ `lb-row-requested`.
289
+
290
+ The rule also refuses the overfit that started this: a ghost row per
291
+ section would be a behavior on the table reaching into every section.
292
+
293
+ ### Where the leak moves
294
+
295
+ Today the leak is the browser's lifecycle: `connectedCallback` written to
296
+ run again, and a value stamp adopted on first connect. With behaviors it
297
+ becomes three rules Loadbare states:
298
+
299
+ 1. Only elements the hub places get behaviors.
300
+ 2. A behavior is attached and detached exactly once.
301
+ 3. Behaviors speak only through stamps and events.
302
+
303
+ They are fewer, and in the hub's terms rather than the browser's. The cost
304
+ moves to Loadbare's design: the ordering the platform gives custom elements
305
+ must be stated, such as whether a host's behavior attaches before or after
306
+ its rows land, and the order among nested behaviors.
307
+
308
+ ### Both, not one
309
+
310
+ Custom elements stay. A control built from scratch still needs
311
+ `ElementInternals`, and a tag with no code is expansion alone. Behaviors
312
+ cover what a tag does badly: code on an element HTML already has, inside
313
+ content models that forbid a tag. The developer's question becomes whether
314
+ they are making a new kind of element, or giving an existing element a job.