@loadbare/app 0.9.0 → 0.11.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 (82) hide show
  1. package/README.md +3 -3
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +107 -90
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/expand.d.ts +6 -1
  6. package/dist/build/expand.d.ts.map +1 -1
  7. package/dist/build/expand.js +112 -26
  8. package/dist/build/expand.js.map +1 -1
  9. package/dist/build/locations.d.ts +2 -3
  10. package/dist/build/locations.d.ts.map +1 -1
  11. package/dist/build/locations.js +2 -3
  12. package/dist/build/locations.js.map +1 -1
  13. package/dist/build/pages.d.ts +3 -4
  14. package/dist/build/pages.d.ts.map +1 -1
  15. package/dist/build/pages.js +3 -4
  16. package/dist/build/pages.js.map +1 -1
  17. package/dist/core/lb-constants.d.ts +27 -24
  18. package/dist/core/lb-constants.d.ts.map +1 -1
  19. package/dist/core/lb-constants.js +103 -168
  20. package/dist/core/lb-constants.js.map +1 -1
  21. package/dist/core/lb-types.d.ts +64 -77
  22. package/dist/core/lb-types.d.ts.map +1 -1
  23. package/dist/core/lb-types.js +40 -7
  24. package/dist/core/lb-types.js.map +1 -1
  25. package/dist/hub/lb-apply.d.ts +47 -37
  26. package/dist/hub/lb-apply.d.ts.map +1 -1
  27. package/dist/hub/lb-apply.js +195 -199
  28. package/dist/hub/lb-apply.js.map +1 -1
  29. package/dist/hub/lb-hub.browser.d.ts +1 -1
  30. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  31. package/dist/hub/lb-hub.browser.js +410 -449
  32. package/dist/hub/lb-hub.browser.js.map +1 -1
  33. package/dist/server/lb-express.d.ts +5 -5
  34. package/dist/server/lb-express.d.ts.map +1 -1
  35. package/dist/server/lb-express.js +35 -66
  36. package/dist/server/lb-express.js.map +1 -1
  37. package/dist/server/lb-server.d.ts +77 -135
  38. package/dist/server/lb-server.d.ts.map +1 -1
  39. package/dist/server/lb-server.js +132 -79
  40. package/dist/server/lb-server.js.map +1 -1
  41. package/docs/TECHREF-1.0.md +908 -585
  42. package/docs/comparison.md +243 -185
  43. package/docs/prior-art.md +15 -14
  44. package/docs/reference/builder.md +9 -3
  45. package/docs/reference/chrome.md +107 -56
  46. package/docs/reference/custom-elements.md +291 -173
  47. package/docs/reference/data-binding.md +381 -374
  48. package/docs/reference/overview.md +12 -10
  49. package/docs/reference/page-files.md +164 -99
  50. package/docs/reference/server.md +2 -2
  51. package/docs/reference/widgets.md +104 -110
  52. package/docs/roadmap.md +32 -39
  53. package/docs/terms-of-art.md +57 -0
  54. package/docs/testing.md +97 -68
  55. package/docs/theory.md +92 -58
  56. package/docs/tutorials/010-pages-and-navigation.md +20 -12
  57. package/docs/tutorials/020-css.md +6 -3
  58. package/docs/tutorials/030-html-decomposition.md +9 -7
  59. package/docs/tutorials/040-displaying-data.md +30 -13
  60. package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
  61. package/docs/tutorials/060-custom-element-code.md +17 -16
  62. package/docs/tutorials/065-conditional-rendering.md +34 -23
  63. package/docs/tutorials/070-displaying-a-list.md +29 -21
  64. package/docs/tutorials/072-inserting-into-a-list.md +24 -16
  65. package/docs/tutorials/074-deleting-from-a-list.md +9 -7
  66. package/docs/tutorials/076-updating-a-list-item.md +11 -10
  67. package/docs/tutorials/080-widget-requests.md +71 -43
  68. package/docs/tutorials/090-using-widget-libraries.md +22 -22
  69. package/docs/what-does-loadbare-extend.md +124 -0
  70. package/package.json +1 -1
  71. package/skills/loadbare-app/SKILL.md +201 -123
  72. package/skills/loadbare-app/references/TECHREF-1.0.md +908 -585
  73. package/skills/loadbare-app/references/builder.md +9 -3
  74. package/skills/loadbare-app/references/chrome.md +107 -56
  75. package/skills/loadbare-app/references/custom-elements.md +291 -173
  76. package/skills/loadbare-app/references/data-binding.md +381 -374
  77. package/skills/loadbare-app/references/overview.md +12 -10
  78. package/skills/loadbare-app/references/page-files.md +164 -99
  79. package/skills/loadbare-app/references/server.md +2 -2
  80. package/skills/loadbare-app/references/widgets.md +104 -110
  81. package/docs/analysis-accidental-complexity.md +0 -149
  82. package/docs/analysis-closed-set.md +0 -210
@@ -19,7 +19,8 @@ Loadbare/app enforces no data types on values between the database and the
19
19
  browser. A uniform and useful approach is difficult to discern, and we do
20
20
  not want to pollute 1.0 with a potentially sub-optimal solution. Until then
21
21
  a value is rendered by browser coercion when it lands on an HTML text
22
- element, or by whatever the widget does when it lands on a widget.
22
+ element or a control, and by whatever the custom element does with
23
+ `lb-column-value` when it lands on one that is not a control.
23
24
 
24
25
  We will then see if a useful solution emerges that Loadbare/app should
25
26
  handle.
@@ -27,24 +28,21 @@ handle.
27
28
  ### Form controls
28
29
 
29
30
  - **Checkboxes and radio buttons are not implemented.** A value does not
30
- land on one and a form does not gather one. Landing needs a decision on
31
+ land on one and the hub does not gather one. Landing needs a decision on
31
32
  what counts as checked, which waits on [Data types](#data-types), and a
32
- radio group is several elements answering to one cell.
33
+ radio group is several elements answering to one column.
33
34
 
34
- ### Run-time state attributes
35
+ ### Run-time stamps
35
36
 
36
- The hub stamps `lb-row-count`, `lb-pending` and `lb-error` for a
37
- stylesheet to read. Their consumer is CSS rather than code.
37
+ The hub stamps `lb-query-row-count`, `lb-request-pending` and
38
+ `lb-request-error` for a stylesheet to read. Their consumer is CSS rather
39
+ than code.
38
40
 
39
- - **Decide `data-` against `lb-`.** These are the only names the hub writes
40
- outside its own namespace, which is either a deliberate signal that CSS
41
- owns them or an inconsistency.
42
- - **Say what `lb-row-count` holds.** It is stamped and never explained.
43
- - **Decide whether an unarrived value needs a signal.** The roadmap proposes
44
- deriving it from an absent `lb-value`. Recommend confirming that and
45
- adding nothing.
41
+ - **Decide whether an unarrived value needs a signal.** An element whose
42
+ column has not landed carries no `lb-column-value`. Recommend confirming
43
+ that as the signal and adding nothing.
46
44
 
47
- ### The widget protocol
45
+ ### Custom element hooks
48
46
 
49
47
  Loadbare/app owns every method name beginning with `lb` on a custom element.
50
48
  That decision is firm; what the set contains is not.
@@ -52,45 +50,43 @@ That decision is firm; what the set contains is not.
52
50
  - **Decide what the build does with an unknown `lb*` method.** An element
53
51
  carrying a method beginning with `lb` that Loadbare/app does not define may
54
52
  be an error, a warning, or ignored.
55
- - **Decide whether a widget may receive a whole row.** A cell lands one
56
- element at a time and offers no escape hatch. An optional `lbAcceptRow`
57
- would sit beside `lbPlaceRow` and `lbRowsLanded`, and this is the most
58
- likely first request from a widget author.
53
+ - **Decide whether a custom element may receive a whole row.** A column
54
+ lands one element at a time and offers no escape hatch. An optional
55
+ `lbAcceptRow` would sit beside `lbPlaceRow` and `lbRowsLanded`, and this is
56
+ the most likely first request from a custom element author.
59
57
 
60
- ### Lists
58
+ ### Nested queries
61
59
 
62
- Imagine we have an HTML `<select>` that displays a fixed list for all
63
- rows in a table, such as `customer_type` for a table of customers. When
64
- all customers can be any customer type, our system as written today
65
- is fine, because all HTML `<select>` elements get the same list.
60
+ Imagine an HTML `<select>` that shows a fixed set of choices on every row of
61
+ a table, such as `customer_type` for a table of customers. When every
62
+ customer may take every customer type, the system as written today is
63
+ fine, because every `<select>` receives the same rows.
66
64
 
67
- But it may be that the list of allowed values is dependent on other values
68
- in the row.
65
+ But the allowed values may depend on other values in the row.
69
66
 
70
-
71
- - **Decide how a new row fills a nested list.** A row added to the outer
72
- list starts with an empty nested list, which fills only when the nested
73
- list's name lands again. See [Master-detail](#master-detail) for what a
74
- nested list is for.
67
+ - **Decide how a new live row fills a nested query.** A row added to the
68
+ outer query starts with its nested query empty, and it fills only when the
69
+ nested query's name lands again. See [Master-detail](#master-detail) for
70
+ what a nested query is for.
75
71
 
76
72
  ```html
77
- <tbody lb-list="accounts">
78
- <template lb-key="id"><tr>
79
- <td lb-cell="name"></td>
80
- <td><select lb-list="statuses"><template lb-key="id"><option lb-cell="label"></option></template></select></td>
73
+ <tbody lb-query="accounts">
74
+ <template><tr>
75
+ <td lb-column="name"></td>
76
+ <td><select lb-query="statuses" lb-column="status"><template><option lb-column="label"></option></template></select></td>
81
77
  </tr></template>
82
78
  </tbody>
83
79
  ```
84
80
 
85
81
  ### The server API
86
82
 
87
- - **Give the chrome a way to state its own queries.** A widget in the chrome
88
- bound to a query forces every page to declare that query, since the page
89
- data run covers one page's set. `lb-navigation` shows the pattern from the
83
+ - **Give the chrome a way to state its own queries.** A custom element in
84
+ the chrome naming a query forces every page to declare that query, since
85
+ the page load covers one page's set. `lb-url` shows the pattern from the
90
86
  framework side, and an application has no equivalent. Recommend a
91
87
  chrome-level query set on `createHub`.
92
- - **State that only `createHub` implements `Hub`.** Otherwise a sixth
93
- operation breaks anyone who wrote the interface by hand.
88
+ - **State that only `createHub` implements `Hub`.** Otherwise a third call
89
+ breaks anyone who wrote the interface by hand.
94
90
  - **Decide the options-argument shape once.** Global hooks, transaction
95
91
  wrapping, error handling and a CSRF token all want the same trailing
96
92
  parameter on `createHub` and `hubRoutes`. Build none of them, but pick
@@ -110,9 +106,16 @@ in the row.
110
106
  definition would let the builder ship only what survives expansion.
111
107
  Recommend deferring the mechanism and reserving the configuration key, so
112
108
  that it lands as an opt-in rather than as a silent drop.
113
- - **Land the remaining validations.** One `lb-hub`, one empty `<main>`, and
114
- every `lb-` attribute known. Recommend landing these now, because refusing
115
- markup that used to build is the kind of change 1.0 gives up.
109
+ - **Land the remaining validations.** One `lb-hub` and one empty `<main>`.
110
+ Recommend landing these now, because refusing markup that used to build is
111
+ the kind of change 1.0 gives up.
112
+ - **Decide whether the build validates HTML.** A page can be well formed and
113
+ still break HTML's rules for what an element may hold, such as a `<div>`
114
+ inside a `<p>`. The parser rearranges it silently, the same way at build
115
+ time and in the browser, so nothing reports it. Checking those rules is a
116
+ validator's job, and html-validate is one the builder could run on the
117
+ source or on the expanded output. Deciding it after 1.0 is free if it
118
+ lands as an opt-in, and it may need a configuration key.
116
119
  - **Confirm the three output names.** `app.html`, `client.js` and `app.css`
117
120
  are about to be fixed in `staticRoutes` as well as in the builder.
118
121
 
@@ -162,12 +165,12 @@ The builder bundles `client.js` from a `client-entry.ts` it generates into
162
165
  `--out` and leaves there. Nothing reads it after the bundle, and an
163
166
  application neither writes nor imports it.
164
167
 
165
- Every `.css` file in every origin joins `app.css`. There is no widget, page
168
+ Every `.css` file in every origin joins `app.css`. There is no custom element, page
166
169
  or chrome stylesheet: the builder concatenates them all, with nothing added,
167
170
  removed or scoped, so what ships is what was authored. Within one origin
168
171
  they are ordered by filename, the full path breaking a tie, and a
169
172
  subdirectory never affects the order. Origins follow the cascade — this
170
- package's own widgets, then each package named in `imports.ts`, then `--src`
173
+ package's own custom elements, then each package named in `imports.ts`, then `--src`
171
174
  last — so an application's own stylesheet always lands after the ones it
172
175
  imported. A file named `00-global.css` sorts first by saying so.
173
176
 
@@ -210,71 +213,84 @@ the whole of `--src` is one flat namespace, as stated in
210
213
 
211
214
  Three kinds of file hold HTML.
212
215
 
213
- | File | Holds | Found in |
214
- | ------------------ | ------------------------------------ | ------------ |
215
- | `chrome.html` | The one HTML document | `--src` |
216
- | `<stub>.page.html` | One page's markup, as a fragment | `--src` |
217
- | `<tag-name>.html` | One widget definition, as a fragment | Every origin |
216
+ | File | Holds | Found in |
217
+ | ------------------ | ----------------------------------------- | ------------ |
218
+ | `chrome.html` | The one HTML document | `--src` |
219
+ | `<stub>.page.html` | One page's markup, as a fragment | `--src` |
220
+ | `<tag-name>.html` | One custom element's markup, as a fragment | Every origin |
218
221
 
219
222
  As stated in [Chrome](#chrome), `chrome.html` is a required singleton.
220
223
 
221
224
  As stated in [Pages](#pages), the `<stub>` of a `.page.html` is the path the
222
225
  page maps to. The builder puts all pages into `dist/app.html` as HTML `<template>` objects
223
- that carry `lb-page="<stub>"`.
226
+ that carry `lb-page="<stub>"`, and `lb-page-title` when the page file has a
227
+ `<title>`.
224
228
 
225
- A widget file is exactly `<kebab-case-name>.html`. A [widget](#widgets) is
226
- an HTML custom element, suitable for organizing large HTML trees, or adding
227
- custom behavior, or both. A widget definition is named for the tag it expands.
229
+ An element file is exactly `<kebab-case-name>.html`. A
230
+ [custom element](#custom-elements) is suitable for organizing large HTML
231
+ trees, or adding custom behavior, or both. An element file is named for the
232
+ tag it expands.
228
233
 
229
234
  Any other HTML file, one that is not `chrome.html`, or `<stub>.page.html` or
230
235
  `<kebab-case-name>.html` will either:
231
- - be an error if it looks like a widget with capitalization
236
+ - be an error if it looks like an element file with capitalization
232
237
  - be skipped
233
238
 
239
+
234
240
  ## HTML
235
241
 
236
242
  In a Loadbare/app application, HTML is static after the build, and once
237
243
  it is sent to the browser on initial page load, no HTML is ever sent
238
244
  again.
239
245
 
240
- ### Widget Expansion
246
+ The developer writes `lb-*` attributes in markup. Script never assigns
247
+ them. The hub and the builder write only their stamps. See
248
+ [The markup checks](#the-markup-checks).
249
+
250
+ ### Expansion
241
251
 
242
252
  The builder executes a process we call "expansion". When it finds
243
253
  a custom element in the HTML it is processing, such as `<my-element>`,
244
- it looks for the file `<my-element>.html`, and follows the expansion
245
- rules to place the contents of `<my-element>.html`.
254
+ it looks for the element file `my-element.html`, and follows the expansion
255
+ rules to place its contents.
246
256
 
247
- A custom element with no definition file is left alone. A custom element
248
- with neither a definition nor a `.browser.ts` script is a build error, as
249
- stated in [Widgets](#widgets).
257
+ A custom element with no element file is left alone. A custom element
258
+ with neither an element file nor a `.browser.ts` script is a build error, as
259
+ stated in [Custom elements](#custom-elements).
260
+
261
+ Each file is parsed on its own, and expansion joins them through the DOM,
262
+ which applies none of HTML's rules for what an element may hold. The browser
263
+ reads the shipped text by those rules, so a join it would rearrange, such as
264
+ a definition's `<dialog>` inside a page's `<p>`, is a build error naming the
265
+ file, the custom element and the path to what would move.
250
266
 
251
267
  #### Build time parameters
252
268
 
253
269
  A build time parameter is supplied as an attribute on a custom element,
254
270
  and is converted to a fixed value within the HTML by the builder.
255
271
 
256
- The attribute value carries an `exp-` prefix, as in `exp-label`, and
257
- the substitution locations use `{{label}}` (no exp-prefix) inside the
258
- widget's HTML definition file:
272
+ The attribute name carries an `exp-` prefix, as in `exp-label`, and
273
+ the substitution locations use `{{label}}` (no prefix) inside the
274
+ element file:
259
275
 
260
276
  ```html
261
- <!-- lb-input.html, the definition -->
277
+ <!-- note-field.html, the element file -->
262
278
  <label>{{label}} <input readonly="{{readonly}}" /></label>
263
279
  ```
264
280
 
265
281
  ```html
266
282
  <!-- a page -->
267
- <lb-input lb-cell="name" exp-label="Name"></lb-input>
283
+ <note-field exp-label="Name"></note-field>
268
284
  ```
269
285
 
270
286
  ```html
271
287
  <!-- dist/app.html -->
272
- <lb-input lb-cell="name" exp-label="Name"
288
+ <note-field exp-label="Name"
273
289
  ><label>Name <input /></label
274
- ></lb-input>
290
+ ></note-field>
275
291
  ```
276
292
 
277
- A definition states a default with `{{name|default}}`, as in
293
+ An element file states a default with `{{name|default}}`, as in
278
294
  `{{button-label|OK}}`. Whitespace around the name and around the default is
279
295
  discarded. The default is a literal.
280
296
 
@@ -297,48 +313,61 @@ A parameter value cannot become markup.
297
313
 
298
314
  #### Slots and templates
299
315
 
300
- A widget definition may contain any number of named templates and optionally
316
+ An element file may contain any number of named templates and optionally
301
317
  one slot.
302
318
 
303
- This simplified definition of `lb-table` names two templates, `head` and
319
+ | Attribute | The builder |
320
+ | ----------------- | ------------------------------------------------------- |
321
+ | `lb-exp-slot` | Puts in it the rest of the custom element's content |
322
+ | `lb-exp-template` | Puts in it the content of the template of the same name |
323
+ | `exp-<name>` | Replaces `{{name}}` in the element file with its value |
324
+
325
+ - `lb-exp-slot` goes on one element in an element file.
326
+ - `lb-exp-template` goes on an element in an element file, and on a
327
+ `<template>` in the custom element's content. Both name the same
328
+ template.
329
+ - `exp-<name>` goes on a custom element.
330
+
331
+ This simplified element file for `lb-table` names two templates, `head` and
304
332
  `foot`, and marks `<tbody>` as the slot. A page supplies whichever templates
305
333
  it wants, and everything else it writes goes to the slot.
306
334
 
307
335
  ```html
308
- <!-- lb-table.html, the definition -->
336
+ <!-- lb-table.html, the element file -->
309
337
  <table>
310
338
  <caption>
311
339
  {{caption}}
312
340
  </caption>
313
- <thead lb-template="head"></thead>
314
- <tbody lb-slot></tbody>
315
- <tfoot lb-template="foot"></tfoot>
341
+ <thead lb-exp-template="head"></thead>
342
+ <tbody lb-exp-slot></tbody>
343
+ <tfoot lb-exp-template="foot"></tfoot>
316
344
  </table>
317
345
  ```
318
346
 
319
- When we use `lb-table` in HTML, the example below supplies the header
320
- template and an unnamed template. The unnamed one names neither `head` nor
321
- `foot`, so it goes to the slot. This usage writes no footer.
347
+ The page below supplies the `head` template and a row template. The row
348
+ template names no destination, so it goes to the slot. This page writes no
349
+ footer.
322
350
 
323
351
  ```html
324
352
  <!-- a page -->
325
- <lb-table lb-list="staff" exp-caption="Everyone, by team">
326
- <template lb-template="head">
353
+ <lb-table lb-query="staff" exp-caption="Everyone, by team">
354
+ <template lb-exp-template="head">
327
355
  <tr><th>Name</th><th>Role</th></tr>
328
356
  </template>
329
- <template lb-key="id">
330
- <tr><td lb-cell="name"></td><td lb-cell="role"></td></tr>
357
+ <template>
358
+ <tr><td lb-column="name"></td><td lb-column="role"></td></tr>
331
359
  </template>
332
360
  </lb-table>
333
361
  ```
334
362
 
335
363
  When a named template is expanded, the `<template>` tag is discarded and its
336
- contents are placed as children of the tag that names it. The built document
337
- holds:
364
+ contents are placed as children of the element that names it. Both
365
+ `lb-exp-slot` and `lb-exp-template` are gone from what ships. The built
366
+ document holds:
338
367
 
339
368
  ```html
340
369
  <!-- dist/app.html -->
341
- <lb-table lb-list="staff" exp-caption="Everyone, by team"
370
+ <lb-table lb-query="staff" exp-caption="Everyone, by team"
342
371
  ><table>
343
372
  <caption>
344
373
  Everyone, by team
@@ -350,10 +379,10 @@ holds:
350
379
  </tr>
351
380
  </thead>
352
381
  <tbody>
353
- <template lb-key="id">
382
+ <template>
354
383
  <tr>
355
- <td lb-cell="name"></td>
356
- <td lb-cell="role"></td>
384
+ <td lb-column="name"></td>
385
+ <td lb-column="role"></td>
357
386
  </tr>
358
387
  </template>
359
388
  </tbody>
@@ -363,75 +392,156 @@ holds:
363
392
 
364
393
  These are build errors:
365
394
 
366
- - A definition with two templates of one name
367
- - A definition with more than one `lb-slot`
368
- - A page naming a template the definition does not have
395
+ - An element file with two destinations of one name
396
+ - An element file with more than one `lb-exp-slot`
397
+ - A page naming a template the element file does not have
369
398
  - A page giving two templates for one name
370
- - Content written in a widget whose definition has no slot
399
+ - Content written in a custom element whose element file has no slot
371
400
 
372
- ### Binding
401
+ ### Names
373
402
 
374
- Binding attributes determine how the hub updates the DOM, either by
375
- directly updating a plain HTML element or instructing
376
- custom widgets to update themselves.
403
+ Loadbare owns the `lb-` prefix in several namespaces: attribute names, the
404
+ values of its attributes, query names, the column names of its own queries,
405
+ request names, custom element names and custom event names. It also owns
406
+ custom element method names beginning with `lb`.
377
407
 
408
+ The application should never name anything with the `lb-` prefix anywhere.
378
409
 
379
- | Attribute | Assigned By | Behavior |
380
- | ------------ | ----------- | ----------------------------------------------------------------------------------------------- |
381
- | lb-list | Developer | Scopes DOM children to a named set of rows; a nested lb-list or lb-row begins a new scope |
382
- | lb-row | Developer | Scopes DOM children to one named row; a nested lb-list or lb-row begins a new scope |
383
- | lb-cell | Developer | This DOM node displays this column of the row in scope |
384
- | lb-show | Developer | This DOM node is present when this column of the row in scope is neither null nor false |
385
- | lb-key | Developer | Names the column that identifies a row, on the row template inside an lb-list |
386
- | lb-key-value | Hub | Stamped on a live row: that row's value of `lb-key` |
387
- | lb-value | Hub | The value that landed on a cell; a widget updates itself from it and a stylesheet selects on it |
410
+ `createHub` refuses, at startup, a page that declares a query or a handler
411
+ whose name begins with `lb-`.
388
412
 
389
- #### How a value lands
413
+ ### Landing
390
414
 
391
- A cell lands one of three ways, and the element decides which. Every cell
392
- that receives a value also carries it as `lb-value`.
415
+ A query is a name for rows, of kind `row` or `rows`, with a key. The server
416
+ declares a page's queries in `<stub>.queries.ts`, and answers every request,
417
+ a page load included, with response items. A response item carries a query
418
+ name, its kind, its key's column name, and one of a row, all rows, or a
419
+ patch. See [The wire](#the-wire).
393
420
 
394
- | Element | Receives the value as |
395
- | -------------------------------------- | --------------------------------- |
396
- | A custom element, tag hyphenated | Its `lb-value` attribute |
397
- | `<select>`, `<textarea>`, or `<input>` | Its `value`, and `lb-value` |
398
- | Any other native element | Its `textContent`, and `lb-value` |
421
+ The hub lands a response item on every element whose `lb-query` names its
422
+ query.
399
423
 
400
- A widget owns whatever control it wraps, so it is handed the value and
401
- renders it itself. A form control shows its state as its `value`, so a
402
- `<select>` keeps its options. Any other native element has no behavior of its
403
- own, so its value is its text.
424
+ | Attribute | Names | The hub |
425
+ | ----------- | -------- | -------------------------------------------------- |
426
+ | `lb-query` | a query | Puts its rows in the element's content |
427
+ | `lb-column` | a column | Sets the element from that column |
428
+ | `lb-show` | a column | Removes it while the named column is null or false |
404
429
 
405
- A form gathers from the same controls it lands on, so a value read back on
406
- submit is the one that landed.
430
+ | Stamp | The hub stamps it with |
431
+ | -------------------- | -------------------------------- |
432
+ | `lb-column-value` | The value it set |
433
+ | `lb-key-value` | The row's key |
434
+ | `lb-row-live` | Nothing; it marks a live row |
435
+ | `lb-query-row-count` | The number of live rows it holds |
407
436
 
408
- An `<input>` of type `checkbox`, `radio`, or `file` receives nothing, not even
409
- `lb-value`, and the hub reports it to the console. A form gathering one skips it the same way.
437
+ The markup names a query and the columns it shows. The kind and the key are
438
+ the server's, and the markup states neither.
410
439
 
411
- A `<select>` with no option for the value shows no selection.
440
+ The row template is the first `<template>` among the descendants of an
441
+ element with `lb-query`, outside any nested `lb-query`. A live row is an
442
+ element the hub cloned from a row template for one row, and carries
443
+ `lb-row-live`.
444
+
445
+ | Kind | Row template | The hub |
446
+ | ------ | ------------ | ----------------------------------- |
447
+ | `row` | no | Lands the row on the element itself |
448
+ | `row` | yes | Lands one live row |
449
+ | `rows` | yes | Lands one live row per row |
450
+ | `rows` | no | Lands nothing |
451
+
452
+ A `rows` query with no row template names the query without showing it,
453
+ which is what an insert form naming the query it adds to is.
454
+
455
+ The nearest ancestor row of an element is the nearest of: the element
456
+ itself when it is a live row, an ancestor that is a live row, and an
457
+ ancestor that carries `lb-query` of kind `row`. The hub reads `lb-column`
458
+ and `lb-show` from the nearest ancestor row. A nested `lb-query` begins a
459
+ new query, and what is inside it is that query's.
412
460
 
413
- Only the hyphen and these three form controls are recognized. The hub holds
414
- no other knowledge of any particular element, and the hyphen also decides
415
- that the hub leaves a widget's own clicks alone — see [Requests](#requests).
461
+ An element's own `lb-query` names what the element holds, and its own
462
+ `lb-column` reads from the row around it. So a
463
+ `<select lb-query="statuses" lb-column="status">` in a live row of
464
+ `accounts` holds the statuses as its options and shows that account's
465
+ `status`. `lb-column` on an element with `lb-query` is undefined unless the
466
+ element is a control.
416
467
 
417
- Nothing an application writes ever sets `lb-value`. Loadbare writes it, and
418
- a widget or a stylesheet reads it.
468
+ Name one query on more than one element to show it in more than one place.
469
+ Every one of them receives it.
470
+
471
+ A query that arrives with nowhere to land is reported to the console.
472
+
473
+ #### How a column lands
474
+
475
+ | Element | The hub sets |
476
+ | ------------------------------------------------ | ------------------------------ |
477
+ | A control | Its `value` |
478
+ | A custom element that is not a control | `lb-column-value` only |
479
+ | Any other element | Its text content |
480
+
481
+ A control is an `<input>`, `<select>` or `<textarea>`, or a form-associated
482
+ custom element with a `value` property that fires `change`. A
483
+ form-associated custom element declares `static formAssociated = true`.
484
+
485
+ The hub stamps `lb-column-value` with the value on every element it sets.
486
+ It never replaces the content of a custom element, which the builder placed
487
+ there from its element file. A custom element that is not a control
488
+ renders `lb-column-value` itself.
489
+
490
+ The hub gathers from the same controls it lands on, so a value read back is
491
+ the one that landed.
492
+
493
+ An `<input>` of type `checkbox`, `radio`, or `file` receives nothing, not
494
+ even `lb-column-value`, and the hub reports it to the console. The hub does
495
+ not gather one either.
496
+
497
+ A `<select>` with no option for the value shows no selection.
419
498
 
420
- Loadbare also removes it, when a successful insert resets the cells it
421
- gathered — see [The round trip](#the-round-trip). A widget reads a removed
422
- `lb-value` as "nothing landed here" and returns its control to its default,
423
- the way a form control with no `value` attribute shows its default. A blank
424
- `lb-value` is a value like any other.
499
+ An element with `lb-column` and no ancestor row receives nothing. The hub
500
+ still gathers from it.
425
501
 
426
502
  The value arrives as the query produced it, with no conversion, so the
427
503
  browser decides what a non-string looks like.
428
504
 
429
- A cell holds one value. A set of values is a list of its own, never an array
430
- in a cell.
505
+ A column holds one value. A set of values is a query of its own, never an
506
+ array in a column.
431
507
 
432
- A widget sees `attributeChangedCallback` for an `lb-value` already present
433
- when it upgrades, so a widget cannot tell a first landing from a refresh,
434
- and does not need to.
508
+ A custom element sees `attributeChangedCallback` for an `lb-column-value`
509
+ already present when it upgrades, so it cannot tell a first landing from a
510
+ refresh, and does not need to.
511
+
512
+ #### Rows
513
+
514
+ The hub matches each row to a live row by its key. It stamps
515
+ `lb-key-value` on each live row, and on an element a `row` lands on itself.
516
+ It stamps `lb-row-live` on each live row and nowhere else, so a `row`
517
+ landed inside another query, such as a total in a table's foot, is not one
518
+ of that query's rows. A stylesheet or a custom element selects live rows
519
+ with `[lb-row-live]`, and never with `[lb-key-value]`.
520
+
521
+ A key is unique within a query. Two rows with one key in the same answer,
522
+ or two live rows showing one key, are reported on the console.
523
+
524
+ All rows decide membership and order: every row is placed in the order
525
+ given, and a live row whose key did not arrive is removed. A patch touches
526
+ only the rows it names and leaves every other live row's contents and
527
+ position alone.
528
+
529
+ A new live row lands immediately before the row template, so rows
530
+ accumulate in the order they arrive. A custom element carrying `lb-query`
531
+ and a row template may place them itself — see [Row hooks](#row-hooks).
532
+
533
+ After every landing the hub stamps the element with `lb-query-row-count`,
534
+ the number of live rows it holds. The server answers with rows and says
535
+ nothing about how many survived, so this is the one fact a page cannot be
536
+ sent. It makes an empty query a stylesheet rule:
537
+
538
+ ```css
539
+ [lb-query-row-count="0"] .roster-empty {
540
+ display: revert;
541
+ }
542
+ ```
543
+
544
+ A row missing its key column is reported to the console and not landed.
435
545
 
436
546
  #### Displaying by condition
437
547
 
@@ -440,166 +550,236 @@ and does not need to.
440
550
  so `"false"` is on, and a query spells a condition as a boolean or a null.
441
551
 
442
552
  ```html
443
- <template lb-key="id">
553
+ <template>
444
554
  <tr>
445
- <td lb-cell="name"></td>
446
- <td><button lb-action="lb-row-delete" lb-show="removable">Remove</button></td>
555
+ <td lb-column="name"></td>
556
+ <td><button lb-request="lb-row-delete" lb-show="removable">Remove</button></td>
447
557
  </tr>
448
558
  </template>
449
559
  ```
450
560
 
451
- It binds as `lb-cell` does, to the row on the nearest scoped ancestor, and on
452
- an element that is itself a scope the column belongs to the row around it. A row
453
- that does not carry the column leaves the element as it is.
454
-
455
- An element that is off is moved into a `<template lb-show="column">` standing
456
- where it stood, and moved back out when the column turns on. It is not
457
- rendered, focused, clicked, announced or gathered. It is moved and never
458
- rebuilt, so a widget keeps its instance. Landing reaches into that template,
459
- and nothing else does, so the element and every cell and scope inside it
460
- return current. A custom element sees `disconnectedCallback` then
461
- `adoptedCallback` going in, and `adoptedCallback` then `connectedCallback`
462
- coming out, and still receives `lb-value` while it is away.
561
+ It reads from the nearest ancestor row, as `lb-column` does, and on an
562
+ element that carries `lb-query` the column belongs to the row around it. A
563
+ row that does not carry the column leaves the element as it is.
564
+ `lb-show` with no ancestor row is undefined.
565
+
566
+ An element that is off is moved into a `<template lb-show="column">`
567
+ standing where it stood, and moved back out when the column turns on. It is
568
+ not rendered, focused, clicked, announced or gathered. It is moved and
569
+ never rebuilt, so a custom element keeps its instance and a control keeps
570
+ what was typed into it. Landing reaches into that template, so the element
571
+ and everything inside it return current. A custom element sees
572
+ `disconnectedCallback` then `adoptedCallback` going in, and
573
+ `adoptedCallback` then `connectedCallback` coming out, and still receives
574
+ `lb-column-value` while it is away.
463
575
 
464
576
  The builder ships every `lb-show` element already inside its template, so
465
577
  nothing conditional shows until its row lands. An absent element's template
466
578
  keeps its place among its siblings, and a position selector counts it.
467
579
 
468
- These are build errors: `lb-show` on a row template's root, with no row
469
- around it (outside every scope, on a scope with none around it, or in a list
470
- scope outside its row template), or on a `<template>`.
580
+ These are build errors: `lb-show` on a row template's root, `lb-show` with
581
+ no `lb-query` around it, and `lb-show` on a `<template>`.
471
582
 
472
583
  Hiding is presentation, and the server still refuses what a request may not
473
584
  do.
474
585
 
475
586
  #### Master-detail
476
587
 
477
- A master is a row and its detail is a list, as two names answered side by
478
- side. A cell never holds rows.
588
+ A master is a `row` query and its detail is a `rows` query, as two names
589
+ answered side by side. A column never holds rows.
479
590
 
480
591
  ```html
481
- <section lb-row="invoice">
482
- <h2 lb-cell="number"></h2>
483
- <span lb-cell="customer"></span>
592
+ <section lb-query="invoice">
593
+ <h2 lb-column="number"></h2>
594
+ <span lb-column="customer"></span>
484
595
  </section>
485
596
 
486
597
  <table>
487
- <tbody lb-list="invoiceLines">
488
- <template lb-key="id"><tr><td lb-cell="item"></td><td lb-cell="amount"></td></tr></template>
598
+ <tbody lb-query="invoiceLines">
599
+ <template><tr><td lb-column="item"></td><td lb-column="amount"></td></tr></template>
489
600
  </tbody>
490
601
  </table>
491
602
  ```
492
603
 
493
- Many masters, each with its own detail, is one list of joined rows: each
494
- detail row carries its master's columns. A widget's `lbPlaceRow` groups them
495
- for display, as `lb-table` builds sections and `lb-options` builds
496
- `<optgroup>`s.
604
+ Many masters, each with its own detail, is one `rows` query of joined rows:
605
+ each detail row carries its master's columns. A custom element's
606
+ `lbPlaceRow` groups them for display, as `lb-table` builds sections and
607
+ `lb-options` builds `<optgroup>`s.
497
608
 
498
- A list nested in another list's rows receives the same rows in every outer
499
- row. That serves a picker offering the same choices on every row, and is
500
- not a way to show a different detail per row.
609
+ A query nested in another query's row template receives the same rows in
610
+ every live row. That serves a picker offering the same choices on every
611
+ row, and is not a way to show a different detail per row.
501
612
 
502
613
  Which master a page shows is a query parm, which a query reads off `ctx`
503
614
  since it takes no argument from the browser — see [Query parms](#query-parms).
504
- When a write creates or removes the master, the server response changes that
505
- parm — see
506
- [When a server response changes the query parms](#when-a-server-response-changes-the-query-parms).
615
+ When a write creates or removes the master, the server response moves the
616
+ URL — see
617
+ [When a server response moves the URL](#when-a-server-response-moves-the-url).
507
618
 
508
619
  ### Requests
509
620
 
510
621
  ---- UNEDITED ----
511
622
 
512
- Any element can carry the `lb-action` attribute. The hub listens for
513
- events `click` and `submit`, and fires a server request when it catches
514
- one of those events.
515
-
516
- The hub does not act on `click` or `submit` on a custom widget, they
517
- must fire their own event. The assumption is a custom widget is present
518
- because custom behavior is desired, and we don't want the hub to conflict
519
- with that custom behavior. A widget is told apart by the hyphen in its tag
520
- name, the same test that decides how a value lands — see
521
- [How a value lands](#how-a-value-lands).
522
-
523
- | lb-action | Written on |
524
- | -------------- | ------------------------------------------------- |
525
- | lb-row-insert | a `<form>`, or a button in a `<tr>`, in a list |
526
- | lb-row-delete | anything inside a live row |
527
- | lb-row-update | a `<form>`, a button in a live row, or a widget cell |
528
- | anything else | must be a named routine in the page's server code |
529
-
530
- The wire format is not visible to the user, but uses the same lb-*
531
- attributes minus their prefix, so that it is intelligible when working on
532
- Loadbare/app itself.
533
-
534
- The hub scopes every request, whether a native element or a widget
535
- dispatched it. It reads the scope from the dispatching element before any
536
- ancestor sees the event, and never overwrites a field the request already
537
- carries.
538
-
539
- | `action` | Filled from scope | Required |
540
- | ---------------- | ------------------------------ | ---------------------- |
541
- | a declared name | `list` or `row`, `key`, `cell` | nothing |
542
- | `lb-row-insert` | `list` | `list`, and `values` |
543
- | `lb-row-delete` | `list`, `key` | both |
544
- | `lb-row-update` | `list`, `key` | both, and `values` |
545
-
546
- An element's own `lb-list` or `lb-row` names what it displays, never where
547
- its request goes. A request belongs to the scope around the element, the
548
- way a control belongs to the form around it. `list` or `row` comes from the
549
- nearest ancestor scope, `key` from the nearest live row inside that scope,
550
- and `cell` from the dispatching element's own `lb-cell`.
551
- A request missing a required field is not sent. The hub never fills
552
- `value`. An `lb-row-insert` or `lb-row-update` from an element carrying
553
- `lb-cell` is a record of one cell, the way a control has a value and a form
554
- has values: `values` holds that cell alone, taken from the `value` the widget
555
- sent or else read from the control it is or wraps, and `value` is not sent
556
- beside it. A widget that sends a `value` from an element carrying no
557
- `lb-cell` is refused, since nothing names the column. Any other
558
- `lb-row-insert` or `lb-row-update` gathers the row it belongs
559
- to: `values` holds every `lb-cell` with a control to read in the nearest
560
- `<form>`, `<tr>` or live row around the dispatching element, itself
561
- included, inside its scope. The cells are found the way a row lands, so a
562
- cell inside a scope nested in the row is that scope's and is not gathered,
563
- while an element carrying a scope and `lb-cell` both is the row's cell.
564
- This is a button's form owner: what else sits
565
- beside the element never changes what is sent. A `<tr>` counts because a
566
- form cannot go around a table row's controls, so a new row in a table is its
567
- own form; a live row counts because it is the row the key names. The scope
568
- itself is never the row, since its cells belong to other rows. A request
569
- from an element in no form or row is not sent, and the hub reports that its
570
- cells belong in a `<form>`. A request that already carries `values` keeps
571
- them, and one whose row has no cell to read is not sent. A click on a
572
- native element that carries either operation and is a cell or
573
- holds cells is not sent, since clicking into one of its controls
574
- would send the row: put the action on a form, on a button, or on a widget
575
- that decides when its cell has changed.
576
-
577
- A widget dispatches the action and, where it wraps a control, that control's
578
- value.
623
+ | Attribute | Names | The hub |
624
+ | ------------ | --------- | -------------------------------------------- |
625
+ | `lb-request` | a request | Issues that request when the element commits |
626
+
627
+ | Stamp | The hub stamps it |
628
+ | -------------------- | ------------------------------------------ |
629
+ | `lb-request-pending` | While the round trip is in flight |
630
+ | `lb-request-error` | When the round trip fails, until the next |
631
+
632
+ The request name is the attribute value of `lb-request`. It names either a
633
+ request Loadbare provides or a declared request: a name the page declares
634
+ under `handlers`, which runs application code.
635
+
636
+ Every request has one shape, on the event and on the wire:
579
637
 
580
638
  ```
581
- { action: "lb-row-insert", list: "rosterList", values: {...} }
582
- { action: "lb-row-delete", list: "rosterList", key: "42" }
583
- { action: "lb-row-update", list: "rosterList", key: "42", values: {...} }
584
- { action: "lb-row-update", list: "rosterList", key: "42", values: { name: "Ann" } }
585
- { action: "selectTab", row: "prefs", cell: "active_tab", value: "two" }
639
+ { name: "lb-row-insert", query: "roster", values: { name: "Ann" } }
640
+ { name: "lb-row-update", query: "roster", key: "42", values: { name: "Ann" } }
641
+ { name: "lb-row-delete", query: "roster", key: "42" }
642
+ { name: "mailRoster", query: "roster", key: "42" }
586
643
  ```
587
644
 
645
+ The hub sends `query`, `key` and `values` as present.
646
+
647
+ #### Committing
648
+
649
+ An element commits on `submit` when it is a form, on `change` when it is a
650
+ control, and on `click` otherwise.
651
+
652
+ A submit button with a form owner never commits on `click`. On a form's
653
+ `submit`, the hub issues the submitter's `lb-request` when it carries one,
654
+ and the form's otherwise, the way `formaction` replaces `action`.
655
+
656
+ A click on a descendant of an element carrying `lb-request` commits that
657
+ element, as long as the element is inside `<lb-hub>` and no interactive
658
+ content lies between them. Interactive content is HTML's: `<a href>`,
659
+ `<button>`, `<input>` other than hidden, `<select>`, `<textarea>`, `<label>`,
660
+ `<details>`, `<iframe>`, `<embed>`, `<audio controls>`, `<video controls>`
661
+ and `<img usemap>`, plus any form-associated custom element. A click on
662
+ one of them belongs to it: a click into an input inside a deletable row
663
+ focuses the input and deletes nothing.
664
+
665
+ The hub ignores a commit on an element carrying `lb-request-pending`.
666
+
667
+ #### Gathering
668
+
669
+ The hub gathers values when it issues a request. It gathers each control
670
+ value under the column its `lb-column` names.
671
+
672
+ | Attribute | The hub |
673
+ | -------------- | ------------------------------------------- |
674
+ | `lb-query` | Issues the request for it |
675
+ | `lb-column` | Gathers the control value under that column |
676
+ | `lb-key-value` | Takes `key` from it |
677
+
678
+ - When the element carrying `lb-request` has `lb-column`, the hub gathers
679
+ its value alone, and sends `values` with one member.
680
+ - Otherwise, when the element is a `<form>`, the hub gathers every
681
+ `lb-column` whose form owner is the element.
682
+ - Otherwise, when the element is in a live row, the hub gathers every
683
+ `lb-column` in that live row.
684
+ - Otherwise the hub gathers every `lb-column` whose form owner is the
685
+ element's form owner.
686
+
687
+ The form owner includes a control that names the form with the HTML `form`
688
+ attribute from anywhere in the document, which is how a table row's
689
+ controls belong to a form that cannot wrap a `<tr>`:
690
+
691
+ ```html
692
+ <form id="add-member" lb-request="lb-row-insert"></form>
693
+ <table lb-query="roster">
694
+ <tbody>
695
+ <template>
696
+ <tr><td lb-column="name"></td><td lb-column="role"></td></tr>
697
+ </template>
698
+ </tbody>
699
+ <tfoot>
700
+ <tr>
701
+ <td><input lb-column="name" form="add-member" /></td>
702
+ <td>
703
+ <input lb-column="role" form="add-member" />
704
+ <button form="add-member">Add</button>
705
+ </td>
706
+ </tr>
707
+ </tfoot>
708
+ </table>
709
+ ```
710
+
711
+ The hub gathers from controls only. It skips a control whose nearest
712
+ ancestor `lb-query` is a descendant of the live row or form, since that
713
+ control belongs to a nested query. An element carrying `lb-query` and
714
+ `lb-column` both, such as a picker, belongs to the row around it and is
715
+ gathered.
716
+
717
+ The hub issues the request for the nearest ancestor `lb-query` of the
718
+ controls it gathered, and takes `key` from their nearest ancestor row when
719
+ that row is of the same query. When the gathered controls have different
720
+ nearest ancestor `lb-query`, the hub reports an error and sends nothing.
721
+
722
+ When it gathers nothing, the hub issues the request for the nearest
723
+ ancestor `lb-query` of the element carrying `lb-request`, and takes `key`
724
+ from that element's nearest ancestor row when that row is of the same
725
+ query.
726
+
727
+ A field the request already carries is its issuer's and is kept. A custom
728
+ element that dispatches its own request with `values` is not gathered over.
729
+
730
+ #### The request names
731
+
732
+ | Request name | Needs | Runs under `crud` |
733
+ | --------------- | ------------------------ | ----------------- |
734
+ | `lb-row-insert` | `query`, `values` | `rowInsert` |
735
+ | `lb-row-update` | `query`, `key`, `values` | `rowUpdate` |
736
+ | `lb-row-delete` | `query`, `key` | `rowDelete` |
737
+
738
+ The hub issues one of these only when it has what the table lists. An
739
+ `lb-row-insert` or `lb-row-update` that gathers nothing is not issued. A
740
+ request that is not issued is reported to the console.
741
+
742
+ Loadbare provides the handlers for these names: each runs the page's `crud`
743
+ entry for the request's query. A declared request runs the application's
744
+ handler under `handlers`, and the hub issues it with whatever it found.
745
+
746
+ A request name beginning with `lb-` that is none of these three is refused
747
+ by the builder in markup, and by the hub when a script dispatches it.
748
+
749
+ For a query the hub serves, the hub answers the request itself, with no
750
+ round trip. The hub serves `lb-url`, and answers `lb-row-update` for it —
751
+ see [Query parms](#query-parms).
752
+
588
753
  #### The request event
589
754
 
590
- The hub does not send a request the moment it catches a click or a submit.
591
- It builds the request, dispatches it from the element that acted as a
592
- bubbling `lb-request` event carrying the request as its `detail`, and a
593
- listener on the hub sends it.
755
+ The hub dispatches every request as the bubbling `lb-request` event before
756
+ sending it, with the request as its `detail`. The event is dispatched from
757
+ the element that committed.
594
758
 
595
- A widget uses that same event, and it is the only channel a widget has for
596
- firing a request of its own. A native element and a hand-written widget
759
+ A custom element uses the same event to issue a request of its own, with at
760
+ least `name` in its `detail`. A native element and a custom element
597
761
  therefore produce identical events.
598
762
 
599
- An ancestor sees the request on its way up and may stop it. The event is
600
- not cancelable, so an interceptor calls `stopPropagation`, not
601
- `preventDefault`. This is what makes a confirmation wrapper possible
602
- without the wrapped element knowing about it.
763
+ The hub completes the request on the event's way down, so every ancestor
764
+ sees it whole on the way up. A request that cannot be issued is stopped
765
+ there. An ancestor may stop it too: the event is not cancelable, so an
766
+ interceptor calls `stopPropagation`, not `preventDefault`. This is what
767
+ makes a confirmation wrapper possible without the wrapped element knowing
768
+ about it.
769
+
770
+ #### Request state
771
+
772
+ The hub stamps `lb-request-pending` on the element that issued a request
773
+ while its round trip is in flight, together with `aria-busy="true"`, and
774
+ removes both when it settles. It stamps `lb-request-error` when the round
775
+ trip fails, and clears it when that element issues its next request.
776
+
777
+ A custom element observes them by naming them in `observedAttributes`. On
778
+ plain HTML a stylesheet is the only consumer: dim a pending button, mark a
779
+ failed one.
780
+
781
+ The hub ignores a commit on an element carrying `lb-request-pending`, so a
782
+ pending element is disabled in fact and a stylesheet only has to show it.
603
783
 
604
784
  #### The round trip
605
785
 
@@ -608,77 +788,63 @@ aborts it and treats it as a failure, since `fetch` imposes no deadline of
608
788
  its own. An application cannot change the deadline.
609
789
 
610
790
  A round trip fails on a server error, on a network failure, or on that
611
- deadline, and all three set `lb-error` on the element that dispatched the
612
- request — see [Request state](#request-state).
613
-
614
- After a write succeeds, the cells it gathered show what the server holds.
615
- An `lb-row-update` does this by landing the row on them. An
616
- `lb-row-insert` does it by resetting them, the way `form.reset()` resets a
617
- form, because the row they held now lives in the list. The hub resets
618
- after it lands the response, and only what it gathered:
619
-
620
- - Each control returns to its default: an `<input>` or `<textarea>` to its
621
- `defaultValue`, a `<select>` to the options marked `selected`. A page
622
- that wants a prefilled insert writes a `value` attribute, as in plain
623
- HTML.
624
- - A control showing something other than the value the hub read holds an
625
- edit made during the round trip. That edit was not sent, and is left.
626
- - `lb-value` comes off each reset cell, and off its control, before the
627
- control resets — see [How a value lands](#how-a-value-lands).
628
- - Values a widget supplied in the request were not gathered, and are not
629
- reset.
630
- - Nothing is dispatched, as `form.reset()` fires no `change`.
631
-
632
- A failed insert resets nothing, so the entry can be corrected.
633
-
634
- A navigation that fails to load its data sets nothing. No element
635
- dispatched it, so there is nothing to stamp, and the hub reports it to the
636
- console. A load started by a control writing a query parm stamps that
637
- control, as a request stamps its origin.
638
-
639
- ### Links
791
+ deadline, and all three stamp `lb-request-error` on the element that issued
792
+ the request — see [Request state](#request-state).
640
793
 
641
- ---- UNEDITED ----
794
+ After a successful `lb-row-insert` gathered from a form, the hub resets the
795
+ form, after it lands the response. A failed insert resets nothing, so the
796
+ entry can be corrected. An `lb-row-update` resets nothing: the row it sent
797
+ lands back on its controls.
642
798
 
643
- An anchor carrying `lb-nav-link` navigates inside the application: the hub
644
- catches the click, pushes the anchor's path and query string onto history,
645
- and swaps the page host in `<main>`. An anchor without it is left alone and behaves like any
646
- other link, so leaving the application is the default and staying in it is
647
- the opt-in.
799
+ A reset of any form, whether the hub's, a reset button's or a script's,
800
+ returns its controls to their defaults, and the hub removes
801
+ `lb-column-value` from each of them. A cancelled reset keeps both.
648
802
 
649
- | Attribute | Assigned By | Behavior |
650
- | ----------- | ----------- | --------------------------------------------------------------------------------------- |
651
- | lb-nav-link | Developer | On an `<a>`: the hub shows the page the anchor's path names, without loading a document |
803
+ A page load that fails lands nothing and is reported to the console. A load
804
+ started by a request for `lb-url` stamps the element that issued it, as any
805
+ request stamps its element.
652
806
 
653
- The attribute takes no value. The hub looks for the nearest ancestor
654
- link to determine the path.
807
+ ### The URL
655
808
 
656
- Path space is flat. A path such as `/members` links to the `members.*` files
657
- on the server.
809
+ ---- UNEDITED ----
658
810
 
659
- The query string is kept. `/transactions?date_begin=2026-09-01` opens the
660
- transactions page narrowed to those dates, and a link to the page it is
661
- already on loads that page again at the new URL without replacing its DOM —
662
- see [Query parms](#query-parms).
811
+ The hub serves the query `lb-url`, of kind `row`, keyed by `lb-path`. It
812
+ lands wherever an element names it, like any server query.
663
813
 
664
- The bare path `/` resolves to `index`.
814
+ | Column | The hub sets it to |
815
+ | ----------------- | ---------------------------------------------- |
816
+ | `lb-path` | The path. The row's key |
817
+ | `lb-page-label` | The page's title, or its stub when it has none |
818
+ | `lb-page-unknown` | Whether no page's stub matches the path |
819
+ | any other name | That query parm |
665
820
 
666
- A path that names no page is detected in the browser, after a successful
667
- 200: every route gets the same document, so there is no server-delivered
668
- 404. A chrome that declares an `lb-unknown-page` dialog gets it opened.
669
- One that declares none gets a console error and nothing on screen. See
670
- [Chrome](#chrome).
821
+ | Attribute | The hub |
822
+ | ---------------- | -------------------------------------------------------- |
823
+ | `lb-url-link` | On a plain primary click, sets `lb-path` from the `href` |
824
+ | `lb-url-push` | Pushes a history entry for this element's update |
825
+ | `lb-url-unknown` | Calls `showModal()` when `lb-page-unknown` is `true` |
826
+
827
+ - `lb-url-link` goes on an `<a>`.
828
+ - `lb-url-push` goes on an element with `lb-request`.
829
+ - `lb-url-unknown` goes on a `<dialog>` inside `<lb-hub>`.
830
+
831
+ The hub shows in `<main>` the page whose stub is `lb-path`. `/` is
832
+ `index`. When `lb-path` takes a new value, the hub loads the page: the
833
+ server runs its `onPageEnter`, then its queries. When a query parm takes a
834
+ new value, the hub reloads the page's queries, and keeps the page's DOM, so
835
+ live rows that come back keep their place.
836
+
837
+ The page's title is the text of the page file's `<title>`, which the builder
838
+ stamps on the page as `lb-page-title`. The hub sets the document title to
839
+ `lb-page-label` when it is not null.
671
840
 
672
841
  ```html
673
- <nav>
674
- <a href="/" lb-nav-link>Home</a>
675
- <a href="/members" lb-nav-link>Members</a>
676
- <a href="https://example.com/docs">Docs</a>
677
- </nav>
842
+ <header lb-query="lb-url">
843
+ <h1>Membership Roster</h1>
844
+ <h2 lb-column="lb-page-label"></h2>
845
+ </header>
678
846
  ```
679
847
 
680
- See also [Navigation Row lb-navigation](#lb-navigation).
681
-
682
848
  #### What a URL names
683
849
 
684
850
  The path names a page, a place in the application. It never names a
@@ -690,70 +856,115 @@ The query string describes what that page has on screen:
690
856
  remembers nothing, so a URL is a reproducible view. Reload it, bookmark it,
691
857
  or mail it to someone, and what they see is what the sender saw.
692
858
 
693
- A page declares what it shows, its queries, and what it allows, its actions.
694
- Loading a page runs its queries. A request performs an action at a position.
695
- A row is addressed by `list` and `key`, taken from where the element sits,
696
- which is how the database already names it. Nothing is fetched by URL, so an
697
- application designs no endpoints.
859
+ A page declares what it shows, its queries, and what it allows, its
860
+ requests. Loading a page runs its queries. A request names a query and,
861
+ where it has one, a key, taken from where the element sits, which is how the
862
+ database already names a row. Nothing is fetched by URL, so an application
863
+ designs no endpoints.
698
864
 
699
865
  That last is where query parms move Loadbare's position. A query still takes
700
866
  no argument from the browser, and a parm arrives the way the session cookie
701
867
  does, as part of the request the context is built from. But a user who
702
- types `?acct=99999` now influences what a query returns. A query parm is
703
- user input, validated like any other where the application reads it, and a
868
+ types `?acct=99999` influences what a query returns. A query parm is user
869
+ input, validated like any other where the application reads it, and a
704
870
  per-session database role means an id outside the caller's reach finds
705
871
  nothing.
706
872
 
707
873
  Loadbare is for applications, not sites. Every route is answered with the
708
874
  same document, and a path that names no page is found out in the browser —
709
- see [Links](#links).
875
+ see [An unknown page](#an-unknown-page).
710
876
 
711
- #### Query parms
877
+ #### Links
712
878
 
713
- A control that narrows what a page shows writes its value into the query
714
- string, rather than sending it:
879
+ An `<a>` carrying `lb-url-link` moves within the application. On a plain
880
+ primary click — the first button, no modifier key, not already handled — the
881
+ hub pushes a history entry for the `href`'s path and query string, and loads
882
+ the page there. Any other click, and any anchor without `lb-url-link`,
883
+ behaves like any other link, so leaving the application is the default and
884
+ staying in it is the opt-in.
715
885
 
716
886
  ```html
717
- <lb-options lb-list="teams" lb-query-parm="team" exp-label="Team:">
718
- <option value="">Every team</option>
719
- <template lb-key="id"><option lb-cell="name"></option></template>
720
- </lb-options>
887
+ <nav>
888
+ <a href="/" lb-url-link>Home</a>
889
+ <a href="/members" lb-url-link>Members</a>
890
+ <a href="https://example.com/docs">Docs</a>
891
+ </nav>
721
892
  ```
722
893
 
723
- | Attribute | Written by | Behavior |
724
- | -------------------- | ---------- | --------------------------------------------------------------- |
725
- | `lb-query-parm` | Developer | On a control: its `change` writes this parm and reloads the page |
726
- | `lb-query-parm-push` | Developer | With `lb-query-parm`: the write pushes a history entry |
894
+ The attribute takes no value.
727
895
 
728
- On `change`, the hub reads the control's value the way a form reads a cell,
729
- and sets that one parm in the URL. Every other parm is left as it is, since
730
- it may have arrived by link with no control on screen to say it again. An
731
- empty value takes the parm out, so a URL is as long as the user has narrowed
732
- the page. A value the URL already carries does nothing.
896
+ Path space is flat. A path such as `/members` links to the `members.*` files
897
+ on the server.
733
898
 
734
- The write replaces the current history entry: changing what a page shows is
735
- not going anywhere, so Back leaves the page rather than walking back through
736
- every choice. `lb-query-parm-push` makes the write push an entry instead.
899
+ The query string is kept. `/transactions?date_begin=2026-09-01` opens the
900
+ transactions page narrowed to those dates.
901
+
902
+ The browser's Back and Forward load the page at the URL they arrive at, the
903
+ same way.
904
+
905
+ #### Query parms
906
+
907
+ A control that narrows what a page shows updates `lb-url` rather than
908
+ sending a request. Put it inside an element naming `lb-url`, with
909
+ `lb-column` naming the parm and `lb-request="lb-row-update"`:
910
+
911
+ ```html
912
+ <div lb-query="lb-url">
913
+ <select lb-column="team" lb-request="lb-row-update">
914
+ <option value="">Every team</option>
915
+ <option value="Engines">Engines</option>
916
+ </select>
917
+ </div>
918
+ ```
737
919
 
738
- The page then loads at the new URL exactly as a cold load of that URL would:
739
- `onPageEnter`, then every query. The page's DOM is kept, and the answer
740
- lands by key, so a list that gets its rows back keeps them and its scroll
741
- position.
920
+ The hub answers the request itself. It sets the parms the request names in
921
+ the URL and leaves every other parm as it is, since one may have arrived by
922
+ link with no control on screen to say it again. An empty value takes the
923
+ parm out, so a URL is as long as the user has narrowed the page. A request
924
+ that leaves the URL as it was does nothing. `lb-page-label` and
925
+ `lb-page-unknown` are the hub's, and a request never sets them.
742
926
 
743
- After every load — cold, by link, by Back, by a write — the hub lands each
744
- parm on the control that writes it, and an absent parm lands empty. A
745
- control therefore shows what the address bar says.
927
+ The update replaces the current history entry: changing what a page shows is
928
+ not going anywhere, so Back leaves the page rather than walking back through
929
+ every choice. `lb-url-push` on the element carrying `lb-request` makes the
930
+ update push an entry instead.
746
931
 
747
- A control that writes a query parm sends no request. One that also carries
748
- `lb-action` has that request refused, since the choice would otherwise be
749
- sent twice.
932
+ When `lb-path` takes a new value, the hub pushes a history entry, and the
933
+ new URL carries only the query parms the request names.
750
934
 
751
- Every round trip carries the query string the browser is showing, a page
752
- load and an action alike. The server hands its parms to `contextFor` — see
935
+ Every load lands `lb-url`, and a parm the URL does not carry lands empty on
936
+ every element naming it as a column, so a control shows what the address
937
+ bar says after Back as well as after a reload. This is the one exception
938
+ to landing, where a row sets the columns it names and leaves the rest as
939
+ they were: the hub reads the column names under `lb-query="lb-url"` and
940
+ names each one in the row it lands.
941
+
942
+ The hub sends the query parms with every round trip, a page load and a
943
+ request alike. The server passes them to `contextFor` — see
753
944
  [The Express server](#the-express-server). Nothing in Loadbare assigns a
754
945
  parm a meaning.
755
946
 
756
- #### When a server response changes the query parms
947
+ #### An unknown page
948
+
949
+ When no page's stub matches the path, `lb-page-unknown` is `true` and
950
+ `lb-page-label` is null. The hub calls `showModal()` on the
951
+ `<dialog lb-url-unknown>`, where the chrome has one, and reports the path to
952
+ the console. A chrome with none shows nothing.
953
+
954
+ `lb-url` lands before the page is looked up, so the dialog shows it by
955
+ naming it like any other element:
956
+
957
+ ```html
958
+ <dialog lb-url-unknown lb-query="lb-url">
959
+ The URL <span lb-column="lb-path"></span> is not in this app.
960
+ </dialog>
961
+ ```
962
+
963
+ A path that names no page is detected in the browser, after a successful
964
+ 200: every route gets the same document, so there is no server-delivered
965
+ 404.
966
+
967
+ #### When a server response moves the URL
757
968
 
758
969
  Some query parms can only be known once a write has run. After an insert,
759
970
  the key of the new row exists only on the server. After a delete, only the
@@ -761,75 +972,66 @@ server knows that the row the page was showing is gone.
761
972
 
762
973
  The usual web answer is Post/Redirect/Get: the server answers the write with
763
974
  a redirect to a URL naming the result, and the browser makes a second request
764
- to load it. Loadbare/app does the same work in one round trip, and never
765
- changes the path.
975
+ to load it. Loadbare/app does the same work in one round trip.
766
976
 
767
- A request's `run` returns `queryParms()`, naming the parms the write decided.
768
- The server loads the page with those parms set, as a cold load of the
769
- resulting URL would: `onPageEnter`, then every query. The server response
770
- carries the parms and that load together. The hub sets the parms in the URL,
771
- replacing the history entry, and then lands the load.
977
+ A handler returns `url()`, a new row for `lb-url`. The server loads the page
978
+ at the resulting URL, as a cold load of it would: `onPageEnter`, then every
979
+ query. The response carries the `lb-url` item followed by that load. The
980
+ hub writes the URL and then lands the load.
772
981
 
773
982
  ```ts
774
983
  rowInsert: {
775
984
  run: async (ctx, { values }) => {
776
985
  const id = await ctx.db.addAccount(values);
777
- return queryParms({ acct: String(id) });
986
+ return url({ acct: String(id) });
778
987
  },
779
988
  refresh: [],
780
989
  },
781
990
  ```
782
991
 
783
- - Only query parms change. A server response cannot send the browser to
784
- another page.
785
- - Parms the response does not name are left as they are. An empty value
786
- removes its parm.
787
- - At least one parm is named, and every value is a string. Otherwise the
788
- server warns, ignores the parms, and runs the refresh set as usual.
789
- - The refresh set does not run, and anything else `run` returned is dropped.
790
- Both were answers for the query string the page is leaving.
992
+ - A column other than `lb-path` is a query parm. Parms the row does not
993
+ name are kept, and an empty value removes its parm.
994
+ - A row carrying `lb-path` for another page enters that page with only the
995
+ parms the row names, and pushes a history entry. Otherwise the update
996
+ replaces the history entry, or pushes one when the element that issued the
997
+ request carries `lb-url-push`.
998
+ - The row names at least one column, and every value is a string. Otherwise
999
+ the server warns, ignores the row, and runs the refresh set as usual.
1000
+ - The refresh set does not run, and anything else the handler returned is
1001
+ dropped. Both were answers for the URL the page is leaving.
791
1002
  - If loading the page fails, the write has still happened. The response
792
- carries the parms alone, and the hub loads the page itself. A failure
793
- there is a page load's, and is not stamped on the element that sent the
794
- request.
1003
+ carries the `lb-url` item alone, and the hub loads the page itself. A
1004
+ failure there is a page load's, and is not stamped on the element that
1005
+ issued the request.
795
1006
  - A user who has left the page by the time the response arrives keeps the
796
1007
  URL they are on.
797
1008
 
798
1009
  The page is loaded with a second context, built by `contextFor` from the new
799
1010
  parms — see [The Express server](#the-express-server).
800
1011
 
801
- ### lb-navigation
802
-
803
- ---- UNEDITED ----
1012
+ ### The markup checks
804
1013
 
805
- Status: 1.0-RC.
1014
+ The builder checks the chrome and every page, after expansion, and refuses
1015
+ to build on any of these:
806
1016
 
807
- Current page, published by the hub as a row. Can be bound anywhere just
808
- like a server-produced result.
1017
+ - An `lb-` attribute that is not a developer attribute: `lb-query`,
1018
+ `lb-column`, `lb-show`, `lb-request`, `lb-url-link`, `lb-url-push`,
1019
+ `lb-url-unknown`. Every other `lb-` attribute is a stamp.
1020
+ `lb-exp-slot` and `lb-exp-template` are consumed by expansion and never
1021
+ reach the check.
1022
+ - `lb-request` naming a value that begins with `lb-` and is not one of the
1023
+ three request names.
1024
+ - `lb-url-link` on anything but an `<a>`.
1025
+ - `lb-url-push` on an element with no `lb-request`.
1026
+ - `lb-url-unknown` on anything but a `<dialog>`, and in the chrome, outside
1027
+ `<lb-hub>`.
1028
+ - `lb-show` on a `<template>`, on a row template's root, or with no
1029
+ `lb-query` around it.
809
1030
 
810
- | Cell | Holds |
811
- | ------------ | ------------------------------------------------------------------- |
812
- | `page-label` | The text of the link to the current page, empty if no link names it |
813
- | `page-uri` | The path and query string as the browser has them, such as `/members?team=Engines` |
1031
+ `lb-column` with no ancestor row is allowed: the hub gathers from it.
814
1032
 
815
- The `page-uri` is taken from the current URL. The `page-label` is taken from the first
816
- `lb-nav-link` link in the document (presumably in a nav bar) whose path matches the
817
- current path, so the query string does not change it.
818
-
819
- The row lands before the page is looked up, so a path that names no page has
820
- it too, and an `lb-unknown-page` dialog, where the chrome has one, displays
821
- it by naming the row like any other subtree.
822
-
823
- It lands only where a subtree names it. A chrome that displays no
824
- navigation is not warned about a row with nowhere to go.
825
-
826
- ```html
827
- <header lb-row="lb-navigation">
828
- <h2 lb-cell="page-label"></h2>
829
- </header>
830
- ```
831
-
832
- See also [Links](#links).
1033
+ Script never assigns an `lb-` attribute, so the markup states every one
1034
+ that exists, and checking the markup is sound.
833
1035
 
834
1036
  ## Application files
835
1037
 
@@ -849,10 +1051,9 @@ The file is an HTML document, which must contain:
849
1051
 
850
1052
  It may also contain:
851
1053
  - `/app.css`, the stylesheet bundle
852
- - A `<dialog>` marked with attribute `lb-unknown-page`, which the hub
853
- will display to the user if an attempt is made to navigate to an
854
- unknown page. It goes inside `<lb-hub>`, like everything the hub acts
855
- on; the builder rejects one placed elsewhere.
1054
+ - A `<dialog lb-url-unknown>`, which the hub opens when the path names no
1055
+ page. It goes inside `<lb-hub>`, like everything the hub acts on; the
1056
+ builder rejects one placed elsewhere.
856
1057
  - The `hidden` attribute on `<body>`, which the hub removes once the first page has landed.
857
1058
 
858
1059
  ```html
@@ -867,52 +1068,66 @@ It may also contain:
867
1068
  </head>
868
1069
  <body hidden>
869
1070
  <lb-hub>
870
- <header lb-row="lb-navigation">
1071
+ <header lb-query="lb-url">
871
1072
  <h1>Membership Roster</h1>
872
- <h2 lb-cell="page-label"></h2>
1073
+ <h2 lb-column="lb-page-label"></h2>
873
1074
  </header>
874
1075
  <nav>
875
- <a href="/" lb-nav-link>Home</a>
876
- <a href="/members" lb-nav-link>Members</a>
1076
+ <a href="/" lb-url-link>Home</a>
1077
+ <a href="/members" lb-url-link>Members</a>
877
1078
  <a href="https://example.org/">Our website</a>
878
1079
  </nav>
879
1080
  <main></main>
880
- <dialog lb-unknown-page lb-row="lb-navigation">
881
- The URL <span lb-cell="page-uri"></span> is not in this app.
1081
+ <dialog lb-url-unknown lb-query="lb-url">
1082
+ The URL <span lb-column="lb-path"></span> is not in this app.
882
1083
  </dialog>
883
1084
  </lb-hub>
884
1085
  </body>
885
1086
  </html>
886
1087
  ```
887
1088
 
888
- The `lb-row`, `lb-cell` and `lb-nav-link` attributes in that example are
889
- ordinary Loadbare/app binding. See [Binding](#binding),
890
- [Links](#links) and [lb-navigation](#lb-navigation).
1089
+ The chrome's `<title>` shows until the first page lands, and each page's
1090
+ title replaces it — see [The URL](#the-url).
891
1091
 
892
1092
  ### Pages
893
1093
 
894
1094
  The page namespace is flat. Loadbare/app does not care where in the `--src`
895
1095
  the page files are located, but they must be unique across the application.
896
- A page `/deep/path/to/mypage.html` is routed to `/mypage`.
1096
+ A page `/deep/path/to/mypage.page.html` is routed to `/mypage`.
897
1097
 
898
1098
  Pages are grouped as
899
- - <stub>.page.html is recognized as navigable and capable of
1099
+ - `<stub>.page.html` is recognized as navigable and capable of
900
1100
  having associated queries and requests
901
- - <stub>.queries.ts are the queries for a page
902
- - <stub>.requests.ts respond to the requests from the browser
1101
+ - `<stub>.queries.ts` are the queries for a page
1102
+ - `<stub>.requests.ts` respond to the requests from the browser
903
1103
 
904
- The <stub> value for a page must match the path used in links,
905
- so that `<a href='/members' lb-nav-link>Members</a>` has matching
906
- files `members.pages.html` et al.
1104
+ The stub of a page must match the path used in links,
1105
+ so that `<a href="/members" lb-url-link>Members</a>` has matching
1106
+ files `members.page.html` et al.
907
1107
 
908
1108
  #### Page HTML
909
1109
 
910
1110
  The markup in `<stub>.page.html` is the same HTML as everywhere else
911
1111
  in a Loadbare/app application, see [HTML](#html).
912
1112
 
913
- The builder wraps each expanded page in `<template lb-page="<stub>">` and
914
- puts it in the built document. The application never writes `lb-page`; the
915
- hub reads it to find the page a path names.
1113
+ A page file may carry a `<title>`. The builder removes it and stamps its
1114
+ text content as `lb-page-title` on the page, and the hub shows it as
1115
+ `lb-page-label`.
1116
+
1117
+ ```html
1118
+ <!-- src/pages/members.page.html -->
1119
+ <title>Members</title>
1120
+ <h1>Members</h1>
1121
+ ```
1122
+
1123
+ The builder wraps each expanded page in
1124
+ `<template lb-page="<stub>" lb-page-title="...">` and puts it in the built
1125
+ document. The hub reads `lb-page` to find the page a path names.
1126
+
1127
+ | Stamp | The builder stamps it with |
1128
+ | --------------- | --------------------------------------------- |
1129
+ | `lb-page` | The page's stub, which the hub reads |
1130
+ | `lb-page-title` | The text content of the page file's `<title>` |
916
1131
 
917
1132
  ## The server
918
1133
 
@@ -976,7 +1191,7 @@ Explaining Express is beyond the scope of this technical reference. The
976
1191
  only real requirement is that the catch-all for app.html is at the end,
977
1192
  so it does not catch any other files.
978
1193
 
979
- `hubRoutes` answers `POST /lb/<page>`, with the query string the browser is
1194
+ `hubRoutes` answers `POST /lb/<stub>`, with the query string the browser is
980
1195
  showing after it, verbatim. It hands `contextFor` that query string's parms
981
1196
  as a second argument, and `contextFor` puts on the context whatever a query
982
1197
  reads from them. They are user input:
@@ -988,7 +1203,7 @@ function contextFor(req: Request, parms: URLSearchParams): HubContext {
988
1203
  ```
989
1204
 
990
1205
  Read the parms from that argument, not from `req.query`. After a request
991
- whose `run` returned `queryParms()`, `contextFor` is called a second time for
1206
+ whose handler returned `url()`, `contextFor` is called a second time for
992
1207
  the same request, with the new parms, to load the page at them. So it must
993
1208
  be safe to call twice, and a write must be visible to the second context by
994
1209
  the time its `run` returns. A handle opened per request without a
@@ -1002,28 +1217,37 @@ the whole path space through unchanged.
1002
1217
  ### Page Queries
1003
1218
 
1004
1219
  `<stub>.queries.ts` exports one object named `queries`, typed `Queries`.
1005
- Each key is a query name, and the markup binds to that name through
1006
- `lb-list` or `lb-row`.
1220
+ Each key is a query name, which the markup names with `lb-query`.
1007
1221
 
1008
- Cardinality belongs to the named query. One named query always returns a single
1009
- row or an array of rows. Build a query using `row()` or `list()` to return
1010
- the two shapes. A query cannot be built without one of these functions.
1011
-
1012
- Queries names cannot begin with `lb-`, that namespace is reserved for
1013
- Loadbare/app queries the hub makes available in the browser, such
1014
- as [lb-navigation](#lb-navigation).
1222
+ A query's kind and key belong to its name. Declare each query with
1223
+ `row(key, run)` or `rows(key, run)`, naming the key column first. A query
1224
+ cannot be built without one of these functions. Every query has a key; an
1225
+ aggregate row answers with a constant one.
1015
1226
 
1016
1227
  ```ts
1017
1228
  // members.queries.ts
1018
- import { list, row, type Queries } from "@loadbare/app/server";
1229
+ import { row, rows, type Queries } from "@loadbare/app/server";
1019
1230
 
1020
1231
  export const queries: Queries = {
1021
- roster: list((ctx) => ctx.db.members()),
1022
- summary: row(async (ctx) => ({ count: String(await ctx.db.memberCount()) })),
1232
+ roster: rows("id", (ctx) => ctx.db.members()),
1233
+ summary: row("id", async (ctx) => ({
1234
+ id: "all",
1235
+ count: String(await ctx.db.memberCount()),
1236
+ })),
1023
1237
  };
1024
1238
  ```
1025
1239
 
1026
- Every query and every request receives `ctx`, the application's own request
1240
+ A `row` query returns one object, and a `rows` query returns an array of
1241
+ objects in the order the page shows them. An answer that disagrees with
1242
+ its declared kind is left out of the response, with a warning naming the
1243
+ query.
1244
+
1245
+ Query names cannot begin with `lb-`, that namespace is reserved for
1246
+ Loadbare/app queries the hub serves in the browser, such as
1247
+ [`lb-url`](#the-url). `createHub` refuses at startup a page that declares
1248
+ such a name as a query, a handler or a `crud` entry.
1249
+
1250
+ Every query and every handler receives `ctx`, the application's own request
1027
1251
  context. The application builds it once per request and hands it to the hub,
1028
1252
  see [The Express server](#the-express-server). Loadbare/app declares it empty
1029
1253
  and never reads it, so a query finds exactly what the application put there,
@@ -1039,36 +1263,32 @@ It has three optional keys.
1039
1263
 
1040
1264
  | Key | Keyed by | Answers |
1041
1265
  | ------------- | --------------------- | -------------------------------------------------- |
1042
- | `actions` | The `lb-action` value | Anything the page chooses to declare |
1043
- | `crud` | A query name | The three reserved `lb-action` values |
1266
+ | `handlers` | The request name | The page's declared requests |
1267
+ | `crud` | A query name | The three requests Loadbare provides |
1044
1268
  | `onPageEnter` | Nothing | Runs once on entering the page, before its queries |
1045
1269
 
1270
+ The server resolves a declared request against `handlers`, and a request
1271
+ Loadbare provides against `crud` under the request's query. A name with no
1272
+ entry there logs a server console warning naming the page and the missing
1273
+ entry, and answers 200 with no response items, so nothing lands and
1274
+ `lb-request-pending` clears from the element that issued the request. A
1275
+ request carrying no name answers 400, and a `run` that throws answers 500.
1046
1276
 
1047
- The hub resolves an `lb-action` value against `actions`, and a reserved
1048
- operation against `crud` under the bound list name. A name with no entry
1049
- there logs a server console warning naming the page and the missing entry,
1050
- and answers 200 with `{}`. The browser applies `{}`, so no cell is set and
1051
- no row is added or removed, and `lb-pending` clears from the element that
1052
- sent the request. A request carrying no action answers 400, and a `run`
1053
- that throws answers 500.
1054
-
1055
- Every entry under `actions` and `crud` has the same two members. `run`
1056
- performs the work, and `refresh` names the queries to re-run once the action
1057
- is complete.
1277
+ Every handler under `handlers` and `crud` has the same two members. `run`
1278
+ performs the work, and `refresh` names the queries to re-run once it has.
1058
1279
 
1059
- `run` receives the same `ctx` and a `where`: the binding in scope when the
1060
- interaction happened, as `list`, `row`, `key`, `cell` and `value`. Under
1061
- `crud` the `where` carries only what that operation is typed to carry.
1280
+ `run` receives the same `ctx` and the request less its name: `query`, `key`
1281
+ and `values`, as present.
1062
1282
 
1063
- `run` may also return results of its own, which are laid over the refreshed
1064
- ones. That is how a delta reaches the browser: wrap it in `patch()`, naming
1065
- the rows that arrived or changed and the keys that went.
1283
+ `run` may also return answers of its own, by query name, which are laid over
1284
+ the refreshed ones. That is how a delta reaches the browser: wrap it in
1285
+ `patch()`, naming the rows that arrived or changed and the keys that went.
1066
1286
 
1067
- `run` may instead return `queryParms()`, naming query parms only the write
1068
- can know, such as the key of a row it inserted. The page then loads at them
1069
- in the same round trip, in place of the refresh set — see
1070
- [When a server response changes the query parms](#when-a-server-response-changes-the-query-parms).
1071
- A `rowDelete` that removes the row on screen returns `queryParms({ acct: "" })`
1287
+ `run` may instead return `url()`, a new row for `lb-url`, naming query parms
1288
+ only the write can know, such as the key of a row it inserted. The page
1289
+ then loads at them in the same round trip, in place of the refresh set — see
1290
+ [When a server response moves the URL](#when-a-server-response-moves-the-url).
1291
+ A `rowDelete` that removes the row on screen returns `url({ acct: "" })`
1072
1292
  and leaves any other delete to its refresh set.
1073
1293
 
1074
1294
  ```ts
@@ -1076,7 +1296,7 @@ and leaves any other delete to its refresh set.
1076
1296
  import { patch, type Requests } from "@loadbare/app/server";
1077
1297
 
1078
1298
  export const requests: Requests = {
1079
- actions: {
1299
+ handlers: {
1080
1300
  resetRoster: {
1081
1301
  run: (ctx) => ctx.db.resetMembers(),
1082
1302
  refresh: ["roster"],
@@ -1085,9 +1305,9 @@ export const requests: Requests = {
1085
1305
  crud: {
1086
1306
  roster: {
1087
1307
  rowDelete: {
1088
- run: async (ctx, where) => {
1089
- await ctx.db.deleteMember(where.key);
1090
- return { roster: patch({ drop: [where.key] }) };
1308
+ run: async (ctx, { key }) => {
1309
+ await ctx.db.deleteMember(key);
1310
+ return { roster: patch({ drop: [key] }) };
1091
1311
  },
1092
1312
  refresh: [],
1093
1313
  },
@@ -1096,86 +1316,142 @@ export const requests: Requests = {
1096
1316
  };
1097
1317
  ```
1098
1318
 
1099
- Each key under a `crud` entry is a reserved `lb-action` value with its prefix
1100
- stripped and the rest camel-cased, so the attribute, the wire and this key
1101
- are one vocabulary. All three operate on a list, because each needs a key and
1102
- a key exists only on a live row.
1319
+ Each key under a `crud` entry is a request name with its prefix stripped and
1320
+ the rest camel-cased, so the attribute, the wire and this key are one
1321
+ vocabulary. A query with no `crud` entry permits none of them.
1103
1322
 
1104
- | `lb-action` | Key under `crud` | `where` carries |
1105
- | ---------------- | ---------------- | ---------------------- |
1106
- | `lb-row-delete` | `rowDelete` | `key` |
1107
- | `lb-row-insert` | `rowInsert` | `values` |
1108
- | `lb-row-update` | `rowUpdate` | `key`, `values` |
1323
+ | Request name | Key under `crud` | `run` receives |
1324
+ | --------------- | ---------------- | --------------- |
1325
+ | `lb-row-insert` | `rowInsert` | `values` |
1326
+ | `lb-row-update` | `rowUpdate` | `key`, `values` |
1327
+ | `lb-row-delete` | `rowDelete` | `key` |
1109
1328
 
1110
1329
  A `rowUpdate` sets the columns `values` names and leaves the rest as they
1111
- are, as an SQL `UPDATE` does. A form sends the cells it holds and a widget
1112
- cell sends itself, so one handler answers both.
1330
+ are, as an SQL `UPDATE` does. A form sends the controls it holds and a
1331
+ control carrying `lb-request` sends itself, so one handler answers both.
1113
1332
 
1114
- ## Widgets
1333
+ ### The wire
1115
1334
 
1116
1335
  ---- UNEDITED ----
1117
1336
 
1118
- A widget is an HTML custom element following these fixed rules:
1119
- - Element name must contain a hyphen, as per hTML rules, like <my-custom-element>
1120
- - HTML code, if present, is `my-custom-element.html`
1121
- - TS code, if present, is in `my-custom-element.browser.ts`. The
1122
- 'browser' segment is a safety feature, requiring the file to be
1337
+ One path, one method. The hub sends every round trip as
1338
+ `POST /lb/<stub>?<query string>`, with the query string the browser is
1339
+ showing. An empty body asks for the page load. Any other body is a
1340
+ request:
1341
+
1342
+ ```ts
1343
+ interface HubRequest {
1344
+ name: string;
1345
+ query?: string;
1346
+ key?: string;
1347
+ values?: Record<string, string>;
1348
+ }
1349
+ ```
1350
+
1351
+ Every response is an array of response items:
1352
+
1353
+ ```ts
1354
+ interface ResponseItem {
1355
+ query: string;
1356
+ kind: "row" | "rows";
1357
+ key: string;
1358
+ row?: Row;
1359
+ rows?: Row[];
1360
+ patch?: { rows?: Row[]; drop?: unknown[] };
1361
+ }
1362
+ ```
1363
+
1364
+ A response item carries exactly one of `row`, `rows` or `patch`. A patch
1365
+ answers a `rows` query only: rows named in `rows` are added or updated, keys
1366
+ in `drop` are removed, and every other row is left alone.
1367
+
1368
+ `createHub(pages)` builds the `Hub`, which has two calls, both answering
1369
+ with response items: `dataForPage(page, ctx)` for a page load, and
1370
+ `runRequest(page, request, ctx)` for a request.
1371
+
1372
+ ## Custom elements
1373
+
1374
+ ---- UNEDITED ----
1375
+
1376
+ A custom element is an element defined by `customElements.define`, and
1377
+ follows these fixed rules in a Loadbare/app application:
1378
+ - Its name contains a hyphen, as HTML requires, like `<my-custom-element>`.
1379
+ - Its element file, if present, is `my-custom-element.html`.
1380
+ - Its script, if present, is `my-custom-element.browser.ts`. The
1381
+ `browser` segment is a safety feature, requiring the file to be
1123
1382
  explicitly named as a browser file, to help prevent unfortunate
1124
1383
  naming collisions where a server file happens to have the name of
1125
- a widget and gets built into the browser bundle.
1384
+ a custom element and gets built into the browser bundle.
1126
1385
 
1127
- A widget must have either one or the other of HTML and Typescript, and
1128
- it may have both. If it has neither, the builder reports an error.
1386
+ A custom element must have one or the other of an element file and a
1387
+ script, and it may have both. If it has neither, the builder reports an
1388
+ error.
1129
1389
 
1130
- To use a widget from a library, add the library to `imports.ts` anywhere
1131
- in the builders `--src`:
1390
+ To use custom elements from a widget library, add the library to
1391
+ `imports.ts` anywhere in the builder's `--src`:
1132
1392
 
1133
1393
  ```ts
1134
1394
  export default ["@scope/library-name"];
1135
1395
  ```
1136
1396
 
1137
- ### Widget authoring
1397
+ Import every attribute name from `@loadbare/app/constants` — see
1398
+ [Constants](#constants). Never write one as a string literal.
1138
1399
 
1139
- ---- UNEDITED ----
1400
+ ### Controls
1140
1401
 
1141
- The expansion grammar, `exp-`, `lb-slot`, `lb-template`, the reserved `LB-`
1142
- namespace, then `lbPlaceRow` and `lbRowsLanded`. One bucket spanning build
1143
- time and run time, because a widget is a definition and a script together.
1402
+ A form-associated custom element with a `value` property that fires
1403
+ `change` is a control. The hub sets its `value` when a column lands, and
1404
+ gathers its `value` when a request is issued, exactly as for a native
1405
+ `<input>`. It commits on `change`, and belongs to its form owner, which the
1406
+ browser tracks and the `form` attribute names.
1144
1407
 
1145
- #### Request state
1408
+ ```ts
1409
+ class NoteField extends HTMLElement {
1410
+ static formAssociated = true;
1411
+ // a `value` property, and a `change` event when the edit commits
1412
+ }
1413
+ ```
1414
+
1415
+ A control that is not yet upgraded when its column lands receives
1416
+ `lb-column-value` alone, since the hub sets `value` only on a control. That
1417
+ happens to an element the builder shipped absent, and to one whose
1418
+ definition loads after the hub lands. A control takes the stamp up the
1419
+ first time it connects, and never again.
1146
1420
 
1147
- The hub stamps these on the element that dispatched a request, which is the
1148
- widget itself when a widget fired it. A widget observes them and reacts; it
1149
- must name them in `observedAttributes` to see them change.
1421
+ A custom element that is not a control receives `lb-column-value` and
1422
+ renders it; its content is never replaced.
1150
1423
 
1151
- | Attribute | Written on | Holds |
1152
- | -------------- | ----------------------- | --------------------------------------------------------------------------------------- |
1153
- | `lb-pending` | The dispatching element | The round trip is in flight |
1154
- | `lb-error` | The dispatching element | The last round trip failed, cleared on the next — see [The round trip](#the-round-trip) |
1155
- | `lb-row-count` | A list scope | How many rows the scope is showing |
1424
+ ### Lifecycle
1156
1425
 
1157
- They are also stamped on plain HTML, where a stylesheet is the only
1158
- consumer: dim a pending button, mark a failed one, and style an empty list
1159
- against `lb-row-count` rather than carrying an empty-state element.
1426
+ | The hub | A custom element sees |
1427
+ | ---------------------------------------- | -------------------------------------------- |
1428
+ | Shows a page | `constructor`, `connectedCallback` |
1429
+ | Creates a live row, before placing it | `constructor` |
1430
+ | Places a live row the first time | `connectedCallback` |
1431
+ | Places it again, on every set of all rows | `disconnectedCallback`, `connectedCallback` |
1432
+ | Removes a live row | `disconnectedCallback` |
1433
+ | Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
1434
+ | Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
1435
+ | Turns on an element shipped absent | `constructor`, `connectedCallback` |
1160
1436
 
1161
- The hub reads one of them. A native `lb-action` element, a button or a
1162
- form, that is performed again while it carries `lb-pending` is ignored: the
1163
- click or submit is cancelled and nothing is sent. A pending native action is
1164
- therefore disabled in fact, and a stylesheet only has to show it. A widget is
1165
- not held back this way, because a widget that sends on change must have its
1166
- latest value sent rather than dropped.
1437
+ A listener on the element itself goes in the constructor, which reads no
1438
+ attribute and no child. A child is looked up when it is needed.
1439
+ `connectedCallback` runs on every move and is written to run again; work
1440
+ done once per instance goes behind a flag.
1167
1441
 
1168
- Alongside `lb-pending` the hub sets `aria-busy="true"` on the same element and
1169
- removes it when the round trip settles, so assistive technology hears the
1170
- state a stylesheet shows.
1442
+ ### Row hooks
1171
1443
 
1172
- #### Row hooks
1444
+ | Method | Implemented By | The hub calls it |
1445
+ | ------------------------------- | -------------- | -------------------------------------------------- |
1446
+ | `lbPlaceRow(el, row, template)` | Developer | To place a live row, detached, on its first arrival and whenever all rows decide the order |
1447
+ | `lbRowsLanded()` | Developer | After the rows land |
1173
1448
 
1174
- | Name | Implemented By | Behavior |
1175
- | ------------------------------- | -------------- | ----------------------------------------------------------- |
1176
- | `applyRow(root, row)` | Loadbare | Fills one scope from one row |
1177
- | `lbPlaceRow(el, row, template)` | Developer | Optional on a list scope: where a row goes |
1178
- | `lbRowsLanded()` | Developer | Optional on a list scope: scaffolding derived from the rows |
1449
+ The hub calls both on a custom element with `lb-query` and a row template.
1450
+ Both are optional. The `RowsHost` interface in `@loadbare/app/types`
1451
+ declares them.
1452
+
1453
+ `applyRow(root, row)`, exported by `@loadbare/app`, fills `root` from one
1454
+ row, the same operation that fills a live row.
1179
1455
 
1180
1456
  ## Internal linkage
1181
1457
 
@@ -1209,16 +1485,16 @@ Loadbare/app owns every file name and pattern in this table. A file so
1209
1485
  named carries its meaning wherever it sits in the `--src` tree, so an
1210
1486
  application must not use one of these names for anything else.
1211
1487
 
1212
- | File | How many | Holds | Defined in |
1213
- | ----------------------- | ---------------------- | -------------------------------------------------------- | ------------------------------------------------------- |
1214
- | `chrome.html` | Exactly one | The application's one HTML document | [Chrome](#chrome) |
1215
- | `imports.ts` | Zero or one | Default-exports an array of widget library package names | [Imported widget libraries](#imported-widget-libraries) |
1216
- | `<stub>.page.html` | One per page | One page's markup, as a fragment | [Pages](#pages) |
1217
- | `<stub>.queries.ts` | Zero or one per page | That page's queries | [Page Queries](#page-queries) |
1218
- | `<stub>.requests.ts` | Zero or one per page | That page's requests | [Page Requests](#page-requests) |
1219
- | `<tag-name>.html` | Zero or one per widget | One widget definition, as a fragment | [Widgets](#widgets) |
1220
- | `<tag-name>.browser.ts` | Zero or one per widget | One widget's script, `.js` also accepted | [Widgets](#widgets) |
1221
- | `*.css` | Any number | A stylesheet, concatenated into `app.css` | [Running the builder](#running-the-builder) |
1488
+ | File | How many | Holds | Defined in |
1489
+ | ----------------------- | ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------- |
1490
+ | `chrome.html` | Exactly one | The application's one HTML document | [Chrome](#chrome) |
1491
+ | `imports.ts` | Zero or one | Default-exports an array of widget library package names | [Imported widget libraries](#imported-widget-libraries) |
1492
+ | `<stub>.page.html` | One per page | One page's markup, as a fragment | [Pages](#pages) |
1493
+ | `<stub>.queries.ts` | Zero or one per page | That page's queries | [Page Queries](#page-queries) |
1494
+ | `<stub>.requests.ts` | Zero or one per page | That page's requests | [Page Requests](#page-requests) |
1495
+ | `<tag-name>.html` | Zero or one per custom element | One element file, as a fragment | [Custom elements](#custom-elements) |
1496
+ | `<tag-name>.browser.ts` | Zero or one per custom element | One custom element's script, `.js` also accepted | [Custom elements](#custom-elements) |
1497
+ | `*.css` | Any number | A stylesheet, concatenated into `app.css` | [Running the builder](#running-the-builder) |
1222
1498
 
1223
1499
  ### Build outputs
1224
1500
 
@@ -1230,7 +1506,7 @@ only when some origin has a stylesheet, `pages.ts` only when some page has a
1230
1506
  | Path | Written | Holds | Read by | Defined in |
1231
1507
  | ------------------ | ----------------- | ------------------------------------------------------------------ | ---------- | ------------------------------------------- |
1232
1508
  | `/app.html` | Always | The chrome, built | The server | [Chrome](#chrome) |
1233
- | `/client.js` | Always | `<lb-hub>` and every widget the application uses | The chrome | [Chrome](#chrome) |
1509
+ | `/client.js` | Always | `<lb-hub>` and every custom element the application uses | The chrome | [Chrome](#chrome) |
1234
1510
  | `/client-entry.ts` | Always | The generated entry `client.js` is bundled from | Nothing | [Running the builder](#running-the-builder) |
1235
1511
  | `/app.css` | With a stylesheet | Every stylesheet in the cascade, concatenated | The chrome | [Chrome](#chrome) |
1236
1512
  | `/pages.ts` | With page data | The one `createHub()` call, over every page's queries and requests | The server | [The Express server](#the-express-server) |
@@ -1240,57 +1516,65 @@ only when some origin has a stylesheet, `pages.ts` only when some page has a
1240
1516
  Loadbare/app owns every key in this table. A widget library declares them.
1241
1517
  An application never does.
1242
1518
 
1243
- | Key | Value | Means | Defined in |
1244
- | ------------------ | ------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------- |
1245
- | `loadbare.widgets` | A path inside the package | Where this package's widgets are. Absent means the whole installed directory | [Imported widget libraries](#imported-widget-libraries) |
1519
+ | Key | Value | Means | Defined in |
1520
+ | ------------------ | ------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------- |
1521
+ | `loadbare.widgets` | A path inside the package | Where this package's custom elements are. Absent means the whole installed directory | [Imported widget libraries](#imported-widget-libraries) |
1246
1522
 
1247
1523
  ### Reserved namespaces
1248
1524
 
1249
1525
  Loadbare/app owns every namespace in this table. Ownership of a name and
1250
1526
  ownership of its behavior are stated separately, because they differ.
1251
1527
 
1252
- | Namespace | Applies to | Loadbare owns | Defined in |
1253
- | --------- | -------------------------- | -------------------------------------------- | ----------------------------------------------- |
1254
- | `lb-*` | HTML attributes | The names and their behavior | [The lb-* namespace](#the-lb--namespace) |
1255
- | `exp-*` | HTML attributes | The behavior; widget authors pick the values | [Build time parameters](#build-time-parameters) |
1256
- | `lb*` | Methods on custom elements | The names and their behavior | [Row hooks](#row-hooks) |
1528
+ | Namespace | Applies to | Loadbare owns | Defined in |
1529
+ | --------- | ----------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------- |
1530
+ | `lb-*` | Attributes, their values, queries, columns of `lb-` queries, requests, custom elements, events | The names and their behavior | [The lb-* namespace](#the-lb--namespace) |
1531
+ | `exp-*` | Attributes on a custom element | The behavior; element file authors pick the names | [Build time parameters](#build-time-parameters) |
1532
+ | `lb*` | Methods on custom elements | The names and their behavior | [Row hooks](#row-hooks) |
1257
1533
 
1258
1534
  #### The lb-* namespace
1259
1535
 
1260
- Every HTML attribute beginning with `lb-` belongs to Loadbare/app. An
1261
- application writes the ones this table names and invents none of its own,
1262
- because a name Loadbare has not defined today it may define tomorrow.
1263
-
1264
- The prefix reaches past HTML. A query name cannot begin with `lb-` either,
1265
- which is what keeps the three operations apart from an application's own
1266
- actions on the wire — see [Page Queries](#page-queries).
1267
-
1268
- Each attribute is defined in one section, and this table says which.
1269
-
1270
- | Attribute | Written by | Defined in |
1271
- | ----------------- | ------------- | --------------------------------------------------- |
1272
- | `lb-list` | Developer | [Binding](#binding) |
1273
- | `lb-row` | Developer | [Binding](#binding) |
1274
- | `lb-cell` | Developer | [Binding](#binding) |
1275
- | `lb-show` | Developer | [Displaying by condition](#displaying-by-condition) |
1276
- | `lb-key` | Developer | [Binding](#binding) |
1277
- | `lb-key-value` | Hub | [Binding](#binding) |
1278
- | `lb-value` | Hub | [How a value lands](#how-a-value-lands) |
1279
- | `lb-action` | Developer | [Requests](#requests) |
1280
- | `lb-nav-link` | Developer | [Links](#links) |
1281
- | `lb-query-parm` | Developer | [Query parms](#query-parms) |
1282
- | `lb-query-parm-push` | Developer | [Query parms](#query-parms) |
1283
- | `lb-pending` | Hub | [Request state](#request-state) |
1284
- | `lb-error` | Hub | [Request state](#request-state) |
1285
- | `lb-row-count` | Hub | [Request state](#request-state) |
1286
- | `lb-unknown-page` | Developer | [Chrome](#chrome) |
1287
- | `lb-slot` | Widget author | [Slots and templates](#slots-and-templates) |
1288
- | `lb-template` | Widget author | [Slots and templates](#slots-and-templates) |
1289
- | `lb-page` | Builder | [Pages](#pages) |
1290
-
1291
- An attribute the builder does not recognize is left alone today. Refusing
1292
- one is on the list of validations still to land — see
1293
- [The builder](#the-builder).
1536
+ Every name beginning with `lb-` belongs to Loadbare/app — see
1537
+ [Names](#names). An application writes the attributes this section names,
1538
+ and invents none of its own, because a name Loadbare has not defined today
1539
+ it may define tomorrow.
1540
+
1541
+ | Attribute | Written by | On | Defined in |
1542
+ | ----------------- | ------------- | ----------------------------------- | --------------------------------------------- |
1543
+ | `lb-query` | Developer | Any element | [Landing](#landing) |
1544
+ | `lb-column` | Developer | Any element | [Landing](#landing) |
1545
+ | `lb-show` | Developer | Any element but a `<template>` | [Displaying by condition](#displaying-by-condition) |
1546
+ | `lb-request` | Developer | Any element | [Requests](#requests) |
1547
+ | `lb-url-link` | Developer | An `<a>` | [Links](#links) |
1548
+ | `lb-url-push` | Developer | An element with `lb-request` | [Query parms](#query-parms) |
1549
+ | `lb-url-unknown` | Developer | A `<dialog>` inside `<lb-hub>` | [An unknown page](#an-unknown-page) |
1550
+ | `lb-exp-slot` | Developer | One element in an element file | [Slots and templates](#slots-and-templates) |
1551
+ | `lb-exp-template` | Developer | An element file, and a `<template>` | [Slots and templates](#slots-and-templates) |
1552
+ | `lb-column-value` | Hub | Every element set from a column | [How a column lands](#how-a-column-lands) |
1553
+ | `lb-key-value` | Hub | A live row, and an element a `row` lands on | [Rows](#rows) |
1554
+ | `lb-row-live` | Hub | A live row | [Rows](#rows) |
1555
+ | `lb-query-row-count` | Hub | An element with a row template | [Rows](#rows) |
1556
+ | `lb-request-pending` | Hub | The element that issued a request | [Request state](#request-state) |
1557
+ | `lb-request-error` | Hub | The element that issued a request | [Request state](#request-state) |
1558
+ | `lb-show` on a `<template>` | Hub and builder | Where an absent element stands | [Displaying by condition](#displaying-by-condition) |
1559
+ | `lb-page` | Builder | A page's `<template>` | [Page HTML](#page-html) |
1560
+ | `lb-page-title` | Builder | A page's `<template>` | [Page HTML](#page-html) |
1561
+
1562
+ The builder refuses any other `lb-` attribute in markup — see
1563
+ [The markup checks](#the-markup-checks).
1564
+
1565
+ ### Reserved queries
1566
+
1567
+ | Query | Kind | Key | Columns | Defined in |
1568
+ | -------- | ----- | --------- | --------------------------------------------------------------- | ------------------- |
1569
+ | `lb-url` | `row` | `lb-path` | `lb-path`, `lb-page-label`, `lb-page-unknown`, every query parm | [The URL](#the-url) |
1570
+
1571
+ ### Reserved request names
1572
+
1573
+ | Request name | Runs under `crud` | Defined in |
1574
+ | --------------- | ----------------- | ----------------------------------- |
1575
+ | `lb-row-insert` | `rowInsert` | [The request names](#the-request-names) |
1576
+ | `lb-row-update` | `rowUpdate` | [The request names](#the-request-names) |
1577
+ | `lb-row-delete` | `rowDelete` | [The request names](#the-request-names) |
1294
1578
 
1295
1579
  ### Reserved tags
1296
1580
 
@@ -1306,16 +1590,55 @@ never defines them.
1306
1590
  Loadbare/app owns every DOM event in this table, both the name and what its
1307
1591
  `detail` carries.
1308
1592
 
1309
- | Event | Dispatched from | Bubbles | Cancelable | Defined in |
1310
- | ------------ | ---------------------- | ------- | ---------- | --------------------------------------- |
1311
- | `lb-request` | The element that acted | Yes | No | [The request event](#the-request-event) |
1312
-
1313
- ### Reserved attributes
1314
-
1315
- Loadbare/app owns every attribute in this table, both the name and its
1316
- behavior.
1317
-
1318
- | Attribute | Written on | Takes a value | Defined in |
1319
- | ----------------- | ----------------------------------- | ------------- | ----------------- |
1320
- | `lb-unknown-page` | A `<dialog>` in chrome | No | [Chrome](#chrome) |
1321
- | `lb-page` | A `<template>`, by the builder only | Yes | [Pages](#pages) |
1593
+ | Event | Dispatched from | Bubbles | Cancelable | Defined in |
1594
+ | ------------ | -------------------------------- | ------- | ---------- | --------------------------------------- |
1595
+ | `lb-request` | The element that committed | Yes | No | [The request event](#the-request-event) |
1596
+
1597
+ ### Reserved methods
1598
+
1599
+ | Method | On | Defined in |
1600
+ | -------------- | -------------------------------------------------- | ----------------------- |
1601
+ | `lbPlaceRow` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
1602
+ | `lbRowsLanded` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
1603
+
1604
+ ### Constants
1605
+
1606
+ `@loadbare/app/constants` exports every name above. Custom element code
1607
+ imports them rather than writing a string.
1608
+
1609
+ | Constant | Value |
1610
+ | ------------------------- | --------------------------------------- |
1611
+ | `ATTR_QUERY` | `lb-query` |
1612
+ | `ATTR_COLUMN` | `lb-column` |
1613
+ | `ATTR_SHOW` | `lb-show` |
1614
+ | `ATTR_COLUMN_VALUE` | `lb-column-value` |
1615
+ | `ATTR_KEY_VALUE` | `lb-key-value` |
1616
+ | `ATTR_ROW_LIVE` | `lb-row-live` |
1617
+ | `LIVE_ROW` | `[lb-row-live]`, the selector for a live row |
1618
+ | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
1619
+ | `ATTR_REQUEST` | `lb-request` |
1620
+ | `ATTR_REQUEST_PENDING` | `lb-request-pending` |
1621
+ | `ATTR_REQUEST_ERROR` | `lb-request-error` |
1622
+ | `ATTR_URL_LINK` | `lb-url-link` |
1623
+ | `ATTR_URL_PUSH` | `lb-url-push` |
1624
+ | `ATTR_URL_UNKNOWN` | `lb-url-unknown` |
1625
+ | `ATTR_EXP_SLOT` | `lb-exp-slot` |
1626
+ | `ATTR_EXP_TEMPLATE` | `lb-exp-template` |
1627
+ | `ATTR_PAGE` | `lb-page` |
1628
+ | `ATTR_PAGE_TITLE` | `lb-page-title` |
1629
+ | `DEVELOPER_ATTRIBUTES` | The seven attributes a developer writes |
1630
+ | `LB_EVENT_NAME` | `lb-request` |
1631
+ | `LB_RESERVED_PREFIX` | `lb-` |
1632
+ | `REQUEST_ROW_INSERT` | `lb-row-insert` |
1633
+ | `REQUEST_ROW_UPDATE` | `lb-row-update` |
1634
+ | `REQUEST_ROW_DELETE` | `lb-row-delete` |
1635
+ | `LB_ROW_REQUESTS` | All three request names |
1636
+ | `KIND_ROW` | `row` |
1637
+ | `KIND_ROWS` | `rows` |
1638
+ | `URL_QUERY` | `lb-url` |
1639
+ | `URL_COLUMN_PATH` | `lb-path` |
1640
+ | `URL_COLUMN_PAGE_LABEL` | `lb-page-label` |
1641
+ | `URL_COLUMN_PAGE_UNKNOWN` | `lb-page-unknown` |
1642
+ | `HUB_TAG_NAME` | `lb-hub` |
1643
+ | `LB_ENDPOINT` | `/lb` |
1644
+ | `LB_REQUEST_TIMEOUT_MS` | `10000` |