@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
@@ -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
package/docs/roadmap.md CHANGED
@@ -148,8 +148,8 @@ breaks nothing.
148
148
 
149
149
  The chrome belongs to no page, so the builder refuses an application query
150
150
  named there, and a custom element in the chrome cannot show application
151
- data. `lb-url` and `lb-choices` show the pattern from the framework side,
152
- and an application has no equivalent.
151
+ data. `lb-url`, `lb-sort` and `lb-filters` show the pattern from the
152
+ framework side, and an application has no equivalent.
153
153
 
154
154
  A chrome-level query set on `createHub` would close this, and it is additive.
155
155
  It takes its place in the [options on `createHub`](#options-on-createhub).
package/docs/testing.md CHANGED
@@ -172,8 +172,8 @@ watch, and surviving a failed build.
172
172
  that refreshes its own `rows` query are refused
173
173
  - an update or delete for several rows runs once per key, lays the patches
174
174
  together, and refreshes once
175
- - `onPageEnter` may return `url()` or `choices()`, and `onChoices` hears
176
- every choice the user makes
175
+ - `onPageEnter` may return `url()`, `sort()` or `filters()`, and
176
+ `onChoices` hears every order and filter the user makes
177
177
  - a refusal carries a message, columns or both, and passes through the
178
178
  engine untouched
179
179
 
@@ -276,8 +276,9 @@ What the hub is tested for here:
276
276
  deadline, under mocked timers, and fails the way any round trip fails
277
277
  - a successful insert gathered from a form resets the form, unless the
278
278
  answer landed the new row on it
279
- - a path with no page reports, lands `lb-page-unknown`, and opens the
280
- `lb-url-unknown` dialog; two pages for one stub report and take the first
279
+ - a path naming a page that does not exist reports, lands `lb-page-label`
280
+ as null, and opens the `lb-url-unknown` dialog; two pages for one stub
281
+ report and take the first
281
282
  - `lb-url` lands with the path, the page's `lb-page-title` or stub as
282
283
  `lb-page-label`, and a column per query parm, and sets the document title
283
284
  - `lb-url-link` pushes state and swaps the page on a plain primary click,
@@ -296,8 +297,6 @@ What the hub is tested for here:
296
297
  queries land
297
298
  - a form pointed at a row by `commandfor` fills from the row the hub holds,
298
299
  and a row not yet entered sends no update or delete
299
- - ticked `lb-select` rows travel as `keys`, and the selection clears once
300
- the request succeeds
301
300
  - a refusal lands its message on the element that committed and on each
302
301
  control it names
303
302
  - `lb-request-done` reaches the element that committed and its ancestors
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loadbare/app",
3
3
  "description": "High performance web app framework for server-bound applications",
4
- "version": "0.25.0",
4
+ "version": "0.27.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: loadbare-app
3
- description: Build server-bound web applications with Loadbare/app. Covers the file conventions the builder finds by name (`chrome.html`, `*.page.html`, `*.queries.ts`, `*.requests.ts`, `imports.ts`, `<tag>.html`, `<tag>.browser.ts`); the `lb-*` attribute vocabulary that lands query rows on HTML and issues requests (`lb-query`, `lb-column`, `lb-show`, `lb-request`); queries declared with `row(key, run)` and `rows(key, run)`; the URL as the hub's own query `lb-url`, with `lb-url-link`, `lb-url-parm`, `lb-url-push` and `lb-url-unknown`; the user's order and filters as the hub's query `lb-choices`, read by `lb-order` and `lb-filter`; whether the page loaded and the server answers as the hub's query `lb-hub`; one form pointed at a row by the `--lb-edit`, `--lb-new` and `--lb-release` commands; rows that hold still under `lb-manual` and are placed again with `--lb-place`; build-time expansion with `exp-*` parameters, `lb-exp-slot` and `lb-exp-template`; custom elements as form-associated controls and row hosts; the Express wiring with `hubRoutes`; and the `@loadbare/widgets` library. Use whenever a task involves `@loadbare/app`, `@loadbare/widgets`, `loadbare-app-build`, an `lb-` attribute, or a page, query, request or custom element file in a Loadbare application. The model is not React, htmx or REST, and cannot be inferred from them.
3
+ description: Build server-bound web applications with Loadbare/app. Covers the file conventions the builder finds by name (`chrome.html`, `*.page.html`, `*.queries.ts`, `*.requests.ts`, `imports.ts`, `<tag>.html`, `<tag>.browser.ts`); the `lb-*` attribute vocabulary that lands query rows on HTML and issues requests (`lb-query`, `lb-column`, `lb-show`, `lb-request`); queries declared with `row(key, run)` and `rows(key, run)`; the URL as the hub's own query `lb-url`, with `lb-url-link`, `lb-url-parm`, `lb-url-push` and `lb-url-unknown`; the user's order and filters as the hub's queries `lb-sort` and `lb-filters`, read by `lb-filter`; whether the page loaded and the server answers as the hub's query `lb-hub-status`; one form pointed at a row by the `--lb-edit`, `--lb-new` and `--lb-release` commands; rows that hold still under `lb-manual` and are placed again with `--lb-place`; build-time expansion with `exp-*` parameters, `lb-exp-slot` and `lb-exp-template`; custom elements as form-associated controls and row hosts; the Express wiring with `hubRoutes`; and the `@loadbare/widgets` library. Use whenever a task involves `@loadbare/app`, `@loadbare/widgets`, `loadbare-app-build`, an `lb-` attribute, or a page, query, request or custom element file in a Loadbare application. The model is not React, htmx or REST, and cannot be inferred from them.
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  package: "@loadbare/app"
@@ -75,7 +75,7 @@ server.ts
75
75
  The chrome carries `<lb-hub>` inside `<body>`, an empty `<main>` inside the
76
76
  hub, and `<script src="/client.js" defer>`. Everything a user touches sits
77
77
  inside `<lb-hub>`. The chrome belongs to no page, so it names only the
78
- hub's queries, `lb-url`, `lb-choices` and `lb-hub`, and sends no declared
78
+ hub's queries, `lb-url`, `lb-sort`, `lb-filters` and `lb-hub-status`, and sends no declared
79
79
  request.
80
80
 
81
81
  A page is an HTML fragment that lands in `<main>`:
@@ -195,7 +195,10 @@ is added by a button with `commandfor` naming the nested list and
195
195
  the form's submit carries it as `{ key: "", values }`, the handler inserts
196
196
  each row with no key, and it leaves when the submit succeeds. So a master
197
197
  and its details are one save, before or after the master is on record; never
198
- gate the details behind `lb-show` on the master being saved. The form needs
198
+ gate the details behind `lb-show` on the master being saved. A form bound
199
+ by Edit or New reads its nested lists again (`lb-read`) with the bound key
200
+ as the parm named for its key column, so the details query reads that parm
201
+ and follows the row the form is bound to; New sends it empty. The form needs
199
202
  a submit. A new outer
200
203
  row is filled from the nested query's last answer, whatever order they land
201
204
  in.
@@ -220,21 +223,19 @@ JSON number or is declared one: `types: { amount: "number" }` beside
220
223
  hold a place.
221
224
 
222
225
  **The user's order and filters are choices, not query parms.** The hub
223
- serves a second query, `lb-choices`, keyed by `lb-path`, one column per
224
- choice, named by the application. A control inside
225
- `lb-query="lb-choices"` with `lb-request="lb-row-update"` makes a choice.
226
- `lb-order="<choice>"` on the element carrying `lb-query` places its rows by
227
- the order that choice holds, in place of the server's while it is not
228
- empty. `lb-filter="col, col"` there hides each row that does not pass the
229
- choice of the same name: `^=Amazon`, `>=2026-01-01 <=2026-03-31`, or plain
230
- text for contains. A choice shows with no round trip, then the server's
231
- `onChoices(ctx, choices)` hears every choice, to keep them;
232
- `onPageEnter` returns `choices()` to restore them. Choices hold until the
233
- page changes. A filter that decides which rows the server sends is a query
234
- parm.
226
+ serves them as two queries, `lb-sort` and `lb-filters`, one row per query
227
+ keyed by the query's name. `lb-sort` holds each query's order as the
228
+ server resolved it; `lb-filters` holds each query's filter values.
229
+ `lb-filter="col, col"` on the element carrying `lb-query` hides each row
230
+ that does not pass its query's filter of the same name: `^=Amazon`,
231
+ `>=2026-01-01 <=2026-03-31`, or plain text for contains. The server's
232
+ `onChoices(ctx, { query, sort, filters })` hears each change, to keep it;
233
+ `onPageEnter` returns `sort()` and `filters()` to restore them. Choices
234
+ hold until the page changes. A filter that decides which rows the server
235
+ sends is a query parm.
235
236
 
236
237
  **A page that failed to load says so.** The hub serves a third query,
237
- `lb-hub`, and stamps its columns on `<lb-hub>` too: `lb-load-state`
238
+ `lb-hub-status`, and stamps its columns on `<lb-hub>` too: `lb-load-state`
238
239
  (`loading`, `loaded`, `failed`), `lb-server-state` (`reachable`, `failing`,
239
240
  `unreachable`), `lb-error` (`network`, `timeout` or the HTTP status),
240
241
  `lb-pending` and `lb-as-of`. Show a server that is down from the chrome's
@@ -247,9 +248,10 @@ nested `<template>`, the next level or the row template; the nth breaks on
247
248
  the order's nth term, to any depth. Contents land before the nested
248
249
  template, so put it inside the heading's element for a container and beside
249
250
  the heading for a heading row. `<tbody>` and `<optgroup>` do not nest. The
250
- heading is filled from the group's first row. `lb-count`, `lb-sum="col"`,
251
- `lb-avg`, `lb-min` and `lb-max` total the rows showing in the nearest group,
252
- or in the whole list outside any.
251
+ heading is filled from the group's first row. `lb-aggregate` with `count`,
252
+ `sum`, `avg`, `min` or `max`, and `lb-column` naming the column, totals the
253
+ rows showing in the nearest group, or in the whole list outside any.
254
+ `count` with no column counts the rows.
253
255
 
254
256
  **Animate with CSS.** The hub stamps `lb-row-created`, `lb-row-changed`,
255
257
  `lb-row-moved` (its values changed and it now stands elsewhere), and
@@ -327,16 +329,6 @@ update or delete at all, silently. So a control with
327
329
  `lb-request="lb-row-update"` saves on a row on record and only holds its
328
330
  value on a new one. Never write script to stop such an update.
329
331
 
330
- **Select rows with `lb-select`, never with a column.** An
331
- `<input type="checkbox" lb-select>` in a row template selects its row; the
332
- hub stamps `lb-row-selected`. A request for that query naming no one row,
333
- such as a form outside the rows, carries the ticked rows as `keys`.
334
- `rowUpdate` and `rowDelete` then run once per key with no new code; a
335
- declared request gets `keys` itself. With none ticked an update or delete
336
- is not sent. Success clears the selection, and so does `lb-show` hiding a
337
- selector. A selecting mode kept in the URL reaches the rows as a column the
338
- query computes from the parm.
339
-
340
332
  **Refuse with `refusal()`, never with a plain throw.** `throw
341
333
  refusal("message")`, `throw refusal({ column: "message" })` or both, from
342
334
  `@loadbare/app/server`. The whole-request message lands as the
@@ -394,8 +386,7 @@ the element sits.
394
386
  shows, a filter the server applies, a date range: each is a query parm,
395
387
  `/accounts?acct=23&from=2026-09-09`, so a reload, a bookmark or a mailed link
396
388
  shows the same thing. The hub serves the query `lb-url`, of kind `row`,
397
- keyed by `lb-path`, with `lb-page-label`, `lb-page-unknown`, and one column
398
- per query parm. A control inside `lb-query="lb-url"` carrying
389
+ keyed by `lb-path`, with `lb-page-label` and one column per query parm. A control inside `lb-query="lb-url"` carrying
399
390
  `lb-request="lb-row-update"` writes its parm; the hub replaces the history
400
391
  entry, or pushes one when the control carries `lb-url-push`, and reloads the
401
392
  page's queries with no request to the server. A parm the URL repeats,
@@ -439,9 +430,9 @@ whole data channel.
439
430
 
440
431
  **The developer writes `lb-*` attributes in markup, and script never
441
432
  assigns them.** The builder refuses any `lb-` attribute other than the
442
- eighteen in [Attributes](references/names.md#attributes): `lb-query`,
443
- `lb-column`, `lb-show`, `lb-order`, `lb-filter`, `lb-manual`, `lb-group`, `lb-count`,
444
- `lb-sum`, `lb-avg`, `lb-min`, `lb-max`, `lb-select`, `lb-request`,
433
+ twelve in [Attributes](references/names.md#attributes): `lb-query`,
434
+ `lb-column`, `lb-show`, `lb-filter`, `lb-manual`, `lb-group`,
435
+ `lb-aggregate`, `lb-request`,
445
436
  `lb-url-link`, `lb-url-parm`, `lb-url-push` and `lb-url-unknown`. `lb-exp-slot` and
446
437
  `lb-exp-template` are expansion's, and never reach the page. Everything
447
438
  else with the prefix is a stamp the hub or the builder writes.
@@ -527,28 +518,44 @@ The application's own definition of a tag overrides a package's.
527
518
  Read these when the summary above does not settle the question. They ship
528
519
  with the package, so no network is needed.
529
520
 
530
- - [`references/overview.md`](references/overview.md) — the map of the
531
- reference, by part of the application.
521
+ [`references/overview.md`](references/overview.md) is the map of the
522
+ reference, in the groups below.
523
+
524
+ The application shell:
525
+
526
+ - [`references/chrome.md`](references/chrome.md) — the chrome, the URL as
527
+ the query `lb-url`, and the hub's state.
528
+ - [`references/server.md`](references/server.md) — the Express server, the
529
+ request context, and where the database layer fits.
530
+
531
+ Building the app:
532
+
533
+ - [`references/builder.md`](references/builder.md) — the builder's options,
534
+ what it reads, where it looks, and what it writes.
535
+ - [`references/css.md`](references/css.md) — how stylesheets are ordered, and
536
+ the stamps a stylesheet selects on.
537
+ - [`references/dev-server.md`](references/dev-server.md) — `devServer` from
538
+ `@loadbare/app/dev`, and the parts to assemble another from.
539
+
540
+ Application pages:
541
+
542
+ - [`references/page-files.md`](references/page-files.md) — what page
543
+ files are, queries, `onPageEnter`, `onChoices`, handlers, `crud`, refresh
544
+ and patch, `url()`, `sort()` and `filters()`.
532
545
  - [`references/data-binding.md`](references/data-binding.md) — landing,
533
546
  gathering, requests, conditions and request state. Start here for
534
547
  anything in a page.
535
- - [`references/page-files.md`](references/page-files.md) — what page
536
- files are, queries, `onPageEnter`, `onChoices`, handlers, `crud`, refresh
537
- and patch, `url()` and `choices()`.
548
+
549
+ Custom elements:
550
+
538
551
  - [`references/custom-elements.md`](references/custom-elements.md) —
539
552
  expansion, parameters, slots, destinations, and custom element code and
540
553
  when it runs.
541
- - [`references/chrome.md`](references/chrome.md) — the chrome, and the URL
542
- as the query `lb-url`.
543
- - [`references/server.md`](references/server.md) — the Express server and
544
- the request context.
545
- - [`references/builder.md`](references/builder.md) — the builder's options,
546
- what it reads, and where it looks.
547
- - [`references/dev-server.md`](references/dev-server.md) — `devServer` from
548
- `@loadbare/app/dev`, and the parts to assemble another from.
549
- - [`references/css.md`](references/css.md) — how stylesheets are ordered.
550
554
  - [`references/widgets.md`](references/widgets.md) — the basic widget
551
555
  library as a package; each widget is in the loadbare-widgets skill.
556
+
557
+ Names:
558
+
552
559
  - [`references/names.md`](references/names.md) — every name Loadbare
553
560
  owns, the constants, and the section defining each. Check a name here
554
561
  before designing around something the other references do not mention.
@@ -101,7 +101,6 @@ server's row lands, so a chrome shows where the page is with no code:
101
101
  |-------------------|---------------------------------------------------------|
102
102
  | `lb-path` | The path, such as `/members`. The row's key |
103
103
  | `lb-page-label` | The page's title, or its stub when it has none |
104
- | `lb-page-unknown` | `true` when no page's stub matches the path |
105
104
  | Any other name | The query parm of that name, or empty when it is absent |
106
105
 
107
106
  The path names a page, never a resource: `/accounts/42` is not a way to
@@ -116,9 +115,9 @@ page file carries; see [page files](./page-files.md#the-page-title). The hub
116
115
  sets the document title to `lb-page-label` whenever it is not null, so the
117
116
  tab and the browser history show the page.
118
117
 
119
- `lb-url` is one of two queries a page may use that the server does not
120
- declare. The other is [`lb-choices`](./data-binding.md#choices). A page
121
- names either the same way, anywhere inside the hub.
118
+ `lb-url` is one of the queries a page may use that the server does not
119
+ declare. The others are `lb-sort`, `lb-filters` and `lb-hub-status`. A page
120
+ names any the same way, anywhere inside the hub.
122
121
 
123
122
  ### Where focus starts
124
123
 
@@ -223,8 +222,8 @@ pushes one instead when the element carrying `lb-request` also carries
223
222
  The hub answers the request itself, with no round trip, then reloads the
224
223
  page's queries at the new URL. The page stays in place, and rows that come
225
224
  back keep their place. A request that leaves the URL as it was does
226
- nothing. `lb-page-label` and `lb-page-unknown` are the hub's, and a request
227
- never sets them.
225
+ nothing. `lb-page-label` is the hub's, and a request
226
+ never sets it.
228
227
 
229
228
  A request that gives `lb-path` a new value enters that page, pushes a
230
229
  history entry, and the new URL carries only the query parms the request
@@ -288,8 +287,8 @@ it; a per-session database role means an id outside the caller's reach
288
287
  finds nothing.
289
288
 
290
289
  An order or a filter the hub applies to rows the page already holds is not
291
- a query parm. It is a [choice](./data-binding.md#choices), which holds until
292
- the page changes.
290
+ a query parm. It is a [sort or a filter](./data-binding.md#sort-and-filters),
291
+ which holds until the page changes.
293
292
 
294
293
  The hub sends the query parms with every round trip, and the server hands
295
294
  them to `contextFor`; see [the Express server](./server.md#database-layer).
@@ -308,8 +307,7 @@ show the path:
308
307
  </dialog>
309
308
  ```
310
309
 
311
- When no page's stub matches the path, `lb-page-unknown` is `true`,
312
- `lb-page-label` is null, `<main>` keeps the page it had, the hub calls
310
+ When the path names a page that does not exist, `lb-page-label` is null, `<main>` keeps the page it had, the hub calls
313
311
  `showModal()` on the dialog, and reports the path to the console. A chrome
314
312
  with no dialog shows nothing. `lb-url` lands before the page is looked up,
315
313
  so the dialog shows the path by naming it like any other element.
@@ -69,7 +69,6 @@ in the stamp, so the selector reads the number and not its text. See
69
69
  | Select | For |
70
70
  |----------------------|-------------------------------------------------|
71
71
  | `[lb-row-live]` | Every live row of a list |
72
- | `[lb-row-selected]` | A row its `lb-select` checkbox ticks |
73
72
  | `[lb-row-editing]` | The row a pointed form is editing |
74
73
  | `[lb-key-value="…"]` | One row, by its key |
75
74
  | `[hidden]` | A row a filter hides; the browser hides it |
@@ -86,11 +85,10 @@ landed inside a list, such as a total in a table's foot, carries a key and
86
85
  is not one of the list's rows.
87
86
 
88
87
  ```css
89
- [lb-row-selected] { background: var(--selected); }
90
88
  [lb-row-editing] { outline: 2px solid var(--accent); }
91
89
  ```
92
90
 
93
- See [Rows](./data-binding.md#rows), [Selecting rows](./data-binding.md#selecting-rows)
91
+ See [Rows](./data-binding.md#rows)
94
92
  and [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form).
95
93
 
96
94
  ### A group
@@ -179,18 +177,19 @@ form:not([lb-key-value=""]) .save-new { display: none; }
179
177
 
180
178
  See [Editing a row in one form](./data-binding.md#editing-a-row-in-one-form).
181
179
 
182
- ### The user's choices
180
+ ### The user's order and filters
183
181
 
184
- An order or a filter the user chose is a column of `lb-choices`, so the
185
- element showing it carries it as `lb-column-value`. Style anything else by
186
- it with `:has()`:
182
+ The order the user chose is the `order` column of `lb-sort`, and a filter
183
+ is a column of `lb-filters`, so the element showing either carries it as
184
+ `lb-column-value`. Style anything else by it with `:has()`:
187
185
 
188
186
  ```css
189
- table:has([lb-column="accounts_order"][lb-column-value="-balance"])
190
- th.balance::after { content: "▼"; }
187
+ :has([lb-column="name"][lb-column-value="^=Amazon"]) th.name::after {
188
+ content: "🔍";
189
+ }
191
190
  ```
192
191
 
193
- See [Choices](./data-binding.md#choices).
192
+ See [Sort and filters](./data-binding.md#sort-and-filters).
194
193
 
195
194
  ### Content shown by condition
196
195
 
@@ -572,18 +572,21 @@ missing a field its name needs, and stamps the dispatching element with
572
572
 
573
573
  Let the event bubble, so an ancestor can stop it before the hub sends it.
574
574
 
575
- A request with `lb-row-update` for `lb-url` or `lb-choices` is keyed by the
576
- path, so an element anywhere on the page may set a query parm or make a
577
- [choice](./data-binding.md#choices) without sitting inside the query:
575
+ A request with `lb-row-update` for `lb-url` is keyed by the path; one for
576
+ `lb-sort` or `lb-filters` is keyed by the query's name. An element anywhere
577
+ on the page may set a query parm or make a
578
+ [sort or a filter](./data-binding.md#sort-and-filters) without sitting
579
+ inside the query:
578
580
 
579
581
  ```ts
580
- import { CHOICES_QUERY, LB_EVENT_NAME, REQUEST_ROW_UPDATE } from "@loadbare/app/constants";
582
+ import { SORT_QUERY, LB_EVENT_NAME, REQUEST_ROW_UPDATE } from "@loadbare/app/constants";
581
583
  import type { HubRequest } from "@loadbare/app/types";
582
584
 
583
585
  const detail: HubRequest = {
584
586
  name: REQUEST_ROW_UPDATE,
585
- query: CHOICES_QUERY,
586
- values: { ledger_order: "-amount" },
587
+ query: SORT_QUERY,
588
+ key: "ledger",
589
+ values: { order: "-amount" },
587
590
  };
588
591
  this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
589
592
  ```
@@ -616,8 +619,8 @@ listener. Import its name as `LB_DONE_EVENT_NAME`.
616
619
  `items` is every response item that landed because of the request, in
617
620
  order. `refusal` is set beside `error` when the handler refused the
618
621
  request, holding its `message` and `columns`. An answer that moved the URL contributes its `lb-url` item, which
619
- carries a key only the server knew, and the page load. A choice contributes
620
- the row of `lb-choices` it made. `error` is set when
622
+ carries a key only the server knew, and the page load. A sort or a filter contributes
623
+ the row of `lb-sort` or `lb-filters` it made. `error` is set when
621
624
  the round trip failed, and then nothing landed. A new row's key is in the
622
625
  answer only when the handler returned it in a patch: a refresh sends all
623
626
  rows, and nothing marks which one is new.