@loadbare/app 0.11.0 → 0.13.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.map +1 -1
  2. package/dist/build/assemble.js +61 -1
  3. package/dist/build/assemble.js.map +1 -1
  4. package/dist/build/cli.js +2 -1
  5. package/dist/build/cli.js.map +1 -1
  6. package/dist/build/elements.d.ts +9 -1
  7. package/dist/build/elements.d.ts.map +1 -1
  8. package/dist/build/elements.js +50 -0
  9. package/dist/build/elements.js.map +1 -1
  10. package/dist/build/origins.d.ts +2 -0
  11. package/dist/build/origins.d.ts.map +1 -1
  12. package/dist/build/origins.js +1 -1
  13. package/dist/build/origins.js.map +1 -1
  14. package/dist/core/lb-constants.d.ts +26 -1
  15. package/dist/core/lb-constants.d.ts.map +1 -1
  16. package/dist/core/lb-constants.js +79 -0
  17. package/dist/core/lb-constants.js.map +1 -1
  18. package/dist/core/lb-types.d.ts +58 -18
  19. package/dist/core/lb-types.d.ts.map +1 -1
  20. package/dist/core/lb-types.js +74 -11
  21. package/dist/core/lb-types.js.map +1 -1
  22. package/dist/hub/lb-apply.d.ts +44 -16
  23. package/dist/hub/lb-apply.d.ts.map +1 -1
  24. package/dist/hub/lb-apply.js +694 -49
  25. package/dist/hub/lb-apply.js.map +1 -1
  26. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  27. package/dist/hub/lb-hub.browser.js +168 -36
  28. package/dist/hub/lb-hub.browser.js.map +1 -1
  29. package/dist/server/lb-express.d.ts +6 -4
  30. package/dist/server/lb-express.d.ts.map +1 -1
  31. package/dist/server/lb-express.js +32 -14
  32. package/dist/server/lb-express.js.map +1 -1
  33. package/dist/server/lb-server.d.ts +49 -10
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/server/lb-server.js +87 -28
  36. package/dist/server/lb-server.js.map +1 -1
  37. package/docs/TECHREF-1.0.md +364 -72
  38. package/docs/comparison.md +16 -10
  39. package/docs/possible-ideas.md +188 -0
  40. package/docs/reference/chrome.md +22 -1
  41. package/docs/reference/custom-elements.md +90 -29
  42. package/docs/reference/data-binding.md +132 -7
  43. package/docs/reference/page-files.md +48 -11
  44. package/docs/reference/server.md +4 -0
  45. package/docs/reference/widgets.md +94 -24
  46. package/docs/roadmap.md +10 -6
  47. package/docs/testing.md +21 -10
  48. package/package.json +2 -3
  49. package/skills/loadbare-app/SKILL.md +85 -12
  50. package/skills/loadbare-app/references/TECHREF-1.0.md +364 -72
  51. package/skills/loadbare-app/references/chrome.md +22 -1
  52. package/skills/loadbare-app/references/custom-elements.md +90 -29
  53. package/skills/loadbare-app/references/data-binding.md +132 -7
  54. package/skills/loadbare-app/references/page-files.md +48 -11
  55. package/skills/loadbare-app/references/server.md +4 -0
  56. package/skills/loadbare-app/references/widgets.md +94 -24
@@ -22,6 +22,10 @@ a value is rendered by browser coercion when it lands on an HTML text
22
22
  element or a control, and by whatever the custom element does with
23
23
  `lb-column-value` when it lands on one that is not a control.
24
24
 
25
+ Two places read a value's type today. The [order](#order) compares values
26
+ by their JSON type, and an [aggregate](#aggregates) reads a sum or an
27
+ average from a JSON number or a string holding a plain decimal.
28
+
25
29
  We will then see if a useful solution emerges that Loadbare/app should
26
30
  handle.
27
31
 
@@ -32,28 +36,15 @@ handle.
32
36
  what counts as checked, which waits on [Data types](#data-types), and a
33
37
  radio group is several elements answering to one column.
34
38
 
35
- ### Run-time stamps
36
-
37
- The hub stamps `lb-query-row-count`, `lb-request-pending` and
38
- `lb-request-error` for a stylesheet to read. Their consumer is CSS rather
39
- than code.
40
-
41
- - **Decide whether an unarrived value needs a signal.** An element whose
42
- column has not landed carries no `lb-column-value`. Recommend confirming
43
- that as the signal and adding nothing.
44
-
45
39
  ### Custom element hooks
46
40
 
47
41
  Loadbare/app owns every method name beginning with `lb` on a custom element.
48
42
  That decision is firm; what the set contains is not.
49
43
 
50
- - **Decide what the build does with an unknown `lb*` method.** An element
51
- carrying a method beginning with `lb` that Loadbare/app does not define may
52
- be an error, a warning, or ignored.
53
44
  - **Decide whether a custom element may receive a whole row.** A column
54
45
  lands one element at a time and offers no escape hatch. An optional
55
- `lbAcceptRow` would sit beside `lbPlaceRow` and `lbRowsLanded`, and this is
56
- the most likely first request from a custom element author.
46
+ `lbAcceptRow` would sit beside `lbRowsLanded`, and this is the most likely
47
+ first request from a custom element author.
57
48
 
58
49
  ### Nested queries
59
50
 
@@ -64,10 +55,9 @@ fine, because every `<select>` receives the same rows.
64
55
 
65
56
  But the allowed values may depend on other values in the row.
66
57
 
67
- - **Decide how a new live row fills a nested query.** A row added to the
68
- outer query starts with its nested query empty, and it fills only when the
69
- nested query's name lands again. See [Master-detail](#master-detail) for
70
- what a nested query is for.
58
+ - **Decide whether a nested query's rows may depend on its row.** A nested
59
+ query has one answer, which every live row receives. See
60
+ [Master-detail](#master-detail) for what a nested query is for.
71
61
 
72
62
  ```html
73
63
  <tbody lb-query="accounts">
@@ -80,18 +70,11 @@ But the allowed values may depend on other values in the row.
80
70
 
81
71
  ### The server API
82
72
 
83
- - **Give the chrome a way to state its own queries.** A custom element in
84
- the chrome naming a query forces every page to declare that query, since
85
- the page load covers one page's set. `lb-url` shows the pattern from the
86
- framework side, and an application has no equivalent. Recommend a
87
- chrome-level query set on `createHub`.
88
- - **State that only `createHub` implements `Hub`.** Otherwise a third call
89
- breaks anyone who wrote the interface by hand.
90
73
  - **Decide the options-argument shape once.** Global hooks, transaction
91
74
  wrapping, error handling and a CSRF token all want the same trailing
92
75
  parameter on `createHub` and `hubRoutes`. Build none of them, but pick
93
76
  where they go.
94
- - **Land `staticRoutes`.** See NEXT.md. It closes the last place where an
77
+ - **Land `staticRoutes`.** It closes the last place where an
95
78
  application hand-writes facts the builder owns.
96
79
 
97
80
  ### The builder
@@ -106,9 +89,6 @@ But the allowed values may depend on other values in the row.
106
89
  definition would let the builder ship only what survives expansion.
107
90
  Recommend deferring the mechanism and reserving the configuration key, so
108
91
  that it lands as an opt-in rather than as a silent drop.
109
- - **Land the remaining validations.** One `lb-hub` and one empty `<main>`.
110
- Recommend landing these now, because refusing markup that used to build is
111
- the kind of change 1.0 gives up.
112
92
  - **Decide whether the build validates HTML.** A page can be well formed and
113
93
  still break HTML's rules for what an element may hold, such as a `<div>`
114
94
  inside a `<p>`. The parser rearranges it silently, the same way at build
@@ -124,9 +104,6 @@ But the allowed values may depend on other values in the row.
124
104
  None of this is surface. It is listed here because trimming it is free
125
105
  today and breaking after 1.0.
126
106
 
127
- - **Drop `./build` from the exports map.** Nothing in the repository imports
128
- it, and nothing outside the CLI should call `resolveElements` or
129
- `clientEntrySource`.
130
107
  - **Audit what the hub does.** It appears to have routines that go beyond
131
108
  what the `lb-*` attribute namespace specifies.
132
109
  - **Keep the rest as constants.** The endpoint, `index` and the timeout are
@@ -425,7 +402,7 @@ query.
425
402
  | ----------- | -------- | -------------------------------------------------- |
426
403
  | `lb-query` | a query | Puts its rows in the element's content |
427
404
  | `lb-column` | a column | Sets the element from that column |
428
- | `lb-show` | a column | Removes it while the named column is null or false |
405
+ | `lb-show` | a column | Removes it while the named column is null or false; `!column` reverses it |
429
406
 
430
407
  | Stamp | The hub stamps it with |
431
408
  | -------------------- | -------------------------------- |
@@ -438,9 +415,10 @@ The markup names a query and the columns it shows. The kind and the key are
438
415
  the server's, and the markup states neither.
439
416
 
440
417
  The row template is the first `<template>` among the descendants of an
441
- element with `lb-query`, outside any nested `lb-query`. A live row is an
442
- element the hub cloned from a row template for one row, and carries
443
- `lb-row-live`.
418
+ element with `lb-query`, outside any nested `lb-query`, or, when that
419
+ template is a [group template](#groups), the innermost template inside it.
420
+ A live row is an element the hub cloned from a row template for one row,
421
+ and carries `lb-row-live`.
444
422
 
445
423
  | Kind | Row template | The hub |
446
424
  | ------ | ------------ | ----------------------------------- |
@@ -521,14 +499,15 @@ with `[lb-row-live]`, and never with `[lb-key-value]`.
521
499
  A key is unique within a query. Two rows with one key in the same answer,
522
500
  or two live rows showing one key, are reported on the console.
523
501
 
524
- All rows decide membership and order: every row is placed in the order
525
- given, and a live row whose key did not arrive is removed. A patch touches
526
- only the rows it names and leaves every other live row's contents and
527
- position alone.
502
+ All rows decide membership: a live row whose key did not arrive leaves —
503
+ see [What happened to a row](#what-happened-to-a-row). A patch touches only
504
+ the rows it names, and leaves every other live row's contents alone.
528
505
 
529
- A new live row lands immediately before the row template, so rows
530
- accumulate in the order they arrive. A custom element carrying `lb-query`
531
- and a row template may place them itself — see [Row hooks](#row-hooks).
506
+ The hub places every live row by the query's [order](#order), immediately
507
+ before the row template, or before its clone in the row's
508
+ [group](#groups). A row moves only when the order puts it somewhere else,
509
+ so all rows landing again unchanged move nothing. A live row already in the
510
+ markup when the first answer lands is one of the query's rows.
532
511
 
533
512
  After every landing the hub stamps the element with `lb-query-row-count`,
534
513
  the number of live rows it holds. The server answers with rows and says
@@ -543,6 +522,162 @@ sent. It makes an empty query a stylesheet rule:
543
522
 
544
523
  A row missing its key column is reported to the console and not landed.
545
524
 
525
+ #### Order
526
+
527
+ A `rows` query's order is a rule over its columns. The server declares it
528
+ with the query, and the URL may carry the user's own:
529
+
530
+ ```ts
531
+ accounts: rows("id", (ctx) => ctx.db.accounts(), {
532
+ order: "category_order,account_name",
533
+ }),
534
+ ```
535
+
536
+ An order is columns separated by commas. A column written after `-` sorts
537
+ descending. The query parm `lb-order-<query>` replaces the declared order
538
+ for that query: `/coa?lb-order-accounts=-account_name`. With neither, rows
539
+ show in the order the server sent them, and a patch's new rows go last.
540
+
541
+ | Values | Compare |
542
+ | -------------------- | ------------------------------------------ |
543
+ | Two numbers | Numerically |
544
+ | Two strings | As the user's language orders them |
545
+ | Two booleans | `false` before `true` |
546
+ | Two different types | Booleans, then numbers, then strings |
547
+ | `null` or missing | After everything ascending, first descending |
548
+
549
+ A number sent as a string sorts as a string, so a column the order reads
550
+ numerically arrives as a JSON number. Rows that compare equal keep the
551
+ order they arrived in, and a patch's new rows come after the rows already
552
+ showing.
553
+
554
+ The hub sorts the rows it has, so a control that writes the order parm
555
+ re-sorts the page with no round trip:
556
+
557
+ ```html
558
+ <div lb-query="lb-url">
559
+ <select lb-column="lb-order-accounts" lb-request="lb-row-update">
560
+ <option value="">By category</option>
561
+ <option value="account_name">By name</option>
562
+ </select>
563
+ </div>
564
+ ```
565
+
566
+ A query that pages or limits its rows decides membership by its order, so
567
+ it reads the parm itself, from the parms `contextFor` receives, and declares
568
+ `serverSortedByUrl: true` beside `order`. A change of the user's order then
569
+ loads the page again. The hub still places the rows it receives by the same
570
+ order, so a patch lands in its place.
571
+
572
+ #### Groups
573
+
574
+ A group is a run of rows sharing the order's leading term. A group
575
+ template is a `<template lb-group>` in the row template's place, holding the
576
+ group's heading and exactly one nested `<template>`, which is where the
577
+ group's contents go: the next group template, or the row template. The nth
578
+ group template breaks on the order's nth term, so groups nest to any depth.
579
+
580
+ ```html
581
+ <table lb-query="accounts">
582
+ <thead><tr><th>Account</th></tr></thead>
583
+ <template lb-group>
584
+ <tbody>
585
+ <tr><th scope="rowgroup" lb-column="category"></th></tr>
586
+ <template><tr><td lb-column="account_name"></td></tr></template>
587
+ </tbody>
588
+ </template>
589
+ </table>
590
+ ```
591
+
592
+ The hub clones the group template where the order breaks and puts the
593
+ group's contents immediately before the clone of its nested template. The
594
+ markup's nesting decides the shape:
595
+
596
+ | The nested template is | The group's contents land | Example |
597
+ | --------------------------------- | ----------------------------------- | -------------------------------- |
598
+ | Inside the heading's element | Inside it | `<tbody>`, `<li>`, `<details>` |
599
+ | Beside the heading | After the heading, as its siblings | A heading `<tr>` in one `<tbody>` |
600
+
601
+ `<tbody>` and `<optgroup>` do not nest, so a table's levels below the first
602
+ are heading rows beside their rows. Content after the nested template lands
603
+ after the group's rows, which is where a group's footer goes.
604
+
605
+ A group's heading is filled from its first row, the same way a live row is
606
+ filled, so it shows any column of the row. The example breaks on
607
+ `category_order` and shows `category`. The hub fills it from no other row.
608
+
609
+ The hub stamps each top-level element of a group's clone with
610
+ `lb-group-live`, `lb-group-column`, the column it breaks on, and
611
+ `lb-group-value`, its value there. A row creates its group, and a group
612
+ leaves with its last row. Groups never move: when a change of order puts
613
+ the groups at a level in another order, each of them is made again.
614
+
615
+ A group template with no nested template is reported to the console. More
616
+ group templates than the order has terms show each extra level as one
617
+ group, and are reported once.
618
+
619
+ #### Aggregates
620
+
621
+ An aggregate attribute sets an element from the rows of the nearest group
622
+ around it, or of the whole list outside any group.
623
+
624
+ | Attribute | Sets the element to |
625
+ | --------------- | --------------------------------------------- |
626
+ | `lb-count` | The number of rows |
627
+ | `lb-sum="col"` | The sum of `col` |
628
+ | `lb-avg="col"` | The average of `col` |
629
+ | `lb-min="col"` | The least value of `col`, by the [order](#order)'s comparison |
630
+ | `lb-max="col"` | The greatest, likewise |
631
+
632
+ ```html
633
+ <tr><td><span lb-count></span> accounts</td><td lb-sum="balance"></td></tr>
634
+ ```
635
+
636
+ It lands as a column does, with `lb-column-value`. A sum and an average
637
+ are exact decimals, read from JSON numbers and from strings holding a plain
638
+ decimal, as Postgres sends `numeric`. A sum has as many places as the most
639
+ precise value, and an average up to two more. `null` and empty values are
640
+ left out, as SQL leaves them out. Any other value is left out and reported
641
+ once. The hub does not format the result.
642
+
643
+ An aggregate covers the rows the browser holds. Where the true total
644
+ matters beyond what the page shows, it is a row the server answers.
645
+
646
+ #### What happened to a row
647
+
648
+ Every change the hub makes to a list is one a stylesheet can see, so an
649
+ application animates with CSS alone and the hub starts no animation.
650
+
651
+ | Attribute | On | Means |
652
+ | ------------------ | -------------------- | -------------------------------------------------- |
653
+ | `lb-row-created` | A live row | Created when the hub last touched it |
654
+ | `lb-row-changed` | A live row | Its values changed when the hub last touched it |
655
+ | `lb-row-moved` | A live row | The order put it somewhere else when last touched |
656
+ | `lb-row-requested` | A live row | `created`, `changed` or `moved` by this page's request |
657
+ | `lb-row-leaving` | A row on its way out | No longer live |
658
+ | `lb-group-leaving` | A group on its way out | No longer live |
659
+
660
+ The hub touches a row when an answer names it. `lb-row-created`,
661
+ `lb-row-changed` and `lb-row-moved` are cleared when it next does, and one
662
+ applied again is removed and added as two changes, so an animation keyed to
663
+ it plays again. `lb-row-requested` is cleared at every landing of the
664
+ row's query, and set only by a landing that answers a request from the page.
665
+
666
+ A row or a group that leaves loses `lb-row-live` or `lb-group-live`, takes
667
+ `lb-row-leaving` or `lb-group-leaving` and `inert`, and is removed once the
668
+ animations running on it have finished, at once when there are none. A
669
+ moved row stays live and moves. A copy of it stays behind where it was,
670
+ leaving.
671
+
672
+ ```css
673
+ [lb-row-created] { animation: arrive 200ms; }
674
+ [lb-row-leaving] { animation: depart 200ms forwards; }
675
+ ```
676
+
677
+ A row never travels between the two places, since that takes measuring, and
678
+ measuring takes script. Scrolling to a row is script too: a custom element
679
+ reads `lb-row-requested` in [`lbRowsLanded`](#row-hooks).
680
+
546
681
  #### Displaying by condition
547
682
 
548
683
  `lb-show` names the column that decides whether an element is present.
@@ -558,6 +693,10 @@ so `"false"` is on, and a query spells a condition as a boolean or a null.
558
693
  </template>
559
694
  ```
560
695
 
696
+ Write `!` before the column to reverse it: `lb-show="!chosen"` is present
697
+ while `chosen` is null or false. One column then decides both of two
698
+ elements, rather than a column and its opposite, which could disagree.
699
+
561
700
  It reads from the nearest ancestor row, as `lb-column` does, and on an
562
701
  element that carries `lb-query` the column belongs to the row around it. A
563
702
  row that does not carry the column leaves the element as it is.
@@ -578,7 +717,8 @@ nothing conditional shows until its row lands. An absent element's template
578
717
  keeps its place among its siblings, and a position selector counts it.
579
718
 
580
719
  These are build errors: `lb-show` on a row template's root, `lb-show` with
581
- no `lb-query` around it, and `lb-show` on a `<template>`.
720
+ no `lb-query` around it, `lb-show` on a `<template>`, and `lb-show="!"` or
721
+ `lb-show="!!column"`.
582
722
 
583
723
  Hiding is presentation, and the server still refuses what a request may not
584
724
  do.
@@ -602,13 +742,15 @@ answered side by side. A column never holds rows.
602
742
  ```
603
743
 
604
744
  Many masters, each with its own detail, is one `rows` query of joined rows:
605
- each detail row carries its master's columns. A custom element's
606
- `lbPlaceRow` groups them for display, as `lb-table` builds sections and
607
- `lb-options` builds `<optgroup>`s.
745
+ each detail row carries its master's columns. A [group
746
+ template](#groups) shows each master as a group, ordered by the master's
747
+ key first.
608
748
 
609
749
  A query nested in another query's row template receives the same rows in
610
750
  every live row. That serves a picker offering the same choices on every
611
- row, and is not a way to show a different detail per row.
751
+ row, and is not a way to show a different detail per row. A live row added
752
+ later is filled from the nested query's last answer, so a request that adds
753
+ one does not refresh the nested query to fill it.
612
754
 
613
755
  Which master a page shows is a query parm, which a query reads off `ctx`
614
756
  since it takes no argument from the browser — see [Query parms](#query-parms).
@@ -767,6 +909,18 @@ interceptor calls `stopPropagation`, not `preventDefault`. This is what
767
909
  makes a confirmation wrapper possible without the wrapped element knowing
768
910
  about it.
769
911
 
912
+ Once what the request brought back has landed, or its round trip has failed,
913
+ the hub dispatches the bubbling `lb-request-done` event from the same
914
+ element. Its `detail`, `HubRequestDone` in `@loadbare/app/types`, holds
915
+ the request as the hub sent it and `items`, every response item that landed
916
+ because of it, in order. An answer that moved the URL contributes its
917
+ `lb-url` item and the page load. `error` is set instead when the round trip
918
+ failed. By then `lb-request-pending` is gone, and a listener sees the page
919
+ as the answer left it. A request the hub or an ancestor stopped was never
920
+ sent, and gets no `lb-request-done`. An element the answer removed from the
921
+ document, such as the row a delete took away, dispatches the event where it
922
+ now is, and no ancestor it had on the page hears it.
923
+
770
924
  #### Request state
771
925
 
772
926
  The hub stamps `lb-request-pending` on the element that issued a request
@@ -804,6 +958,14 @@ A page load that fails lands nothing and is reported to the console. A load
804
958
  started by a request for `lb-url` stamps the element that issued it, as any
805
959
  request stamps its element.
806
960
 
961
+ A response answers for the URL its request was sent from, path and query
962
+ string both. One that returns after the URL has moved lands nothing, since
963
+ it describes what the user is no longer looking at: a request's answer,
964
+ including any `url()` it carries, and a page load overtaken by a newer one.
965
+ A write whose answer is dropped this way has still happened. Its
966
+ `lb-request-done` carries no items and no error, and an insert still resets
967
+ its form.
968
+
807
969
  ### The URL
808
970
 
809
971
  ---- UNEDITED ----
@@ -834,6 +996,14 @@ server runs its `onPageEnter`, then its queries. When a query parm takes a
834
996
  new value, the hub reloads the page's queries, and keeps the page's DOM, so
835
997
  live rows that come back keep their place.
836
998
 
999
+ Once an entered page's queries have landed, the hub focuses the first
1000
+ element in `<main>` the user can operate: not disabled, not in a closed
1001
+ `<dialog>`, not `inert`, not `hidden`, not in an absent `lb-show` branch,
1002
+ and one that takes the focus. Entering is a cold load, a new `lb-path`,
1003
+ and Back or Forward to another page. A change of query parm enters no
1004
+ page, and focus stays where it is, as it does when it is already in
1005
+ `<main>`.
1006
+
837
1007
  The page's title is the text of the page file's `<title>`, which the builder
838
1008
  stamps on the page as `lb-page-title`. The hub sets the document title to
839
1009
  `lb-page-label` when it is not null.
@@ -941,8 +1111,10 @@ names each one in the row it lands.
941
1111
 
942
1112
  The hub sends the query parms with every round trip, a page load and a
943
1113
  request alike. The server passes them to `contextFor` — see
944
- [The Express server](#the-express-server). Nothing in Loadbare assigns a
945
- parm a meaning.
1114
+ [The Express server](#the-express-server). Loadbare assigns one parm a
1115
+ meaning, `lb-order-<query>`, the user's [order](#order) for a query, and it
1116
+ reaches `contextFor` like any other. A request that changes only order
1117
+ parms of queries the hub sorts itself makes no round trip.
946
1118
 
947
1119
  #### An unknown page
948
1120
 
@@ -1003,8 +1175,31 @@ rowInsert: {
1003
1175
  carries the `lb-url` item alone, and the hub loads the page itself. A
1004
1176
  failure there is a page load's, and is not stamped on the element that
1005
1177
  issued the request.
1006
- - A user who has left the page by the time the response arrives keeps the
1007
- URL they are on.
1178
+ - A user who has moved by the time the response arrives, to another page or
1179
+ to other query parms, keeps the URL they are on.
1180
+
1181
+ `onPageEnter` may return `url()` the same way, to move the page before
1182
+ anything shows, as an application restoring the filters and order the user
1183
+ last had does. The page loads at the new URL in the same round trip, and
1184
+ that load does not follow `onPageEnter` again. A `url()` leading to the
1185
+ page and parms already in hand is ignored, so the page loads once. When to
1186
+ restore is the application's rule, such as only when the URL carries no
1187
+ parms of its own.
1188
+
1189
+ ```ts
1190
+ // The application put the parms on ctx in contextFor.
1191
+ onPageEnter: async (ctx) => {
1192
+ if (ctx.parms.size > 0) return;
1193
+ const saved = await ctx.db.savedView("accounts");
1194
+ if (saved) return url(saved);
1195
+ },
1196
+ ```
1197
+
1198
+ Saving what the user has on screen is a declared request with
1199
+ `refresh: []`, whose handler each page passes to a routine the
1200
+ application's pages share. A custom element naming `lb-url` sees every
1201
+ change of the URL and issues it, a change of order that made no round trip
1202
+ included.
1008
1203
 
1009
1204
  The page is loaded with a second context, built by `contextFor` from the new
1010
1205
  parms — see [The Express server](#the-express-server).
@@ -1015,7 +1210,8 @@ The builder checks the chrome and every page, after expansion, and refuses
1015
1210
  to build on any of these:
1016
1211
 
1017
1212
  - An `lb-` attribute that is not a developer attribute: `lb-query`,
1018
- `lb-column`, `lb-show`, `lb-request`, `lb-url-link`, `lb-url-push`,
1213
+ `lb-column`, `lb-show`, `lb-group`, `lb-count`, `lb-sum`, `lb-avg`,
1214
+ `lb-min`, `lb-max`, `lb-request`, `lb-url-link`, `lb-url-push`,
1019
1215
  `lb-url-unknown`. Every other `lb-` attribute is a stamp.
1020
1216
  `lb-exp-slot` and `lb-exp-template` are consumed by expansion and never
1021
1217
  reach the check.
@@ -1027,6 +1223,19 @@ to build on any of these:
1027
1223
  `<lb-hub>`.
1028
1224
  - `lb-show` on a `<template>`, on a row template's root, or with no
1029
1225
  `lb-query` around it.
1226
+ - `lb-show="!"` or `lb-show="!!column"`, which reverse no column.
1227
+ - `lb-group` on anything but a `<template>`, or on one holding no
1228
+ `<template>` for its contents.
1229
+ - An aggregate on a `<template>`, and any but `lb-count` naming no column.
1230
+ - A chrome without exactly one `<lb-hub>` and exactly one `<main>`, a
1231
+ `<main>` outside `<lb-hub>`, or a `<main>` with anything in it but
1232
+ whitespace.
1233
+ - A page carrying an `<lb-hub>` or a `<main>`.
1234
+
1235
+ The builder also reads the script of every custom element it ships, other
1236
+ than `<lb-hub>`, and refuses one that declares a method, accessor or field
1237
+ whose name begins with `lb` and a capital and is not in
1238
+ [Reserved methods](#reserved-methods).
1030
1239
 
1031
1240
  `lb-column` with no ancestor row is allowed: the hub gathers from it.
1032
1241
 
@@ -1046,7 +1255,8 @@ where the standard landmarks go: header, nav, footer, main.
1046
1255
  The file is an HTML document, which must contain:
1047
1256
  - `<lb-hub>` inside `<body>`. The hub can only act on its children,
1048
1257
  that is why it is usually nested right below `<body>`.
1049
- - One empty `<main>` inside `<lb-hub>`.
1258
+ - One empty `<main>` inside `<lb-hub>`. The builder refuses a chrome
1259
+ without exactly one of each, and a page carrying either.
1050
1260
  - The script tag for `/client.js`, the javascript bundle.
1051
1261
 
1052
1262
  It may also contain:
@@ -1156,6 +1366,8 @@ The builder writes `pages.ts` into `--out` whenever any page has a
1156
1366
  `.queries.ts` or a `.requests.ts` file. It imports each of them, calls
1157
1367
  `createHub()` once over the lot, and exports the result as `hub`. The
1158
1368
  server imports that rather than maintaining the registry by hand.
1369
+ `createHub` is the only implementation of the `Hub` interface, which may
1370
+ gain members in any release; an application never implements it.
1159
1371
 
1160
1372
  ```ts
1161
1373
  // server.ts
@@ -1220,7 +1432,8 @@ the whole path space through unchanged.
1220
1432
  Each key is a query name, which the markup names with `lb-query`.
1221
1433
 
1222
1434
  A query's kind and key belong to its name. Declare each query with
1223
- `row(key, run)` or `rows(key, run)`, naming the key column first. A query
1435
+ `row(key, run)` or `rows(key, run, options)`, naming the key column first.
1436
+ A `rows` query's `options` declare its [order](#order). A query
1224
1437
  cannot be built without one of these functions. Every query has a key; an
1225
1438
  aggregate row answers with a constant one.
1226
1439
 
@@ -1238,14 +1451,15 @@ export const queries: Queries = {
1238
1451
  ```
1239
1452
 
1240
1453
  A `row` query returns one object, and a `rows` query returns an array of
1241
- objects in the order the page shows them. An answer that disagrees with
1454
+ objects, which the page shows in that order when the query declares none. An answer that disagrees with
1242
1455
  its declared kind is left out of the response, with a warning naming the
1243
1456
  query.
1244
1457
 
1245
1458
  Query names cannot begin with `lb-`, that namespace is reserved for
1246
1459
  Loadbare/app queries the hub serves in the browser, such as
1247
1460
  [`lb-url`](#the-url). `createHub` refuses at startup a page that declares
1248
- such a name as a query, a handler or a `crud` entry.
1461
+ such a name as a query, a handler or a `crud` entry, and a query whose order
1462
+ has a term naming no column.
1249
1463
 
1250
1464
  Every query and every handler receives `ctx`, the application's own request
1251
1465
  context. The application builds it once per request and hands it to the hub,
@@ -1265,7 +1479,7 @@ It has three optional keys.
1265
1479
  | ------------- | --------------------- | -------------------------------------------------- |
1266
1480
  | `handlers` | The request name | The page's declared requests |
1267
1481
  | `crud` | A query name | The three requests Loadbare provides |
1268
- | `onPageEnter` | Nothing | Runs once on entering the page, before its queries |
1482
+ | `onPageEnter` | Nothing | Runs once on entering the page, before its queries, and may move the URL |
1269
1483
 
1270
1484
  The server resolves a declared request against `handlers`, and a request
1271
1485
  Loadbare provides against `crud` under the request's query. A name with no
@@ -1276,6 +1490,9 @@ request carrying no name answers 400, and a `run` that throws answers 500.
1276
1490
 
1277
1491
  Every handler under `handlers` and `crud` has the same two members. `run`
1278
1492
  performs the work, and `refresh` names the queries to re-run once it has.
1493
+ A query belongs there when its answer changed, never to fill an element the
1494
+ request's answer creates: that element is filled from the last answer its
1495
+ query landed.
1279
1496
 
1280
1497
  `run` receives the same `ctx` and the request less its name: `query`, `key`
1281
1498
  and `values`, as present.
@@ -1284,6 +1501,14 @@ and `values`, as present.
1284
1501
  the refreshed ones. That is how a delta reaches the browser: wrap it in
1285
1502
  `patch()`, naming the rows that arrived or changed and the keys that went.
1286
1503
 
1504
+ A refreshed `rows` query sends every row to say what changed. An update or
1505
+ a delete names its row by key, so its handler always knows what changed and
1506
+ answers with a patch, which the hub places by the query's [order](#order):
1507
+ `createHub` refuses at startup a `crud` `rowUpdate` or `rowDelete` on a
1508
+ `rows` query whose `refresh` names that same query. A `row` query sends one
1509
+ row and may refresh itself. An insert may refresh its own query, since its
1510
+ key exists only once the row does.
1511
+
1287
1512
  `run` may instead return `url()`, a new row for `lb-url`, naming query parms
1288
1513
  only the write can know, such as the key of a row it inserted. The page
1289
1514
  then loads at them in the same round trip, in place of the refresh set — see
@@ -1365,9 +1590,14 @@ A response item carries exactly one of `row`, `rows` or `patch`. A patch
1365
1590
  answers a `rows` query only: rows named in `rows` are added or updated, keys
1366
1591
  in `drop` are removed, and every other row is left alone.
1367
1592
 
1593
+ A `rows` item also carries `order` and `serverSortedByUrl` when the query
1594
+ declares them.
1595
+
1368
1596
  `createHub(pages)` builds the `Hub`, which has two calls, both answering
1369
- with response items: `dataForPage(page, ctx)` for a page load, and
1370
- `runRequest(page, request, ctx)` for a request.
1597
+ with response items: `dataForPage(page, ctx, options)` for a page load, and
1598
+ `runRequest(page, request, ctx)` for a request. `options.parms` are the
1599
+ parms the page is loaded at, and `options.follow` false ignores a `url()`
1600
+ from `onPageEnter`; `hubRoutes` passes both.
1371
1601
 
1372
1602
  ## Custom elements
1373
1603
 
@@ -1397,6 +1627,25 @@ export default ["@scope/library-name"];
1397
1627
  Import every attribute name from `@loadbare/app/constants` — see
1398
1628
  [Constants](#constants). Never write one as a string literal.
1399
1629
 
1630
+ ### What the hub and an element say to each other
1631
+
1632
+ Each direction has one mechanism for each kind of message:
1633
+
1634
+ | Direction | What | How | Today |
1635
+ | -------------- | ------------------------------------------- | ---------------------------- | ------------------------------------------------ |
1636
+ | 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` |
1637
+ | Hub to element | State the platform already names | A property | A control's `value` |
1638
+ | Hub to element | Work the hub needs done now, while landing | An optional `lb` method | `lbRowsLanded` |
1639
+ | Hub to element | A moment an element started | A bubbling event | `lb-request-done` |
1640
+ | Element to hub | A moment | A bubbling event | `lb-request`, and `change`, `click` and `submit` |
1641
+
1642
+ State is an attribute, because a stylesheet can select on it and an element
1643
+ that upgrades late still finds it. A method is for work the hub cannot go
1644
+ on without, since it acts on one element at one point in landing and nothing
1645
+ else can do it. A moment is an event, because the element that cares is
1646
+ often an ancestor of the one it concerns, and an event reaches it with no
1647
+ knowledge of either.
1648
+
1400
1649
  ### Controls
1401
1650
 
1402
1651
  A form-associated custom element with a `value` property that fires
@@ -1428,8 +1677,9 @@ renders it; its content is never replaced.
1428
1677
  | Shows a page | `constructor`, `connectedCallback` |
1429
1678
  | Creates a live row, before placing it | `constructor` |
1430
1679
  | Places a live row the first time | `connectedCallback` |
1431
- | Places it again, on every set of all rows | `disconnectedCallback`, `connectedCallback` |
1432
- | Removes a live row | `disconnectedCallback` |
1680
+ | Moves a live row the order put elsewhere | `connectedMoveCallback` where the browser moves in place, otherwise `disconnectedCallback`, `connectedCallback` |
1681
+ | Leaves a copy of a moved row behind | The copy's `constructor`, `connectedCallback`, and later `disconnectedCallback` |
1682
+ | Removes a live row | `disconnectedCallback`, once its animations end |
1433
1683
  | Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
1434
1684
  | Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
1435
1685
  | Turns on an element shipped absent | `constructor`, `connectedCallback` |
@@ -1441,14 +1691,27 @@ done once per instance goes behind a flag.
1441
1691
 
1442
1692
  ### Row hooks
1443
1693
 
1444
- | Method | Implemented By | The hub calls it |
1445
- | ------------------------------- | -------------- | -------------------------------------------------- |
1446
- | `lbPlaceRow(el, row, template)` | Developer | To place a live row, detached, on its first arrival and whenever all rows decide the order |
1447
- | `lbRowsLanded()` | Developer | After the rows land |
1694
+ | Method | Implemented By | The hub calls it |
1695
+ | ---------------- | -------------- | ------------------------------------------------------ |
1696
+ | `lbRowsLanded()` | Developer | After the rows land, with every row placed and stamped |
1448
1697
 
1449
- The hub calls both on a custom element with `lb-query` and a row template.
1450
- Both are optional. The `RowsHost` interface in `@loadbare/app/types`
1451
- declares them.
1698
+ The hub calls it on a custom element with `lb-query` and a row template.
1699
+ It is optional. The `RowsHost` interface in `@loadbare/app/types` declares
1700
+ it. The hub places every row and builds every group itself, so a custom
1701
+ element reacts to rows and never places them. Scrolling to the row the
1702
+ page's request created is the case:
1703
+
1704
+ ```ts
1705
+ lbRowsLanded() {
1706
+ this.querySelector(`[${ATTR_ROW_REQUESTED}="${REQUESTED_CREATED}"]`)
1707
+ ?.scrollIntoView({ block: "nearest" });
1708
+ }
1709
+ ```
1710
+
1711
+ A custom element the builder shipped absent under `lb-show` has not
1712
+ upgraded while its column is off. When the column first turns on and it
1713
+ upgrades, the hub lands the query's last answer on it again, as all rows,
1714
+ so the hook runs.
1452
1715
 
1453
1716
  `applyRow(root, row)`, exported by `@loadbare/app`, fills `root` from one
1454
1717
  row, the same operation that fills a live row.
@@ -1547,12 +1810,17 @@ it may define tomorrow.
1547
1810
  | `lb-url-link` | Developer | An `<a>` | [Links](#links) |
1548
1811
  | `lb-url-push` | Developer | An element with `lb-request` | [Query parms](#query-parms) |
1549
1812
  | `lb-url-unknown` | Developer | A `<dialog>` inside `<lb-hub>` | [An unknown page](#an-unknown-page) |
1813
+ | `lb-group` | Developer | A `<template>` holding a `<template>` | [Groups](#groups) |
1814
+ | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` | Developer | Any element but a `<template>` | [Aggregates](#aggregates) |
1550
1815
  | `lb-exp-slot` | Developer | One element in an element file | [Slots and templates](#slots-and-templates) |
1551
1816
  | `lb-exp-template` | Developer | An element file, and a `<template>` | [Slots and templates](#slots-and-templates) |
1552
1817
  | `lb-column-value` | Hub | Every element set from a column | [How a column lands](#how-a-column-lands) |
1553
1818
  | `lb-key-value` | Hub | A live row, and an element a `row` lands on | [Rows](#rows) |
1554
1819
  | `lb-row-live` | Hub | A live row | [Rows](#rows) |
1555
1820
  | `lb-query-row-count` | Hub | An element with a row template | [Rows](#rows) |
1821
+ | `lb-group-live`, `lb-group-column`, `lb-group-value` | Hub | Each top-level element of a group | [Groups](#groups) |
1822
+ | `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) |
1823
+ | `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) |
1556
1824
  | `lb-request-pending` | Hub | The element that issued a request | [Request state](#request-state) |
1557
1825
  | `lb-request-error` | Hub | The element that issued a request | [Request state](#request-state) |
1558
1826
  | `lb-show` on a `<template>` | Hub and builder | Where an absent element stands | [Displaying by condition](#displaying-by-condition) |
@@ -1568,6 +1836,12 @@ The builder refuses any other `lb-` attribute in markup — see
1568
1836
  | -------- | ----- | --------- | --------------------------------------------------------------- | ------------------- |
1569
1837
  | `lb-url` | `row` | `lb-path` | `lb-path`, `lb-page-label`, `lb-page-unknown`, every query parm | [The URL](#the-url) |
1570
1838
 
1839
+ ### Reserved query parms
1840
+
1841
+ | Parm | Means | Defined in |
1842
+ | ------------------ | --------------------------- | --------------- |
1843
+ | `lb-order-<query>` | The user's order for a query | [Order](#order) |
1844
+
1571
1845
  ### Reserved request names
1572
1846
 
1573
1847
  | Request name | Runs under `crud` | Defined in |
@@ -1593,12 +1867,12 @@ Loadbare/app owns every DOM event in this table, both the name and what its
1593
1867
  | Event | Dispatched from | Bubbles | Cancelable | Defined in |
1594
1868
  | ------------ | -------------------------------- | ------- | ---------- | --------------------------------------- |
1595
1869
  | `lb-request` | The element that committed | Yes | No | [The request event](#the-request-event) |
1870
+ | `lb-request-done` | The element that committed | Yes | No | [The request event](#the-request-event) |
1596
1871
 
1597
1872
  ### Reserved methods
1598
1873
 
1599
1874
  | Method | On | Defined in |
1600
1875
  | -------------- | -------------------------------------------------- | ----------------------- |
1601
- | `lbPlaceRow` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
1602
1876
  | `lbRowsLanded` | A custom element with `lb-query` and a row template | [Row hooks](#row-hooks) |
1603
1877
 
1604
1878
  ### Constants
@@ -1616,6 +1890,22 @@ imports them rather than writing a string.
1616
1890
  | `ATTR_ROW_LIVE` | `lb-row-live` |
1617
1891
  | `LIVE_ROW` | `[lb-row-live]`, the selector for a live row |
1618
1892
  | `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
1893
+ | `ATTR_GROUP` | `lb-group` |
1894
+ | `ATTR_GROUP_LIVE` | `lb-group-live` |
1895
+ | `ATTR_GROUP_COLUMN` | `lb-group-column` |
1896
+ | `ATTR_GROUP_VALUE` | `lb-group-value` |
1897
+ | `LIVE_GROUP` | `[lb-group-live]`, the selector for a group's element |
1898
+ | `ATTR_COUNT`, `ATTR_SUM`, `ATTR_AVG`, `ATTR_MIN`, `ATTR_MAX` | `lb-count`, `lb-sum`, `lb-avg`, `lb-min`, `lb-max` |
1899
+ | `AGGREGATES` | All five aggregate attributes |
1900
+ | `ORDER_PARM_PREFIX` | `lb-order-` |
1901
+ | `ORDER_DESCENDING` | `-`, written before a column in an order |
1902
+ | `ATTR_ROW_CREATED` | `lb-row-created` |
1903
+ | `ATTR_ROW_CHANGED` | `lb-row-changed` |
1904
+ | `ATTR_ROW_MOVED` | `lb-row-moved` |
1905
+ | `ATTR_ROW_REQUESTED` | `lb-row-requested` |
1906
+ | `REQUESTED_CREATED`, `REQUESTED_CHANGED`, `REQUESTED_MOVED` | `created`, `changed`, `moved` |
1907
+ | `ATTR_ROW_LEAVING` | `lb-row-leaving` |
1908
+ | `ATTR_GROUP_LEAVING` | `lb-group-leaving` |
1619
1909
  | `ATTR_REQUEST` | `lb-request` |
1620
1910
  | `ATTR_REQUEST_PENDING` | `lb-request-pending` |
1621
1911
  | `ATTR_REQUEST_ERROR` | `lb-request-error` |
@@ -1626,8 +1916,10 @@ imports them rather than writing a string.
1626
1916
  | `ATTR_EXP_TEMPLATE` | `lb-exp-template` |
1627
1917
  | `ATTR_PAGE` | `lb-page` |
1628
1918
  | `ATTR_PAGE_TITLE` | `lb-page-title` |
1629
- | `DEVELOPER_ATTRIBUTES` | The seven attributes a developer writes |
1919
+ | `DEVELOPER_ATTRIBUTES` | The thirteen attributes a developer writes |
1630
1920
  | `LB_EVENT_NAME` | `lb-request` |
1921
+ | `LB_DONE_EVENT_NAME` | `lb-request-done` |
1922
+ | `SHOW_NOT` | `!`, written before a column in `lb-show` |
1631
1923
  | `LB_RESERVED_PREFIX` | `lb-` |
1632
1924
  | `REQUEST_ROW_INSERT` | `lb-row-insert` |
1633
1925
  | `REQUEST_ROW_UPDATE` | `lb-row-update` |