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