@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
@@ -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>`
@@ -14,10 +14,9 @@ that declares a query, a handler or a `crud` entry whose name begins with
14
14
 
15
15
  | Flag | Default | Names | Defined in |
16
16
  | ---------- | ------- | ----------------------------------------- | --------------------------------------------------- |
17
- | `--src` | `src` | The application's own tree, scanned whole | [Running the builder](./builder.md#running-the-builder) |
18
- | `--out` | `dist` | Where the builder writes | [Running the builder](./builder.md#running-the-builder) |
19
- | `--watch` | off | Rebuild on change under `--src` | [Running the builder](./builder.md#running-the-builder) |
20
- | `--minify` | off | Minify `client.js` and `app.css` | [Running the builder](./builder.md#running-the-builder) |
17
+ | `--src` | `src` | The application's own tree, scanned whole | [Running the builder](./builder.md) |
18
+ | `--out` | `dist` | Where the builder writes | [Running the builder](./builder.md) |
19
+ | `--minify` | off | Minify `client.js` and `app.css` | [Running the builder](./builder.md) |
21
20
 
22
21
  ## Reserved file names
23
22
 
@@ -66,7 +65,7 @@ A widget package declares these. An application never does.
66
65
 
67
66
  ## Attributes
68
67
 
69
- The developer writes eighteen attributes in pages and the chrome, and the
68
+ The developer writes ten attributes in pages and the chrome, and the
70
69
  builder refuses any other `lb-` attribute there. `lb-exp-slot` and
71
70
  `lb-exp-template` are written for expansion, which removes them before the
72
71
  check. The hub and the builder write the rest, as stamps, and script never
@@ -77,12 +76,10 @@ assigns one.
77
76
  | `lb-query` | Developer | [Landing](./data-binding.md#landing) |
78
77
  | `lb-column` | Developer | [Landing](./data-binding.md#landing) |
79
78
  | `lb-show` | Developer | [Conditional rendering](./data-binding.md#conditional-rendering) |
80
- | `lb-order` | Developer | [Order](./data-binding.md#order) |
81
79
  | `lb-filter` | Developer | [Filters](./data-binding.md#filters) |
82
80
  | `lb-manual` | Developer | [Placing by hand](./data-binding.md#placing-by-hand) |
83
81
  | `lb-group` | Developer | [Groups](./data-binding.md#groups) |
84
- | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` | Developer | [Aggregates](./data-binding.md#aggregates) |
85
- | `lb-select` | Developer | [Selecting rows](./data-binding.md#selecting-rows) |
82
+ | `lb-aggregate` | Developer | [Aggregates](./data-binding.md#aggregates) |
86
83
  | `lb-request` | Developer | [Requests](./data-binding.md#requests) |
87
84
  | `lb-url-link` | Developer | [Links](./chrome.md#links) |
88
85
  | `lb-url-parm` | Developer | [A link from a row](./chrome.md#a-link-from-a-row) |
@@ -100,7 +97,6 @@ assigns one.
100
97
  | `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested` | Hub | [What happened to a row](./data-binding.md#what-happened-to-a-row) |
101
98
  | `lb-row-leaving`, `lb-group-leaving` | Hub | [What happened to a row](./data-binding.md#what-happened-to-a-row) |
102
99
  | `lb-row-editing` | Hub | [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form) |
103
- | `lb-row-selected` | Hub | [Selecting rows](./data-binding.md#selecting-rows) |
104
100
  | `lb-request-pending` | Hub | [Request state](./data-binding.md#request-state) |
105
101
  | `lb-request-error` | Hub | [Request state](./data-binding.md#request-state) |
106
102
  | `lb-request-refused` | Hub | [Refusals](./data-binding.md#refusals) |
@@ -116,9 +112,10 @@ them, and a page declares none.
116
112
 
117
113
  | Query | Kind | Key | Columns | Defined in |
118
114
  | ------------ | ----- | --------- | --------------------------------------------------------------- | ------------------------------------- |
119
- | `lb-url` | `row` | `lb-path` | `lb-path`, `lb-page-label`, `lb-page-unknown`, every query parm | [The URL](./chrome.md#the-url) |
120
- | `lb-choices` | `row` | `lb-path` | `lb-path`, every choice | [Choices](./data-binding.md#choices) |
121
- | `lb-hub` | `row` | `lb-path` | `lb-path`, `lb-load-state`, `lb-server-state`, `lb-error`, `lb-pending`, `lb-as-of` | [The hub's state](./chrome.md#the-hubs-state) |
115
+ | `lb-url` | `row` | `lb-path` | `lb-path`, `lb-page-label`, every query parm | [The URL](./chrome.md#the-url) |
116
+ | `lb-sort` | `rows` | `query` | `query`, `order` | [Sort and filters](./data-binding.md#sort-and-filters) |
117
+ | `lb-filters` | `rows` | `query` | `query`, every filter column | [Sort and filters](./data-binding.md#sort-and-filters) |
118
+ | `lb-hub-status` | `row` | `lb-path` | `lb-path`, `lb-load-state`, `lb-server-state`, `lb-error`, `lb-pending`, `lb-as-of` | [The hub's state](./chrome.md#the-hubs-state) |
122
119
 
123
120
  No query parm is reserved: a column of `lb-url` whose name begins with
124
121
  `lb-` is never a query parm.
@@ -176,7 +173,7 @@ accessor or field named `lb` and a capital.
176
173
 
177
174
  | Entry point | Exports | Defined in |
178
175
  | -------------------------- | ---------------------------------------------------------------- | ---------------------------------------------- |
179
- | `@loadbare/app/server` | `row`, `rows`, `patch`, `url`, `choices`, `refusal`, `createHub`, and the types `Queries`, `Requests`, `HubContext`, `Hub` | [Page Files](./page-files.md), [The data channel](./server.md#the-data-channel) |
176
+ | `@loadbare/app/server` | `row`, `rows`, `patch`, `url`, `sort`, `filters`, `refusal`, `createHub`, and the types `Queries`, `Requests`, `HubContext`, `Hub` | [Page Files](./page-files.md), [The data channel](./server.md#the-data-channel) |
180
177
  | `@loadbare/app/express` | `hubRoutes` | [The Express Server](./server.md) |
181
178
  | `@loadbare/app/dev` | `devServer`, `appServer`, `gate`, `devProxy`, `DOWN_MESSAGE` | [The Dev Server](./dev-server.md) |
182
179
  | `@loadbare/app/build` | `buildApp`, `watchApp` | [Assembling your own](./dev-server.md#assembling-your-own) |
@@ -207,12 +204,16 @@ imports them rather than writing a string.
207
204
  | `ATTR_GROUP_COLUMN` | `lb-group-column` |
208
205
  | `ATTR_GROUP_VALUE` | `lb-group-value` |
209
206
  | `LIVE_GROUP` | `[lb-group-live]`, the selector for a group's element |
210
- | `ATTR_COUNT`, `ATTR_SUM`, `ATTR_AVG`, `ATTR_MIN`, `ATTR_MAX` | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` |
211
- | `AGGREGATES` | All five aggregate attributes |
207
+ | `ATTR_AGGREGATE` | `lb-aggregate` |
208
+ | `AGGREGATE_COUNT`, `AGGREGATE_SUM`, `AGGREGATE_AVG`, `AGGREGATE_MIN`, `AGGREGATE_MAX` | `count`, `sum`, `avg`, `min`, `max` |
209
+ | `AGGREGATES` | All five aggregate functions |
212
210
  | `ORDER_DESCENDING` | `-`, written before a column in an order |
213
211
  | `COLUMN_TYPES` | `number`, `boolean`, `text` |
214
- | `CHOICES_QUERY` | `lb-choices` |
215
- | `ATTR_ORDER` | `lb-order` |
212
+ | `SORT_QUERY` | `lb-sort` |
213
+ | `SORT_COLUMN_QUERY` | `query` |
214
+ | `SORT_COLUMN_ORDER` | `order` |
215
+ | `FILTERS_QUERY` | `lb-filters` |
216
+ | `FILTERS_COLUMN_QUERY` | `query` |
216
217
  | `ATTR_FILTER` | `lb-filter` |
217
218
  | `ATTR_MANUAL` | `lb-manual` |
218
219
  | `MANUAL_ORDER`, `MANUAL_FILTER` | `order`, `filter`: the sides `lb-manual` names |
@@ -236,13 +237,12 @@ imports them rather than writing a string.
236
237
  | `LB_COMMANDS` | All four commands |
237
238
  | `LB_COMMAND_PREFIX` | `--lb-` |
238
239
  | `ATTR_ROW_EDITING` | `lb-row-editing` |
239
- | `ATTR_SELECT` | `lb-select` |
240
- | `ATTR_ROW_SELECTED` | `lb-row-selected` |
241
240
  | `LB_RESERVED_PREFIX` | `lb-` |
242
241
  | `REQUEST_ROW_INSERT` | `lb-row-insert` |
243
242
  | `REQUEST_ROW_UPDATE` | `lb-row-update` |
244
243
  | `REQUEST_ROW_DELETE` | `lb-row-delete` |
245
244
  | `LB_ROW_REQUESTS` | All three request names |
245
+ | `REQUEST_READ` | `lb-read`, the hub's own read of a bound form's lists; markup never writes it |
246
246
  | `LB_METHODS` | The four reserved methods |
247
247
  | `BROWSER_LIFECYCLE` | The browser callbacks the build refuses |
248
248
  | `KIND_ROW` | `row` |
@@ -250,8 +250,7 @@ imports them rather than writing a string.
250
250
  | `URL_QUERY` | `lb-url` |
251
251
  | `URL_COLUMN_PATH` | `lb-path` |
252
252
  | `URL_COLUMN_PAGE_LABEL` | `lb-page-label` |
253
- | `URL_COLUMN_PAGE_UNKNOWN` | `lb-page-unknown` |
254
- | `HUB_QUERY` | `lb-hub` |
253
+ | `HUB_QUERY` | `lb-hub-status` |
255
254
  | `HUB_COLUMN_LOAD_STATE` | `lb-load-state` |
256
255
  | `HUB_COLUMN_SERVER_STATE` | `lb-server-state` |
257
256
  | `HUB_COLUMN_ERROR` | `lb-error` |
@@ -268,7 +267,7 @@ imports them rather than writing a string.
268
267
  | `ATTR_EXP_TEMPLATE` | `lb-exp-template` |
269
268
  | `ATTR_PAGE` | `lb-page` |
270
269
  | `ATTR_PAGE_TITLE` | `lb-page-title` |
271
- | `DEVELOPER_ATTRIBUTES` | The eighteen attributes a developer writes |
270
+ | `DEVELOPER_ATTRIBUTES` | The ten attributes a developer writes |
272
271
  | `HUB_TAG_NAME` | `lb-hub` |
273
272
  | `LB_ENDPOINT` | `/lb` |
274
273
  | `LB_REQUEST_TIMEOUT_MS` | `10000` |
@@ -152,8 +152,8 @@ optional:
152
152
 
153
153
  | Key | Runs |
154
154
  |---------------|--------------------------------------------------------|
155
- | `onPageEnter` | Before the page's queries, when the page loads; may move the URL or restore choices |
156
- | `onChoices` | Each time the user makes a choice |
155
+ | `onPageEnter` | Before the page's queries, when the page loads; may move the URL or restore the user's order and filters |
156
+ | `onChoices` | Each time the user changes the order or a filter |
157
157
  | `handlers` | The page's declared requests, by request name |
158
158
  | `crud` | The requests Loadbare provides, by query name |
159
159
 
@@ -165,14 +165,11 @@ name:
165
165
  | `name` | The request name, from `lb-request` |
166
166
  | `query` | The query the request is for |
167
167
  | `key` | The key of the row the request is for |
168
- | `keys` | The keys of the rows selected, in place of `key` |
169
168
  | `rows` | A form's nested rows, by query: each `key` and `values` |
170
169
  | `values` | The gathered control values, by column |
171
170
 
172
171
  The hub sends `query`, `key` and `values` when it finds them; see
173
- [Gathering](./data-binding.md#gathering). It sends `keys` for a request that
174
- acts on the rows the user selected; see
175
- [Selecting rows](./data-binding.md#selecting-rows).
172
+ [Gathering](./data-binding.md#gathering).
176
173
 
177
174
  ### onPageEnter
178
175
 
@@ -193,34 +190,41 @@ loads there in the same round trip, and that load does not follow
193
190
  `onPageEnter` again. A `url()` leading to the parms already in hand is
194
191
  ignored. See [Moving the URL](#moving-the-url).
195
192
 
196
- It may return `choices()`, alone or beside `url()`, to restore the user's
197
- [choices](./data-binding.md#choices) for the page:
193
+ It may return `sort()` and `filters()`, alone or beside `url()`, to restore
194
+ the user's [order and filters](./data-binding.md#sort-and-filters) for the
195
+ page:
198
196
 
199
197
  ```ts
200
198
  // src/pages/directory.requests.ts
201
- import { choices, url, type Requests } from "@loadbare/app/server";
199
+ import { sort, filters, url, type Requests } from "@loadbare/app/server";
202
200
 
203
201
  export const requests: Requests = {
204
202
  onPageEnter: async (ctx) => {
205
203
  const saved = await ctx.db.savedView("directory");
206
- if (saved) return { ...url(saved.parms), ...choices(saved.choices) };
204
+ if (saved) {
205
+ return {
206
+ ...url(saved.parms),
207
+ ...sort(saved.order),
208
+ ...filters(saved.filters),
209
+ };
210
+ }
207
211
  },
208
- onChoices: (ctx, chosen) => ctx.db.saveChoices("directory", chosen),
212
+ onChoices: (ctx, change) => ctx.db.saveChoices(change),
209
213
  };
210
214
  ```
211
215
 
212
- The page load answers `lb-choices` first, so the rows land by the choices.
213
- When `url()` moves the page, the choices arrive with the load at the new
214
- URL. `choices()`, like `url()`, takes columns whose values are strings, at
215
- least one of them; anything else is reported on the server console and
216
- ignored.
216
+ The page load answers `lb-sort` and `lb-filters` first, so the rows land by
217
+ the order and the filters. When `url()` moves the page, the choices arrive
218
+ with the load at the new URL. `sort()` and `filters()`, like `url()`, take
219
+ columns whose values are strings, at least one of them; anything else is
220
+ reported on the server console and ignored.
217
221
 
218
222
  ### onChoices
219
223
 
220
- `onChoices` runs each time the user makes a choice, with every choice made
221
- on the page, by column, as strings. It is how an application keeps them for
222
- `onPageEnter` to restore, and answers nothing, by convention. Whatever it
223
- returns lands. A page without it is not told.
224
+ `onChoices` runs each time the user changes the order or a filter, with the
225
+ query and its new `sort` or `filters`, as strings. It is how an application
226
+ keeps them for `onPageEnter` to restore, and answers nothing, by
227
+ convention. Whatever it returns lands. A page without it is not told.
224
228
 
225
229
  ### handlers
226
230
 
@@ -264,12 +268,6 @@ are one vocabulary:
264
268
  | `lb-row-update` | `rowUpdate` | `query`, `key`, `values`, and `rows` from a form |
265
269
  | `lb-row-delete` | `rowDelete` | `query`, `key` |
266
270
 
267
- An `lb-row-update` or `lb-row-delete` carrying `keys` runs `rowUpdate` or
268
- `rowDelete` once for each key, with that key, so a handler written for one
269
- row serves several. The patches the runs return are laid together, and the
270
- refresh set runs once. A `url()` returned for one of several rows is
271
- reported and ignored. A declared request receives `keys` as it is.
272
-
273
271
  `rows` holds the rows of a query nested in the form the request came from,
274
272
  by query name, each with its `key` and the `values` of its own controls; see
275
273
  [A form over a set of rows](./data-binding.md#a-form-over-a-set-of-rows).
@@ -199,7 +199,6 @@ interface HubRequest {
199
199
  name: string;
200
200
  query?: string;
201
201
  key?: string;
202
- keys?: string[];
203
202
  values?: Record<string, string>;
204
203
  rows?: Record<string, { key: string; values: Record<string, string> }[]>;
205
204
  }
@@ -229,9 +228,9 @@ in `drop` are removed, and every other row is left alone. A `rows` item on
229
228
  the answer that loads the page also carries the query's declared `order`
230
229
  and `types`, which the hub keeps for every answer after it.
231
230
 
232
- A choice is sent as `lb-row-update` for `lb-choices`, keyed by the path,
233
- with every choice made on the page as `values`; see
234
- [Choices](./data-binding.md#choices).
231
+ A sort or a filter is sent as `lb-row-update` for `lb-sort` or `lb-filters`,
232
+ keyed by the query's name, with the new order or filter as `values`; see
233
+ [Sort and filters](./data-binding.md#sort-and-filters).
235
234
 
236
235
  `createHub(pages)` builds the `Hub`, which has two calls, both answering
237
236
  with response items: `dataForPage(page, ctx, options)` for a page load, and