@loadbare/app 0.5.6 → 0.7.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 (143) hide show
  1. package/README.md +3 -4
  2. package/dist/build/assemble.d.ts +1 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +81 -7
  5. package/dist/build/assemble.js.map +1 -0
  6. package/dist/build/cli.d.ts +2 -2
  7. package/dist/build/cli.js +3 -2
  8. package/dist/build/cli.js.map +1 -0
  9. package/dist/build/elements.js +1 -0
  10. package/dist/build/elements.js.map +1 -0
  11. package/dist/build/expand.d.ts.map +1 -1
  12. package/dist/build/expand.js +20 -32
  13. package/dist/build/expand.js.map +1 -0
  14. package/dist/build/format.js +1 -0
  15. package/dist/build/format.js.map +1 -0
  16. package/dist/build/locations.d.ts +4 -4
  17. package/dist/build/locations.d.ts.map +1 -1
  18. package/dist/build/locations.js +16 -5
  19. package/dist/build/locations.js.map +1 -0
  20. package/dist/build/origins.d.ts +0 -13
  21. package/dist/build/origins.d.ts.map +1 -1
  22. package/dist/build/origins.js +33 -8
  23. package/dist/build/origins.js.map +1 -0
  24. package/dist/build/package-root.js +1 -0
  25. package/dist/build/package-root.js.map +1 -0
  26. package/dist/build/pages.d.ts +7 -3
  27. package/dist/build/pages.d.ts.map +1 -1
  28. package/dist/build/pages.js +14 -7
  29. package/dist/build/pages.js.map +1 -0
  30. package/dist/build/styles.js +1 -0
  31. package/dist/build/styles.js.map +1 -0
  32. package/dist/core/lb-constants.d.ts +15 -13
  33. package/dist/core/lb-constants.d.ts.map +1 -1
  34. package/dist/core/lb-constants.js +104 -53
  35. package/dist/core/lb-constants.js.map +1 -0
  36. package/dist/core/lb-types.d.ts +103 -62
  37. package/dist/core/lb-types.d.ts.map +1 -1
  38. package/dist/core/lb-types.js +12 -3
  39. package/dist/core/lb-types.js.map +1 -0
  40. package/dist/hub/lb-apply.d.ts +28 -4
  41. package/dist/hub/lb-apply.d.ts.map +1 -1
  42. package/dist/hub/lb-apply.js +227 -40
  43. package/dist/hub/lb-apply.js.map +1 -0
  44. package/dist/hub/lb-hub.browser.d.ts +1 -1
  45. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  46. package/dist/hub/lb-hub.browser.js +165 -110
  47. package/dist/hub/lb-hub.browser.js.map +1 -0
  48. package/dist/server/lb-express.d.ts +8 -5
  49. package/dist/server/lb-express.d.ts.map +1 -1
  50. package/dist/server/lb-express.js +46 -33
  51. package/dist/server/lb-express.js.map +1 -0
  52. package/dist/server/lb-server.d.ts +67 -45
  53. package/dist/server/lb-server.d.ts.map +1 -1
  54. package/dist/server/lb-server.js +56 -18
  55. package/dist/server/lb-server.js.map +1 -0
  56. package/docs/TECHREF-1.0.md +1107 -0
  57. package/docs/analysis-accidental-complexity.md +149 -0
  58. package/docs/reference/builder.md +4 -4
  59. package/docs/reference/chrome.md +10 -9
  60. package/docs/reference/custom-elements.md +54 -46
  61. package/docs/reference/data-binding.md +179 -97
  62. package/docs/reference/overview.md +1 -1
  63. package/docs/reference/page-files.md +64 -49
  64. package/docs/reference/server.md +25 -6
  65. package/docs/reference/widgets.md +22 -30
  66. package/docs/roadmap.md +68 -22
  67. package/docs/testing.md +47 -17
  68. package/docs/theory.md +116 -3
  69. package/docs/tutorials/010-pages-and-navigation.md +8 -8
  70. package/docs/tutorials/040-displaying-data.md +9 -9
  71. package/docs/tutorials/050-actions.md +5 -5
  72. package/docs/tutorials/060-custom-element-code.md +1 -1
  73. package/docs/tutorials/065-conditional-rendering.md +4 -4
  74. package/docs/tutorials/070-displaying-a-list.md +24 -47
  75. package/docs/tutorials/072-inserting-into-a-list.md +18 -15
  76. package/docs/tutorials/074-deleting-from-a-list.md +15 -17
  77. package/docs/tutorials/076-updating-a-list-item.md +20 -22
  78. package/docs/tutorials/080-widget-requests.md +22 -35
  79. package/docs/tutorials/090-using-widget-libraries.md +1 -1
  80. package/package.json +2 -3
  81. package/dist/hub/lb-rows.d.ts +0 -18
  82. package/dist/hub/lb-rows.d.ts.map +0 -1
  83. package/dist/hub/lb-rows.js +0 -106
  84. package/dist/tests/assemble.test.d.ts +0 -8
  85. package/dist/tests/assemble.test.d.ts.map +0 -1
  86. package/dist/tests/assemble.test.js +0 -58
  87. package/dist/tests/elements.test.d.ts +0 -8
  88. package/dist/tests/elements.test.d.ts.map +0 -1
  89. package/dist/tests/elements.test.js +0 -118
  90. package/dist/tests/expand.test.d.ts +0 -10
  91. package/dist/tests/expand.test.d.ts.map +0 -1
  92. package/dist/tests/expand.test.js +0 -250
  93. package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
  94. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
  95. package/dist/tests/fixtures/elements/collision/imports.js +0 -1
  96. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
  97. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
  98. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
  99. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
  100. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
  101. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
  102. package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
  103. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
  104. package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
  105. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
  106. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
  107. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
  108. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
  109. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
  110. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
  111. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
  112. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
  113. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
  114. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
  115. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
  116. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
  117. package/dist/tests/helpers/console.d.ts +0 -20
  118. package/dist/tests/helpers/console.d.ts.map +0 -1
  119. package/dist/tests/helpers/console.js +0 -28
  120. package/dist/tests/helpers/dom.d.ts +0 -18
  121. package/dist/tests/helpers/dom.d.ts.map +0 -1
  122. package/dist/tests/helpers/dom.js +0 -22
  123. package/dist/tests/lb-apply.test.d.ts +0 -8
  124. package/dist/tests/lb-apply.test.d.ts.map +0 -1
  125. package/dist/tests/lb-apply.test.js +0 -153
  126. package/dist/tests/lb-express.test.d.ts +0 -14
  127. package/dist/tests/lb-express.test.d.ts.map +0 -1
  128. package/dist/tests/lb-express.test.js +0 -238
  129. package/dist/tests/lb-rows.test.d.ts +0 -12
  130. package/dist/tests/lb-rows.test.d.ts.map +0 -1
  131. package/dist/tests/lb-rows.test.js +0 -336
  132. package/dist/tests/lb-server.test.d.ts +0 -9
  133. package/dist/tests/lb-server.test.d.ts.map +0 -1
  134. package/dist/tests/lb-server.test.js +0 -495
  135. package/dist/tests/origins.test.d.ts +0 -10
  136. package/dist/tests/origins.test.d.ts.map +0 -1
  137. package/dist/tests/origins.test.js +0 -369
  138. package/dist/tests/pages.test.d.ts +0 -6
  139. package/dist/tests/pages.test.d.ts.map +0 -1
  140. package/dist/tests/pages.test.js +0 -98
  141. package/dist/tests/styles.test.d.ts +0 -7
  142. package/dist/tests/styles.test.d.ts.map +0 -1
  143. package/dist/tests/styles.test.js +0 -76
@@ -1,10 +1,14 @@
1
1
  # Data Binding
2
2
 
3
3
  A page binds its elements to server data with four attributes, and asks the
4
- server to change that data with four more. The page writes all eight into
5
- its HTML; the server declares which queries it answers and which operations
4
+ server to change that data with one more. The developer writes all five into
5
+ the HTML; the server declares which queries it answers and which operations
6
6
  it permits, and refuses anything it has not declared.
7
7
 
8
+ One vocabulary and one containment ladder: a list holds rows, a row holds
9
+ cells. A column is the second axis, and it is what the value of `lb-cell` or
10
+ `lb-key` always holds.
11
+
8
12
  ## A page that binds data
9
13
 
10
14
  Here is a page that binds a scalar, a list, and three requests:
@@ -13,23 +17,23 @@ Here is a page that binds a scalar, a list, and three requests:
13
17
  <!-- src/pages/members.page.html -->
14
18
  <h1>Members</h1>
15
19
 
16
- <p lb-query="dues">Dues collected this year: <span lb-cell="total"></span></p>
20
+ <p lb-row="dues">Dues collected this year: <span lb-cell="total"></span></p>
17
21
 
18
- <form lb-insert lb-query="roster">
19
- <input lb-cell="name" placeholder="Name" />
20
- <button type="submit">Add member</button>
21
- </form>
22
+ <section lb-list="roster">
23
+ <form lb-action="lb-row-insert">
24
+ <input lb-cell="name" placeholder="Name" />
25
+ <button type="submit">Add member</button>
26
+ </form>
22
27
 
23
- <lb-list lb-query="roster">
24
28
  <ul>
25
29
  <template lb-key="id">
26
30
  <li>
27
31
  <lb-input lb-cell="name"></lb-input>
28
- <button lb-delete>Remove</button>
32
+ <button lb-action="lb-row-delete">Remove</button>
29
33
  </li>
30
34
  </template>
31
35
  </ul>
32
- </lb-list>
36
+ </section>
33
37
  ```
34
38
 
35
39
  The queries named here — `dues` and `roster` — and the operations the page
@@ -37,85 +41,135 @@ asks for are declared on the server; see [page files](./page-files.md).
37
41
 
38
42
  ## Binding
39
43
 
40
- | Attribute | Names |
41
- |------------|----------------------------------------------------------|
42
- | `lb-query` | The query a subtree displays |
43
- | `lb-key` | The cell that identifies a row, and a live row's own key |
44
- | `lb-cell` | The cell an element displays |
45
- | `lb-value` | Where a widget receives its value |
46
-
47
- A binding is scoped by ancestry. An element binds to the query on its
48
- nearest ancestor carrying `lb-query`, and to the row on its nearest ancestor
49
- carrying `lb-key`. Nothing else establishes scope: an element outside every
50
- `lb-query` is bound to nothing and displays nothing.
44
+ | Attribute | Written by | Names |
45
+ |----------------|-------------|-------------------------------------------|
46
+ | `lb-list` | a developer | The set of rows a subtree displays |
47
+ | `lb-row` | a developer | The one row a subtree displays |
48
+ | `lb-key` | a developer | The column that identifies a row |
49
+ | `lb-cell` | a developer | The column an element displays |
50
+ | `lb-key-value` | the hub | A live row's own key |
51
+ | `lb-value` | the hub | The value that landed on a cell |
52
+
53
+ A binding is scoped by ancestry. Either scope attribute scopes its DOM
54
+ children, and a nested one of either kind begins a new scope, so an element
55
+ binds to the name on its nearest ancestor carrying one, and to the row on its
56
+ nearest ancestor carrying `lb-key-value`. Nothing else establishes scope: an
57
+ element outside every scope is bound to nothing and displays nothing, and a
58
+ result never crosses into a nested scope.
59
+
60
+ Which of the two a subtree writes is not a choice about display. Cardinality
61
+ is a property of the name, so one name answers with one shape, always. A page
62
+ that shows the roster both as a set and as a single row declares two queries,
63
+ `rosterList` and `rosterRow`, and binds each with the attribute that matches
64
+ what it answers with.
51
65
 
52
66
  Bind an element to a cell by putting `lb-cell` on the element that shows the
53
- value. The scope's own root counts as a cell if it carries one, which is how
54
- an `<option>` — whose content model is text — displays the value it is.
67
+ value. Its value is a column name and never a cell name: a cell has no name
68
+ of its own, because it is identified by its row and its column, and the row
69
+ arrives from scope. The scope's own root counts as a cell if it carries one,
70
+ which is how an `<option>` — whose content model is text — displays the value
71
+ it is.
55
72
 
56
73
  Name the same query on more than one subtree to display it in more than one
57
74
  place. Every subtree gets the result.
58
75
 
59
- One query is the hub's rather than the server's: `lb-navigation`, landed on
60
- every navigation with the cells `page-label` and `page-uri`. It binds the
61
- same way, and the `lb-` prefix on its name is what marks it as Loadbare's —
62
- see [Where the page is](./chrome.md#where-the-page-is).
76
+ One name is the hub's rather than the server's: `lb-navigation`, one row
77
+ landed on every navigation with the columns `page-label` and `page-uri`. It binds the
78
+ same way. Its name is reserved, as every value beginning with `lb-` is in
79
+ every `lb-` attribute: the server refuses a page that declares a query so
80
+ named — see [Where the page is](./chrome.md#where-the-page-is).
63
81
 
64
- `lb-key` holds one name in two positions. On a row template it names the
65
- cell that identifies a row; on a row that is showing, it carries that row's
66
- key value. A template is never a row, so the two never collide.
82
+ A key has a name and a value, and they are two attributes. `lb-key` on a row
83
+ template names the column that identifies a row: it is a property of the
84
+ list, since a list without a fixed key column is meaningless, written where
85
+ the rows land. `lb-key-value` on a row that is showing carries that row's
86
+ value of it. A developer writes the first and never the second.
67
87
 
68
88
  ### Where a bound value lands
69
89
 
70
- A value lands on a bound element one of two ways.
90
+ A value lands on a bound element one of three ways.
71
91
 
72
- | Element | Receives the value as |
73
- |------------------|--------------------------|
74
- | A native element | Its `textContent` |
75
- | A custom element | Its `lb-value` attribute |
92
+ | Element | Receives the value as |
93
+ |----------------------------------------|-----------------------------------|
94
+ | A custom element | Its `lb-value` attribute |
95
+ | `<select>`, `<textarea>`, or `<input>` | Its `value`, and `lb-value` |
96
+ | Any other native element | Its `textContent`, and `lb-value` |
76
97
 
77
- A native element has no behavior of its own, so its value is its text. A
78
- widget owns whatever control it wraps, so it is handed the value and renders
79
- it itself — see [Custom Elements](./custom-elements.md#code) for
80
- observing `lb-value`.
98
+ A widget owns whatever control it wraps, so it is handed the value and
99
+ renders it itself — see [Custom Elements](./custom-elements.md#code) for
100
+ observing `lb-value`. A form control shows its state as its `value`, so a
101
+ `<select>` keeps its options. Any other native element has no behavior of its
102
+ own, so its value is its text. Checkboxes, radio buttons and file inputs
103
+ receive nothing, not even `lb-value`, and the hub reports it to the console.
81
104
 
82
105
  Nothing an application writes ever sets `lb-value`. It is written by
83
- Loadbare and read by the widget it is written on.
106
+ Loadbare and read by a widget or a stylesheet.
84
107
 
85
- ### Rows
108
+ ### Lists
86
109
 
87
- A query that answers with many rows is delivered to a list widget, which
88
- clones its row template once per row and binds each clone to one row. A
89
- native element has one destination for a value and cannot acquire children,
90
- so a query that answers with rows must be bound to a widget that accepts
91
- them.
110
+ Write `lb-list` on any element, and a `<template>` inside it carrying
111
+ `lb-key`. The hub clones that template once per row, fills each clone, and
112
+ reconciles what is showing against what arrived. No widget is involved, and
113
+ none is needed.
92
114
 
93
- Bind the widget to the query with `lb-query`, and name the key cell on the
94
- `<template>` inside it with `lb-key`. See
95
- [The Basic Widget Library](./widgets.md) for the widgets that accept
96
- rows, and for `lb-group` and `lb-sort`, which a list widget reads to decide
97
- where a row goes.
115
+ An array is the whole set, so it decides membership and order: a row whose
116
+ key did not arrive is gone. A patch touches only the rows it names and leaves
117
+ every other row's contents and position alone.
98
118
 
99
- ## Requests
119
+ A widget enters only where the rows need scaffolding or placement that only
120
+ it can decide — a `<select>` that builds an `<optgroup>` per distinct value, a
121
+ table that sections and sorts. Such a widget carries `lb-list` itself and
122
+ implements one or both of two optional methods; see
123
+ [Decorating a list](./custom-elements.md#decorating-a-list) and
124
+ [The Basic Widget Library](./widgets.md).
100
125
 
101
- Four attributes turn an interaction into a request:
126
+ A scope with `lb-list` and no row template displays nothing, and that is not
127
+ an error. It is bound to the list without showing it, which is what an insert
128
+ form naming the list it adds a row to already is.
102
129
 
103
- | Written | Asks for | Carries |
104
- |---------------------------|------------------|------------------------------|
105
- | `lb-action="name"` | The named action | `name`, and what is in scope |
106
- | `lb-delete` | `tupleDelete` | `query`, `key` |
107
- | `lb-insert` on a `<form>` | `tupleInsert` | `query`, `values` |
108
- | `lb-update` on a `<form>` | `tupleUpdate` | `query`, `key`, `values` |
109
-
110
- The shipped `<lb-input>` widget asks for a fifth, `cellChange`, carrying
111
- `query`, `key`, `cell`, and `value`. It sends one when the page applies
112
- `data-fire-on-change` to it, and stays quiet otherwise — an input inside an
113
- `lb-insert` or `lb-update` form is read again by the form on submit, so a
114
- widget that sent on its own would write the same edit twice.
130
+ ## Requests
115
131
 
116
- Declare every one of these on the server. A name the page has not declared,
117
- and an operation a query does not permit, are refused; see
118
- [hooks, actions, CRUD](./page-files.md#hooks-actions-crud).
132
+ One attribute turns an interaction into a request. `lb-action` names what the
133
+ server is asked for: an action the page declared, or one of Loadbare's
134
+ reserved names, which are the CRUD operations.
135
+
136
+ One attribute name, one wire field, one set of values. The request field
137
+ `action` carries this attribute's value verbatim, so nothing is translated
138
+ between the markup and the server, and the CRUD key is the same value with
139
+ the prefix stripped and the rest camel-cased.
140
+
141
+ | Written | Asks for | Carries |
142
+ |-------------------------------------------|--------------|-------------------------------|
143
+ | `lb-action="name"` | That action | The scope, and what is in it |
144
+ | `lb-action="lb-row-delete"` | `rowDelete` | `list`, `key` |
145
+ | `lb-action="lb-row-insert"` on a `<form>` | `rowInsert` | `list`, `values` |
146
+ | `lb-action="lb-row-update"` on a `<form>` | `rowUpdate` | `list`, `key`, `values` |
147
+ | `lb-action="lb-cell-change"` on a widget | `cellChange` | `list`, `key`, `cell`, `value`|
148
+
149
+ All four operations are list operations. Each needs a key, and a key exists
150
+ only on a live row the hub stamped inside a list, so a single-row scope is
151
+ read-only and a declared action is the only thing it can send. An application
152
+ that wants a writable single row declares a list that answers with one row.
153
+
154
+ A name beginning with `lb-` is reserved, in `lb-action` and in either scope
155
+ attribute alike. The server refuses a page that declares an action or a query
156
+ so named, which is what lets a reserved name be added later without colliding
157
+ with one an application already uses. That reservation is also the whole of
158
+ the wire discriminant: a value beginning with `lb-` is an operation, and
159
+ anything else is a name the page declared.
160
+
161
+ The hub sends the first four from a native element on the element's own
162
+ event: a form on submit, anything else on click. A cell change needs a
163
+ widget to say what a change is, so only a widget sends it: the shipped
164
+ `<lb-input>` does when it carries `lb-action="lb-cell-change"`, and stays
165
+ quiet otherwise — an input inside an `lb-row-insert` or `lb-row-update` form is read
166
+ again by the form on submit, so a widget that sent on its own would write
167
+ the same edit twice.
168
+
169
+ Declare every action on the server, and permit every operation on its list. A
170
+ name the page has not declared, and an operation a list does not permit, are
171
+ refused; see
172
+ [requests, actions, CRUD](./page-files.md#requests-actions-crud).
119
173
 
120
174
  ### Actions
121
175
 
@@ -126,10 +180,10 @@ one of the four CRUD operations:
126
180
  <button lb-action="mailRoster">Mail the roster</button>
127
181
  ```
128
182
 
129
- An action carries whatever binding is in scope at the element — `query`,
130
- `key`, `cell` — and nothing else. There is no argument list. A button
131
- carries no value, so the server computes the whole of the new state and the
132
- page displays only what came back.
183
+ An action carries whatever binding is in scope at the element — `list` or
184
+ `row`, whichever scoped it, plus `key` and `cell` — and nothing else. There is
185
+ no argument list. A button carries no value, so the server computes the whole
186
+ of the new state and the page displays only what came back.
133
187
 
134
188
  Write `lb-action` on a widget to have the widget decide what performing the
135
189
  action means. Loadbare turns a click into a request for a native element
@@ -139,30 +193,31 @@ on change, not on click.
139
193
  ### Deleting a row
140
194
 
141
195
  ```html
142
- <button lb-delete>Remove</button>
196
+ <button lb-action="lb-row-delete">Remove</button>
143
197
  ```
144
198
 
145
- `lb-delete` needs no name. The row's `lb-query` and `lb-key` are already in
146
- scope, and they are all the server needs to know which row is meant and
147
- whether the query permits deleting it.
199
+ `lb-row-delete` needs nothing declared. The row's `lb-list` and `lb-key-value`
200
+ are already in scope, and they are all the server needs to know which row is
201
+ meant and whether the list permits deleting it.
148
202
 
149
- Write `lb-delete` on a native element, the same as `lb-action`. A widget
150
- sends its own request.
203
+ The hub sends it from a native element. A widget sends its own request.
151
204
 
152
205
  ### Forms
153
206
 
154
- `lb-insert` and `lb-update` both gather every `lb-cell` inside the form into
155
- one values map, read from the control each cell is or wraps. They differ in
156
- one thing: `lb-update` also carries the key of the row it is inside, and
157
- `lb-insert` carries none, because there is no row yet.
207
+ A `<form>` performs its `lb-action` on submit. `lb-row-insert` and `lb-row-update`
208
+ both gather every `lb-cell` inside the form into one values map, read from
209
+ the control each cell is or wraps. They differ in one thing: `lb-row-update`
210
+ also carries the key of the row it is inside, and `lb-row-insert` carries none,
211
+ because there is no row yet. A declared name on a form sends that action on
212
+ submit, carrying the binding and no values.
158
213
 
159
- Put an `lb-update` form inside the row it edits, so it has that row's key
214
+ Put an `lb-row-update` form inside the row it edits, so it has that row's key
160
215
  from the same ancestor a delete button reads:
161
216
 
162
217
  ```html
163
218
  <template lb-key="id">
164
219
  <li>
165
- <form lb-update>
220
+ <form lb-action="lb-row-update">
166
221
  <input lb-cell="name" />
167
222
  <button type="submit">Save</button>
168
223
  </form>
@@ -180,9 +235,33 @@ Loadbare ships static HTML and hydrates elements that are already in the
180
235
  document. There is no `if`, and none is needed: write every possibility into
181
236
  the page, and control which of them is showing.
182
237
 
183
- Use the standard `hidden` attribute, or CSS, and set it from a value the
184
- server sent. A widget that receives a value in `lb-value` is the natural
185
- place to do it — it is handed the current state and decides what to show:
238
+ Every cell carries the value that landed on it as `lb-value`, so a
239
+ stylesheet can show or hide part of a page from a value the server sent. Bind
240
+ a column the page does not display to a hidden element:
241
+
242
+ ```html
243
+ <template lb-key="id">
244
+ <tr>
245
+ <td lb-cell="name"></td>
246
+ <td lb-cell="locked" hidden></td>
247
+ <td><button lb-action="lb-row-delete">Remove</button></td>
248
+ </tr>
249
+ </template>
250
+ ```
251
+
252
+ ```css
253
+ tr:has([lb-cell="locked"][lb-value="true"]) button {
254
+ display: none;
255
+ }
256
+ ```
257
+
258
+ `lb-value` holds the string the query sent, so the query decides the spelling
259
+ the selector matches. A stylesheet can hide a control but cannot disable one,
260
+ and hiding is presentation: the server still refuses what a request may not
261
+ do.
262
+
263
+ Where showing a case takes more than a selector, a widget receives the value
264
+ in `lb-value` and decides what to show:
186
265
 
187
266
  ```ts
188
267
  for (const step of steps) step.hidden = step.dataset.step !== value;
@@ -193,31 +272,34 @@ HTML.
193
272
 
194
273
  ### An empty list
195
274
 
196
- A list widget stamps itself with `data-rows`, the number of rows it is
275
+ The hub stamps every list scope with `lb-row-count`, the number of rows it is
197
276
  showing. It is the one conditional a page cannot be sent, because the server
198
- answers with rows and says nothing about how many survived. It makes an
199
- empty list a stylesheet rule rather than code in every list widget:
277
+ answers with rows and says nothing about how many survived. It makes an empty
278
+ list a stylesheet rule rather than code anywhere:
200
279
 
201
280
  ```html
202
- <lb-list lb-query="roster">
281
+ <div lb-list="roster">
203
282
  <ul>
204
283
  <template lb-key="id">
205
284
  <li lb-cell="name"></li>
206
285
  </template>
207
286
  </ul>
208
287
  <p class="roster-empty">No members yet.</p>
209
- </lb-list>
288
+ </div>
210
289
  ```
211
290
 
212
291
  ```css
213
292
  .roster-empty {
214
293
  display: none;
215
294
  }
216
- lb-list[data-rows="0"] .roster-empty {
295
+ [lb-row-count="0"] .roster-empty {
217
296
  display: revert;
218
297
  }
219
298
  ```
220
299
 
300
+ The scope is a `<div>` here rather than the `<ul>`, so that the empty message
301
+ is inside it and the same rule can reach both.
302
+
221
303
  ## Request state
222
304
 
223
305
  Loadbare stamps two attributes on the element a request came from — the
@@ -225,11 +307,11 @@ button, the form, or the widget itself:
225
307
 
226
308
  | Attribute | Means |
227
309
  |-------------------|-------------------------------------------|
228
- | `data-lb-pending` | The request is in flight |
229
- | `data-lb-error` | The last request from this element failed |
310
+ | `lb-pending` | The request is in flight |
311
+ | `lb-error` | The last request from this element failed |
230
312
 
231
- `data-lb-pending` is set when the request goes out and removed when it
232
- settles. `data-lb-error` is set on a failed response, a network failure, or
313
+ `lb-pending` is set when the request goes out and removed when it
314
+ settles. `lb-error` is set on a failed response, a network failure, or
233
315
  a timeout alike, and cleared when that element sends its next request.
234
316
 
235
317
  Neither one carries any meaning beyond the fact it states. Dim a pending
@@ -26,7 +26,7 @@ serves, and the widgets those pages are made of.
26
26
  |---------------------------------------------------------|--------------------------------|
27
27
  | [`<name>.page.html`](./page-files.md#html) | The page's HTML |
28
28
  | [`<name>.queries.ts`](./page-files.md#queries) | The data the page displays |
29
- | [`<name>.hooks.ts`](./page-files.md#hooks-actions-crud) | Data Channel handlers |
29
+ | [`<name>.requests.ts`](./page-files.md#requests-actions-crud) | Data Channel handlers |
30
30
  | [Data Binding](./data-binding.md) | Connecting HTML to server data |
31
31
 
32
32
  ## Widgets
@@ -1,13 +1,13 @@
1
1
  # Page Files
2
2
 
3
3
  A page is a set of files sharing one base name. The application writes the
4
- HTML, and adds queries and hooks when the page shows data.
4
+ HTML, and adds queries and requests when the page shows data.
5
5
 
6
- | File | Holds |
7
- |---------------------|-------------------------------|
8
- | `<name>.page.html` | The page's HTML |
9
- | `<name>.queries.ts` | The data the page displays |
10
- | `<name>.hooks.ts` | What the page does when asked |
6
+ | File | Holds |
7
+ |----------------------|-------------------------------|
8
+ | `<name>.page.html` | The page's HTML |
9
+ | `<name>.queries.ts` | The data the page displays |
10
+ | `<name>.requests.ts` | What the page does when asked |
11
11
 
12
12
  Name the landing page `index.page.html`. A bare `/` resolves to `index`, and
13
13
  every other path names the page of the same name.
@@ -25,12 +25,12 @@ is supplied by [chrome.html](./chrome.md).
25
25
  <h1>About</h1>
26
26
  <p>This is the about page.</p>
27
27
 
28
- <div lb-query="visits">
28
+ <div lb-row="visits">
29
29
  <p>This page has been visited <span lb-cell="count"></span> times.</p>
30
30
  </div>
31
31
  ```
32
32
 
33
- Bind elements to data with `lb-query`, `lb-cell`, and the rest of the
33
+ Bind elements to data with `lb-list` or `lb-row`, `lb-cell`, and the rest of the
34
34
  attribute vocabulary in [Data Binding](./data-binding.md).
35
35
 
36
36
  A page that displays no data needs no other file.
@@ -38,56 +38,64 @@ A page that displays no data needs no other file.
38
38
  ## Queries
39
39
 
40
40
  Export `queries` from `<name>.queries.ts`. Each key is a name the HTML binds
41
- to with `lb-query`, and each value takes the request context and returns
41
+ to with `lb-list` or `lb-row`, and each value takes the request context and returns
42
42
  that query's result:
43
43
 
44
44
  ```ts
45
45
  // src/pages/about.queries.ts
46
- import { type Queries } from "@loadbare/app/server";
46
+ import { row, type Queries } from "@loadbare/app/server";
47
47
 
48
48
  export const queries: Queries = {
49
- visits: async (ctx) => ({ count: String(await ctx.db.visitCount()) }),
49
+ visits: row(async (ctx) => ({ count: String(await ctx.db.visitCount()) })),
50
50
  };
51
51
  ```
52
52
 
53
- Return every cell as a string. Format numbers, dates, and money in the query,
54
- so the browser displays a value it never computes.
53
+ Declare each query with `row()` or `list()`. Cardinality is a property of the
54
+ name rather than of any one answer, so one name answers with one shape,
55
+ always, and `createHub` refuses an answer that disagrees. A page that needs
56
+ the same data as one row and as a set declares two queries.
55
57
 
56
- Return many rows through `rows()`:
58
+ The hub hands each cell to the browser untouched and takes no position on
59
+ its type, so what a number, a date or a null looks like is decided here, in
60
+ the query. Formatting it here means the browser displays a value it never
61
+ computes.
62
+
63
+ Declare a query that answers with many rows using `list()`, and return the
64
+ array itself:
57
65
 
58
66
  ```ts
59
67
  // src/pages/directory.queries.ts
60
- import { rows, type Queries } from "@loadbare/app/server";
68
+ import { list, type Queries } from "@loadbare/app/server";
61
69
 
62
70
  export const queries: Queries = {
63
- directory: async (ctx) => rows(await ctx.db.directory()),
71
+ directory: list((ctx) => ctx.db.directory()),
64
72
  };
65
73
  ```
66
74
 
67
- Give every row a cell that identifies it, and name that cell with `lb-key` in
68
- the HTML. Return the rows in the order the page shows them.
75
+ Give every row a column that identifies it, and name that column with
76
+ `lb-key` in the HTML. Return the rows in the order the page shows them.
69
77
 
70
- Return the full result every time. Sending only what changed is a hook's job —
78
+ Return the full result every time. Sending only what changed is a request's job —
71
79
  see [refresh and patch](#refresh-and-patch).
72
80
 
73
- ## Hooks, actions, CRUD
81
+ ## Requests, actions, CRUD
74
82
 
75
- Export `hooks` from `<name>.hooks.ts`. It holds three keys, each optional:
83
+ Export `requests` from `<name>.requests.ts`. It holds three keys, each optional:
76
84
 
77
- | Key | Runs |
78
- |-------------|------------------------------------------------------|
79
- | `beforeGet` | Before the page's queries, on a request for the page |
80
- | `actions` | What the page may be asked to do, by name |
81
- | `crud` | The four operations a query permits on its rows |
85
+ | Key | Runs |
86
+ |---------------|------------------------------------------------------|
87
+ | `onPageEnter` | Before the page's queries, on entering the page |
88
+ | `actions` | What the page may be asked to do, by name |
89
+ | `crud` | The four operations a list permits on its rows |
82
90
 
83
- ### beforeGet
91
+ ### onPageEnter
84
92
 
85
93
  ```ts
86
- // src/pages/about.hooks.ts
87
- import { type Hooks } from "@loadbare/app/server";
94
+ // src/pages/about.requests.ts
95
+ import { type Requests } from "@loadbare/app/server";
88
96
 
89
- export const hooks: Hooks = {
90
- beforeGet: (ctx) => ctx.db.recordVisit(),
97
+ export const requests: Requests = {
98
+ onPageEnter: (ctx) => ctx.db.recordVisit(),
91
99
  };
92
100
  ```
93
101
 
@@ -99,7 +107,7 @@ Declare an action under the name the HTML gives `lb-action`. Pair what it
99
107
  does with the queries to re-run once it has:
100
108
 
101
109
  ```ts
102
- export const hooks: Hooks = {
110
+ export const requests: Requests = {
103
111
  actions: {
104
112
  resetVisits: {
105
113
  run: (ctx) => ctx.db.resetVisits(),
@@ -113,29 +121,36 @@ Declare every action the page allows. A name the page does not declare is
113
121
  refused.
114
122
 
115
123
  Read where the interaction happened from `run`'s second argument, which
116
- carries `query`, `key`, `cell`, and `value` when the element that dispatched
117
- the request had them.
124
+ carries `list` or `row`, whichever attribute scoped the element, plus `key`,
125
+ `cell` and `value` when the element that dispatched the request had them.
118
126
 
119
127
  ### crud
120
128
 
121
- Declare CRUD operations under `crud`, keyed by the query they operate on. Each
122
- one takes the binding its trigger supplies:
129
+ Declare CRUD operations under `crud`, keyed by the list they operate on. All
130
+ four are list operations: each needs a key, and a key exists only on a live
131
+ row inside a list, so a single-row scope is read-only and a declared action is
132
+ the only thing it can send. Each operation takes the binding its trigger
133
+ supplies:
134
+
135
+ | Operation | The page writes | `run` receives |
136
+ |---------------|------------------------------------------|------------------------|
137
+ | `cellChange` | `<lb-input lb-action="lb-cell-change">` | `key`, `cell`, `value` |
138
+ | `rowDelete` | `lb-action="lb-row-delete"` | `key` |
139
+ | `rowInsert` | `<form lb-action="lb-row-insert">` | `values` |
140
+ | `rowUpdate` | `<form lb-action="lb-row-update">` | `key`, `values` |
123
141
 
124
- | Operation | The page writes | `run` receives |
125
- |---------------|--------------------|------------------------|
126
- | `cellChange` | `<lb-input data-fire-on-change>` | `key`, `cell`, `value` |
127
- | `tupleDelete` | `lb-delete` | `key` |
128
- | `tupleInsert` | `<form lb-insert>` | `values` |
129
- | `tupleUpdate` | `<form lb-update>` | `key`, `values` |
142
+ The operation names are reserved: a name beginning with `lb-` cannot be
143
+ declared under `actions` or as a query, and `createHub` refuses a page that
144
+ tries.
130
145
 
131
146
  ```ts
132
- // src/pages/directory.hooks.ts
133
- import { patch, type Hooks } from "@loadbare/app/server";
147
+ // src/pages/directory.requests.ts
148
+ import { patch, type Requests } from "@loadbare/app/server";
134
149
 
135
- export const hooks: Hooks = {
150
+ export const requests: Requests = {
136
151
  crud: {
137
152
  directory: {
138
- tupleInsert: {
153
+ rowInsert: {
139
154
  run: async (ctx, { values }) => {
140
155
  const entry = await ctx.db.addDirectoryEntry(values);
141
156
  return { directory: patch({ rows: [entry] }) };
@@ -147,8 +162,8 @@ export const hooks: Hooks = {
147
162
  };
148
163
  ```
149
164
 
150
- Declare every operation the query permits. An operation a query does not
151
- declare is refused, and a query with no `crud` entry permits none.
165
+ Declare every operation the list permits. An operation a list does not
166
+ declare is refused, and a name with no `crud` entry permits none.
152
167
 
153
168
  ### refresh and patch
154
169
 
@@ -159,7 +174,7 @@ would. What `run` returns is laid over the refreshed queries:
159
174
 
160
175
  | Result | States |
161
176
  |--------------------------|---------------------------------------------|
162
- | `rows([...])` | The entire set, and its order |
177
+ | `[...]` | The entire set, and its order |
163
178
  | `patch({ rows: [...] })` | These rows arrive or change; the rest stand |
164
179
  | `patch({ drop: [...] })` | These keys are gone; the rest stand |
165
180