@loadbare/app 0.18.2 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +17 -17
  2. package/dist/build/assemble.d.ts +7 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +81 -11
  5. package/dist/build/assemble.js.map +1 -1
  6. package/dist/build/cli.d.ts +3 -0
  7. package/dist/build/cli.d.ts.map +1 -1
  8. package/dist/build/cli.js +6 -1
  9. package/dist/build/cli.js.map +1 -1
  10. package/dist/build/declared.d.ts +25 -0
  11. package/dist/build/declared.d.ts.map +1 -0
  12. package/dist/build/declared.js +62 -0
  13. package/dist/build/declared.js.map +1 -0
  14. package/dist/build/pages.d.ts +6 -0
  15. package/dist/build/pages.d.ts.map +1 -1
  16. package/dist/build/pages.js +32 -12
  17. package/dist/build/pages.js.map +1 -1
  18. package/docs/comparison.md +697 -484
  19. package/docs/departures.md +63 -0
  20. package/docs/miswarts.md +140 -0
  21. package/docs/opinions.md +155 -0
  22. package/docs/reference/builder.md +20 -4
  23. package/docs/reference/chrome.md +45 -10
  24. package/docs/reference/css.md +0 -1
  25. package/docs/reference/custom-elements.md +3 -25
  26. package/docs/reference/data-binding.md +265 -48
  27. package/docs/reference/names.md +250 -0
  28. package/docs/reference/overview.md +6 -0
  29. package/docs/reference/page-files.md +77 -9
  30. package/docs/reference/server.md +54 -1
  31. package/docs/roadmap.md +55 -43
  32. package/docs/testing.md +62 -28
  33. package/docs/theory.md +348 -108
  34. package/docs/tutorials/040-displaying-data.md +1 -1
  35. package/package.json +1 -1
  36. package/skills/loadbare-app/SKILL.md +19 -13
  37. package/skills/loadbare-app/references/builder.md +20 -4
  38. package/skills/loadbare-app/references/chrome.md +45 -10
  39. package/skills/loadbare-app/references/css.md +0 -1
  40. package/skills/loadbare-app/references/custom-elements.md +3 -25
  41. package/skills/loadbare-app/references/data-binding.md +265 -48
  42. package/skills/loadbare-app/references/miswarts.md +140 -0
  43. package/skills/loadbare-app/references/names.md +250 -0
  44. package/skills/loadbare-app/references/overview.md +6 -0
  45. package/skills/loadbare-app/references/page-files.md +77 -9
  46. package/skills/loadbare-app/references/server.md +54 -1
  47. package/docs/TECHREF-1.0.md +0 -2312
  48. package/docs/possible-ideas.md +0 -314
  49. package/docs/theory-old.md +0 -839
  50. package/docs/what-does-loadbare-extend.md +0 -124
  51. package/skills/loadbare-app/references/TECHREF-1.0.md +0 -2312
@@ -1,2312 +0,0 @@
1
- # What 1.0 release means
2
-
3
- This is the complete technical reference to the upcoming Release 1.0.
4
-
5
- Method-of-work is to continually revise this document to what we want
6
- to be true, then revise the code, docs, and tests to ensure it is true.
7
-
8
- ## Blockers
9
-
10
- We cannot declare 1.0 until we determine if these blockers can be
11
- added later w/o breaking changes. If we are reasonably confident
12
- that they can be decided later w/o breaking changes, then we rename
13
- them to "Out of Scope" or move them to the roadmap. Otherwise,
14
- they must be resolved before we declare 1.0.
15
-
16
- ### Nested queries
17
-
18
- Imagine an HTML `<select>` that shows a fixed set of choices on every row of
19
- a table, such as `customer_type` for a table of customers. When every
20
- customer may take every customer type, the system as written today is
21
- fine, because every `<select>` receives the same rows.
22
-
23
- But the allowed values may depend on other values in the row.
24
-
25
- - **Decide whether a nested query's rows may depend on its row.** A nested
26
- query has one answer, which every live row receives. See
27
- [Master-detail](#master-detail) for what a nested query is for.
28
-
29
- ```html
30
- <tbody lb-query="accounts">
31
- <template><tr>
32
- <td lb-column="name"></td>
33
- <td><select lb-query="statuses" lb-column="status"><template><option lb-column="label"></option></template></select></td>
34
- </tr></template>
35
- </tbody>
36
- ```
37
-
38
- ### The server API
39
-
40
- - **Land `staticRoutes`.** It closes the last place where an
41
- application hand-writes facts the builder owns.
42
-
43
- ### The builder
44
-
45
- - **Adopt a configuration file and fold `imports.ts` into it.** It holds
46
- `out`, `minify` and `imports`, and lives where `imports.ts` lives now, so a
47
- repository with several applications gets one per application. `--src`
48
- stays a flag, since it is what locates the file. State the precedence
49
- between a flag and a key once. Its key names join the permanent surface,
50
- so this lands before 1.0 or not at all. It reserves a key for the
51
- roadmap's tree-shaking CSS by tag name.
52
- - **Confirm the three output names.** `app.html`, `client.js` and `app.css`
53
- are about to be fixed in `staticRoutes` as well as in the builder.
54
-
55
- ### The internal surface
56
-
57
- None of this is surface. It is listed here because trimming it is free
58
- today and breaking after 1.0.
59
-
60
- - **Audit what the hub does.** Every routine it runs answers to an `lb-*`
61
- attribute, a [reserved query](#reserved-queries), or a
62
- [reserved event](#reserved-events) or [method](#reserved-methods). Trim
63
- whatever does not, and leave what stays clean: no dead code, no routine
64
- written twice, no comment the code contradicts.
65
- - **Keep the rest as constants.** The endpoint, `index` and the timeout are
66
- linkage between the builder and the hub rather than facts an application
67
- varies. Adding a knob for any of them buys a second shape of application
68
- to reason about.
69
-
70
- ## The build
71
-
72
- ### Running the builder
73
-
74
- ```
75
- loadbare-app-build [--src src] [--out dist] [--watch] [--minify]
76
- ```
77
-
78
- `--src` names the tree the builder scans, and is defined under
79
- [What the builder scans](#what-the-builder-scans).
80
-
81
- `--out` names the directory the builder writes into, and defaults to `dist`.
82
- Every output path in this document is written relative to it, so an
83
- application that passes `--out` reads `dist/app.html` here as
84
- `<out>/app.html`. See [Build outputs](#build-outputs).
85
-
86
- `--watch` builds once, then rebuilds on every change to an `.html`, `.ts` or
87
- `.css` file under `--src`. A build that throws is reported and the watch
88
- continues. There is no dev server and no browser reload.
89
-
90
- `--minify` minifies `client.js` and `app.css`. It leaves `app.html` alone,
91
- which stays markup an application can read.
92
-
93
- The builder bundles `client.js` from a `client-entry.ts` it generates into
94
- `--out` and leaves there. Nothing reads it after the bundle, and an
95
- application neither writes nor imports it.
96
-
97
- Every `.css` file in every origin joins `app.css`. There is no custom element, page
98
- or chrome stylesheet: the builder concatenates them all, with nothing added,
99
- removed or scoped, so what ships is what was authored. Within one origin
100
- they are ordered by filename, the full path breaking a tie, and a
101
- subdirectory never affects the order. Origins follow the cascade — this
102
- package's own custom elements, then each package named in `imports.ts`, then `--src`
103
- last — so an application's own stylesheet always lands after the ones it
104
- imported. A file named `00-global.css` sorts first by saying so.
105
-
106
- ### What the builder scans
107
-
108
- #### The current project
109
-
110
- The builder scans one directory named by its `--src` parameter,
111
- which defaults to `src/`.
112
-
113
- The sections below often state something like, "anywhere in
114
- the `--src` tree", because Loadbare/app assigns semantic meaning
115
- to file names, not subdir names.
116
-
117
- #### Imported widget libraries
118
-
119
- Any number of widget libraries can be used. They are named
120
- in the file `imports.ts`, located anywhere in builder `--src`.
121
- A project can leave out `imports.ts`, or have exactly one, but
122
- two or more is an error.
123
-
124
- ```ts
125
- // imports.ts, anywhere in --src
126
- export default ["@namespace/widget-library"];
127
- ```
128
-
129
- If the named package's `package.json` declares where the widgets
130
- are, Loadbare scans that. Otherwise it scans the entire installed
131
- directory. The declaration, if present, would look like this:
132
-
133
- ```json
134
- "loadbare": { "widgets": "./dist" }
135
- ```
136
-
137
- ### File names and the builder
138
-
139
- The builder finds a file by its name. A subdirectory carries no meaning, so
140
- the whole of `--src` is one flat namespace, as stated in
141
- [What the builder scans](#what-the-builder-scans).
142
-
143
- Three kinds of file hold HTML.
144
-
145
- | File | Holds | Found in |
146
- | ------------------ | ----------------------------------------- | ------------ |
147
- | `chrome.html` | The one HTML document | `--src` |
148
- | `<stub>.page.html` | One page's markup, as a fragment | `--src` |
149
- | `<tag-name>.html` | One custom element's markup, as a fragment | Every origin |
150
-
151
- As stated in [Chrome](#chrome), `chrome.html` is a required singleton.
152
-
153
- As stated in [Pages](#pages), the `<stub>` of a `.page.html` is the path the
154
- page maps to. The builder puts all pages into `dist/app.html` as HTML `<template>` objects
155
- that carry `lb-page="<stub>"`, and `lb-page-title` when the page file has a
156
- `<title>`.
157
-
158
- An element file is exactly `<kebab-case-name>.html`. A
159
- [custom element](#custom-elements) is suitable for organizing large HTML
160
- trees, or adding custom behavior, or both. An element file is named for the
161
- tag it expands.
162
-
163
- Any other HTML file, one that is not `chrome.html`, or `<stub>.page.html` or
164
- `<kebab-case-name>.html` will either:
165
- - be an error if it looks like an element file with capitalization
166
- - be skipped
167
-
168
-
169
- ## HTML
170
-
171
- In a Loadbare/app application, HTML is static after the build, and once
172
- it is sent to the browser on initial page load, no HTML is ever sent
173
- again.
174
-
175
- The developer writes `lb-*` attributes in markup. Script never assigns
176
- them. The hub and the builder write only their stamps, and the hub writes
177
- the `name` of a radio button in a row template. See
178
- [The markup checks](#the-markup-checks).
179
-
180
- ### Expansion
181
-
182
- The builder executes a process we call "expansion". When it finds
183
- a custom element in the HTML it is processing, such as `<my-element>`,
184
- it looks for the element file `my-element.html`, and follows the expansion
185
- rules to place its contents.
186
-
187
- A custom element with no element file is left alone. A custom element
188
- with neither an element file nor a `.browser.ts` script is a build error, as
189
- stated in [Custom elements](#custom-elements).
190
-
191
- Each file is parsed on its own, and expansion joins them through the DOM,
192
- which applies none of HTML's rules for what an element may hold. The browser
193
- reads the shipped text by those rules, so a join it would rearrange, such as
194
- a definition's `<dialog>` inside a page's `<p>`, is a build error naming the
195
- file, the custom element and the path to what would move.
196
-
197
- #### Build time parameters
198
-
199
- A build time parameter is supplied as an attribute on a custom element,
200
- and is converted to a fixed value within the HTML by the builder.
201
-
202
- The attribute name carries an `exp-` prefix, as in `exp-label`, and
203
- the substitution locations use `{{label}}` (no prefix) inside the
204
- element file:
205
-
206
- ```html
207
- <!-- note-field.html, the element file -->
208
- <label>{{label}} <input readonly="{{readonly}}" /></label>
209
- ```
210
-
211
- ```html
212
- <!-- a page -->
213
- <note-field exp-label="Name"></note-field>
214
- ```
215
-
216
- ```html
217
- <!-- dist/app.html -->
218
- <note-field exp-label="Name"
219
- ><label>Name <input /></label
220
- ></note-field>
221
- ```
222
-
223
- An element file states a default with `{{name|default}}`, as in
224
- `{{button-label|OK}}`. Whitespace around the name and around the default is
225
- discarded. The default is a literal.
226
-
227
- | The attribute is assigned | Expansion defines | Result |
228
- | ------------------------- | ------------------- | -------------------------------- |
229
- | `exp-name="text"` | `{{name}}` | `text` |
230
- | `exp-name=""` | `{{name}}` | An empty string |
231
- | Nothing | `{{name\|default}}` | `default` |
232
- | Nothing | `{{name}}` | Nothing, and the attribute drops |
233
- | `exp-name` | No `{{name}}` | A build error |
234
-
235
- Attributes without the `exp-` prefix are ignored during expansion.
236
-
237
- A placeholder is the whole of an attribute value or the whole of a text node.
238
- `title="Hello {{name}}"` ships literally.
239
-
240
- A placeholder name carrying a capital is a build error.
241
-
242
- A parameter value cannot become markup.
243
-
244
- #### Slots and templates
245
-
246
- An element file may contain any number of named templates and optionally
247
- one slot.
248
-
249
- | Attribute | The builder |
250
- | ----------------- | ------------------------------------------------------- |
251
- | `lb-exp-slot` | Puts in it the rest of the custom element's content |
252
- | `lb-exp-template` | Puts in it the content of the template of the same name |
253
- | `exp-<name>` | Replaces `{{name}}` in the element file with its value |
254
-
255
- - `lb-exp-slot` goes on one element in an element file.
256
- - `lb-exp-template` goes on an element in an element file, and on a
257
- `<template>` in the custom element's content. Both name the same
258
- template.
259
- - `exp-<name>` goes on a custom element.
260
-
261
- This simplified element file for `lb-table` names two templates, `head` and
262
- `foot`, and marks `<tbody>` as the slot. A page supplies whichever templates
263
- it wants, and everything else it writes goes to the slot.
264
-
265
- ```html
266
- <!-- lb-table.html, the element file -->
267
- <table>
268
- <caption>
269
- {{caption}}
270
- </caption>
271
- <thead lb-exp-template="head"></thead>
272
- <tbody lb-exp-slot></tbody>
273
- <tfoot lb-exp-template="foot"></tfoot>
274
- </table>
275
- ```
276
-
277
- The page below supplies the `head` template and a row template. The row
278
- template names no destination, so it goes to the slot. This page writes no
279
- footer.
280
-
281
- ```html
282
- <!-- a page -->
283
- <lb-table lb-query="staff" exp-caption="Everyone, by team">
284
- <template lb-exp-template="head">
285
- <tr><th>Name</th><th>Role</th></tr>
286
- </template>
287
- <template>
288
- <tr><td lb-column="name"></td><td lb-column="role"></td></tr>
289
- </template>
290
- </lb-table>
291
- ```
292
-
293
- When a named template is expanded, the `<template>` tag is discarded and its
294
- contents are placed as children of the element that names it. Both
295
- `lb-exp-slot` and `lb-exp-template` are gone from what ships. The built
296
- document holds:
297
-
298
- ```html
299
- <!-- dist/app.html -->
300
- <lb-table lb-query="staff" exp-caption="Everyone, by team"
301
- ><table>
302
- <caption>
303
- Everyone, by team
304
- </caption>
305
- <thead>
306
- <tr>
307
- <th>Name</th>
308
- <th>Role</th>
309
- </tr>
310
- </thead>
311
- <tbody>
312
- <template>
313
- <tr>
314
- <td lb-column="name"></td>
315
- <td lb-column="role"></td>
316
- </tr>
317
- </template>
318
- </tbody>
319
- <tfoot></tfoot></table
320
- ></lb-table>
321
- ```
322
-
323
- These are build errors:
324
-
325
- - An element file with two destinations of one name
326
- - An element file with more than one `lb-exp-slot`
327
- - A page naming a template the element file does not have
328
- - A page giving two templates for one name
329
- - Content written in a custom element whose element file has no slot
330
-
331
- ### Names
332
-
333
- Loadbare owns the `lb-` prefix in several namespaces: attribute names, the
334
- values of its attributes, query names, the column names of its own queries,
335
- request names, custom element names and custom event names. It also owns
336
- custom element method names beginning with `lb`.
337
-
338
- The application should never name anything with the `lb-` prefix anywhere.
339
-
340
- `createHub` refuses, at startup, a page that declares a query or a handler
341
- whose name begins with `lb-`.
342
-
343
- ### Landing
344
-
345
- A query is a name for rows, of kind `row` or `rows`, with a key. The server
346
- declares a page's queries in `<stub>.queries.ts`, and answers every request,
347
- a page load included, with response items. A response item carries a query
348
- name, its kind, its key's column name, and one of a row, all rows, or a
349
- patch. See [The wire](#the-wire).
350
-
351
- The hub lands a response item on every element whose `lb-query` names its
352
- query.
353
-
354
- | Attribute | Names | The hub |
355
- | ----------- | -------- | -------------------------------------------------- |
356
- | `lb-query` | a query | Puts its rows in the element's content |
357
- | `lb-column` | a column | Sets the element from that column |
358
- | `lb-show` | a column | Removes it while the named column is null or false; `!column` reverses it |
359
-
360
- | Stamp | The hub stamps it with |
361
- | -------------------- | -------------------------------- |
362
- | `lb-column-value` | The value it set |
363
- | `lb-key-value` | The row's key |
364
- | `lb-row-live` | Nothing; it marks a live row |
365
- | `lb-query-row-count` | The number of live rows it shows |
366
-
367
- The markup names a query and the columns it shows. The kind and the key are
368
- the server's, and the markup states neither.
369
-
370
- The row template is the first `<template>` among the descendants of an
371
- element with `lb-query`, outside any nested `lb-query`, or, when that
372
- template is a [group template](#groups), the innermost template inside it.
373
- A live row is an element the hub cloned from a row template for one row,
374
- and carries `lb-row-live`.
375
-
376
- | Kind | Row template | The hub |
377
- | ------ | ------------ | ----------------------------------- |
378
- | `row` | no | Lands the row on the element itself |
379
- | `row` | yes | Lands one live row |
380
- | `rows` | yes | Lands one live row per row |
381
- | `rows` | no | Lands nothing |
382
-
383
- A `rows` query with no row template names the query without showing it,
384
- which is what an insert form naming the query it adds to is.
385
-
386
- The nearest ancestor row of an element is the nearest of: the element
387
- itself when it is a live row, an ancestor that is a live row, and an
388
- ancestor that carries `lb-query` of kind `row`. The hub reads `lb-column`
389
- and `lb-show` from the nearest ancestor row. A nested `lb-query` begins a
390
- new query, and what is inside it is that query's.
391
-
392
- An element's own `lb-query` names what the element holds, and its own
393
- `lb-column` reads from the row around it. So a
394
- `<select lb-query="statuses" lb-column="status">` in a live row of
395
- `accounts` holds the statuses as its options and shows that account's
396
- `status`. `lb-column` on an element with `lb-query` is undefined unless the
397
- element is a control.
398
-
399
- Name one query on more than one element to show it in more than one place.
400
- Every one of them receives it.
401
-
402
- A query that arrives with nowhere to land is reported to the console,
403
- unless its name begins with `lb-`.
404
-
405
- #### How a column lands
406
-
407
- | Element | The hub sets |
408
- | ------------------------------------------------ | ------------------------------ |
409
- | A control | Its `value` |
410
- | A custom element that is not a control | `lb-column-value` only |
411
- | Any other element | Its text content |
412
-
413
- A control is an `<input>`, `<select>` or `<textarea>`, or a form-associated
414
- custom element with a `value` property that fires `change`. A checkbox and
415
- a radio button are controls too, by the rules below. A
416
- form-associated custom element declares `static formAssociated = true`.
417
-
418
- The hub stamps `lb-column-value` with the value on every element it sets.
419
- It never replaces the content of a custom element, which the builder placed
420
- there from its element file. A custom element that is not a control
421
- renders `lb-column-value` itself.
422
-
423
- The hub gathers from the same controls it lands on, so a value read back is
424
- the one that landed.
425
-
426
- A checkbox's state is `checked`, not `value`: HTML's quirk, and so the
427
- hub's, so that every other part of the vocabulary treats it as any control.
428
- The hub checks it by the rule `lb-show` is on by: `null` and `false` are
429
- unchecked, and every other value is checked, with no string read. It
430
- gathers `"true"` or `"false"`, so an unchecked box still says so, where a
431
- native form submission leaves it out. The checkbox's own `value`
432
- attribute means nothing to the hub.
433
-
434
- ```html
435
- <input type="checkbox" lb-column="active" lb-request="lb-row-update" />
436
- ```
437
-
438
- A radio button's state is `checked` as well. The hub checks it when its
439
- `value` attribute equals the value as a string, and unchecks it otherwise,
440
- `null` included. Of the radio buttons naming one column, the hub gathers
441
- the `value` of the one checked, or `""` when none is.
442
-
443
- ```html
444
- <input type="radio" lb-column="shift" value="day" lb-request="lb-row-update" />
445
- <input type="radio" lb-column="shift" value="night" lb-request="lb-row-update" />
446
- ```
447
-
448
- An `<input>` of type `file` receives nothing, not even `lb-column-value`,
449
- and the hub reports it to the console. The hub does not gather one either.
450
-
451
- A `<select>` with no option for the value shows no selection.
452
-
453
- An element with `lb-column` and no ancestor row receives nothing. The hub
454
- still gathers from it.
455
-
456
- The value arrives as the query produced it, with no conversion, so the
457
- browser decides what a non-string looks like.
458
-
459
- A column holds one value. A set of values is a query of its own, never an
460
- array in a column.
461
-
462
- A custom element sees `attributeChangedCallback` for an `lb-column-value`
463
- already present when it upgrades, so it cannot tell a first landing from a
464
- refresh, and does not need to.
465
-
466
- #### Rows
467
-
468
- The hub matches each row to a live row by its key. It stamps
469
- `lb-key-value` on each live row, and on an element a `row` lands on itself.
470
- It stamps `lb-row-live` on each live row and nowhere else, so a `row`
471
- landed inside another query, such as a total in a table's foot, is not one
472
- of that query's rows. A stylesheet or a custom element selects live rows
473
- with `[lb-row-live]`, and never with `[lb-key-value]`.
474
-
475
- A `row` query answering `null` lands every column its element names as
476
- null, and the element loses `lb-key-value`. Under a row template, its live
477
- row leaves.
478
-
479
- A key is unique within a query. Two rows with one key in the same answer,
480
- or two live rows showing one key, are reported on the console.
481
-
482
- All rows decide membership: a live row whose key did not arrive leaves —
483
- see [What happened to a row](#what-happened-to-a-row). A patch touches only
484
- the rows it names, and leaves every other live row's contents alone.
485
-
486
- The hub places every live row by the query's [order](#order), immediately
487
- before the row template, or before its clone in the row's
488
- [group](#groups). A row moves only when the order puts it somewhere else,
489
- so all rows landing again unchanged move nothing. A live row already in the
490
- markup when the first answer lands is one of the query's rows.
491
-
492
- After every landing the hub stamps the element with `lb-query-row-count`,
493
- the number of live rows it shows. A row a [filter](#filters) hides is not
494
- counted. The server answers with rows and says
495
- nothing about how many survived, so this is the one fact a page cannot be
496
- sent. It makes an empty query a stylesheet rule:
497
-
498
- ```css
499
- [lb-query-row-count="0"] .roster-empty {
500
- display: revert;
501
- }
502
- ```
503
-
504
- A row missing its key column is reported to the console and not landed.
505
-
506
- When the focus is in a live row that placing moves, the hub puts it back on
507
- the same element and scrolls it into view. When that row leaves, or a
508
- filter hides it, the focus goes to the first element the user can operate
509
- in the first row showing.
510
-
511
- #### Order
512
-
513
- A `rows` query's order is a rule over its columns. The server declares it
514
- with the query, and the user may choose another:
515
-
516
- ```ts
517
- accounts: rows("id", (ctx) => ctx.db.accounts(), {
518
- order: "category_order,account_name",
519
- }),
520
- ```
521
-
522
- An order is columns separated by commas. A column written after `-` sorts
523
- descending. `lb-order`, on the element carrying `lb-query`, names a column
524
- of [`lb-choices`](#choices) holding the user's order, and while that column
525
- is not empty it replaces the declared order. With neither, rows show in the
526
- order the server sent them, and a patch's new rows go last.
527
-
528
- | Values | Compare |
529
- | -------------------- | ------------------------------------------ |
530
- | Two numbers | Numerically |
531
- | Two strings | As the user's language orders them |
532
- | Two booleans | `false` before `true` |
533
- | Two different types | Booleans, then numbers, then strings |
534
- | `null` or missing | After everything ascending, first descending |
535
-
536
- A number sent as a string sorts as a string, so a column the order reads
537
- numerically arrives as a JSON number, or its query declares its type.
538
- `types` names a type for any of a `rows` query's columns, one of `number`,
539
- `boolean` and `text`, and the column compares read as that type whatever
540
- JSON type its values arrive as:
541
-
542
- ```ts
543
- accounts: rows("id", (ctx) => ctx.db.accounts(), {
544
- order: "-balance",
545
- types: { balance: "number" },
546
- }),
547
- ```
548
-
549
- A value that is no value of its declared type compares as `null` does.
550
- `createHub` refuses at startup a type it does not know.
551
-
552
- The declared order and types travel once, on the answer that loads the
553
- page, and the hub keeps them with the query's answer for every answer after
554
- it, all rows or a patch. `readAs` in
555
- `@loadbare/app/types` is the rule the hub applies.
556
-
557
- Rows that compare equal keep the order they arrived in, and a patch's new
558
- rows come after the rows already showing.
559
-
560
- The hub sorts the rows it has, so a choice of order places them again with
561
- no round trip:
562
-
563
- ```html
564
- <div lb-query="lb-choices">
565
- <select lb-column="accounts_order" lb-request="lb-row-update">
566
- <option value="">By category</option>
567
- <option value="account_name">By name</option>
568
- </select>
569
- </div>
570
-
571
- <table lb-query="accounts" lb-order="accounts_order">
572
- ...
573
- </table>
574
- ```
575
-
576
- #### Filters
577
-
578
- `lb-filter`, on the element carrying `lb-query`, names the columns of its
579
- rows a choice may filter, separated by commas. The column of
580
- [`lb-choices`](#choices) of the same name holds the filter. A row that does
581
- not pass every filter is `hidden`, and a ticked `lb-select` in it is
582
- cleared. A group whose rows are all hidden is hidden. An empty choice
583
- filters nothing.
584
-
585
- ```html
586
- <table lb-query="accounts" lb-filter="account_name, balance">
587
- ```
588
-
589
- A filter is terms, each an operator and a value. A term begins wherever
590
- whitespace comes before an operator, so a value may hold spaces. Text with
591
- no operator is `*=`. A term with no value is left out. Every term must
592
- pass: `>=2026-01-01 <=2026-03-31`.
593
-
594
- | Operator | Compares |
595
- | -------------------------- | ------------------------------------------------- |
596
- | `^=`, `$=`, `*=` | As text, ignoring case: begins with, ends with, contains |
597
- | `=`, `!=`, `<`, `<=`, `>`, `>=` | As the [order](#order) compares, with the value read as the column's type |
598
-
599
- The column's type is its declared [type](#order), if its query declares
600
- one. Otherwise it is a number for a JSON number, `true` or `false` for a
601
- boolean, and text otherwise. A value that is not of the column's type, and
602
- a column that is null or missing, pass only `!=`.
603
-
604
- `parseFilter`, `passes` and `passesTerm` in `@loadbare/app/types` are the
605
- rule the hub applies.
606
-
607
- #### Groups
608
-
609
- A group is a run of rows sharing the order's leading term. A group
610
- template is a `<template lb-group>` in the row template's place, holding the
611
- group's heading and exactly one nested `<template>`, which is where the
612
- group's contents go: the next group template, or the row template. The nth
613
- group template breaks on the order's nth term, so groups nest to any depth.
614
-
615
- ```html
616
- <table lb-query="accounts">
617
- <thead><tr><th>Account</th></tr></thead>
618
- <template lb-group>
619
- <tbody>
620
- <tr><th scope="rowgroup" lb-column="category"></th></tr>
621
- <template><tr><td lb-column="account_name"></td></tr></template>
622
- </tbody>
623
- </template>
624
- </table>
625
- ```
626
-
627
- The hub clones the group template where the order breaks and puts the
628
- group's contents immediately before the clone of its nested template. The
629
- markup's nesting decides the shape:
630
-
631
- | The nested template is | The group's contents land | Example |
632
- | --------------------------------- | ----------------------------------- | -------------------------------- |
633
- | Inside the heading's element | Inside it | `<tbody>`, `<li>`, `<details>` |
634
- | Beside the heading | After the heading, as its siblings | A heading `<tr>` in one `<tbody>` |
635
-
636
- `<tbody>` and `<optgroup>` do not nest, so a table's levels below the first
637
- are heading rows beside their rows. Content after the nested template lands
638
- after the group's rows, which is where a group's footer goes.
639
-
640
- A group's heading is filled from its first row, the same way a live row is
641
- filled, so it shows any column of the row. The example breaks on
642
- `category_order` and shows `category`. The hub fills it from no other row.
643
-
644
- The hub stamps each top-level element of a group's clone with
645
- `lb-group-live`, `lb-group-column`, the column it breaks on, and
646
- `lb-group-value`, its value there. A row creates its group, and a group
647
- leaves with its last row. When the order changes, a group whose run goes
648
- on is kept and moved, and a group whose run ended leaves.
649
-
650
- A group template with no nested template is reported to the console. More
651
- group templates than the order has terms show each extra level as one
652
- group, and are reported once.
653
-
654
- #### Aggregates
655
-
656
- An aggregate attribute sets an element from the rows of the nearest group
657
- around it, or of the whole list outside any group.
658
-
659
- | Attribute | Sets the element to |
660
- | --------------- | --------------------------------------------- |
661
- | `lb-count` | The number of rows |
662
- | `lb-sum="col"` | The sum of `col` |
663
- | `lb-avg="col"` | The average of `col` |
664
- | `lb-min="col"` | The least value of `col`, by the [order](#order)'s comparison |
665
- | `lb-max="col"` | The greatest, likewise |
666
-
667
- ```html
668
- <tr><td><span lb-count></span> accounts</td><td lb-sum="balance"></td></tr>
669
- ```
670
-
671
- It lands as a column does, with `lb-column-value`. A sum and an average
672
- are exact decimals, read from JSON numbers and from strings holding a plain
673
- decimal, as Postgres sends `numeric`. A sum has as many places as the most
674
- precise value, and an average up to two more. `null` and empty values are
675
- left out, as SQL leaves them out. Any other value is left out and reported
676
- once. The hub does not format the result.
677
-
678
- An aggregate covers the rows showing, so a row a [filter](#filters) hides
679
- is left out. Where the true total
680
- matters beyond what the page shows, it is a row the server answers.
681
-
682
- #### What happened to a row
683
-
684
- Every change the hub makes to a list is one a stylesheet can see, so an
685
- application animates with CSS alone and the hub starts no animation.
686
-
687
- | Attribute | On | Means |
688
- | ------------------ | -------------------- | -------------------------------------------------- |
689
- | `lb-row-created` | A live row | Created when the hub last touched it |
690
- | `lb-row-changed` | A live row | Its values changed when the hub last touched it |
691
- | `lb-row-moved` | A live row | Its values changed, and it now stands elsewhere |
692
- | `lb-row-requested` | A live row | `created`, `changed` or `moved` by this page's request |
693
- | `lb-row-leaving` | A row on its way out | No longer live |
694
- | `lb-group-leaving` | A group on its way out | No longer live |
695
-
696
- The hub touches a row when an answer names it. `lb-row-created`,
697
- `lb-row-changed` and `lb-row-moved` are cleared when it next does, and one
698
- applied again is removed and added as two changes, so an animation keyed to
699
- it plays again. `lb-row-requested` is cleared at every landing of the
700
- row's query, and set only by a landing that answers a request from the page.
701
-
702
- A row or a group that leaves loses `lb-row-live` or `lb-group-live`, takes
703
- `lb-row-leaving` or `lb-group-leaving` and `inert`, and is removed once the
704
- animations running on it have finished, at once when there are none. A
705
- row leaves when all rows arrive without its key, or a patch drops it.
706
-
707
- A row is moved when its values changed and the nearest rows whose values did
708
- not change, before and after it, are not the ones it stood between. A row
709
- it passed is not stamped, and a change of the user's order, which changes no
710
- values, stamps none. A moved row stays live and moves.
711
-
712
- ```css
713
- [lb-row-created] { animation: arrive 200ms; }
714
- [lb-row-leaving] { animation: depart 200ms forwards; }
715
- ```
716
-
717
- A row never travels between the two places, since that takes measuring, and
718
- measuring takes script. Scrolling to a row is script too: a custom element
719
- reads `lb-row-requested` in [`lbRowsLanded`](#row-hooks).
720
-
721
- #### Displaying by condition
722
-
723
- `lb-show` names the column that decides whether an element is present.
724
- `null` and `false` are off, and every other value is on. No string is read,
725
- so `"false"` is on, and a query spells a condition as a boolean or a null.
726
-
727
- ```html
728
- <template>
729
- <tr>
730
- <td lb-column="name"></td>
731
- <td><button lb-request="lb-row-delete" lb-show="removable">Remove</button></td>
732
- </tr>
733
- </template>
734
- ```
735
-
736
- Write `!` before the column to reverse it: `lb-show="!chosen"` is present
737
- while `chosen` is null or false. One column then decides both of two
738
- elements, rather than a column and its opposite, which could disagree.
739
-
740
- It reads from the nearest ancestor row, as `lb-column` does, and on an
741
- element that carries `lb-query` the column belongs to the row around it. A
742
- row that does not carry the column leaves the element as it is.
743
- `lb-show` with no ancestor row is undefined.
744
-
745
- An element that is off is moved into a `<template lb-show="column">`
746
- standing where it stood, and moved back out when the column turns on. It is
747
- not rendered, focused, clicked, announced or gathered. It is moved and
748
- never rebuilt, so a custom element keeps its instance and a control keeps
749
- what was typed into it. Landing reaches into that template, so the element
750
- and everything inside it return current. A custom element hears nothing
751
- going in or coming out, and still receives `lb-column-value` while it is
752
- away.
753
-
754
- The builder ships every `lb-show` element already inside its template, so
755
- nothing conditional shows until its row lands. An absent element's template
756
- keeps its place among its siblings, and a position selector counts it.
757
-
758
- These are build errors: `lb-show` on a row template's root, `lb-show` with
759
- no `lb-query` around it, `lb-show` on a `<template>`, and `lb-show="!"` or
760
- `lb-show="!!column"`.
761
-
762
- Hiding is presentation, and the server still refuses what a request may not
763
- do.
764
-
765
- #### Master-detail
766
-
767
- A master is a `row` query and its detail is a `rows` query, as two names
768
- answered side by side. A column never holds rows.
769
-
770
- ```html
771
- <section lb-query="invoice">
772
- <h2 lb-column="number"></h2>
773
- <span lb-column="customer"></span>
774
- </section>
775
-
776
- <table>
777
- <tbody lb-query="invoiceLines">
778
- <template><tr><td lb-column="item"></td><td lb-column="amount"></td></tr></template>
779
- </tbody>
780
- </table>
781
- ```
782
-
783
- Many masters, each with its own detail, is one `rows` query of joined rows:
784
- each detail row carries its master's columns. A [group
785
- template](#groups) shows each master as a group, ordered by the master's
786
- key first.
787
-
788
- A query nested in another query's row template receives the same rows in
789
- every live row. That serves a picker offering the same choices on every
790
- row, and is not a way to show a different detail per row. A live row added
791
- later is filled from the nested query's last answer, so a request that adds
792
- one does not refresh the nested query to fill it.
793
-
794
- Which master a page shows is a query parm, which a query reads off `ctx`
795
- since it takes no argument from the browser — see [Query parms](#query-parms).
796
- When a write creates or removes the master, the server response moves the
797
- URL — see
798
- [When a server response moves the URL](#when-a-server-response-moves-the-url).
799
-
800
- ### Requests
801
-
802
- ---- UNEDITED ----
803
-
804
- | Attribute | Names | The hub |
805
- | ------------ | --------- | -------------------------------------------- |
806
- | `lb-request` | a request | Issues that request when the element commits |
807
-
808
- | Stamp | The hub stamps it |
809
- | -------------------- | ------------------------------------------ |
810
- | `lb-request-pending` | While the round trip is in flight |
811
- | `lb-request-error` | When the round trip fails, until the next |
812
-
813
- The request name is the attribute value of `lb-request`. It names either a
814
- request Loadbare provides or a declared request: a name the page declares
815
- under `handlers`, which runs application code.
816
-
817
- Every request has one shape, on the event and on the wire:
818
-
819
- ```
820
- { name: "lb-row-insert", query: "roster", values: { name: "Ann" } }
821
- { name: "lb-row-update", query: "roster", key: "42", values: { name: "Ann" } }
822
- { name: "lb-row-delete", query: "roster", key: "42" }
823
- { name: "mailRoster", query: "roster", key: "42" }
824
- { name: "lb-row-update", query: "roster", keys: ["42", "43"], values: { role: "lead" } }
825
- { name: "lb-row-insert", query: "invoice", values: { memo: "May" }, rows: { lines: [{ key: "1", values: { qty: "3" } }] } }
826
- ```
827
-
828
- The hub sends `query`, `key` and `values` as present, and `keys` in place of
829
- `key` for a request acting on the rows selected — see
830
- [Selecting rows](#selecting-rows).
831
-
832
- #### Committing
833
-
834
- An element commits on `submit` when it is a form, on `change` when it is a
835
- control, and on `click` otherwise.
836
-
837
- A submit button with a form owner never commits on `click`. On a form's
838
- `submit`, the hub issues the submitter's `lb-request` when it carries one,
839
- and the form's otherwise, the way `formaction` replaces `action`.
840
-
841
- A click on a descendant of an element carrying `lb-request` commits that
842
- element, as long as the element is inside `<lb-hub>` and no interactive
843
- content lies between them. Interactive content is HTML's: `<a href>`,
844
- `<button>`, `<input>` other than hidden, `<select>`, `<textarea>`, `<label>`,
845
- `<details>`, `<iframe>`, `<embed>`, `<audio controls>`, `<video controls>`
846
- and `<img usemap>`, plus any form-associated custom element. A click on
847
- one of them belongs to it: a click into an input inside a deletable row
848
- focuses the input and deletes nothing.
849
-
850
- The hub ignores a commit on an element carrying `lb-request-pending`.
851
-
852
- #### Gathering
853
-
854
- The hub gathers values when it issues a request. It gathers each control
855
- value under the column its `lb-column` names.
856
-
857
- | Attribute | The hub |
858
- | -------------- | ------------------------------------------- |
859
- | `lb-query` | Issues the request for it |
860
- | `lb-column` | Gathers the control value under that column |
861
- | `lb-key-value` | Takes `key` from it |
862
-
863
- - When the element carrying `lb-request` has `lb-column`, the hub gathers
864
- its value alone, and sends `values` with one member.
865
- - Otherwise, when the element is a `<form>`, the hub gathers every
866
- `lb-column` whose form owner is the element.
867
- - Otherwise, when the element is in a live row, the hub gathers every
868
- `lb-column` in that live row.
869
- - Otherwise the hub gathers every `lb-column` whose form owner is the
870
- element's form owner.
871
-
872
- The form owner includes a control that names the form with the HTML `form`
873
- attribute from anywhere in the document, which is how a table row's
874
- controls belong to a form that cannot wrap a `<tr>`:
875
-
876
- ```html
877
- <form id="add-member" lb-request="lb-row-insert"></form>
878
- <table lb-query="roster">
879
- <tbody>
880
- <template>
881
- <tr><td lb-column="name"></td><td lb-column="role"></td></tr>
882
- </template>
883
- </tbody>
884
- <tfoot>
885
- <tr>
886
- <td><input lb-column="name" form="add-member" /></td>
887
- <td>
888
- <input lb-column="role" form="add-member" />
889
- <button form="add-member">Add</button>
890
- </td>
891
- </tr>
892
- </tfoot>
893
- </table>
894
- ```
895
-
896
- The hub gathers from controls only. It skips a control whose nearest
897
- ancestor `lb-query` is a descendant of the live row or form, since that
898
- control belongs to a nested query, except that a form gathers the rows of a
899
- query nested in its own — see [A form over a set of rows](#a-form-over-a-set-of-rows). An element carrying `lb-query` and
900
- `lb-column` both, such as a picker, belongs to the row around it and is
901
- gathered.
902
-
903
- The hub issues the request for the nearest ancestor `lb-query` of the
904
- controls it gathered, and takes `key` from their nearest ancestor row when
905
- that row is of the same query. When the gathered controls have different
906
- nearest ancestor `lb-query`, the hub reports an error and sends nothing.
907
-
908
- When it gathers nothing, the hub issues the request for the nearest
909
- ancestor `lb-query` of the element carrying `lb-request`, and takes `key`
910
- from that element's nearest ancestor row when that row is of the same
911
- query.
912
-
913
- #### A form over a set of rows
914
-
915
- A form gathers the live rows of a query nested directly in its own query,
916
- besides its own controls. Each goes as its key and the values of the
917
- controls in it, under the nested query's name:
918
-
919
- ```
920
- { name: "lb-row-insert", query: "paychecks", values: { pay_date: "2026-10-02" },
921
- rows: { deduction_types: [{ key: "401k", values: { amount: "50" } },
922
- { key: "dental", values: { amount: "" } }] } }
923
- ```
924
-
925
- The form's own query is the one it carries, or else the nearest around it.
926
- A nested query is gathered when its nearest ancestor `lb-query` is the
927
- form's own, so one level only: a query nested deeper shows the same rows in
928
- every row around it, and its keys would repeat. Every live row of it goes,
929
- whatever its values, and only controls inside a live row of it.
930
-
931
- An insert, an update and a declared request carry `rows`; a delete does
932
- not. A form whose only controls are in nested rows sends for its own query,
933
- keyed by the row landed on it or the row it is pointed at, with empty
934
- `values`. A live row gathers nothing nested.
935
-
936
- The handler for the form's query receives `rows` beside `values`, so the
937
- master and its details are written together. The server checks that
938
- `rows` holds string keys and string values by query, and ignores a request
939
- whose `rows` does not.
940
-
941
- A nested query answers once for the page, as anywhere — see
942
- [Master-detail](#master-detail). The form enters a new master with its
943
- details, or edits the master the page shows by a query parm, whose details
944
- the nested query reads from the same parm. A pointed form fills its own
945
- controls from the row it points at, and its nested rows stay the nested
946
- query's answer.
947
-
948
- #### A row not yet entered
949
-
950
- A row whose key is the empty string is a new one, not yet entered. It
951
- arises two ways: a `row` query answers with an empty key, as one reading
952
- the master's key from an empty query parm does, or `--lb-new` points a form
953
- (see [A form pointed at a row](#a-form-pointed-at-a-row)). Either way the
954
- element carries `lb-key-value=""`.
955
-
956
- The hub sends no `key` for it, whether the key came from where the element
957
- sits or from a request a custom element dispatched. An `lb-row-insert` and
958
- a declared request are sent without one. An `lb-row-update` and an
959
- `lb-row-delete` are not sent, because nothing is on record to change, and
960
- nothing is reported: it is the row's state, not a mistake. So a control
961
- saving its own column on `change` saves on a row on record, and on a new
962
- row only holds what a submit will gather. No ancestor sees a request not
963
- sent, and it gets no `lb-request-done`.
964
-
965
- An update or a delete with no row around it at all has no key either, and
966
- is reported to the console, as any request missing what it needs is.
967
-
968
- A field the request already carries is its issuer's and is kept. A custom
969
- element that dispatches its own request with `values` is not gathered over.
970
-
971
- #### The request names
972
-
973
- | Request name | Needs | Runs under `crud` |
974
- | --------------- | ------------------------ | ----------------- |
975
- | `lb-row-insert` | `query`, `values` | `rowInsert` |
976
- | `lb-row-update` | `query`, `key`, `values` | `rowUpdate` |
977
- | `lb-row-delete` | `query`, `key` | `rowDelete` |
978
-
979
- `keys` stands in for `key`. The hub issues one of these only when it has
980
- what the table lists. An
981
- `lb-row-insert` or `lb-row-update` that gathers nothing is not issued. A
982
- request that is not issued is reported to the console.
983
-
984
- Loadbare provides the handlers for these names: each runs the page's `crud`
985
- entry for the request's query. A declared request runs the application's
986
- handler under `handlers`, and the hub issues it with whatever it found.
987
-
988
- A request name beginning with `lb-` that is none of these three is refused
989
- by the builder in markup, and by the hub when a script dispatches it.
990
-
991
- For a query the hub serves, the hub answers the request itself, with no
992
- round trip. The hub serves `lb-url`, and answers `lb-row-update` for it —
993
- see [Query parms](#query-parms).
994
-
995
- #### A form pointed at a row
996
-
997
- A set of rows the user edits or adds to has one form on the page, naming
998
- the same query, outside every row. A button points it with HTML's
999
- `commandfor`, naming the form's `id`, and `command`:
1000
-
1001
- | Command | On a button | The hub |
1002
- | -------------- | --------------------------------- | ----------------------------------------- |
1003
- | `--lb-edit` | In a live row of the form's query | Fills the form from that row, keyed by it |
1004
- | `--lb-new` | Anywhere | Starts the form empty, for an insert |
1005
- | `--lb-release` | Anywhere | Lets the form go without saving |
1006
-
1007
- HTML requires a custom command to begin with `--`, and fires it as a
1008
- `command` event on the form, which the hub hears.
1009
-
1010
- ```html
1011
- <tbody lb-query="paychecks">
1012
- <template><tr>
1013
- <td lb-column="pay_date"></td>
1014
- <td><button type="button" commandfor="paycheck" command="--lb-edit">Edit</button></td>
1015
- </tr></template>
1016
- </tbody>
1017
- <button type="button" commandfor="paycheck" command="--lb-new">New</button>
1018
-
1019
- <dialog>
1020
- <form id="paycheck" lb-query="paychecks" lb-request="lb-row-update">
1021
- <input lb-column="pay_date" type="date" />
1022
- <button type="submit">Save</button>
1023
- <button type="submit" lb-request="lb-row-insert">Save new</button>
1024
- <button type="button" commandfor="paycheck" command="--lb-release">Cancel</button>
1025
- </form>
1026
- </dialog>
1027
- ```
1028
-
1029
- Which row a form edits is interaction state, not a view, so it is not a
1030
- query parm. The user edits only a row they can see, so Edit fills the form
1031
- from the row as the hub holds it, every column the query answered, with no
1032
- round trip. A row that must be fetched to be edited is another page, with
1033
- that row as its master.
1034
-
1035
- Pointing a form first empties it: every column it names lands as null, and
1036
- the form resets, so each control returns to its default. Edit then lands
1037
- the row on the form as on any element a `row` lands on, stamps the form
1038
- `lb-key-value` with the row's key, and stamps the row `lb-row-editing`.
1039
- Gathering then takes the key from the form. New stamps an empty key, making
1040
- the form [a row not yet entered](#a-row-not-yet-entered). Both focus the
1041
- form's first control.
1042
-
1043
- | The form's `lb-key-value` | The form is |
1044
- | ------------------------- | ------------------ |
1045
- | A key | Editing that row |
1046
- | Empty | Entering a new row |
1047
- | Absent | Not in use |
1048
-
1049
- A stylesheet shows the form's buttons by it, such as Save while it has a
1050
- key and Save new while the key is empty.
1051
-
1052
- A request from the form that succeeds lets it go, as `--lb-release` does:
1053
- the form empties, and both stamps go. One that fails leaves the form
1054
- pointed, as the user had it.
1055
-
1056
- When the form is inside a `<dialog>`, Edit and New call `showModal()` on it,
1057
- and letting go closes it. The dialog closing by any of HTML's means,
1058
- Escape, `command="close"` or `method="dialog"`, lets the form go. A form in
1059
- no dialog opens nothing; it is always showing, or a stylesheet shows it
1060
- while it carries `lb-key-value`.
1061
-
1062
- The form names a `rows` query and holds no row template, so that query's
1063
- answers land nothing on it, and what the user typed survives a refresh.
1064
-
1065
- A command the hub cannot carry out is reported to the console: Edit from
1066
- outside a live row of the form's query, Edit for a key the hub holds no row
1067
- for, and any command aimed at an element that is not a form carrying
1068
- `lb-query`.
1069
-
1070
- #### Selecting rows
1071
-
1072
- A checkbox carrying `lb-select` in a row template selects the live row it
1073
- sits in, and the hub stamps the row `lb-row-selected` while it is ticked.
1074
- It is not a column: it carries no `lb-column` or `lb-request`, lands
1075
- nothing, and is not gathered.
1076
-
1077
- A request for the query that names no one row, neither from inside one of
1078
- its rows nor from a form pointed at one, acts on the rows selected. When
1079
- any `lb-select` checkbox of that query is showing, the hub adds the keys of
1080
- those ticked as `keys`:
1081
-
1082
- ```
1083
- { name: "lb-row-update", query: "transactions", keys: ["4", "9"], values: { acct: "12" } }
1084
- ```
1085
-
1086
- - `lb-row-update` and `lb-row-delete` are sent with `keys`, and with none
1087
- ticked are not sent, silently, as for [a row not yet
1088
- entered](#a-row-not-yet-entered).
1089
- - A declared request is sent with `keys`, empty when none is ticked, and the
1090
- handler decides.
1091
- - `lb-row-insert` adds a row, and the selection is not its.
1092
- - With no `lb-select` checkbox of the query showing, the request is an
1093
- ordinary one, and carries no `keys`.
1094
-
1095
- The server runs the page's `rowUpdate` or `rowDelete` once for each key,
1096
- with that key, as for each row alone, so a handler written for one row
1097
- serves several. It lays the patches the runs return together, and runs the
1098
- refresh set once. A run returning `url()` is reported and ignored, since
1099
- several rows cannot each choose one view. A declared request receives
1100
- `keys` as sent.
1101
-
1102
- After the request succeeds, the hub clears every `lb-select` checkbox of the
1103
- query and the stamps with them. A failed request leaves the selection.
1104
-
1105
- Only a checkbox that is showing counts: one parked by `lb-show` is in
1106
- template content, out of reach. Parking one also clears its tick, so a
1107
- selection never waits out of sight to return stale.
1108
-
1109
- Which rows are selected is interaction state, as which row a form edits is.
1110
- A selecting mode the page keeps in the URL reaches the rows as a column the
1111
- query computes from the parm, which `lb-show` reads, since `lb-show` reads
1112
- from the row it sits in.
1113
-
1114
- #### The request event
1115
-
1116
- The hub dispatches every request as the bubbling `lb-request` event before
1117
- sending it, with the request as its `detail`. The event is dispatched from
1118
- the element that committed.
1119
-
1120
- A custom element uses the same event to issue a request of its own, with at
1121
- least `name` in its `detail`. A native element and a custom element
1122
- therefore produce identical events.
1123
-
1124
- The hub completes the request on the event's way down, so every ancestor
1125
- sees it whole on the way up. A request that cannot be issued is stopped
1126
- there. An ancestor may stop it too: the event is not cancelable, so an
1127
- interceptor calls `stopPropagation`, not `preventDefault`. This is what
1128
- makes a confirmation wrapper possible without the wrapped element knowing
1129
- about it.
1130
-
1131
- Once what the request brought back has landed, or its round trip has failed,
1132
- the hub calls `lbRequestDone(detail, origin)` on the committing element and
1133
- every custom element around it, innermost first (see
1134
- [Lifecycle](#lifecycle)), then dispatches the bubbling `lb-request-done`
1135
- event from the same element. Its `detail`, `HubRequestDone` in `@loadbare/app/types`, holds
1136
- the request as the hub sent it and `items`, every response item that landed
1137
- because of it, in order. An answer that moved the URL contributes its
1138
- `lb-url` item and the page load. `error` is set instead when the round trip
1139
- failed. By then `lb-request-pending` is gone, and a listener sees the page
1140
- as the answer left it. A request the hub or an ancestor stopped was never
1141
- sent, and gets no `lb-request-done`. An element the answer removed from the
1142
- document, such as the row a delete took away, dispatches the event where it
1143
- now is, and no ancestor it had on the page hears it.
1144
-
1145
- #### Request state
1146
-
1147
- The hub stamps `lb-request-pending` on the element that issued a request
1148
- while its round trip is in flight, together with `aria-busy="true"`, and
1149
- removes both when it settles. It stamps `lb-request-error` when the round
1150
- trip fails, and clears it when that element issues its next request.
1151
-
1152
- A custom element observes them by naming them in `observedAttributes`. On
1153
- plain HTML a stylesheet is the only consumer: dim a pending button, mark a
1154
- failed one.
1155
-
1156
- The hub ignores a commit on an element carrying `lb-request-pending`, so a
1157
- pending element is disabled in fact and a stylesheet only has to show it.
1158
-
1159
- #### Refusals
1160
-
1161
- A handler refuses a request by throwing `refusal()`, exported by
1162
- `@loadbare/app/server`, with words written for the user: a message for the
1163
- request as a whole, a message per column, or both.
1164
-
1165
- ```ts
1166
- throw refusal("That series already has paychecks entered.");
1167
- throw refusal({ amount: "Must be more than zero" });
1168
- throw refusal("Two fields need attention.", { amount: "…", pay_date: "…" });
1169
- ```
1170
-
1171
- It is thrown rather than returned, so it may come from anything the handler
1172
- calls, and rolls back whatever the application has open as any throw does.
1173
- `hubRoutes` answers it with status 422 and `{ refusal: { message?, columns? } }`.
1174
- Anything else thrown is a failure, answered 500 with no body, so no stack
1175
- or database message reaches the browser.
1176
-
1177
- The hub shows a refusal where it belongs:
1178
-
1179
- - It stamps the element that committed `lb-request-refused`, whose value is
1180
- the whole-request message, empty when there is none. A stylesheet shows
1181
- it with `attr()`.
1182
- - For each column, it calls `setCustomValidity(message)` on the control the
1183
- request gathered that column from, and `reportValidity()` on the first,
1184
- so the browser shows the message there and moves focus to it. A column
1185
- the request gathered no such control for is reported to the console.
1186
- - `lb-request-done` carries the refusal as `refusal`, beside `error`.
1187
-
1188
- A refusal is not a failure: `lb-request-error` is not stamped and nothing is
1189
- reported. It stands until the user edits any control the request gathered,
1190
- a form holding them resets, or the element that committed sends again;
1191
- then every message it put up is withdrawn at once. While a control carries
1192
- one, the browser holds its form's submit, as for any invalid control.
1193
-
1194
- A form-associated custom element takes a column's message when it defines
1195
- `setCustomValidity` and `reportValidity`; one wrapping a native control
1196
- passes both to it. A request for several rows takes the whole-request
1197
- message only.
1198
-
1199
- #### The round trip
1200
-
1201
- A request the hub sends has ten seconds to come back. Past that the hub
1202
- aborts it and treats it as a failure, since `fetch` imposes no deadline of
1203
- its own. An application cannot change the deadline.
1204
-
1205
- A round trip fails on a server error, on a network failure, or on that
1206
- deadline, and all three stamp `lb-request-error` on the element that issued
1207
- the request — see [Request state](#request-state). A refusal is not a
1208
- failure — see [Refusals](#refusals).
1209
-
1210
- After a successful `lb-row-insert` gathered from a form, the hub resets the
1211
- form, after it lands the response. A failed insert resets nothing, so the
1212
- entry can be corrected. An `lb-row-update` resets nothing: the row it sent
1213
- lands back on its controls.
1214
-
1215
- A reset of any form, whether the hub's, a reset button's or a script's,
1216
- returns its controls to their defaults, and the hub removes
1217
- `lb-column-value` from each of them. A cancelled reset keeps both.
1218
-
1219
- A page load that fails lands nothing and is reported to the console. A load
1220
- started by a request for `lb-url` stamps the element that issued it, as any
1221
- request stamps its element.
1222
-
1223
- A response answers for the URL its request was sent from, path and query
1224
- string both. One that returns after the URL has moved lands nothing, since
1225
- it describes what the user is no longer looking at: a request's answer,
1226
- including any `url()` it carries, and a page load overtaken by a newer one.
1227
- A write whose answer is dropped this way has still happened. Its
1228
- `lb-request-done` carries no items and no error, and an insert still resets
1229
- its form.
1230
-
1231
- ### The URL
1232
-
1233
- ---- UNEDITED ----
1234
-
1235
- The hub serves the query `lb-url`, of kind `row`, keyed by `lb-path`. It
1236
- lands wherever an element names it, like any server query.
1237
-
1238
- | Column | The hub sets it to |
1239
- | ----------------- | ---------------------------------------------- |
1240
- | `lb-path` | The path. The row's key |
1241
- | `lb-page-label` | The page's title, or its stub when it has none |
1242
- | `lb-page-unknown` | Whether no page's stub matches the path |
1243
- | any other name | That query parm |
1244
-
1245
- | Attribute | The hub |
1246
- | ---------------- | -------------------------------------------------------- |
1247
- | `lb-url-link` | On a plain primary click, sets `lb-path` from the `href` |
1248
- | `lb-url-push` | Pushes a history entry for this element's update |
1249
- | `lb-url-unknown` | Calls `showModal()` when `lb-page-unknown` is `true` |
1250
-
1251
- - `lb-url-link` goes on an `<a>`.
1252
- - `lb-url-push` goes on an element with `lb-request`.
1253
- - `lb-url-unknown` goes on a `<dialog>` inside `<lb-hub>`.
1254
-
1255
- The hub shows in `<main>` the page whose stub is `lb-path`. `/` is
1256
- `index`. When `lb-path` takes a new value, the hub loads the page: the
1257
- server runs its `onPageEnter`, then its queries. When a query parm takes a
1258
- new value, the hub reloads the page's queries, and keeps the page's DOM, so
1259
- live rows that come back keep their place.
1260
-
1261
- Once an entered page's queries have landed, the hub focuses the first
1262
- element in `<main>` the user can operate: not disabled, not in a closed
1263
- `<dialog>`, not `inert`, not `hidden`, not in an absent `lb-show` branch,
1264
- and one that takes the focus. Entering is a cold load, a new `lb-path`,
1265
- and Back or Forward to another page. A change of query parm enters no
1266
- page, and focus stays where it is, as it does when it is already in
1267
- `<main>`.
1268
-
1269
- The page's title is the text of the page file's `<title>`, which the builder
1270
- stamps on the page as `lb-page-title`. The hub sets the document title to
1271
- `lb-page-label` when it is not null.
1272
-
1273
- ```html
1274
- <header lb-query="lb-url">
1275
- <h1>Membership Roster</h1>
1276
- <h2 lb-column="lb-page-label"></h2>
1277
- </header>
1278
- ```
1279
-
1280
- #### What a URL names
1281
-
1282
- The path names a page, a place in the application. It never names a
1283
- resource, and Loadbare/app does not reproduce REST: `/accounts/42` is not a
1284
- way to reach account 42.
1285
-
1286
- The query string describes what that page has on screen:
1287
- `/accounts?acct=23&from=2026-09-09`. It never commands, and the server
1288
- remembers nothing, so a URL is a reproducible view. Reload it, bookmark it,
1289
- or mail it to someone, and what they see is what the sender saw.
1290
-
1291
- A page declares what it shows, its queries, and what it allows, its
1292
- requests. Loading a page runs its queries. A request names a query and,
1293
- where it has one, a key, taken from where the element sits, which is how the
1294
- database already names a row. Nothing is fetched by URL, so an application
1295
- designs no endpoints.
1296
-
1297
- That last is where query parms move Loadbare's position. A query still takes
1298
- no argument from the browser, and a parm arrives the way the session cookie
1299
- does, as part of the request the context is built from. But a user who
1300
- types `?acct=99999` influences what a query returns. A query parm is user
1301
- input, validated like any other where the application reads it, and a
1302
- per-session database role means an id outside the caller's reach finds
1303
- nothing.
1304
-
1305
- Loadbare is for applications, not sites. Every route is answered with the
1306
- same document, and a path that names no page is found out in the browser —
1307
- see [An unknown page](#an-unknown-page).
1308
-
1309
- #### Links
1310
-
1311
- An `<a>` carrying `lb-url-link` moves within the application. On a plain
1312
- primary click — the first button, no modifier key, not already handled — the
1313
- hub pushes a history entry for the `href`'s path and query string, and loads
1314
- the page there. Any other click, and any anchor without `lb-url-link`,
1315
- behaves like any other link, so leaving the application is the default and
1316
- staying in it is the opt-in.
1317
-
1318
- ```html
1319
- <nav>
1320
- <a href="/" lb-url-link>Home</a>
1321
- <a href="/members" lb-url-link>Members</a>
1322
- <a href="https://example.com/docs">Docs</a>
1323
- </nav>
1324
- ```
1325
-
1326
- The attribute takes no value.
1327
-
1328
- Path space is flat. A path such as `/members` links to the `members.*` files
1329
- on the server.
1330
-
1331
- The query string is kept. `/transactions?date_begin=2026-09-01` opens the
1332
- transactions page narrowed to those dates.
1333
-
1334
- The browser's Back and Forward load the page at the URL they arrive at, the
1335
- same way.
1336
-
1337
- #### Query parms
1338
-
1339
- A control that narrows what a page shows updates `lb-url` rather than
1340
- sending a request. Put it inside an element naming `lb-url`, with
1341
- `lb-column` naming the parm and `lb-request="lb-row-update"`:
1342
-
1343
- ```html
1344
- <div lb-query="lb-url">
1345
- <select lb-column="team" lb-request="lb-row-update">
1346
- <option value="">Every team</option>
1347
- <option value="Engines">Engines</option>
1348
- </select>
1349
- </div>
1350
- ```
1351
-
1352
- The hub answers the request itself. It sets the parms the request names in
1353
- the URL and leaves every other parm as it is, since one may have arrived by
1354
- link with no control on screen to say it again. An empty value takes the
1355
- parm out, so a URL is as long as the user has narrowed the page. A request
1356
- that leaves the URL as it was does nothing. `lb-page-label` and
1357
- `lb-page-unknown` are the hub's, and a request never sets them.
1358
-
1359
- The update replaces the current history entry: changing what a page shows is
1360
- not going anywhere, so Back leaves the page rather than walking back through
1361
- every choice. `lb-url-push` on the element carrying `lb-request` makes the
1362
- update push an entry instead.
1363
-
1364
- When `lb-path` takes a new value, the hub pushes a history entry, and the
1365
- new URL carries only the query parms the request names.
1366
-
1367
- Every load lands `lb-url`, and a parm the URL does not carry lands empty on
1368
- every element naming it as a column, so a control shows what the address
1369
- bar says after Back as well as after a reload. This is the one exception
1370
- to landing, where a row sets the columns it names and leaves the rest as
1371
- they were: the hub reads the column names under `lb-query="lb-url"` and
1372
- names each one in the row it lands.
1373
-
1374
- The hub sends the query parms with every round trip, a page load and a
1375
- request alike. The server passes them to `contextFor` — see
1376
- [The Express server](#the-express-server). Loadbare assigns no parm a
1377
- meaning: a column of `lb-url` whose name begins with `lb-` is never a query
1378
- parm. An order or a filter the hub applies to rows already on the page is
1379
- a [choice](#choices), not a parm.
1380
-
1381
- #### An unknown page
1382
-
1383
- When no page's stub matches the path, `lb-page-unknown` is `true` and
1384
- `lb-page-label` is null. The hub calls `showModal()` on the
1385
- `<dialog lb-url-unknown>`, where the chrome has one, and reports the path to
1386
- the console. A chrome with none shows nothing.
1387
-
1388
- `lb-url` lands before the page is looked up, so the dialog shows it by
1389
- naming it like any other element:
1390
-
1391
- ```html
1392
- <dialog lb-url-unknown lb-query="lb-url">
1393
- The URL <span lb-column="lb-path"></span> is not in this app.
1394
- </dialog>
1395
- ```
1396
-
1397
- A path that names no page is detected in the browser, after a successful
1398
- 200: every route gets the same document, so there is no server-delivered
1399
- 404.
1400
-
1401
- #### When a server response moves the URL
1402
-
1403
- Some query parms can only be known once a write has run. After an insert,
1404
- the key of the new row exists only on the server. After a delete, only the
1405
- server knows that the row the page was showing is gone.
1406
-
1407
- The usual web answer is Post/Redirect/Get: the server answers the write with
1408
- a redirect to a URL naming the result, and the browser makes a second request
1409
- to load it. Loadbare/app does the same work in one round trip.
1410
-
1411
- A handler returns `url()`, a new row for `lb-url`. The server loads the page
1412
- at the resulting URL, as a cold load of it would: `onPageEnter`, then every
1413
- query. The response carries the `lb-url` item followed by that load. The
1414
- hub writes the URL and then lands the load.
1415
-
1416
- ```ts
1417
- rowInsert: {
1418
- run: async (ctx, { values }) => {
1419
- const id = await ctx.db.addAccount(values);
1420
- return url({ acct: String(id) });
1421
- },
1422
- refresh: [],
1423
- },
1424
- ```
1425
-
1426
- - A column other than `lb-path` is a query parm. Parms the row does not
1427
- name are kept, and an empty value removes its parm.
1428
- - A row carrying `lb-path` for another page enters that page with only the
1429
- parms the row names, and pushes a history entry. Otherwise the update
1430
- replaces the history entry, or pushes one when the element that issued the
1431
- request carries `lb-url-push`.
1432
- - The row names at least one column, and every value is a string. Otherwise
1433
- the server warns, ignores the row, and runs the refresh set as usual.
1434
- - The refresh set does not run, and anything else the handler returned is
1435
- dropped. Both were answers for the URL the page is leaving.
1436
- - If loading the page fails, the write has still happened. The response
1437
- carries the `lb-url` item alone, and the hub loads the page itself. A
1438
- failure there is a page load's, and is not stamped on the element that
1439
- issued the request.
1440
- - A user who has moved by the time the response arrives, to another page or
1441
- to other query parms, keeps the URL they are on.
1442
-
1443
- `onPageEnter` may return `url()` the same way, to move the page before
1444
- anything shows, as an application restoring the parms the user last had
1445
- does. The page loads at the new URL in the same round trip, and
1446
- that load does not follow `onPageEnter` again. A `url()` leading to the
1447
- page and parms already in hand is ignored, so the page loads once. When to
1448
- restore is the application's rule, such as only when the URL carries no
1449
- parms of its own.
1450
-
1451
- ```ts
1452
- // The application put the parms on ctx in contextFor.
1453
- onPageEnter: async (ctx) => {
1454
- if (ctx.parms.size > 0) return;
1455
- const saved = await ctx.db.savedView("accounts");
1456
- if (saved) return url(saved);
1457
- },
1458
- ```
1459
-
1460
- Saving what the user has on screen is a declared request with
1461
- `refresh: []`, whose handler each page passes to a routine the
1462
- application's pages share. A custom element naming `lb-url` sees every
1463
- change of the URL and issues it.
1464
-
1465
- The page is loaded with a second context, built by `contextFor` from the new
1466
- parms — see [The Express server](#the-express-server).
1467
-
1468
- ### Choices
1469
-
1470
- The hub serves the query `lb-choices`, of kind `row`, keyed by `lb-path`,
1471
- with a column for each choice the user made for how rows show: an
1472
- [order](#order) or a [filter](#filters). The application names the
1473
- columns. It lands wherever an element names it, like any server query.
1474
-
1475
- A control inside `lb-query="lb-choices"` that sends `lb-row-update` makes a
1476
- choice. A request with `lb-row-update` for `lb-choices` or `lb-url` from
1477
- anywhere on the page is keyed by the path, so a custom element may
1478
- dispatch one without sitting inside the query.
1479
-
1480
- A choice joins the ones already made, and needs no round trip to show:
1481
- every element whose `lb-order` or `lb-filter` reads a column that changed
1482
- lands its query's last answer again, so it shows what a round trip carrying
1483
- the choice would. Then the hub sends `lb-row-update` for `lb-choices`,
1484
- keyed by the path, with every choice made on the page as `values`, and no
1485
- element pending. The server runs the page's `onChoices` with them, when it
1486
- has one, and otherwise answers nothing. Whatever `onChoices` returns lands,
1487
- and by convention it returns nothing.
1488
-
1489
- Choices hold until the page changes, and a change of query parm keeps them.
1490
- On entering a page, every column named under `lb-query="lb-choices"` lands
1491
- empty. `onPageEnter` restores them by returning `choices()`, alone or
1492
- spread beside `url()`:
1493
-
1494
- ```ts
1495
- onPageEnter: async (ctx) => {
1496
- const saved = await ctx.db.savedChoices("accounts");
1497
- if (saved) return choices(saved);
1498
- },
1499
- onChoices: (ctx, chosen) => ctx.db.saveChoices("accounts", chosen),
1500
- ```
1501
-
1502
- The page load then answers the `lb-choices` item first, then the queries.
1503
- When `url()` moves the page, the choices arrive with the load at the new
1504
- URL. A
1505
- `choices()` naming no column, or holding a value that is not a string, is
1506
- reported on the server console and ignored, as for `url()`.
1507
-
1508
- ### The markup checks
1509
-
1510
- The builder checks the chrome and every page, after expansion, and refuses
1511
- to build on any of these:
1512
-
1513
- - An `lb-` attribute that is not a developer attribute: `lb-query`,
1514
- `lb-column`, `lb-show`, `lb-group`, `lb-count`, `lb-sum`, `lb-avg`,
1515
- `lb-min`, `lb-max`, `lb-order`, `lb-filter`, `lb-request`, `lb-select`,
1516
- `lb-url-link`, `lb-url-push`, `lb-url-unknown`. Every other `lb-` attribute is a stamp.
1517
- `lb-exp-slot` and `lb-exp-template` are consumed by expansion and never
1518
- reach the check.
1519
- - `lb-request` naming a value that begins with `lb-` and is not one of the
1520
- three request names.
1521
- - `lb-url-link` on anything but an `<a>`.
1522
- - `lb-url-push` on an element with no `lb-request`.
1523
- - `lb-url-unknown` on anything but a `<dialog>`, and in the chrome, outside
1524
- `<lb-hub>`.
1525
- - `lb-show` on a `<template>`, on a row template's root, or with no
1526
- `lb-query` around it.
1527
- - `lb-show="!"` or `lb-show="!!column"`, which reverse no column.
1528
- - `lb-order` or `lb-filter` on an element not carrying `lb-query`, or naming
1529
- no column.
1530
- - `lb-select` on anything but an `<input type="checkbox">`, outside a row
1531
- template, or beside `lb-column` or `lb-request`.
1532
- - A `command` beginning `--lb-` that is not one of the three
1533
- [commands](#a-form-pointed-at-a-row), and one of them with no
1534
- `commandfor`.
1535
- - `lb-group` on anything but a `<template>`, or on one holding no
1536
- `<template>` for its contents.
1537
- - An aggregate on a `<template>`, and any but `lb-count` naming no column.
1538
- - A chrome without exactly one `<lb-hub>` and exactly one `<main>`, a
1539
- `<main>` outside `<lb-hub>`, or a `<main>` with anything in it but
1540
- whitespace.
1541
- - A page carrying an `<lb-hub>` or a `<main>`.
1542
-
1543
- The builder also reads the script of every custom element it ships, other
1544
- than `<lb-hub>`, and refuses one that declares a method, accessor or field
1545
- whose name begins with `lb` and a capital and is not in
1546
- [Reserved methods](#reserved-methods).
1547
-
1548
- `lb-column` with no ancestor row is allowed: the hub gathers from it.
1549
-
1550
- Script never assigns an `lb-` attribute, so the markup states every one
1551
- that exists, and checking the markup is sound.
1552
-
1553
- ### Uncommon layouts
1554
-
1555
- The hub supports these layouts and adds no check to refuse them. They are
1556
- uncommon, and Loadbare/app does not shape itself around them.
1557
-
1558
- - **A radio button in a row template.** The browser groups radio buttons
1559
- by `name`, and a row template repeats one `name` in every live row. The
1560
- hub stamps each radio button's `name` with the `name` written, or its
1561
- column when none is, followed by `-` and the live row's key, so each live
1562
- row holds a group of its own.
1563
- - **Several rows not yet entered, each holding radio buttons.** A
1564
- [row not yet entered](#a-row-not-yet-entered) has an empty key, so the
1565
- radio buttons of every such row share one `name` and form one group.
1566
- - **Radio buttons populated from a query.** A radio button in a query's row
1567
- template answers to that query's row, as any element there does, so which
1568
- row and column it lands from and gathers to follows from where the layout
1569
- puts it. The result is the same as in any framework that populates radio
1570
- buttons from a query. A choice among a query's rows is better made with a
1571
- form-associated control, such as `lb-options` in `@loadbare/widgets`.
1572
-
1573
- ## Application files
1574
-
1575
- ### Chrome
1576
-
1577
- The Loadbare/app builder always requires exactly one file
1578
- named `chrome.html`, located anywhere in builder `--src`.
1579
-
1580
- The chrome is the application's single HTML document. This is
1581
- where the standard landmarks go: header, nav, footer, main.
1582
-
1583
- The file is an HTML document, which must contain:
1584
- - `<lb-hub>` inside `<body>`. The hub can only act on its children,
1585
- that is why it is usually nested right below `<body>`.
1586
- - One empty `<main>` inside `<lb-hub>`. The builder refuses a chrome
1587
- without exactly one of each, and a page carrying either.
1588
- - The script tag for `/client.js`, the javascript bundle.
1589
-
1590
- It may also contain:
1591
- - `/app.css`, the stylesheet bundle
1592
- - A `<dialog lb-url-unknown>`, which the hub opens when the path names no
1593
- page. It goes inside `<lb-hub>`, like everything the hub acts on; the
1594
- builder rejects one placed elsewhere.
1595
- - The `hidden` attribute on `<body>`, which the hub removes once the first page has landed.
1596
-
1597
- ```html
1598
- <!-- src/chrome.html -->
1599
- <!doctype html>
1600
- <html lang="en">
1601
- <head>
1602
- <meta charset="utf-8" />
1603
- <title>Membership Roster</title>
1604
- <script src="/client.js" defer></script>
1605
- <link rel="stylesheet" href="/app.css" />
1606
- </head>
1607
- <body hidden>
1608
- <lb-hub>
1609
- <header lb-query="lb-url">
1610
- <h1>Membership Roster</h1>
1611
- <h2 lb-column="lb-page-label"></h2>
1612
- </header>
1613
- <nav>
1614
- <a href="/" lb-url-link>Home</a>
1615
- <a href="/members" lb-url-link>Members</a>
1616
- <a href="https://example.org/">Our website</a>
1617
- </nav>
1618
- <main></main>
1619
- <dialog lb-url-unknown lb-query="lb-url">
1620
- The URL <span lb-column="lb-path"></span> is not in this app.
1621
- </dialog>
1622
- </lb-hub>
1623
- </body>
1624
- </html>
1625
- ```
1626
-
1627
- The chrome's `<title>` shows until the first page lands, and each page's
1628
- title replaces it — see [The URL](#the-url).
1629
-
1630
- ### Pages
1631
-
1632
- The page namespace is flat. Loadbare/app does not care where in the `--src`
1633
- the page files are located, but they must be unique across the application.
1634
- A page `/deep/path/to/mypage.page.html` is routed to `/mypage`.
1635
-
1636
- Pages are grouped as
1637
- - `<stub>.page.html` is recognized as navigable and capable of
1638
- having associated queries and requests
1639
- - `<stub>.queries.ts` are the queries for a page
1640
- - `<stub>.requests.ts` respond to the requests from the browser
1641
-
1642
- The stub of a page must match the path used in links,
1643
- so that `<a href="/members" lb-url-link>Members</a>` has matching
1644
- files `members.page.html` et al.
1645
-
1646
- #### Page HTML
1647
-
1648
- The markup in `<stub>.page.html` is the same HTML as everywhere else
1649
- in a Loadbare/app application, see [HTML](#html).
1650
-
1651
- A page file may carry a `<title>`. The builder removes it and stamps its
1652
- text content as `lb-page-title` on the page, and the hub shows it as
1653
- `lb-page-label`.
1654
-
1655
- ```html
1656
- <!-- src/pages/members.page.html -->
1657
- <title>Members</title>
1658
- <h1>Members</h1>
1659
- ```
1660
-
1661
- The builder wraps each expanded page in
1662
- `<template lb-page="<stub>" lb-page-title="...">` and puts it in the built
1663
- document. The hub reads `lb-page` to find the page a path names.
1664
-
1665
- | Stamp | The builder stamps it with |
1666
- | --------------- | --------------------------------------------- |
1667
- | `lb-page` | The page's stub, which the hub reads |
1668
- | `lb-page-title` | The text content of the page file's `<title>` |
1669
-
1670
- ## The server
1671
-
1672
- A page's server half is two files, `<stub>.queries.ts` and
1673
- `<stub>.requests.ts`. Each pairs with `<stub>.page.html` by sharing its
1674
- stub, as stated in [Pages](#pages). Either may be absent, and one with no
1675
- matching page is a build error.
1676
-
1677
- ### The Express server
1678
-
1679
- Loadbare/app ships no server. The application writes an ordinary Express
1680
- app. Express is a peer dependency, installed by the application.
1681
-
1682
- The builder's artifacts oblige that server to handle four things: the
1683
- client script, the stylesheet, the hub's data channel, and the one HTML
1684
- document.
1685
-
1686
- | Handle | With |
1687
- | ---------------------------- | ------------------- |
1688
- | `/client.js` | `dist/client.js` |
1689
- | `/app.css` | `dist/app.css` |
1690
- | `hubRoutes(hub, contextFor)` | The hub's own route |
1691
- | Every other GET | `dist/app.html` |
1692
-
1693
- The builder writes `pages.ts` into `--out` whenever any page has a
1694
- `.queries.ts` or a `.requests.ts` file. It imports each of them, calls
1695
- `createHub()` once over the lot, and exports the result as `hub`. The
1696
- server imports that rather than maintaining the registry by hand.
1697
- `createHub` is the only implementation of the `Hub` interface, which may
1698
- gain members in any release; an application never implements it.
1699
-
1700
- ```ts
1701
- // server.ts
1702
- import path from "node:path";
1703
- import { readFileSync } from "node:fs";
1704
- import express, { type Request } from "express";
1705
- import { hubRoutes } from "@loadbare/app/express";
1706
- import type { HubContext } from "@loadbare/app/server";
1707
- import { hub } from "./dist/pages";
1708
- import { openDb } from "./src/database";
1709
-
1710
- const DIST = path.resolve("dist");
1711
- const app = express();
1712
-
1713
- // All data requests are handled by the hub
1714
- // This example shows an application-specific openDB()
1715
- function contextFor(_req: Request): HubContext {
1716
- return { db: openDb() };
1717
- }
1718
- app.use(hubRoutes(hub, contextFor));
1719
-
1720
- // Static assets, then the catch-all for app.html
1721
- app.get("/client.js", (_req, res) => res.sendFile(path.join(DIST, "client.js")));
1722
- app.get("/app.css", (_req, res) => res.sendFile(path.join(DIST, "app.css")));
1723
- app.get(/.*/, (_req, res) =>
1724
- res.type("html").send(readFileSync(path.join(DIST, "app.html"), "utf-8")),
1725
- );
1726
-
1727
- app.listen(8787);
1728
- ```
1729
-
1730
- Explaining Express is beyond the scope of this technical reference. The
1731
- only real requirement is that the catch-all for app.html is at the end,
1732
- so it does not catch any other files.
1733
-
1734
- `hubRoutes` mounts `express.json()` on its own routes, and takes that
1735
- function's options under `json` in an optional third argument. The default
1736
- limit of 100 KB refuses an oversized body with a 413 before any handler
1737
- runs, and the server log names the option. A page that sends a file's text
1738
- as a value needs more:
1739
-
1740
- ```ts
1741
- app.use(hubRoutes(hub, contextFor, { json: { limit: "5mb" } }));
1742
- ```
1743
-
1744
- `hubRoutes` answers `POST /lb/<stub>`, with the query string the browser is
1745
- showing after it, verbatim. It hands `contextFor` that query string's parms
1746
- as a second argument, and `contextFor` puts on the context whatever a query
1747
- reads from them. They are user input:
1748
-
1749
- ```ts
1750
- function contextFor(req: Request, parms: URLSearchParams): HubContext {
1751
- return { db: openDb(), acct: parms.get("acct") ?? "" };
1752
- }
1753
- ```
1754
-
1755
- Read the parms from that argument, not from `req.query`. After a request
1756
- whose handler returned `url()`, `contextFor` is called a second time for
1757
- the same request, with the new parms, to load the page at them. So it must
1758
- be safe to call twice, and a write must be visible to the second context by
1759
- the time its `run` returns. A handle opened per request without a
1760
- transaction around it is both.
1761
-
1762
- Give the server the origin root. The hub reaches its own endpoints by
1763
- absolute path, so an application cannot be hosted under a subpath such as
1764
- `example.com/myapp/`, and anything proxying in front of the server passes
1765
- the whole path space through unchanged.
1766
-
1767
- ### Page Queries
1768
-
1769
- `<stub>.queries.ts` exports one object named `queries`, typed `Queries`.
1770
- Each key is a query name, which the markup names with `lb-query`.
1771
-
1772
- A query's kind and key belong to its name. Declare each query with
1773
- `row(key, run)` or `rows(key, run, options)`, naming the key column first.
1774
- A `rows` query's `options` declare its [order](#order). A query
1775
- cannot be built without one of these functions. Every query has a key; an
1776
- aggregate row answers with a constant one.
1777
-
1778
- ```ts
1779
- // members.queries.ts
1780
- import { row, rows, type Queries } from "@loadbare/app/server";
1781
-
1782
- export const queries: Queries = {
1783
- roster: rows("id", (ctx) => ctx.db.members()),
1784
- summary: row("id", async (ctx) => ({
1785
- id: "all",
1786
- count: String(await ctx.db.memberCount()),
1787
- })),
1788
- };
1789
- ```
1790
-
1791
- A `row` query returns one object, or `null` when it finds no row, and a
1792
- `rows` query returns an array of objects, which the page shows in that order when the query declares none. An answer that disagrees with
1793
- its declared kind is left out of the response, with a warning naming the
1794
- query.
1795
-
1796
- Query names cannot begin with `lb-`, that namespace is reserved for
1797
- Loadbare/app queries the hub serves in the browser, such as
1798
- [`lb-url`](#the-url). `createHub` refuses at startup a page that declares
1799
- such a name as a query, a handler or a `crud` entry, and a query whose order
1800
- has a term naming no column.
1801
-
1802
- Every query and every handler receives `ctx`, the application's own request
1803
- context. The application builds it once per request and hands it to the hub,
1804
- see [The Express server](#the-express-server). Loadbare/app declares it empty
1805
- and never reads it, so a query finds exactly what the application put there,
1806
- which is usually a database handle opened for the authenticated caller.
1807
-
1808
- Queries should not return deltas, deltas are handled in the `.requests.ts` file
1809
- as explained in the next section.
1810
-
1811
- ### Page Requests
1812
-
1813
- `<stub>.requests.ts` exports one object named `requests`, typed `Requests`.
1814
- It has four optional keys.
1815
-
1816
- | Key | Keyed by | Answers |
1817
- | ------------- | --------------------- | -------------------------------------------------- |
1818
- | `handlers` | The request name | The page's declared requests |
1819
- | `crud` | A query name | The three requests Loadbare provides |
1820
- | `onPageEnter` | Nothing | Runs once on entering the page, before its queries, and may move the URL or restore [choices](#choices) |
1821
- | `onChoices` | Nothing | Runs with every choice made on the page, each time the user makes one |
1822
-
1823
- The server resolves a declared request against `handlers`, and a request
1824
- Loadbare provides against `crud` under the request's query. A name with no
1825
- entry there logs a server console warning naming the page and the missing
1826
- entry, and answers 200 with no response items, so nothing lands and
1827
- `lb-request-pending` clears from the element that issued the request. A
1828
- request carrying no name answers 400, and a `run` that throws answers 500.
1829
-
1830
- Every handler under `handlers` and `crud` has the same two members. `run`
1831
- performs the work, and `refresh` names the queries to re-run once it has.
1832
- A query belongs there when its answer changed, never to fill an element the
1833
- request's answer creates: that element is filled from the last answer its
1834
- query landed.
1835
-
1836
- `run` receives the same `ctx` and the request less its name: `query`, `key`
1837
- and `values`, as present.
1838
-
1839
- `run` may also return answers of its own, by query name, which are laid over
1840
- the refreshed ones. That is how a delta reaches the browser: wrap it in
1841
- `patch()`, naming the rows that arrived or changed and the keys that went.
1842
-
1843
- A refreshed `rows` query sends every row to say what changed. An update or
1844
- a delete names its row by key, and an insert has the new row's key once it
1845
- has written it, so each handler knows what changed and answers with a
1846
- patch, which the hub places by the query's [order](#order): `createHub`
1847
- refuses at startup a `crud` `rowInsert`, `rowUpdate` or `rowDelete` on a
1848
- `rows` query whose `refresh` names that same query. A `row` query sends one
1849
- row and may refresh itself.
1850
-
1851
- `run` may instead return `url()`, a new row for `lb-url`, naming query parms
1852
- only the write can know, such as the key of a row it inserted. The page
1853
- then loads at them in the same round trip, in place of the refresh set — see
1854
- [When a server response moves the URL](#when-a-server-response-moves-the-url).
1855
- A `rowDelete` that removes the row on screen returns `url({ acct: "" })`
1856
- and leaves any other delete to its refresh set.
1857
-
1858
- ```ts
1859
- // members.requests.ts
1860
- import { patch, type Requests } from "@loadbare/app/server";
1861
-
1862
- export const requests: Requests = {
1863
- handlers: {
1864
- resetRoster: {
1865
- run: (ctx) => ctx.db.resetMembers(),
1866
- refresh: ["roster"],
1867
- },
1868
- },
1869
- crud: {
1870
- roster: {
1871
- rowDelete: {
1872
- run: async (ctx, { key }) => {
1873
- await ctx.db.deleteMember(key);
1874
- return { roster: patch({ drop: [key] }) };
1875
- },
1876
- refresh: [],
1877
- },
1878
- },
1879
- },
1880
- };
1881
- ```
1882
-
1883
- Each key under a `crud` entry is a request name with its prefix stripped and
1884
- the rest camel-cased, so the attribute, the wire and this key are one
1885
- vocabulary. A query with no `crud` entry permits none of them.
1886
-
1887
- | Request name | Key under `crud` | `run` receives |
1888
- | --------------- | ---------------- | --------------- |
1889
- | `lb-row-insert` | `rowInsert` | `values` |
1890
- | `lb-row-update` | `rowUpdate` | `key`, `values` |
1891
- | `lb-row-delete` | `rowDelete` | `key` |
1892
-
1893
- A `rowUpdate` sets the columns `values` names and leaves the rest as they
1894
- are, as an SQL `UPDATE` does. A form sends the controls it holds and a
1895
- control carrying `lb-request` sends itself, so one handler answers both.
1896
-
1897
- ### The wire
1898
-
1899
- ---- UNEDITED ----
1900
-
1901
- One path, one method. The hub sends every round trip as
1902
- `POST /lb/<stub>?<query string>`, with the query string the browser is
1903
- showing. An empty body asks for the page load. Any other body is a
1904
- request:
1905
-
1906
- ```ts
1907
- interface HubRequest {
1908
- name: string;
1909
- query?: string;
1910
- key?: string;
1911
- keys?: string[];
1912
- values?: Record<string, string>;
1913
- rows?: Record<string, { key: string; values: Record<string, string> }[]>;
1914
- }
1915
- ```
1916
-
1917
- A refused request is answered with status 422 and
1918
- `{ refusal: { message?, columns? } }`, and any other failure with 500 and
1919
- no body — see [Refusals](#refusals). Every other response is an array of
1920
- response items:
1921
-
1922
- ```ts
1923
- interface ResponseItem {
1924
- query: string;
1925
- kind: "row" | "rows";
1926
- key: string;
1927
- row?: Row;
1928
- rows?: Row[];
1929
- patch?: { rows?: Row[]; drop?: unknown[] };
1930
- }
1931
- ```
1932
-
1933
- A response item carries exactly one of `row`, `rows` or `patch`. A patch
1934
- answers a `rows` query only: rows named in `rows` are added or updated, keys
1935
- in `drop` are removed, and every other row is left alone.
1936
-
1937
- A `rows` item also carries `order` when the query declares one.
1938
-
1939
- A choice is sent as `lb-row-update` for `lb-choices`, keyed by the path,
1940
- with every choice made on the page as `values`; see [Choices](#choices).
1941
-
1942
- `createHub(pages)` builds the `Hub`, which has two calls, both answering
1943
- with response items: `dataForPage(page, ctx, options)` for a page load, and
1944
- `runRequest(page, request, ctx)` for a request. `options.parms` are the
1945
- parms the page is loaded at, and `options.follow` false ignores a `url()`
1946
- from `onPageEnter`; `hubRoutes` passes both.
1947
-
1948
- ## Custom elements
1949
-
1950
- ---- UNEDITED ----
1951
-
1952
- A custom element is an element defined by `customElements.define`, and
1953
- follows these fixed rules in a Loadbare/app application:
1954
- - Its name contains a hyphen, as HTML requires, like `<my-custom-element>`.
1955
- - Its element file, if present, is `my-custom-element.html`.
1956
- - Its script, if present, is `my-custom-element.browser.ts`. The
1957
- `browser` segment is a safety feature, requiring the file to be
1958
- explicitly named as a browser file, to help prevent unfortunate
1959
- naming collisions where a server file happens to have the name of
1960
- a custom element and gets built into the browser bundle.
1961
-
1962
- A custom element must have one or the other of an element file and a
1963
- script, and it may have both. If it has neither, the builder reports an
1964
- error.
1965
-
1966
- To use custom elements from a widget library, add the library to
1967
- `imports.ts` anywhere in the builder's `--src`:
1968
-
1969
- ```ts
1970
- export default ["@scope/library-name"];
1971
- ```
1972
-
1973
- Import every attribute name from `@loadbare/app/constants` — see
1974
- [Constants](#constants). Never write one as a string literal.
1975
-
1976
- ### What the hub and an element say to each other
1977
-
1978
- Each direction has one mechanism for each kind of message:
1979
-
1980
- | Direction | What | How | Today |
1981
- | -------------- | ------------------------------------------- | ---------------------------- | ------------------------------------------------ |
1982
- | Hub to element | State that lasts | An attribute the hub stamps | `lb-column-value`, `lb-key-value`, `lb-query-row-count`, `lb-group-*`, `lb-row-*`, `lb-request-pending`, `lb-request-error` |
1983
- | Hub to element | State the platform already names | A property | A control's `value` |
1984
- | Hub to element | What the hub did | An optional `lb` method | `lbAttached`, `lbRowsLanded`, `lbRequestDone`, `lbDetached` |
1985
- | Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
1986
- | Element to hub | A moment | A bubbling event | `lb-request`, and `change`, `click` and `submit` |
1987
-
1988
- State is an attribute, because a stylesheet can select on it. A method is
1989
- how the hub tells one element what it just did to that element. A moment is an event, because the element that cares is
1990
- often an ancestor of the one it concerns, and an event reaches it with no
1991
- knowledge of either.
1992
-
1993
- ### Controls
1994
-
1995
- A form-associated custom element with a `value` property that fires
1996
- `change` is a control. The hub sets its `value` when a column lands, and
1997
- gathers its `value` when a request is issued, exactly as for a native
1998
- `<input>`. It commits on `change`, and belongs to its form owner, which the
1999
- browser tracks and the `form` attribute names.
2000
-
2001
- ```ts
2002
- class NoteField extends HTMLElement {
2003
- static formAssociated = true;
2004
- // a `value` property, and a `change` event when the edit commits
2005
- }
2006
- ```
2007
-
2008
- A custom element that is not a control receives `lb-column-value` and
2009
- renders it; its content is never replaced.
2010
-
2011
- ### Lifecycle
2012
-
2013
- A custom element hears the hub's lifecycle, never the browser's. The hub
2014
- calls four optional methods, which the `HubLifecycle` interface in
2015
- `@loadbare/app/types` declares:
2016
-
2017
- | The hub | A custom element hears |
2018
- | ------------------------------------------------ | ------------------------------- |
2019
- | Places it: chrome at startup, a page, a new live row, a new group | `lbAttached()`, once, after every column has landed on it |
2020
- | Lands rows on it | `lbRowsLanded()` |
2021
- | Finishes a request committed by it or inside it | `lbRequestDone(detail, origin)` |
2022
- | Removes it: a row or group done leaving, the page replaced | `lbDetached()`, once |
2023
- | Moves a live row the order put elsewhere | Nothing |
2024
- | Turns `lb-show` off or on | Nothing |
2025
- | Hides a row or a group for a filter | Nothing |
2026
-
2027
- The chrome and a page are placed as the page's first answer lands, or once
2028
- that round trip fails. An element under an `lb-show` that is off is placed
2029
- inside its template, so `lbAttached` runs whether it shows or not. `lbAttached` reaches an outer
2030
- custom element before the ones inside it, and `lbDetached` the inner ones
2031
- first. `lbRequestDone` reaches the committing element and every custom
2032
- element around it, innermost first, before the `lb-request-done` event.
2033
- An element a script creates hears none of them.
2034
-
2035
- A listener on the element itself goes in the constructor, which reads no
2036
- attribute and no child. A child is looked up when it is needed. Work done
2037
- once per instance goes in `lbAttached`, and its undoing in `lbDetached`.
2038
-
2039
- The build refuses a script that declares `connectedCallback`,
2040
- `disconnectedCallback`, `adoptedCallback` or `connectedMoveCallback`.
2041
-
2042
- ### Row hooks
2043
-
2044
- The hub calls `lbRowsLanded()` on a custom element with `lb-query` and a row
2045
- template, after the rows land, with every row placed and stamped. The hub
2046
- places every row and builds every group itself, so a custom element reacts
2047
- to rows and never places them. Scrolling to the row the page's request
2048
- created is the case:
2049
-
2050
- ```ts
2051
- lbRowsLanded() {
2052
- this.querySelector(`[${ATTR_ROW_REQUESTED}="${REQUESTED_CREATED}"]`)
2053
- ?.scrollIntoView({ block: "nearest" });
2054
- }
2055
- ```
2056
-
2057
- A custom element under an `lb-show` that is off holds its rows all the
2058
- same, and the hook runs as they land.
2059
-
2060
- `applyRow(root, row)`, exported by `@loadbare/app`, fills `root` from one
2061
- row, the same operation that fills a live row.
2062
-
2063
- ## Internal linkage
2064
-
2065
- ---- UNEDITED ----
2066
-
2067
- The endpoint path, `index` for the bare path, the constant holding the
2068
- request deadline, the generated client entry, and the `./build` export.
2069
- Nobody outside this package reads any of it. The deadline itself is
2070
- behavior an application sees — see [The round trip](#the-round-trip) — and
2071
- giving it a knob later takes nothing away.
2072
-
2073
- ## Cross-reference
2074
-
2075
- Every name Loadbare/app owns, in one place. Each row names the section
2076
- that defines it. The definition lives there and only there.
2077
-
2078
- ### Builder flags
2079
-
2080
- Loadbare/app owns every flag in this table.
2081
-
2082
- | Flag | Default | Names | Defined in |
2083
- | ---------- | ------- | ----------------------------------------- | ------------------------------------------------- |
2084
- | `--src` | `src` | The application's own tree, scanned whole | [What the builder scans](#what-the-builder-scans) |
2085
- | `--out` | `dist` | Where the builder writes | [Running the builder](#running-the-builder) |
2086
- | `--watch` | off | Rebuild on change under `--src` | [Running the builder](#running-the-builder) |
2087
- | `--minify` | off | Minify `client.js` and `app.css` | [Running the builder](#running-the-builder) |
2088
-
2089
- ### Reserved file names
2090
-
2091
- Loadbare/app owns every file name and pattern in this table. A file so
2092
- named carries its meaning wherever it sits in the `--src` tree, so an
2093
- application must not use one of these names for anything else.
2094
-
2095
- | File | How many | Holds | Defined in |
2096
- | ----------------------- | ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------- |
2097
- | `chrome.html` | Exactly one | The application's one HTML document | [Chrome](#chrome) |
2098
- | `imports.ts` | Zero or one | Default-exports an array of widget library package names | [Imported widget libraries](#imported-widget-libraries) |
2099
- | `<stub>.page.html` | One per page | One page's markup, as a fragment | [Pages](#pages) |
2100
- | `<stub>.queries.ts` | Zero or one per page | That page's queries | [Page Queries](#page-queries) |
2101
- | `<stub>.requests.ts` | Zero or one per page | That page's requests | [Page Requests](#page-requests) |
2102
- | `<tag-name>.html` | Zero or one per custom element | One element file, as a fragment | [Custom elements](#custom-elements) |
2103
- | `<tag-name>.browser.ts` | Zero or one per custom element | One custom element's script, `.js` also accepted | [Custom elements](#custom-elements) |
2104
- | `*.css` | Any number | A stylesheet, concatenated into `app.css` | [Running the builder](#running-the-builder) |
2105
-
2106
- ### Build outputs
2107
-
2108
- Loadbare/app owns every file name in this table. The builder writes them
2109
- into `--out`. Which of them exist depends on the application: `app.css`
2110
- only when some origin has a stylesheet, `pages.ts` only when some page has a
2111
- `.queries.ts` or a `.requests.ts`.
2112
-
2113
- | Path | Written | Holds | Read by | Defined in |
2114
- | ------------------ | ----------------- | ------------------------------------------------------------------ | ---------- | ------------------------------------------- |
2115
- | `/app.html` | Always | The chrome, built | The server | [Chrome](#chrome) |
2116
- | `/client.js` | Always | `<lb-hub>` and every custom element the application uses | The chrome | [Chrome](#chrome) |
2117
- | `/client-entry.ts` | Always | The generated entry `client.js` is bundled from | Nothing | [Running the builder](#running-the-builder) |
2118
- | `/app.css` | With a stylesheet | Every stylesheet in the cascade, concatenated | The chrome | [Chrome](#chrome) |
2119
- | `/pages.ts` | With page data | The one `createHub()` call, over every page's queries and requests | The server | [The Express server](#the-express-server) |
2120
-
2121
- ### Reserved package.json keys
2122
-
2123
- Loadbare/app owns every key in this table. A widget library declares them.
2124
- An application never does.
2125
-
2126
- | Key | Value | Means | Defined in |
2127
- | ------------------ | ------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------- |
2128
- | `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) |
2129
-
2130
- ### Reserved namespaces
2131
-
2132
- Loadbare/app owns every namespace in this table. Ownership of a name and
2133
- ownership of its behavior are stated separately, because they differ.
2134
-
2135
- | Namespace | Applies to | Loadbare owns | Defined in |
2136
- | --------- | ----------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------- |
2137
- | `lb-*` | Attributes, their values, queries, columns of `lb-` queries, requests, custom elements, events | The names and their behavior | [The lb-* namespace](#the-lb--namespace) |
2138
- | `--lb-*` | Values of HTML's `command` attribute | The names and their behavior | [Reserved commands](#reserved-commands) |
2139
- | `exp-*` | Attributes on a custom element | The behavior; element file authors pick the names | [Build time parameters](#build-time-parameters) |
2140
- | `lb*` | Methods on custom elements | The names and their behavior | [Row hooks](#row-hooks) |
2141
-
2142
- #### The lb-* namespace
2143
-
2144
- Every name beginning with `lb-` belongs to Loadbare/app — see
2145
- [Names](#names). An application writes the attributes this section names,
2146
- and invents none of its own, because a name Loadbare has not defined today
2147
- it may define tomorrow.
2148
-
2149
- | Attribute | Written by | On | Defined in |
2150
- | ----------------- | ------------- | ----------------------------------- | --------------------------------------------- |
2151
- | `lb-query` | Developer | Any element | [Landing](#landing) |
2152
- | `lb-column` | Developer | Any element | [Landing](#landing) |
2153
- | `lb-show` | Developer | Any element but a `<template>` | [Displaying by condition](#displaying-by-condition) |
2154
- | `lb-request` | Developer | Any element | [Requests](#requests) |
2155
- | `lb-select` | Developer | A checkbox in a row template | [Selecting rows](#selecting-rows) |
2156
- | `lb-url-link` | Developer | An `<a>` | [Links](#links) |
2157
- | `lb-url-push` | Developer | An element with `lb-request` | [Query parms](#query-parms) |
2158
- | `lb-url-unknown` | Developer | A `<dialog>` inside `<lb-hub>` | [An unknown page](#an-unknown-page) |
2159
- | `lb-group` | Developer | A `<template>` holding a `<template>` | [Groups](#groups) |
2160
- | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` | Developer | Any element but a `<template>` | [Aggregates](#aggregates) |
2161
- | `lb-order` | Developer | An element with `lb-query` | [Order](#order) |
2162
- | `lb-filter` | Developer | An element with `lb-query` | [Filters](#filters) |
2163
- | `lb-exp-slot` | Developer | One element in an element file | [Slots and templates](#slots-and-templates) |
2164
- | `lb-exp-template` | Developer | An element file, and a `<template>` | [Slots and templates](#slots-and-templates) |
2165
- | `lb-column-value` | Hub | Every element set from a column | [How a column lands](#how-a-column-lands) |
2166
- | `lb-key-value` | Hub | A live row, an element a `row` lands on, and a pointed form | [Rows](#rows), [A form pointed at a row](#a-form-pointed-at-a-row) |
2167
- | `lb-row-editing` | Hub | The live row a form is pointed at | [A form pointed at a row](#a-form-pointed-at-a-row) |
2168
- | `lb-row-selected` | Hub | A live row its `lb-select` checkbox ticks | [Selecting rows](#selecting-rows) |
2169
- | `lb-row-live` | Hub | A live row | [Rows](#rows) |
2170
- | `lb-query-row-count` | Hub | An element with a row template | [Rows](#rows) |
2171
- | `lb-group-live`, `lb-group-column`, `lb-group-value` | Hub | Each top-level element of a group | [Groups](#groups) |
2172
- | `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested` | Hub | A live row | [What happened to a row](#what-happened-to-a-row) |
2173
- | `lb-row-leaving`, `lb-group-leaving` | Hub | A row or a group on its way out | [What happened to a row](#what-happened-to-a-row) |
2174
- | `lb-request-pending` | Hub | The element that issued a request | [Request state](#request-state) |
2175
- | `lb-request-error` | Hub | The element that issued a request | [Request state](#request-state) |
2176
- | `lb-request-refused` | Hub | The element whose request was refused | [Refusals](#refusals) |
2177
- | `lb-show` on a `<template>` | Hub and builder | Where an absent element stands | [Displaying by condition](#displaying-by-condition) |
2178
- | `lb-page` | Builder | A page's `<template>` | [Page HTML](#page-html) |
2179
- | `lb-page-title` | Builder | A page's `<template>` | [Page HTML](#page-html) |
2180
-
2181
- The builder refuses any other `lb-` attribute in markup — see
2182
- [The markup checks](#the-markup-checks).
2183
-
2184
- ### Reserved queries
2185
-
2186
- | Query | Kind | Key | Columns | Defined in |
2187
- | -------- | ----- | --------- | --------------------------------------------------------------- | ------------------- |
2188
- | `lb-url` | `row` | `lb-path` | `lb-path`, `lb-page-label`, `lb-page-unknown`, every query parm | [The URL](#the-url) |
2189
- | `lb-choices` | `row` | `lb-path` | `lb-path`, every choice | [Choices](#choices) |
2190
-
2191
- ### Reserved query parms
2192
-
2193
- None. A column of `lb-url` whose name begins with `lb-` is never a query
2194
- parm — see [Query parms](#query-parms).
2195
-
2196
- ### Reserved request names
2197
-
2198
- | Request name | Runs under `crud` | Defined in |
2199
- | --------------- | ----------------- | ----------------------------------- |
2200
- | `lb-row-insert` | `rowInsert` | [The request names](#the-request-names) |
2201
- | `lb-row-update` | `rowUpdate` | [The request names](#the-request-names) |
2202
- | `lb-row-delete` | `rowDelete` | [The request names](#the-request-names) |
2203
-
2204
- ### Reserved commands
2205
-
2206
- Loadbare/app owns every value of HTML's `command` attribute beginning with
2207
- `--lb-`.
2208
-
2209
- | Command | Defined in |
2210
- | -------------- | --------------------------------------------------- |
2211
- | `--lb-edit` | [A form pointed at a row](#a-form-pointed-at-a-row) |
2212
- | `--lb-new` | [A form pointed at a row](#a-form-pointed-at-a-row) |
2213
- | `--lb-release` | [A form pointed at a row](#a-form-pointed-at-a-row) |
2214
-
2215
- ### Reserved tags
2216
-
2217
- Loadbare/app owns every tag in this table. An application writes them and
2218
- never defines them.
2219
-
2220
- | Tag | Written in | Means | Defined in |
2221
- | ---------- | ---------- | ------------------------------ | ----------------- |
2222
- | `<lb-hub>` | The chrome | The application's live element | [Chrome](#chrome) |
2223
-
2224
- ### Reserved events
2225
-
2226
- Loadbare/app owns every DOM event in this table, both the name and what its
2227
- `detail` carries.
2228
-
2229
- | Event | Dispatched from | Bubbles | Cancelable | Defined in |
2230
- | ------------ | -------------------------------- | ------- | ---------- | --------------------------------------- |
2231
- | `lb-request` | The element that committed | Yes | No | [The request event](#the-request-event) |
2232
- | `lb-request-done` | The element that committed | Yes | No | [The request event](#the-request-event) |
2233
-
2234
- ### Reserved methods
2235
-
2236
- | Method | On | Defined in |
2237
- | --------------- | --------------------------------------------------- | ----------------------- |
2238
- | `lbAttached` | Any custom element the hub places | [Lifecycle](#lifecycle) |
2239
- | `lbRowsLanded` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
2240
- | `lbRequestDone` | A custom element that committed, or holds one that did | [Lifecycle](#lifecycle) |
2241
- | `lbDetached` | Any custom element the hub removes | [Lifecycle](#lifecycle) |
2242
-
2243
- ### Constants
2244
-
2245
- `@loadbare/app/constants` exports every name above. Custom element code
2246
- imports them rather than writing a string.
2247
-
2248
- | Constant | Value |
2249
- | ------------------------- | --------------------------------------- |
2250
- | `ATTR_QUERY` | `lb-query` |
2251
- | `ATTR_COLUMN` | `lb-column` |
2252
- | `ATTR_SHOW` | `lb-show` |
2253
- | `ATTR_COLUMN_VALUE` | `lb-column-value` |
2254
- | `ATTR_KEY_VALUE` | `lb-key-value` |
2255
- | `ATTR_ROW_LIVE` | `lb-row-live` |
2256
- | `LIVE_ROW` | `[lb-row-live]`, the selector for a live row |
2257
- | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
2258
- | `ATTR_GROUP` | `lb-group` |
2259
- | `ATTR_GROUP_LIVE` | `lb-group-live` |
2260
- | `ATTR_GROUP_COLUMN` | `lb-group-column` |
2261
- | `ATTR_GROUP_VALUE` | `lb-group-value` |
2262
- | `LIVE_GROUP` | `[lb-group-live]`, the selector for a group's element |
2263
- | `ATTR_COUNT`, `ATTR_SUM`, `ATTR_AVG`, `ATTR_MIN`, `ATTR_MAX` | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` |
2264
- | `AGGREGATES` | All five aggregate attributes |
2265
- | `ORDER_DESCENDING` | `-`, written before a column in an order |
2266
- | `CHOICES_QUERY` | `lb-choices` |
2267
- | `ATTR_ORDER` | `lb-order` |
2268
- | `ATTR_FILTER` | `lb-filter` |
2269
- | `FILTER_OPERATORS` | Every filter operator, the longer first |
2270
- | `ATTR_ROW_CREATED` | `lb-row-created` |
2271
- | `ATTR_ROW_CHANGED` | `lb-row-changed` |
2272
- | `ATTR_ROW_MOVED` | `lb-row-moved` |
2273
- | `ATTR_ROW_REQUESTED` | `lb-row-requested` |
2274
- | `REQUESTED_CREATED`, `REQUESTED_CHANGED`, `REQUESTED_MOVED` | `created`, `changed`, `moved` |
2275
- | `ATTR_ROW_LEAVING` | `lb-row-leaving` |
2276
- | `ATTR_GROUP_LEAVING` | `lb-group-leaving` |
2277
- | `COMMAND_EDIT`, `COMMAND_NEW`, `COMMAND_RELEASE` | `--lb-edit`, `--lb-new`, `--lb-release` |
2278
- | `LB_COMMANDS` | All three commands |
2279
- | `LB_COMMAND_PREFIX` | `--lb-` |
2280
- | `ATTR_ROW_EDITING` | `lb-row-editing` |
2281
- | `ATTR_SELECT` | `lb-select` |
2282
- | `ATTR_ROW_SELECTED` | `lb-row-selected` |
2283
- | `ATTR_REQUEST` | `lb-request` |
2284
- | `ATTR_REQUEST_PENDING` | `lb-request-pending` |
2285
- | `ATTR_REQUEST_ERROR` | `lb-request-error` |
2286
- | `ATTR_REQUEST_REFUSED` | `lb-request-refused` |
2287
- | `LB_REFUSAL_STATUS` | `422` |
2288
- | `ATTR_URL_LINK` | `lb-url-link` |
2289
- | `ATTR_URL_PUSH` | `lb-url-push` |
2290
- | `ATTR_URL_UNKNOWN` | `lb-url-unknown` |
2291
- | `ATTR_EXP_SLOT` | `lb-exp-slot` |
2292
- | `ATTR_EXP_TEMPLATE` | `lb-exp-template` |
2293
- | `ATTR_PAGE` | `lb-page` |
2294
- | `ATTR_PAGE_TITLE` | `lb-page-title` |
2295
- | `DEVELOPER_ATTRIBUTES` | The sixteen attributes a developer writes |
2296
- | `LB_EVENT_NAME` | `lb-request` |
2297
- | `LB_DONE_EVENT_NAME` | `lb-request-done` |
2298
- | `SHOW_NOT` | `!`, written before a column in `lb-show` |
2299
- | `LB_RESERVED_PREFIX` | `lb-` |
2300
- | `REQUEST_ROW_INSERT` | `lb-row-insert` |
2301
- | `REQUEST_ROW_UPDATE` | `lb-row-update` |
2302
- | `REQUEST_ROW_DELETE` | `lb-row-delete` |
2303
- | `LB_ROW_REQUESTS` | All three request names |
2304
- | `KIND_ROW` | `row` |
2305
- | `KIND_ROWS` | `rows` |
2306
- | `URL_QUERY` | `lb-url` |
2307
- | `URL_COLUMN_PATH` | `lb-path` |
2308
- | `URL_COLUMN_PAGE_LABEL` | `lb-page-label` |
2309
- | `URL_COLUMN_PAGE_UNKNOWN` | `lb-page-unknown` |
2310
- | `HUB_TAG_NAME` | `lb-hub` |
2311
- | `LB_ENDPOINT` | `/lb` |
2312
- | `LB_REQUEST_TIMEOUT_MS` | `10000` |