@loadbare/app 0.8.1 → 0.8.2

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 (34) hide show
  1. package/dist/core/lb-constants.d.ts +2 -0
  2. package/dist/core/lb-constants.d.ts.map +1 -1
  3. package/dist/core/lb-constants.js +18 -4
  4. package/dist/core/lb-constants.js.map +1 -1
  5. package/dist/hub/lb-apply.d.ts +10 -0
  6. package/dist/hub/lb-apply.d.ts.map +1 -1
  7. package/dist/hub/lb-apply.js +15 -1
  8. package/dist/hub/lb-apply.js.map +1 -1
  9. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  10. package/dist/hub/lb-hub.browser.js +116 -35
  11. package/dist/hub/lb-hub.browser.js.map +1 -1
  12. package/dist/server/lb-express.d.ts +13 -5
  13. package/dist/server/lb-express.d.ts.map +1 -1
  14. package/dist/server/lb-express.js +26 -7
  15. package/dist/server/lb-express.js.map +1 -1
  16. package/dist/server/lb-server.d.ts +3 -0
  17. package/dist/server/lb-server.d.ts.map +1 -1
  18. package/dist/server/lb-server.js.map +1 -1
  19. package/docs/TECHREF-1.0.md +93 -22
  20. package/docs/comparison.md +8 -7
  21. package/docs/reference/chrome.md +5 -3
  22. package/docs/reference/data-binding.md +28 -0
  23. package/docs/reference/server.md +11 -0
  24. package/docs/reference/widgets.md +6 -2
  25. package/docs/roadmap.md +16 -0
  26. package/docs/theory.md +8 -1
  27. package/docs/tutorials/010-pages-and-navigation.md +2 -1
  28. package/package.json +1 -1
  29. package/skills/loadbare-app/SKILL.md +25 -8
  30. package/skills/loadbare-app/references/TECHREF-1.0.md +93 -22
  31. package/skills/loadbare-app/references/chrome.md +5 -3
  32. package/skills/loadbare-app/references/data-binding.md +28 -0
  33. package/skills/loadbare-app/references/server.md +11 -0
  34. package/skills/loadbare-app/references/widgets.md +6 -2
@@ -361,7 +361,8 @@ Vuetify for Vue) are not usable in another without a wrapper.
361
361
 
362
362
  ### Loadbare/app
363
363
 
364
- Every request from the hub is `POST /lb?page=<name>` with a JSON body.
364
+ Every request from the hub is `POST /lb/<page>` with a JSON body, and carries
365
+ the query string the browser is showing.
365
366
 
366
367
  - An empty body asks for the page's whole query set. The server runs the
367
368
  page's `onPageEnter`, then every query the page declares.
@@ -679,12 +680,12 @@ blocker. Serving the static files from a separate origin is a roadmap item.
679
680
 
680
681
  First load of any path:
681
682
 
682
- | Request | Returns |
683
- |--------------------------|--------------------------------------------|
684
- | `GET /<path>` | `app.html`: the chrome and every page as a `<template>` |
685
- | `GET /client.js` | The hub and every used widget, one bundle |
686
- | `GET /app.css` | Every stylesheet, one file |
687
- | `POST /lb?page=<name>` | The page's query results as JSON |
683
+ | Request | Returns |
684
+ |---------------------------|--------------------------------------------|
685
+ | `GET /<path>` | `app.html`: the chrome and every page as a `<template>` |
686
+ | `GET /client.js` | The hub and every used widget, one bundle |
687
+ | `GET /app.css` | Every stylesheet, one file |
688
+ | `POST /lb/<page>?<query>` | The page's query results as JSON |
688
689
 
689
690
  The three static files are fixed at release and can be compressed, cached,
690
691
  and served from a CDN. There are no per-route bundles, lazy chunks or module
@@ -73,7 +73,9 @@ sits directly inside `<body>`, with banner, nav, footer and `<main>` inside
73
73
  it, so Loadbare can act on all of them.
74
74
 
75
75
  A navigation anchor's `href` is a path, and the path names a page:
76
- `/members` shows `members.page.html`. A bare `/` resolves to `index`, so the
76
+ `/members` shows `members.page.html`. A query string on it is kept, so
77
+ `/members?team=Engines` opens the page narrowed; see
78
+ [Query parms](./data-binding.md#query-parms). A bare `/` resolves to `index`, so the
77
79
  landing page is the one named `index.page.html`. An anchor without
78
80
  `lb-nav-link` is left alone and behaves like any other link.
79
81
 
@@ -100,10 +102,10 @@ shows a value — so a chrome displays the current page with no code at all:
100
102
  | Cell | Holds |
101
103
  |--------------|-----------------------------------------------------------|
102
104
  | `page-label` | The text of the `lb-nav-link` anchor for the path, or empty if none |
103
- | `page-uri` | The path as the browser has it, such as `/members` |
105
+ | `page-uri` | The path and query string as the browser has them, such as `/members?team=Engines` |
104
106
 
105
107
  The label is the nav's. The hub takes it from the first `lb-nav-link`
106
- anchor whose `href` names the current page, so a click, a reload and the
108
+ anchor whose path names the current page, whatever its query string, so a click, a reload and the
107
109
  back button all land the same text, and a path no anchor names lands an
108
110
  empty label. A chrome that shows the label somewhere fixed should expect
109
111
  that case for a page reachable only by URL.
@@ -304,6 +304,34 @@ Leave `lb-action` off a widget inside an `lb-row-insert` or `lb-row-update`
304
304
  form. The form reads every `lb-cell` in it on submit, so a widget that also
305
305
  sent its own would write the same edit twice.
306
306
 
307
+ ## Query parms
308
+
309
+ A control that narrows what the page shows writes its value into the query
310
+ string instead of sending a request. `lb-query-parm` names the parm:
311
+
312
+ ```html
313
+ <select lb-query-parm="team">
314
+ <option value="">Every team</option>
315
+ <option value="Engines">Engines</option>
316
+ </select>
317
+ ```
318
+
319
+ On `change` the hub sets that one parm in the URL, leaving every other parm
320
+ alone, and takes it out when the value is empty. The write replaces the
321
+ current history entry; add `lb-query-parm-push` to push one instead. The page
322
+ then loads at the new URL, exactly as a cold load of it would, without
323
+ replacing its DOM.
324
+
325
+ After every load the hub lands each parm on the control that writes it, and
326
+ an absent parm lands empty, so the control shows what the address bar says.
327
+
328
+ A control that writes a query parm sends no request, and one that also
329
+ carries `lb-action` has that request refused.
330
+
331
+ The server reads the parms off `req.query` in `contextFor`; see
332
+ [the Express server](./server.md#database-layer). See
333
+ [Query parms](../TECHREF-1.0.md#query-parms) for the whole rule.
334
+
307
335
  ## Conditional rendering
308
336
 
309
337
  Loadbare ships static HTML and hydrates elements that are already in the
@@ -137,6 +137,17 @@ declare module "@loadbare/app/server" {
137
137
  }
138
138
  ```
139
139
 
140
+ The query string the browser is showing arrives on every data request, so
141
+ `req.query` holds the page's query parms. Put on the context whatever a query
142
+ reads from them, and treat them as user input:
143
+
144
+ ```ts
145
+ function contextFor(req: Request): HubContext {
146
+ const { team } = req.query;
147
+ return { db: openDb(), team: typeof team === "string" ? team : "" };
148
+ }
149
+ ```
150
+
140
151
  Add a field for anything else a request needs — the authenticated user, a
141
152
  request id, a feature flag set. Queries and requests read them from `ctx`; see
142
153
  [page files](./page-files.md).
@@ -56,7 +56,8 @@ action carries a value.
56
56
  | ---------- | ----- |
57
57
  | `exp-label` | the visible `<label>` text |
58
58
 
59
- Requires `lb-action` — a change with none logs and sends nothing.
59
+ Requires `lb-action` or `lb-query-parm` — a change with neither logs and
60
+ sends nothing. With `lb-query-parm` the hub writes the choice into the URL.
60
61
 
61
62
  ## `lb-options`
62
63
 
@@ -81,8 +82,11 @@ hand. The author supplies the row template inside the widget (via
81
82
  once, with `lb-key`.
82
83
  - `data-group` on the row template sections the options into `<optgroup>`s,
83
84
  one per distinct value, created and removed as rows arrive and leave.
85
+ - `lb-value` selects the option with that key, including one that arrives
86
+ after the value did.
84
87
  - A `change` sends the action named by `lb-action`, value from the
85
- select's `.value`.
88
+ select's `.value`. With `lb-query-parm` instead, the hub writes the
89
+ choice into the URL.
86
90
 
87
91
  `lb-picker` is this same class with its row template supplied by the
88
92
  definition instead of the page — see below.
package/docs/roadmap.md CHANGED
@@ -103,6 +103,22 @@ If a dev team wishes to make their own widgets that identify `lb-list` or `lb-ro
103
103
  Perhaps a utility that can be called, like `getDataScope(el)`, to help
104
104
  clean up the code in these cases.
105
105
 
106
+ ### Refresh narrowed by query parm
107
+
108
+ A query parm write loads the whole page again, every query at once. Keyed
109
+ landing keeps the rows that came back, so nothing on screen is disturbed, but
110
+ the server does work for queries the parm never touched.
111
+
112
+ A query could declare the parms it reads, and the hub, which holds the old
113
+ URL and the new, could send the names that changed. The server would run
114
+ only the queries that read one of them, and still remember nothing.
115
+
116
+ ### Query parm history as a user preference
117
+
118
+ A query parm write replaces the history entry, and `lb-query-parm-push`
119
+ pushes one. Which of the two a user wants may be the user's to say, rather
120
+ than the page's.
121
+
106
122
  ### Build-time checking of `lb-action` against the declared requests
107
123
 
108
124
  Release 1.0 resolves every `lb-action` value at request time. A value naming
package/docs/theory.md CHANGED
@@ -210,6 +210,12 @@ empty slot and has no opinion about what goes in it. It does not:
210
210
  - require or prevent any authentication solution
211
211
  - expect or hinder the use of an ORM, or of `@loadbare/db`
212
212
 
213
+ Loadbare is for applications, not sites. Every route is answered with
214
+ the same document, and a path that names no page is found out in the
215
+ browser rather than answered with a 404. The path names a page, and the
216
+ query string describes what that page has on screen, so the address bar
217
+ is always true and a URL can be reloaded or mailed to someone.
218
+
213
219
  The rule for one-time chores, such as standing up an Express Server,
214
220
  is that they stay as close as possible to "set and forget", so that
215
221
  their cost is paid once and they are not a tax on
@@ -520,7 +526,8 @@ in the browser, providing an SPA is fairly simple.
520
526
  The hub catches anchor clicks, and checks if the anchor contains the
521
527
  attribute `lb-nav-link`. If so, the hub interprets it as in-app
522
528
  navigation, swaps the anchor's `href` into `<main>`, and sends a request
523
- to the server for the page data.
529
+ to the server for the page data. The query string rides along, so the
530
+ server always knows what URL the browser is showing.
524
531
 
525
532
  Anchors without the attribute behave as normal links.
526
533
 
@@ -77,7 +77,8 @@ If a `<dialog lb-unknown-page>` element is present in the HTML, it will be
77
77
  displayed to the user when a URL is entered that has no matching page in the app.
78
78
  The `lb-row` and `lb-cell` attributes will be explained when we get to data
79
79
  binding; for now, know that `lb-navigation` is a query the hub itself answers
80
- on every navigation, and `page-uri` is the path that was asked for. The
80
+ on every navigation, and `page-uri` is the path and query string that were
81
+ asked for. The
81
82
  widget library ships this dialog ready-made as `<lb-unknown-page>`, which
82
83
  [Using Widget Libraries](./090-using-widget-libraries.md) covers.
83
84
 
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.8.1",
4
+ "version": "0.8.2",
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 binds HTML to server data and sends requests (`lb-list`, `lb-row`, `lb-cell`, `lb-key`, `lb-show`, `lb-action`); build-time widget expansion with `exp-*` parameters, `lb-slot` and `lb-template`; custom element code; 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 widget 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 binds HTML to server data and sends requests (`lb-list`, `lb-row`, `lb-cell`, `lb-key`, `lb-show`, `lb-action`); query parms in the URL with `lb-query-parm`; build-time widget expansion with `exp-*` parameters, `lb-slot` and `lb-template`; custom element code; 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 widget 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"
@@ -159,10 +159,25 @@ for display by a widget's `lbPlaceRow`. A list nested inside another list's
159
159
  rows receives the same rows in every outer row; use it for a picker, never
160
160
  for per-row detail.
161
161
 
162
- **A URL names a page, never a resource.** Do not design `/accounts/42`. A
163
- row is addressed by `list` and `key`, taken from where the element sits.
164
- Queries take no arguments from the browser; which record a page shows is
165
- server state reached through `ctx`.
162
+ **A URL's path names a page, never a resource.** Do not design
163
+ `/accounts/42`. A row is addressed by `list` and `key`, taken from where the
164
+ element sits.
165
+
166
+ **The query string is what the page has on screen.** Which record a page
167
+ shows, a filter, a date range: each is a query parm,
168
+ `/accounts?acct=23&from=2026-09-09`, so a reload, a bookmark or a mailed link
169
+ shows the same thing. A control writes one with `lb-query-parm="acct"`, which
170
+ replaces the history entry, reloads the page at the new URL, and sends no
171
+ request. A link writes several with an ordinary `lb-nav-link` href. Queries
172
+ still take no arguments: `contextFor` reads `req.query` onto `ctx`, and a
173
+ query reads it there. A parm is user input, so validate it where you read
174
+ it. Do not keep a selection in server state set by an action; that forces
175
+ `refresh: []` and a hand-written re-answer of everything the selection
176
+ touches.
177
+
178
+ **Loadbare is for applications, not sites.** Every route is answered with
179
+ `app.html`, and a path that names no page is found in the browser, not
180
+ answered with a 404. That is the design, not a defect to report.
166
181
 
167
182
  **There are no endpoints to write.** `hubRoutes(hub, contextFor)` is the
168
183
  whole data channel. Declare every action under `actions` and every
@@ -238,8 +253,8 @@ with the package, so no network is needed.
238
253
  - [`references/overview.md`](references/overview.md) — the map of the
239
254
  reference, by part of the application.
240
255
  - [`references/data-binding.md`](references/data-binding.md) — every `lb-`
241
- attribute, requests, forms, conditions and request state. Start here for
242
- anything in a page.
256
+ attribute, requests, forms, query parms, conditions and request state.
257
+ Start here for anything in a page.
243
258
  - [`references/page-files.md`](references/page-files.md) — queries,
244
259
  `onPageEnter`, actions, CRUD, refresh and patch.
245
260
  - [`references/custom-elements.md`](references/custom-elements.md) —
@@ -254,5 +269,7 @@ with the package, so no network is needed.
254
269
  - [`references/widgets.md`](references/widgets.md) — the basic widget
255
270
  library.
256
271
  - [`references/TECHREF-1.0.md`](references/TECHREF-1.0.md) — the reserved
257
- names, and the open items that block 1.0. Read it before designing around
272
+ names, what a URL names and
273
+ [query parms](references/TECHREF-1.0.md#query-parms), and the open items
274
+ that block 1.0. Read it before designing around
258
275
  something the other references do not mention.
@@ -106,14 +106,6 @@ in the row.
106
106
  stays a flag, since it is what locates the file. State the precedence
107
107
  between a flag and a key once. Its key names join the permanent surface,
108
108
  so this lands before 1.0 or not at all.
109
- - **Decide whether a URL carries view parameters.** A URL names a page and
110
- nothing finer — see [What a URL names](#what-a-url-names) — and a query
111
- takes no argument from the browser. So a page has nowhere to keep which
112
- record it shows, a filter, a sort, or a collapsed section: a reload or a
113
- shared link loses them, and a filter is lost whenever an action re-runs its
114
- query. Parameters on a page URL, as `<form method="get">` puts its fields
115
- in the query string, are one answer. Deciding against them, and leaving
116
- view state to the application, is another. Write whichever one down.
117
109
  - **Decide CSS pairing.** Naming a stylesheet `<tag>.css` beside its
118
110
  definition would let the builder ship only what survives expansion.
119
111
  Recommend deferring the mechanism and reserving the configuration key, so
@@ -507,8 +499,8 @@ A list nested in another list's rows receives the same rows in every outer
507
499
  row. That serves a picker offering the same choices on every row, and is
508
500
  not a way to show a different detail per row.
509
501
 
510
- Which master a page shows is server state reached through `ctx`, since a
511
- query takes no argument from the browser — see the blocker on view parameters.
502
+ Which master a page shows is a query parm, which a query reads off `ctx`
503
+ since it takes no argument from the browser — see [Query parms](#query-parms).
512
504
 
513
505
  ### Requests
514
506
 
@@ -638,15 +630,16 @@ A failed insert resets nothing, so the entry can be corrected.
638
630
 
639
631
  A navigation that fails to load its data sets nothing. No element
640
632
  dispatched it, so there is nothing to stamp, and the hub reports it to the
641
- console.
633
+ console. A load started by a control writing a query parm stamps that
634
+ control, as a request stamps its origin.
642
635
 
643
636
  ### Links
644
637
 
645
638
  ---- UNEDITED ----
646
639
 
647
640
  An anchor carrying `lb-nav-link` navigates inside the application: the hub
648
- catches the click, pushes the anchor's path onto history, and swaps the page
649
- host in `<main>`. An anchor without it is left alone and behaves like any
641
+ catches the click, pushes the anchor's path and query string onto history,
642
+ and swaps the page host in `<main>`. An anchor without it is left alone and behaves like any
650
643
  other link, so leaving the application is the default and staying in it is
651
644
  the opt-in.
652
645
 
@@ -660,6 +653,11 @@ link to determine the path.
660
653
  Path space is flat. A path such as `/members` links to the `members.*` files
661
654
  on the server.
662
655
 
656
+ The query string is kept. `/transactions?date_begin=2026-09-01` opens the
657
+ transactions page narrowed to those dates, and a link to the page it is
658
+ already on loads that page again at the new URL without replacing its DOM —
659
+ see [Query parms](#query-parms).
660
+
663
661
  The bare path `/` resolves to `index`.
664
662
 
665
663
  A path that names no page is detected in the browser, after a successful
@@ -680,19 +678,77 @@ See also [Navigation Row lb-navigation](#lb-navigation).
680
678
 
681
679
  #### What a URL names
682
680
 
683
- A URL names a page, a place in the application. It never names a resource,
684
- and Loadbare/app does not reproduce REST: `/accounts/42` is not a way to
685
- reach account 42.
681
+ The path names a page, a place in the application. It never names a
682
+ resource, and Loadbare/app does not reproduce REST: `/accounts/42` is not a
683
+ way to reach account 42.
684
+
685
+ The query string describes what that page has on screen:
686
+ `/accounts?acct=23&from=2026-09-09`. It never commands, and the server
687
+ remembers nothing, so a URL is a reproducible view. Reload it, bookmark it,
688
+ or mail it to someone, and what they see is what the sender saw.
686
689
 
687
690
  A page declares what it shows, its queries, and what it allows, its actions.
688
691
  Loading a page runs its queries. A request performs an action at a position.
689
692
  A row is addressed by `list` and `key`, taken from where the element sits,
690
693
  which is how the database already names it. Nothing is fetched by URL, so an
691
- application designs no endpoints, and the browser reaches only what a page
692
- publishes.
694
+ application designs no endpoints.
695
+
696
+ That last is where query parms move Loadbare's position. A query still takes
697
+ no argument from the browser, and a parm arrives the way the session cookie
698
+ does, as part of the request the context is built from. But a user who
699
+ types `?acct=99999` now influences what a query returns. A query parm is
700
+ user input, validated like any other where the application reads it, and a
701
+ per-session database role means an id outside the caller's reach finds
702
+ nothing.
703
+
704
+ Loadbare is for applications, not sites. Every route is answered with the
705
+ same document, and a path that names no page is found out in the browser —
706
+ see [Links](#links).
707
+
708
+ #### Query parms
709
+
710
+ A control that narrows what a page shows writes its value into the query
711
+ string, rather than sending it:
712
+
713
+ ```html
714
+ <lb-options lb-list="teams" lb-query-parm="team" exp-label="Team:">
715
+ <option value="">Every team</option>
716
+ <template lb-key="id"><option lb-cell="name"></option></template>
717
+ </lb-options>
718
+ ```
719
+
720
+ | Attribute | Written by | Behavior |
721
+ | -------------------- | ---------- | --------------------------------------------------------------- |
722
+ | `lb-query-parm` | Developer | On a control: its `change` writes this parm and reloads the page |
723
+ | `lb-query-parm-push` | Developer | With `lb-query-parm`: the write pushes a history entry |
693
724
 
694
- Whether a page URL may carry parameters, so that a link can open the page on
695
- one record, is undecided — see the blockers.
725
+ On `change`, the hub reads the control's value the way a form reads a cell,
726
+ and sets that one parm in the URL. Every other parm is left as it is, since
727
+ it may have arrived by link with no control on screen to say it again. An
728
+ empty value takes the parm out, so a URL is as long as the user has narrowed
729
+ the page. A value the URL already carries does nothing.
730
+
731
+ The write replaces the current history entry: changing what a page shows is
732
+ not going anywhere, so Back leaves the page rather than walking back through
733
+ every choice. `lb-query-parm-push` makes the write push an entry instead.
734
+
735
+ The page then loads at the new URL exactly as a cold load of that URL would:
736
+ `onPageEnter`, then every query. The page's DOM is kept, and the answer
737
+ lands by key, so a list that gets its rows back keeps them and its scroll
738
+ position.
739
+
740
+ After every load — cold, by link, by Back, by a write — the hub lands each
741
+ parm on the control that writes it, and an absent parm lands empty. A
742
+ control therefore shows what the address bar says.
743
+
744
+ A control that writes a query parm sends no request. One that also carries
745
+ `lb-action` has that request refused, since the choice would otherwise be
746
+ sent twice.
747
+
748
+ Every round trip carries the query string the browser is showing, a page
749
+ load and an action alike. The server reads the parms off `req.query` in
750
+ `contextFor` — see [The Express server](#the-express-server). Nothing in
751
+ Loadbare assigns a parm a meaning.
696
752
 
697
753
  ### lb-navigation
698
754
 
@@ -706,10 +762,11 @@ like a server-produced result.
706
762
  | Cell | Holds |
707
763
  | ------------ | ------------------------------------------------------------------- |
708
764
  | `page-label` | The text of the link to the current page, empty if no link names it |
709
- | `page-uri` | The path as the browser has it, such as `/members` |
765
+ | `page-uri` | The path and query string as the browser has them, such as `/members?team=Engines` |
710
766
 
711
767
  The `page-uri` is taken from the current URL. The `page-label` is taken from the first
712
- `lb-nav-link` link in the document (presumably in a nav bar) that matches the URI.
768
+ `lb-nav-link` link in the document (presumably in a nav bar) whose path matches the
769
+ current path, so the query string does not change it.
713
770
 
714
771
  The row lands before the page is looked up, so a path that names no page has
715
772
  it too, and an `lb-unknown-page` dialog, where the chrome has one, displays
@@ -871,6 +928,18 @@ Explaining Express is beyond the scope of this technical reference. The
871
928
  only real requirement is that the catch-all for app.html is at the end,
872
929
  so it does not catch any other files.
873
930
 
931
+ `hubRoutes` answers `POST /lb/<page>`, with the query string the browser is
932
+ showing after it, verbatim. So `req.query` holds the page's query parms, and
933
+ `contextFor` puts on the context whatever a query reads from them. They are
934
+ user input:
935
+
936
+ ```ts
937
+ function contextFor(req: Request): HubContext {
938
+ const { acct } = req.query;
939
+ return { db: openDb(), acct: typeof acct === "string" ? acct : "" };
940
+ }
941
+ ```
942
+
874
943
  Give the server the origin root. The hub reaches its own endpoints by
875
944
  absolute path, so an application cannot be hosted under a subpath such as
876
945
  `example.com/myapp/`, and anything proxying in front of the server passes
@@ -1148,6 +1217,8 @@ Each attribute is defined in one section, and this table says which.
1148
1217
  | `lb-value` | Hub | [How a value lands](#how-a-value-lands) |
1149
1218
  | `lb-action` | Developer | [Requests](#requests) |
1150
1219
  | `lb-nav-link` | Developer | [Links](#links) |
1220
+ | `lb-query-parm` | Developer | [Query parms](#query-parms) |
1221
+ | `lb-query-parm-push` | Developer | [Query parms](#query-parms) |
1151
1222
  | `lb-pending` | Hub | [Request state](#request-state) |
1152
1223
  | `lb-error` | Hub | [Request state](#request-state) |
1153
1224
  | `lb-row-count` | Hub | [Request state](#request-state) |
@@ -73,7 +73,9 @@ sits directly inside `<body>`, with banner, nav, footer and `<main>` inside
73
73
  it, so Loadbare can act on all of them.
74
74
 
75
75
  A navigation anchor's `href` is a path, and the path names a page:
76
- `/members` shows `members.page.html`. A bare `/` resolves to `index`, so the
76
+ `/members` shows `members.page.html`. A query string on it is kept, so
77
+ `/members?team=Engines` opens the page narrowed; see
78
+ [Query parms](./data-binding.md#query-parms). A bare `/` resolves to `index`, so the
77
79
  landing page is the one named `index.page.html`. An anchor without
78
80
  `lb-nav-link` is left alone and behaves like any other link.
79
81
 
@@ -100,10 +102,10 @@ shows a value — so a chrome displays the current page with no code at all:
100
102
  | Cell | Holds |
101
103
  |--------------|-----------------------------------------------------------|
102
104
  | `page-label` | The text of the `lb-nav-link` anchor for the path, or empty if none |
103
- | `page-uri` | The path as the browser has it, such as `/members` |
105
+ | `page-uri` | The path and query string as the browser has them, such as `/members?team=Engines` |
104
106
 
105
107
  The label is the nav's. The hub takes it from the first `lb-nav-link`
106
- anchor whose `href` names the current page, so a click, a reload and the
108
+ anchor whose path names the current page, whatever its query string, so a click, a reload and the
107
109
  back button all land the same text, and a path no anchor names lands an
108
110
  empty label. A chrome that shows the label somewhere fixed should expect
109
111
  that case for a page reachable only by URL.
@@ -304,6 +304,34 @@ Leave `lb-action` off a widget inside an `lb-row-insert` or `lb-row-update`
304
304
  form. The form reads every `lb-cell` in it on submit, so a widget that also
305
305
  sent its own would write the same edit twice.
306
306
 
307
+ ## Query parms
308
+
309
+ A control that narrows what the page shows writes its value into the query
310
+ string instead of sending a request. `lb-query-parm` names the parm:
311
+
312
+ ```html
313
+ <select lb-query-parm="team">
314
+ <option value="">Every team</option>
315
+ <option value="Engines">Engines</option>
316
+ </select>
317
+ ```
318
+
319
+ On `change` the hub sets that one parm in the URL, leaving every other parm
320
+ alone, and takes it out when the value is empty. The write replaces the
321
+ current history entry; add `lb-query-parm-push` to push one instead. The page
322
+ then loads at the new URL, exactly as a cold load of it would, without
323
+ replacing its DOM.
324
+
325
+ After every load the hub lands each parm on the control that writes it, and
326
+ an absent parm lands empty, so the control shows what the address bar says.
327
+
328
+ A control that writes a query parm sends no request, and one that also
329
+ carries `lb-action` has that request refused.
330
+
331
+ The server reads the parms off `req.query` in `contextFor`; see
332
+ [the Express server](./server.md#database-layer). See
333
+ [Query parms](./TECHREF-1.0.md#query-parms) for the whole rule.
334
+
307
335
  ## Conditional rendering
308
336
 
309
337
  Loadbare ships static HTML and hydrates elements that are already in the
@@ -137,6 +137,17 @@ declare module "@loadbare/app/server" {
137
137
  }
138
138
  ```
139
139
 
140
+ The query string the browser is showing arrives on every data request, so
141
+ `req.query` holds the page's query parms. Put on the context whatever a query
142
+ reads from them, and treat them as user input:
143
+
144
+ ```ts
145
+ function contextFor(req: Request): HubContext {
146
+ const { team } = req.query;
147
+ return { db: openDb(), team: typeof team === "string" ? team : "" };
148
+ }
149
+ ```
150
+
140
151
  Add a field for anything else a request needs — the authenticated user, a
141
152
  request id, a feature flag set. Queries and requests read them from `ctx`; see
142
153
  [page files](./page-files.md).
@@ -56,7 +56,8 @@ action carries a value.
56
56
  | ---------- | ----- |
57
57
  | `exp-label` | the visible `<label>` text |
58
58
 
59
- Requires `lb-action` — a change with none logs and sends nothing.
59
+ Requires `lb-action` or `lb-query-parm` — a change with neither logs and
60
+ sends nothing. With `lb-query-parm` the hub writes the choice into the URL.
60
61
 
61
62
  ## `lb-options`
62
63
 
@@ -81,8 +82,11 @@ hand. The author supplies the row template inside the widget (via
81
82
  once, with `lb-key`.
82
83
  - `data-group` on the row template sections the options into `<optgroup>`s,
83
84
  one per distinct value, created and removed as rows arrive and leave.
85
+ - `lb-value` selects the option with that key, including one that arrives
86
+ after the value did.
84
87
  - A `change` sends the action named by `lb-action`, value from the
85
- select's `.value`.
88
+ select's `.value`. With `lb-query-parm` instead, the hub writes the
89
+ choice into the URL.
86
90
 
87
91
  `lb-picker` is this same class with its row template supplied by the
88
92
  definition instead of the page — see below.