@loadbare/app 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/build/assemble.d.ts +1 -2
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +31 -37
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/cli.d.ts +4 -10
  6. package/dist/build/cli.d.ts.map +1 -1
  7. package/dist/build/cli.js +11 -33
  8. package/dist/build/cli.js.map +1 -1
  9. package/dist/build/declared.js +1 -1
  10. package/dist/build/declared.js.map +1 -1
  11. package/dist/core/lb-constants.d.ts +15 -13
  12. package/dist/core/lb-constants.d.ts.map +1 -1
  13. package/dist/core/lb-constants.js +47 -51
  14. package/dist/core/lb-constants.js.map +1 -1
  15. package/dist/core/lb-types.d.ts +12 -11
  16. package/dist/core/lb-types.d.ts.map +1 -1
  17. package/dist/core/lb-types.js +7 -0
  18. package/dist/core/lb-types.js.map +1 -1
  19. package/dist/hub/lb-apply.d.ts +10 -34
  20. package/dist/hub/lb-apply.d.ts.map +1 -1
  21. package/dist/hub/lb-apply.js +92 -151
  22. package/dist/hub/lb-apply.js.map +1 -1
  23. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  24. package/dist/hub/lb-hub.browser.js +115 -127
  25. package/dist/hub/lb-hub.browser.js.map +1 -1
  26. package/dist/hub/lb-lifecycle.d.ts.map +1 -1
  27. package/dist/hub/lb-lifecycle.js +4 -11
  28. package/dist/hub/lb-lifecycle.js.map +1 -1
  29. package/dist/server/lb-express.d.ts.map +1 -1
  30. package/dist/server/lb-express.js +3 -3
  31. package/dist/server/lb-express.js.map +1 -1
  32. package/dist/server/lb-server.d.ts +35 -19
  33. package/dist/server/lb-server.d.ts.map +1 -1
  34. package/dist/server/lb-server.js +141 -88
  35. package/dist/server/lb-server.js.map +1 -1
  36. package/docs/binding-cases.md +150 -0
  37. package/docs/comparison.md +34 -38
  38. package/docs/opinions.md +2 -2
  39. package/docs/reference/chrome.md +8 -10
  40. package/docs/reference/css.md +9 -10
  41. package/docs/reference/custom-elements.md +11 -8
  42. package/docs/reference/data-binding.md +93 -134
  43. package/docs/reference/names.md +21 -22
  44. package/docs/reference/page-files.md +24 -26
  45. package/docs/reference/server.md +3 -4
  46. package/docs/roadmap.md +2 -2
  47. package/docs/testing.md +5 -6
  48. package/package.json +1 -1
  49. package/skills/loadbare-app/SKILL.md +55 -48
  50. package/skills/loadbare-app/references/chrome.md +8 -10
  51. package/skills/loadbare-app/references/css.md +9 -10
  52. package/skills/loadbare-app/references/custom-elements.md +11 -8
  53. package/skills/loadbare-app/references/data-binding.md +93 -134
  54. package/skills/loadbare-app/references/names.md +21 -22
  55. package/skills/loadbare-app/references/page-files.md +24 -26
  56. package/skills/loadbare-app/references/server.md +3 -4
@@ -101,7 +101,6 @@ server's row lands, so a chrome shows where the page is with no code:
101
101
  |-------------------|---------------------------------------------------------|
102
102
  | `lb-path` | The path, such as `/members`. The row's key |
103
103
  | `lb-page-label` | The page's title, or its stub when it has none |
104
- | `lb-page-unknown` | `true` when no page's stub matches the path |
105
104
  | Any other name | The query parm of that name, or empty when it is absent |
106
105
 
107
106
  The path names a page, never a resource: `/accounts/42` is not a way to
@@ -116,9 +115,9 @@ page file carries; see [page files](./page-files.md#the-page-title). The hub
116
115
  sets the document title to `lb-page-label` whenever it is not null, so the
117
116
  tab and the browser history show the page.
118
117
 
119
- `lb-url` is one of two queries a page may use that the server does not
120
- declare. The other is [`lb-choices`](./data-binding.md#choices). A page
121
- names either the same way, anywhere inside the hub.
118
+ `lb-url` is one of the queries a page may use that the server does not
119
+ declare. The others are `lb-sort`, `lb-filters` and `lb-hub-status`. A page
120
+ names any the same way, anywhere inside the hub.
122
121
 
123
122
  ### Where focus starts
124
123
 
@@ -223,8 +222,8 @@ pushes one instead when the element carrying `lb-request` also carries
223
222
  The hub answers the request itself, with no round trip, then reloads the
224
223
  page's queries at the new URL. The page stays in place, and rows that come
225
224
  back keep their place. A request that leaves the URL as it was does
226
- nothing. `lb-page-label` and `lb-page-unknown` are the hub's, and a request
227
- never sets them.
225
+ nothing. `lb-page-label` is the hub's, and a request
226
+ never sets it.
228
227
 
229
228
  A request that gives `lb-path` a new value enters that page, pushes a
230
229
  history entry, and the new URL carries only the query parms the request
@@ -288,8 +287,8 @@ it; a per-session database role means an id outside the caller's reach
288
287
  finds nothing.
289
288
 
290
289
  An order or a filter the hub applies to rows the page already holds is not
291
- a query parm. It is a [choice](./data-binding.md#choices), which holds until
292
- the page changes.
290
+ a query parm. It is a [sort or a filter](./data-binding.md#sort-and-filters),
291
+ which holds until the page changes.
293
292
 
294
293
  The hub sends the query parms with every round trip, and the server hands
295
294
  them to `contextFor`; see [the Express server](./server.md#database-layer).
@@ -308,8 +307,7 @@ show the path:
308
307
  </dialog>
309
308
  ```
310
309
 
311
- When no page's stub matches the path, `lb-page-unknown` is `true`,
312
- `lb-page-label` is null, `<main>` keeps the page it had, the hub calls
310
+ When the path names a page that does not exist, `lb-page-label` is null, `<main>` keeps the page it had, the hub calls
313
311
  `showModal()` on the dialog, and reports the path to the console. A chrome
314
312
  with no dialog shows nothing. `lb-url` lands before the page is looked up,
315
313
  so the dialog shows the path by naming it like any other element.
@@ -69,7 +69,6 @@ in the stamp, so the selector reads the number and not its text. See
69
69
  | Select | For |
70
70
  |----------------------|-------------------------------------------------|
71
71
  | `[lb-row-live]` | Every live row of a list |
72
- | `[lb-row-selected]` | A row its `lb-select` checkbox ticks |
73
72
  | `[lb-row-editing]` | The row a pointed form is editing |
74
73
  | `[lb-key-value="…"]` | One row, by its key |
75
74
  | `[hidden]` | A row a filter hides; the browser hides it |
@@ -86,11 +85,10 @@ landed inside a list, such as a total in a table's foot, carries a key and
86
85
  is not one of the list's rows.
87
86
 
88
87
  ```css
89
- [lb-row-selected] { background: var(--selected); }
90
88
  [lb-row-editing] { outline: 2px solid var(--accent); }
91
89
  ```
92
90
 
93
- See [Rows](./data-binding.md#rows), [Selecting rows](./data-binding.md#selecting-rows)
91
+ See [Rows](./data-binding.md#rows)
94
92
  and [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form).
95
93
 
96
94
  ### A group
@@ -179,18 +177,19 @@ form:not([lb-key-value=""]) .save-new { display: none; }
179
177
 
180
178
  See [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form).
181
179
 
182
- ### The user's choices
180
+ ### The user's order and filters
183
181
 
184
- An order or a filter the user chose is a column of `lb-choices`, so the
185
- element showing it carries it as `lb-column-value`. Style anything else by
186
- it with `:has()`:
182
+ The order the user chose is the `order` column of `lb-sort`, and a filter
183
+ is a column of `lb-filters`, so the element showing either carries it as
184
+ `lb-column-value`. Style anything else by it with `:has()`:
187
185
 
188
186
  ```css
189
- table:has([lb-column="accounts_order"][lb-column-value="-balance"])
190
- th.balance::after { content: "▼"; }
187
+ :has([lb-column="name"][lb-column-value="^=Amazon"]) th.name::after {
188
+ content: "🔍";
189
+ }
191
190
  ```
192
191
 
193
- See [Choices](./data-binding.md#choices).
192
+ See [Sort and filters](./data-binding.md#sort-and-filters).
194
193
 
195
194
  ### Content shown by condition
196
195
 
@@ -572,18 +572,21 @@ missing a field its name needs, and stamps the dispatching element with
572
572
 
573
573
  Let the event bubble, so an ancestor can stop it before the hub sends it.
574
574
 
575
- A request with `lb-row-update` for `lb-url` or `lb-choices` is keyed by the
576
- path, so an element anywhere on the page may set a query parm or make a
577
- [choice](./data-binding.md#choices) without sitting inside the query:
575
+ A request with `lb-row-update` for `lb-url` is keyed by the path; one for
576
+ `lb-sort` or `lb-filters` is keyed by the query's name. An element anywhere
577
+ on the page may set a query parm or make a
578
+ [sort or a filter](./data-binding.md#sort-and-filters) without sitting
579
+ inside the query:
578
580
 
579
581
  ```ts
580
- import { CHOICES_QUERY, LB_EVENT_NAME, REQUEST_ROW_UPDATE } from "@loadbare/app/constants";
582
+ import { SORT_QUERY, LB_EVENT_NAME, REQUEST_ROW_UPDATE } from "@loadbare/app/constants";
581
583
  import type { HubRequest } from "@loadbare/app/types";
582
584
 
583
585
  const detail: HubRequest = {
584
586
  name: REQUEST_ROW_UPDATE,
585
- query: CHOICES_QUERY,
586
- values: { ledger_order: "-amount" },
587
+ query: SORT_QUERY,
588
+ key: "ledger",
589
+ values: { order: "-amount" },
587
590
  };
588
591
  this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
589
592
  ```
@@ -616,8 +619,8 @@ listener. Import its name as `LB_DONE_EVENT_NAME`.
616
619
  `items` is every response item that landed because of the request, in
617
620
  order. `refusal` is set beside `error` when the handler refused the
618
621
  request, holding its `message` and `columns`. An answer that moved the URL contributes its `lb-url` item, which
619
- carries a key only the server knew, and the page load. A choice contributes
620
- the row of `lb-choices` it made. `error` is set when
622
+ carries a key only the server knew, and the page load. A sort or a filter contributes
623
+ the row of `lb-sort` or `lb-filters` it made. `error` is set when
621
624
  the round trip failed, and then nothing landed. A new row's key is in the
622
625
  answer only when the handler returned it in a patch: a refresh sends all
623
626
  rows, and nothing marks which one is new.
@@ -2,7 +2,7 @@
2
2
 
3
3
  A page shows server data by naming queries and columns in its HTML, and asks
4
4
  the server to change that data by naming requests. The developer writes
5
- eighteen `lb-` attributes. The server declares every query a page may show and
5
+ ten `lb-` attributes. The server declares every query a page may show and
6
6
  every request it may send, and refuses anything it has not declared.
7
7
 
8
8
  Everything is a query. A query is a name for rows, of kind `row` or `rows`,
@@ -46,12 +46,9 @@ each with its key, and permits the three requests on `roster`; see
46
46
  | `lb-query` | Any element | The query whose rows land in it |
47
47
  | `lb-column` | Any element | The column it shows, and the column it gathers |
48
48
  | `lb-show` | Any element but `<template>` | The column that decides whether it is present |
49
- | `lb-order` | An element with `lb-query` | The choice holding the user's order; see [Order](#order) |
50
- | `lb-filter` | An element with `lb-query` | The columns a choice filters; see [Filters](#filters) |
49
+ | `lb-filter` | An element with `lb-query` | The columns the user may filter; see [Filters](#filters) |
51
50
  | `lb-group` | A `<template>` | Nothing: it marks a group template; see [Groups](#groups) |
52
- | `lb-count` | Any element | Nothing: it shows a count of rows; see [Aggregates](#aggregates) |
53
- | `lb-sum`, `lb-avg`, `lb-min`, `lb-max` | Any element | The column aggregated; see [Aggregates](#aggregates) |
54
- | `lb-select` | A checkbox in a row template | Nothing: it selects its live row; see [Selecting rows](#selecting-rows) |
51
+ | `lb-aggregate` | Any element | The function it applies to `lb-column`; see [Aggregates](#aggregates) |
55
52
  | `lb-request` | Any element | The request it sends when it commits |
56
53
  | `lb-url-link` | An `<a>` | A link between pages; see [chrome](./chrome.md#links) |
57
54
  | `lb-url-parm` | An `<a lb-url-link>` in a row | Columns of the row its `href` carries as parms; see [chrome](./chrome.md#a-link-from-a-row) |
@@ -73,7 +70,6 @@ builder writes a stamp, and a stylesheet or a custom element reads it.
73
70
  | `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested`, `lb-row-unplaced` | What happened to a row; see [What happened to a row](#what-happened-to-a-row) |
74
71
  | `lb-row-leaving`, `lb-group-leaving` | A row or group on its way out |
75
72
  | `lb-row-editing` | The row a form is pointed at; see [Editing a row in one form](#editing-a-row-in-one-form) |
76
- | `lb-row-selected` | A row whose `lb-select` is ticked |
77
73
  | `lb-request-pending` | The element's request is in flight |
78
74
  | `lb-request-error` | The element's last request failed |
79
75
  | `lb-request-refused` | The handler refused it; see [Refusals](#refusals) |
@@ -185,7 +181,7 @@ or `""`:
185
181
 
186
182
  A checkbox lands what it sends, so it shows the same state after a round
187
183
  trip through a query whose values are strings, such as `lb-url` or
188
- `lb-choices`. A query parm the URL does not carry lands as `""`, so the
184
+ `lb-filters`. A query parm the URL does not carry lands as `""`, so the
189
185
  checkbox is unchecked. Unchecked sends `"false"` rather than nothing, so a
190
186
  flag can be set back. A checkbox carrying a `value` attribute is not a flag
191
187
  but one of a set, holding a query parm the URL repeats; see
@@ -271,25 +267,22 @@ roster: rows("id", (ctx) => ctx.db.members(), { order: "team,name" }),
271
267
  With no order, rows show in the order they arrived, and a patch's new rows
272
268
  go last.
273
269
 
274
- The user's order replaces the server's. Write `lb-order` on the element
275
- carrying `lb-query`, naming a column of [`lb-choices`](#choices). That
276
- column holds an order written the same way, and while it is empty the
277
- server's order places the rows:
270
+ The user's order replaces the server's. The hub serves the query
271
+ [`lb-sort`](#sort-and-filters), one row per `rows` query, keyed by the
272
+ query's name, whose `order` column holds an order written the same way.
273
+ While it is empty, the server's order places the rows:
278
274
 
279
275
  ```html
280
- <div lb-query="lb-choices">
281
- <select lb-column="roster_order" lb-request="lb-row-update">
282
- <option value="">By team</option>
283
- <option value="name">By name</option>
284
- <option value="-joined">Newest first</option>
285
- </select>
286
- </div>
287
-
288
- <ul lb-query="roster" lb-order="roster_order">
276
+ <ul lb-query="roster">
289
277
  <template><li lb-column="name"></li></template>
290
278
  </ul>
291
279
  ```
292
280
 
281
+ A control makes a choice by sending `lb-row-update` for `lb-sort`, keyed by
282
+ the query's name; the `<lb-table>` widget's sortable headings do this for
283
+ each column. The hub places the rows again from the ones it already holds,
284
+ and tells the server through `onChoices`.
285
+
293
286
  | Values | Compare |
294
287
  |----------------------|----------------------------------------------|
295
288
  | Two numbers | Numerically |
@@ -321,7 +314,7 @@ user works through them. It names `order`, `filter` or both, separated by a
321
314
  space:
322
315
 
323
316
  ```html
324
- <lb-table lb-query="entries" lb-order="entries_order"
317
+ <lb-table lb-query="entries"
325
318
  lb-filter="description, is_unmatched" lb-manual="order filter">
326
319
  ```
327
320
 
@@ -382,20 +375,19 @@ the move.
382
375
 
383
376
  Write `lb-filter` on the element carrying `lb-query`, naming the columns of
384
377
  its rows the user may filter, separated by commas. The column of
385
- [`lb-choices`](#choices) of the same name holds the filter for each:
378
+ [`lb-filters`](#sort-and-filters) of the same name holds the filter for
379
+ each:
386
380
 
387
381
  ```html
388
- <div lb-query="lb-choices">
389
- <input lb-column="name" lb-request="lb-row-update" placeholder="Name" />
390
- </div>
391
-
392
382
  <ul lb-query="roster" lb-filter="name, team">
393
383
  <template><li lb-column="name"></li></template>
394
384
  </ul>
395
385
  ```
396
386
 
397
- A row that does not pass every filter is `hidden`, and a ticked `lb-select`
398
- in it is cleared. Under `lb-manual="filter"`, a row an answer changed stays
387
+ A filter is made by sending `lb-row-update` for `lb-filters`, keyed by the
388
+ query's name; the `<lb-table>` widget's selection does this.
389
+
390
+ A row that does not pass every filter is `hidden`. Under `lb-manual="filter"`, a row an answer changed stays
399
391
  showing until the rows are placed again; see
400
392
  [Placing by hand](#placing-by-hand). A group whose rows are all hidden is hidden too
401
393
  ([miswart](../miswarts.md#rows-hidden-by-a-filter)). An empty
@@ -494,23 +486,24 @@ group, and are reported once.
494
486
 
495
487
  ### Aggregates
496
488
 
497
- `lb-count`, `lb-sum="col"`, `lb-avg="col"`, `lb-min="col"` and
498
- `lb-max="col"` set an element from the rows of the nearest group around it,
499
- or of the whole list outside any group:
489
+ `lb-aggregate` sets an element from the rows of the nearest group around
490
+ it, or of the whole list outside any group. It names the function, and
491
+ `lb-column` names the column:
500
492
 
501
493
  ```html
502
- <tr><td><span lb-count></span> people</td><td lb-sum="salary"></td></tr>
494
+ <tr><td><span lb-aggregate="count"></span> people</td><td lb-aggregate="sum" lb-column="salary"></td></tr>
503
495
  ```
504
496
 
505
- | Attribute | Sets the element to |
497
+ | `lb-aggregate` | Sets the element to |
506
498
  |----------------|-----------------------------------------------------------|
507
- | `lb-count` | The number of rows |
508
- | `lb-sum="col"` | The sum of `col` |
509
- | `lb-avg="col"` | The average of `col` |
510
- | `lb-min="col"` | The least value of `col`, by the [order](#order)'s comparison |
511
- | `lb-max="col"` | The greatest, likewise |
512
-
513
- An aggregate lands as a column does, with `lb-column-value`. A sum and an
499
+ | `count` | The number of rows; with `lb-column`, the values in it that are not null |
500
+ | `sum` | The sum of the column |
501
+ | `avg` | The average of the column |
502
+ | `min` | The least value of the column, by the [order](#order)'s comparison |
503
+ | `max` | The greatest, likewise |
504
+
505
+ An aggregate lands as a column does, with `lb-column-value`; its
506
+ `lb-column` names the column it totals, and no row lands on it. A sum and an
514
507
  average are exact decimals, read from JSON numbers and from strings holding
515
508
  a plain decimal, as Postgres sends `numeric`. A sum has as many places as
516
509
  the most precise value, and an average up to two more. `null` and empty
@@ -578,8 +571,8 @@ and its `lbRowsLanded` runs as its rows land. See
578
571
  [Holding rows](./custom-elements.md#holding-rows).
579
572
 
580
573
  A request always goes to the server, and a query re-runs only when a
581
- response names it. A [choice](#choices) is the one thing landed from what
582
- the hub kept. A patch that
574
+ response names it. A [sort or a filter](#sort-and-filters) is the one thing
575
+ landed from what the hub kept. A patch that
583
576
  arrives before all of a query's rows is not kept, since it is not the whole
584
577
  of anything.
585
578
 
@@ -753,8 +746,7 @@ Every request has one shape:
753
746
  ```
754
747
 
755
748
  The hub takes `query`, `key` and `values` from where the element sits, and
756
- sends each when it has one. A request acting on several rows carries `keys`
757
- in place of `key`; see [Selecting rows](#selecting-rows). The server runs the handler for the name and
749
+ sends each when it has one. The server runs the handler for the name and
758
750
  answers with response items, which land like any others.
759
751
 
760
752
  ### Committing
@@ -1009,10 +1001,10 @@ The hub sends one of these only when it has what the table lists, and
1009
1001
  otherwise reports to the console. An insert or an update that gathers
1010
1002
  nothing is not sent.
1011
1003
 
1012
- For a query the hub serves, `lb-url` or `lb-choices`, the hub answers
1013
- `lb-row-update` itself, with no round trip; see
1014
- [Query parms](./chrome.md#query-parms) and [Choices](#choices). The third,
1015
- `lb-hub`, takes no request.
1004
+ For a query the hub serves, `lb-url`, `lb-sort` or `lb-filters`, the hub
1005
+ answers `lb-row-update` itself, with no round trip; see
1006
+ [Query parms](./chrome.md#query-parms) and
1007
+ [Sort and filters](#sort-and-filters). `lb-hub-status` takes no request.
1016
1008
 
1017
1009
  A key comes from a live row, or from an element a `row` landed on, so all
1018
1010
  three work against either kind:
@@ -1118,6 +1110,27 @@ The rows a form's answer lands are placed by the list's `lb-manual`, as any
1118
1110
  answer's are, whether the form was pointed by Edit or by New; see
1119
1111
  [Placing by hand](#placing-by-hand).
1120
1112
 
1113
+ A list nested in the form follows the row the form is bound to. When Edit
1114
+ or New binds the form, the hub drops the list's rows not yet entered and
1115
+ asks the server for the list's query again, as the request `lb-read`, with
1116
+ the bound row's key added to the query string as the parm named for the
1117
+ form's key column. `contextFor` receives it among the parms, and the query
1118
+ reads it as it reads any parm, so one query serves a master the URL names
1119
+ and a master Edit binds. New sends the parm empty, and the query answers
1120
+ with no rows:
1121
+
1122
+ ```ts
1123
+ function contextFor(req: Request, parms: URLSearchParams): HubContext {
1124
+ return { db: openDb(), paycheck: parms.get("paycheck_id") ?? "" };
1125
+ }
1126
+
1127
+ deductions: rows("id", (ctx) => ctx.db.deductionsFor(ctx.paycheck)),
1128
+ ```
1129
+
1130
+ A form with no list inside it sends nothing when it is bound. An answer
1131
+ that returns after the form is bound to another row, or after the page
1132
+ moved on, lands nothing.
1133
+
1121
1134
  A successful request from the form lets it go, as Cancel does: the form
1122
1135
  empties, its controls return to their defaults, and the stamps go. A failed
1123
1136
  one leaves the form as the user had it, to correct.
@@ -1145,63 +1158,6 @@ outside a live row of the form's query, Edit for a key the hub holds no row
1145
1158
  for, and any of these three commands aimed at an element that is not a form
1146
1159
  carrying `lb-query`.
1147
1160
 
1148
- ### Selecting rows
1149
-
1150
- A checkbox carrying `lb-select` in a row template selects the live row it
1151
- sits in. It is not a column: it carries no `lb-column`, lands nothing and
1152
- is not gathered. The hub stamps the row `lb-row-selected` while it is
1153
- ticked.
1154
-
1155
- A request for the query that names no one row then acts on the rows
1156
- selected, and carries their keys as `keys`:
1157
-
1158
- ```html
1159
- <tbody lb-query="transactions">
1160
- <template>
1161
- <tr>
1162
- <td><input type="checkbox" lb-select lb-show="selecting" /></td>
1163
- <td lb-column="description"></td>
1164
- <td lb-column="amount"></td>
1165
- </tr>
1166
- </template>
1167
- </tbody>
1168
-
1169
- <form lb-query="transactions" lb-request="lb-row-update">
1170
- <lb-picker lb-query="accounts" lb-column="acct" exp-label="Account" exp-column="name"></lb-picker>
1171
- <button type="submit">Apply</button>
1172
- <button type="button" lb-request="lb-row-delete">Delete</button>
1173
- </form>
1174
- ```
1175
-
1176
- ```json
1177
- { "name": "lb-row-update", "query": "transactions", "keys": ["4", "9"], "values": { "acct": "12" } }
1178
- ```
1179
-
1180
- | Request | With rows selected | With none ticked |
1181
- |------------------|----------------------------------------|----------------------|
1182
- | `lb-row-update` | Sent with `keys` | Not sent |
1183
- | `lb-row-delete` | Sent with `keys` | Not sent |
1184
- | A declared one | Sent with `keys` | Sent with `keys: []` |
1185
- | `lb-row-insert` | Sent as ever; the selection is not its | Sent as ever |
1186
-
1187
- An update or delete with nothing ticked has nothing to act on, and nothing
1188
- is reported. A request from inside a row still names that row alone. The
1189
- server runs the page's `rowUpdate` or `rowDelete` once for each key, as for
1190
- each row alone, and refreshes once; see [crud](./page-files.md#crud).
1191
-
1192
- After the request succeeds, the hub clears the selection. A failed one
1193
- leaves it, to try again.
1194
-
1195
- Only a checkbox that is showing counts. Turning a selector off with
1196
- `lb-show` also clears its tick, so leaving a selecting mode abandons the
1197
- selection rather than keeping it out of sight. With every selector off, a
1198
- request for the query is an ordinary one again.
1199
-
1200
- Which rows are selected, and whether the page is in a selecting mode, is
1201
- interaction state. A mode the URL keeps, such as `?selecting=1`, reaches
1202
- the rows as a column the query computes from the parm, which `lb-show`
1203
- reads.
1204
-
1205
1161
  ### Declared requests
1206
1162
 
1207
1163
  A request name the page declares under `handlers` runs the application's
@@ -1331,7 +1287,7 @@ returns its controls to their defaults, and the hub removes
1331
1287
  `lb-column-value` from each of them. A cancelled reset keeps both.
1332
1288
 
1333
1289
  A page load that fails lands nothing and is reported to the console, and
1334
- the query `lb-hub` says so; see [The hub's state](./chrome.md#the-hubs-state).
1290
+ the query `lb-hub-status` says so; see [The hub's state](./chrome.md#the-hubs-state).
1335
1291
  A load
1336
1292
  started by a request for `lb-url` stamps the element that sent it, as any
1337
1293
  request stamps its element.
@@ -1363,28 +1319,28 @@ answers it with no round trip:
1363
1319
  See [The URL](./chrome.md#the-url) for its columns, links, history, and the
1364
1320
  unknown-page dialog.
1365
1321
 
1366
- ## Choices
1322
+ ## Sort and filters
1367
1323
 
1368
- The hub serves a second query, `lb-choices`: one row keyed by `lb-path`,
1369
- with a column for each choice the user made for how rows show, an
1370
- [order](#order) or a [filter](#filters). The application names the
1371
- columns. A control inside `lb-query="lb-choices"` that sends
1372
- `lb-row-update` makes a choice, and an element anywhere may make one by
1373
- dispatching the `lb-request` event with `lb-row-update` for `lb-choices`,
1374
- which the hub keys by the path; see
1324
+ The hub serves two queries for how rows show: `lb-sort` and `lb-filters`,
1325
+ one row per `rows` query, keyed by the query's name. `lb-sort` holds each
1326
+ query's order as the server resolved it; `lb-filters` holds each query's
1327
+ filter values. An element makes a sort or a filter by dispatching
1328
+ `lb-request` with `lb-row-update` for `lb-sort` or `lb-filters`, keyed by
1329
+ the query's name; the `<lb-table>` widget's sortable headings and its
1330
+ selection do this; see
1375
1331
  [Sending a request](./custom-elements.md#sending-a-request).
1376
1332
 
1377
- A choice joins the ones already made, and shows with no round trip: every
1378
- element whose `lb-order` or `lb-filter` reads a column that changed lands
1379
- its query's last answer again, as a round trip carrying the choice would
1380
- have. Then the hub sends the server every choice made on the page, as
1381
- strings, in `lb-row-update` for `lb-choices`. No element waits on it, and
1382
- the page's [`onChoices`](./page-files.md#onchoices) runs, when it has one.
1333
+ A sort or a filter shows with no round trip: the hub places and filters the
1334
+ rows it already holds again, as a round trip carrying the choice would
1335
+ have. Then it sends the server the choice, in `lb-row-update` for `lb-sort`
1336
+ or `lb-filters`. No element waits on it, and the page's
1337
+ [`onChoices`](./page-files.md#onchoices) runs, when it has one, with the
1338
+ query and its new `sort` or `filters`.
1383
1339
 
1384
1340
  Choices hold until the page changes, and a change of query parm keeps them.
1385
- On entering a page, every column named under `lb-query="lb-choices"` lands
1386
- empty, unless the page's `onPageEnter` restores them. Choices are not query
1387
- parms, and the URL does not carry them.
1341
+ On entering a page, `lb-sort` and `lb-filters` land empty, unless the
1342
+ page's `onPageEnter` restores them with `sort()` and `filters()`. Choices
1343
+ are not query parms, and the URL does not carry them.
1388
1344
 
1389
1345
  ## The markup checks
1390
1346
 
@@ -1392,16 +1348,18 @@ The developer writes `lb-` attributes in markup, and script never assigns
1392
1348
  them. The builder checks the markup of the chrome and of every page, after
1393
1349
  expansion, and refuses to build:
1394
1350
 
1395
- - an `lb-` attribute that is not one of the eighteen the developer writes;
1351
+ - an `lb-` attribute that is not one of the ten the developer writes;
1396
1352
  `lb-exp-slot` and `lb-exp-template` are consumed by expansion and never
1397
1353
  reach the check
1398
1354
  - `lb-request` naming an `lb-` request other than the three Loadbare provides
1399
1355
  - `lb-query` naming a query the page does not declare; every page and the
1400
- chrome may name `lb-url` and `lb-choices`, and the chrome names no other
1356
+ chrome may name `lb-url`, `lb-sort`, `lb-filters` and `lb-hub-status`,
1357
+ and the chrome names no other
1401
1358
  - `lb-request` naming a request the page does not declare under `handlers`;
1402
1359
  the chrome sends none
1403
1360
  - one of the three requests for a query whose `crud` entry does not declare
1404
- it, or any but `lb-row-update` for `lb-url` or `lb-choices`; the query is
1361
+ it, or any but `lb-row-update` for `lb-url`, `lb-sort` or `lb-filters`;
1362
+ the query is
1405
1363
  a form's own when the form carries `lb-query`, and otherwise the nearest
1406
1364
  `lb-query` around the element
1407
1365
  - `lb-url-link` on anything but an `<a>`
@@ -1416,20 +1374,21 @@ expansion, and refuses to build:
1416
1374
  - `lb-show` on a `<template>`, or on a row template's root
1417
1375
  - `lb-show` with no `lb-query` around it
1418
1376
  - `lb-show="!"` or `lb-show="!!column"`, which reverse no column
1419
- - `lb-order` or `lb-filter` on an element not carrying `lb-query`, or naming
1420
- no column
1377
+ - `lb-filter` on an element not carrying `lb-query`, or naming no column
1421
1378
  - `lb-manual` on an element not carrying `lb-query`, or naming anything but
1422
1379
  `order`, `filter` or both
1423
- - `lb-select` on anything but `<input type="checkbox">`, outside a row
1424
- template, or beside `lb-column` or `lb-request`
1380
+ - `lb-group` on anything but a `<template>`, or on one holding no
1381
+ `<template>` for its contents
1382
+ - `lb-aggregate` on a `<template>`, naming a function other than `count`,
1383
+ `sum`, `avg`, `min` or `max`, or, for any but `count`, beside no
1384
+ `lb-column`
1385
+ - `lb-count`, `lb-sum`, `lb-avg`, `lb-min` or `lb-max`, with what to write
1386
+ instead
1425
1387
  - a `command` beginning `--lb-` that is not one of the four commands
1426
1388
  - one of the four commands with no `commandfor`
1427
1389
  - `--lb-new` aimed at an element the markup holds that is neither a
1428
1390
  `<form>` nor a list carrying `lb-query` nested directly in a form's own
1429
1391
  query
1430
- - `lb-group` on anything but a `<template>`, or on one holding no
1431
- `<template>` for its contents
1432
- - an aggregate on a `<template>`, or any but `lb-count` naming no column
1433
1392
  - a chrome without exactly one `<lb-hub>` and exactly one `<main>`, a
1434
1393
  `<main>` outside `<lb-hub>`, or a `<main>` holding anything but whitespace
1435
1394
  - a page carrying an `<lb-hub>` or a `<main>`