@loadbare/app 0.8.2 → 0.10.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 (80) hide show
  1. package/README.md +3 -3
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +74 -66
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/expand.d.ts.map +1 -1
  6. package/dist/build/expand.js +20 -19
  7. package/dist/build/expand.js.map +1 -1
  8. package/dist/build/locations.d.ts +2 -3
  9. package/dist/build/locations.d.ts.map +1 -1
  10. package/dist/build/locations.js +2 -3
  11. package/dist/build/locations.js.map +1 -1
  12. package/dist/build/pages.d.ts +3 -4
  13. package/dist/build/pages.d.ts.map +1 -1
  14. package/dist/build/pages.js +3 -4
  15. package/dist/build/pages.js.map +1 -1
  16. package/dist/core/lb-constants.d.ts +25 -23
  17. package/dist/core/lb-constants.d.ts.map +1 -1
  18. package/dist/core/lb-constants.js +95 -158
  19. package/dist/core/lb-constants.js.map +1 -1
  20. package/dist/core/lb-types.d.ts +73 -75
  21. package/dist/core/lb-types.d.ts.map +1 -1
  22. package/dist/core/lb-types.js +58 -5
  23. package/dist/core/lb-types.js.map +1 -1
  24. package/dist/hub/lb-apply.d.ts +47 -37
  25. package/dist/hub/lb-apply.d.ts.map +1 -1
  26. package/dist/hub/lb-apply.js +174 -193
  27. package/dist/hub/lb-apply.js.map +1 -1
  28. package/dist/hub/lb-hub.browser.d.ts +1 -1
  29. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  30. package/dist/hub/lb-hub.browser.js +419 -415
  31. package/dist/hub/lb-hub.browser.js.map +1 -1
  32. package/dist/server/lb-express.d.ts +20 -13
  33. package/dist/server/lb-express.d.ts.map +1 -1
  34. package/dist/server/lb-express.js +50 -52
  35. package/dist/server/lb-express.js.map +1 -1
  36. package/dist/server/lb-server.d.ts +81 -116
  37. package/dist/server/lb-server.d.ts.map +1 -1
  38. package/dist/server/lb-server.js +151 -48
  39. package/dist/server/lb-server.js.map +1 -1
  40. package/docs/TECHREF-1.0.md +893 -558
  41. package/docs/comparison.md +222 -185
  42. package/docs/prior-art.md +15 -14
  43. package/docs/reference/builder.md +9 -3
  44. package/docs/reference/chrome.md +107 -56
  45. package/docs/reference/custom-elements.md +199 -173
  46. package/docs/reference/data-binding.md +375 -370
  47. package/docs/reference/overview.md +12 -10
  48. package/docs/reference/page-files.md +161 -86
  49. package/docs/reference/server.md +13 -7
  50. package/docs/reference/widgets.md +104 -110
  51. package/docs/roadmap.md +36 -31
  52. package/docs/terms-of-art.md +57 -0
  53. package/docs/testing.md +97 -68
  54. package/docs/theory.md +92 -58
  55. package/docs/tutorials/010-pages-and-navigation.md +20 -12
  56. package/docs/tutorials/020-css.md +6 -3
  57. package/docs/tutorials/030-html-decomposition.md +9 -7
  58. package/docs/tutorials/040-displaying-data.md +30 -13
  59. package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
  60. package/docs/tutorials/060-custom-element-code.md +17 -16
  61. package/docs/tutorials/065-conditional-rendering.md +34 -23
  62. package/docs/tutorials/070-displaying-a-list.md +29 -21
  63. package/docs/tutorials/072-inserting-into-a-list.md +24 -16
  64. package/docs/tutorials/074-deleting-from-a-list.md +9 -7
  65. package/docs/tutorials/076-updating-a-list-item.md +11 -10
  66. package/docs/tutorials/080-widget-requests.md +71 -43
  67. package/docs/tutorials/090-using-widget-libraries.md +22 -22
  68. package/package.json +1 -1
  69. package/skills/loadbare-app/SKILL.md +178 -111
  70. package/skills/loadbare-app/references/TECHREF-1.0.md +893 -558
  71. package/skills/loadbare-app/references/builder.md +9 -3
  72. package/skills/loadbare-app/references/chrome.md +107 -56
  73. package/skills/loadbare-app/references/custom-elements.md +199 -173
  74. package/skills/loadbare-app/references/data-binding.md +375 -370
  75. package/skills/loadbare-app/references/overview.md +12 -10
  76. package/skills/loadbare-app/references/page-files.md +161 -86
  77. package/skills/loadbare-app/references/server.md +13 -7
  78. package/skills/loadbare-app/references/widgets.md +104 -110
  79. package/docs/analysis-accidental-complexity.md +0 -149
  80. package/docs/analysis-closed-set.md +0 -210
package/docs/testing.md CHANGED
@@ -49,8 +49,8 @@ application override a built-in.
49
49
  ## The console is an interface
50
50
 
51
51
  The browser half of Loadbare App reports every failure it survives through
52
- `console.error` and `console.warn` — a query with no scope, a list widget
53
- with no template, a change event with no coordinates. These are not
52
+ `console.error` and `console.warn` — a query nothing names, a row with no
53
+ key column, a request missing what it needs. These are not
54
54
  diagnostics. They are the framework's entire error channel on the client, and
55
55
  the behavior under test is frequently *that Loadbare reported and carried on*
56
56
  rather than that it produced a value.
@@ -71,11 +71,23 @@ the most of what Loadbare promises, so it is first.
71
71
  - `exp-foo` supplied to a definition with no `{{foo}}`
72
72
  - `{{camelCase}}` in a definition, which no tag could ever supply
73
73
  - a cycle in the definition graph
74
- - two `lb-template` destinations with one name
74
+ - two `lb-exp-template` destinations with one name
75
75
  - two authored templates for one destination
76
76
  - a template naming a destination the definition does not have
77
- - content written inside a definition with no `lb-slot`
78
- - more than one `lb-slot`
77
+ - content written inside a definition with no `lb-exp-slot`
78
+ - more than one `lb-exp-slot`
79
+
80
+ **Markup checks.** The assembled chrome and every page are checked before
81
+ anything ships:
82
+
83
+ - an `lb-` attribute that is not one the developer writes
84
+ - `lb-request="lb-…"` naming none of the requests Loadbare provides
85
+ - `lb-url-link` off an `<a>`, `lb-url-push` without `lb-request`, and
86
+ `lb-url-unknown` off a `<dialog>` or outside `<lb-hub>`
87
+ - `lb-show` on a `<template>`, on a row template's root, or with no row
88
+ around it
89
+
90
+ A page file's `<title>` is lifted out and stamped as `lb-page-title`.
79
91
 
80
92
  **Substitution.** An unset placeholder drops the attribute rather than
81
93
  emitting it empty, which is what HTML's boolean attributes require and what
@@ -113,48 +125,59 @@ needs a browser either.
113
125
  `server/lb-server.ts` opens a socket, so the fixtures are counting stubs.
114
126
 
115
127
  - `onPageEnter` runs before any query, and the whole query set runs after it
116
- - an unknown page answers `{}` and says so
128
+ - an unknown page answers `[]` and says so
129
+ - every answer goes out as a response item carrying the kind and key its
130
+ query declared; an answer of the wrong kind is left out and reported
117
131
  - an unknown query name in a refresh set is skipped, and its siblings run
118
- - an action the page did not declare is refused — the rule that keeps the
119
- wire from reaching anything the page has not published
120
- - the refresh set runs after the action, against the same context
121
- - **what the action stated wins over what the refresh produced**, because
132
+ - a declared request the page did not declare under `handlers`, and a row
133
+ request with no `crud` entry for its query, are refused — the rule that
134
+ keeps the wire from reaching anything the page has not published
135
+ - a reserved name declared as a query or a handler is refused at startup
136
+ - the refresh set runs after the handler, against the same context
137
+ - what the handler stated wins over what the refresh produced, because
122
138
  that is how a `patch` reaches the browser at all, and it is one spread
123
139
  operator away from silently reversing
140
+ - a handler returning `url()` answers with the `lb-url` item alone, and runs
141
+ no refresh set
124
142
 
125
143
  `tests/lb-express.test.ts` runs against a listening app: the page comes from
126
- the query string, an undeclared operation is refused with a 400, and a
144
+ the path, the query parms reach `contextFor`, a `url()` answer is followed by
145
+ the page loaded at the new URL, an undeclared request is refused, and a
127
146
  malformed body does not throw.
128
147
 
129
148
  ## Tier 3 — Landing
130
149
 
131
- `applyData`, `applyRow` and `applyList` are the most intricate code in the
132
- framework and the most likely to break in ways nobody notices. They are also
133
- pure DOM: no fetch, no widget upgrade, no history. jsdom is real evidence
134
- here.
135
-
136
- - a native element receives its value as text, a hyphenated one as `lb-value`
137
- - the root of a scope counts as a cell if it carries one, which is what makes
150
+ `applyResponse`, `applyRow` and `applyRows` are the most intricate code in
151
+ the framework and the most likely to break in ways nobody notices. They are
152
+ also pure DOM: no fetch, no custom element upgrade, no history. jsdom is real
153
+ evidence here.
154
+
155
+ - a control receives its value as `value`, any other native element as text,
156
+ a custom element that is not a control as `lb-column-value` alone, and
157
+ every one carries `lb-column-value`
158
+ - the four combinations of kind and row template: a `row` with no row
159
+ template lands on the element itself and stamps its `lb-key-value`
160
+ - a live row's root counts as a column if it carries one, which is what makes
138
161
  an `<option>` row possible
139
- - an element carrying both a scope and `lb-cell` is a cell of the row around
140
- it, and the cells inside it are its own scope's
141
- - a query with no scope is reported and skipped; several scopes for one query
142
- are all filled; a set of rows landing on a scope bound with `lb-row`
143
- is reported rather than thrown
144
- - `rows` decides membership and order, so a key that did not arrive is gone
145
- - `patch` disturbs only what it names, in contents and in position
146
- - `lb-row-count` is counted from the DOM after reconciliation, so a set and a
147
- patch ending in the same state report the same number
148
- - the `place` callback is called for a fresh row always, and for an existing
149
- row only under `rows`. That is today's behavior, not a decision — a patch
150
- therefore never re-places a row whose sort key changed. The test states what
151
- is true now, and is the one that flips if that changes.
152
-
153
- **Two properties**, written as loops rather than with a library. Applying the
154
- same set twice is applying it once. And a set reached through any
155
- sequence of patches is that set reached from empty. Convergence is the
156
- actual contract of a reconciler, and those two say it better than twenty
157
- examples.
162
+ - an element carrying both `lb-query` and `lb-column` takes its column from
163
+ the row around it, and the columns inside it are its own query's
164
+ - a query nothing names is reported and skipped; several elements naming one
165
+ query are all filled
166
+ - all rows decide membership and order, so a key that did not arrive is gone
167
+ - a patch disturbs only what it names, in contents and in position
168
+ - `lb-query-row-count` is counted from the DOM after reconciliation, so all
169
+ rows and a patch ending in the same state report the same number
170
+ - `lbPlaceRow` is called for a fresh row always, and for an existing row
171
+ only under all rows. That is today's behavior, not a decision — a patch
172
+ therefore never re-places a row whose sort key changed. The test states
173
+ what is true now, and is the one that flips if that changes.
174
+ - `lb-show` moves an element into a template and back, and landing reaches
175
+ inside it
176
+
177
+ Two properties, written as loops rather than with a library. Applying the
178
+ same rows twice is applying them once. And rows reached through any sequence
179
+ of patches are those rows reached from empty. Convergence is the actual
180
+ contract of a reconciler, and those two say it better than twenty examples.
158
181
 
159
182
  ## Tier 4 — The hub
160
183
 
@@ -179,41 +202,47 @@ help.
179
202
 
180
203
  What the hub is tested for here:
181
204
 
182
- - `requestFor` builds each of the three operations a native element can
183
- send, and refuses a reserved name it cannot turn into one
184
- - a click finds the nearest `lb-action`, and skips a hyphenated tag and a
185
- form, both of which own the interaction themselves
186
- - a submit gathers the form's cells, and reports a cell with no control
187
- - an insert or update gathers the nearest form, `<tr>` or live row inside
188
- its scope, whatever shares the button's cell: one ghost row among its
189
- neighbours, a whole live row, a form nested in a row, a button's form
190
- owner; and refuses a `<div>` holding cells, pointing to `<form>`
191
- - an insert or update from an element carrying `lb-cell` sends that cell
192
- alone, from the value a widget sent or else its control; refuses a value
193
- with no `lb-cell` to name its column; and refuses a click on a native
194
- element that is a cell
195
- - gathering skips the cells of a nested scope, so a picker in a row sends
196
- its own cell and nothing about its options
197
- - a request arriving with no action, or with a reserved name that is not one
198
- of the three, is refused before it reaches the wire
199
- - `lb-pending` lands on the element that dispatched, `lb-error` replaces it
200
- on failure, and the next request clears it
201
- - `aria-busy` comes and goes with `lb-pending`; a native button or form
202
- performed again while pending sends nothing, and a widget still sends
203
- - a path with no page host reports and opens the unknown-page dialog; two
204
- hosts for one name report and take the first
205
- - `lb-navigation` lands with the path and the label of the link that names it
206
- - `lb-nav-link` pushes state and swaps the host, `popstate` reverses it, and
207
- an ordinary anchor is left alone
205
+ - an element commits on its own event: a form on `submit`, a control on
206
+ `change`, anything else on `click`; a submit button with a form owner
207
+ never commits on click, and the submitter's `lb-request` wins over the
208
+ form's
209
+ - gathering follows the order: an element with `lb-column` alone, a
210
+ `<form>`'s controls, a live row's controls, the form owner's controls,
211
+ including controls joined by the `form` attribute and form-associated
212
+ custom elements
213
+ - gathering skips the controls of a nested `lb-query`, so a picker in a row
214
+ sends its own column and nothing about its options
215
+ - the request goes to the nearest ancestor `lb-query` of what was gathered,
216
+ and takes `key` from the nearest ancestor row of that query
217
+ - `lb-row-insert`, `lb-row-update` and `lb-row-delete` missing what they
218
+ need are not sent, and a reserved name that is not one of the three is
219
+ refused before it reaches the wire
220
+ - an ancestor may stop the `lb-request` event, and a request is complete
221
+ before an ancestor sees it
222
+ - `lb-request-pending` lands on the element that committed,
223
+ `lb-request-error` replaces it on failure, and the next request clears
224
+ it; `aria-busy` comes and goes with `lb-request-pending`, and a commit on
225
+ a pending element is ignored
226
+ - a successful insert gathered from a form resets the form
227
+ - a path with no page reports, lands `lb-page-unknown`, and opens the
228
+ `lb-url-unknown` dialog; two pages for one stub report and take the first
229
+ - `lb-url` lands with the path, the page's `lb-page-title` or stub as
230
+ `lb-page-label`, and a column per query parm, and sets the document title
231
+ - `lb-url-link` pushes state and swaps the page on a plain primary click,
232
+ `popstate` reverses it, and an ordinary anchor is left alone
233
+ - a control inside `lb-query="lb-url"` writes its parm, replacing the history
234
+ entry or pushing one with `lb-url-push`, and the page's queries reload
235
+ - an `lb-url` item in a server answer writes the URL and lands the page
236
+ loaded there
208
237
  - `hidden` comes off the body once a page has landed, including the page
209
238
  that failed to load
210
239
 
211
240
  Widgets are small and their logic is local, so jsdom carries them: a value
212
- reaches the control the widget owns, a change dispatches the declared action,
213
- an absent control or absent action is reported rather than thrown,
214
- `lb-options` turns a key into a value and removes an emptied `<optgroup>`,
215
- `lb-table` groups and sorts, removes an emptied section heading, and takes
216
- its `colSpan` from the row template the page wrote.
241
+ reaches the control the widget owns, the widget is a form-associated control
242
+ the hub commits and gathers, `lb-options` turns a key into a value and
243
+ removes an emptied `<optgroup>`, `lb-table` groups and sorts, removes an
244
+ emptied section heading, and takes its `colSpan` from the row template the
245
+ page wrote.
217
246
 
218
247
  A widget extends `HTMLElement` and registers itself as its module loads, so
219
248
  unlike tier 3 it needs a window before the module exists. `installWindow()`
package/docs/theory.md CHANGED
@@ -425,33 +425,45 @@ database, but it does force server code to return data in the shapes of
425
425
  rows and sets of rows.
426
426
 
427
427
  That scoping decision allowed us to reduce the data binding vocabulary
428
- to four foundations:
429
-
430
- | Attribute | Relational idea | HTML precedent |
431
- | --------- | --------------- | -------------------------------------------------- |
432
- | `lb-list` | A set of rows | `<select>` or `<ul>`, a container of its items |
433
- | `lb-row` | A single row | `<form>`, one record of fields |
434
- | `lb-cell` | A column | `name` on a form control, which field this is |
435
- | `lb-key` | The primary key | `value` on an `<option>`, identity apart from text |
428
+ to one idea: everything is a query. A query is a name for rows, of kind
429
+ `row` or `rows`, with a key. The server declares all three facts about a
430
+ query, so the markup names the query and the columns it shows, and
431
+ repeats nothing the server already said. Three attributes carry the whole
432
+ of it:
433
+
434
+ | Attribute | Relational idea | HTML precedent |
435
+ | ----------- | -------------------------- | ---------------------------------------------- |
436
+ | `lb-query` | A query, one row or many | `<select>` or `<form>`, a container of records |
437
+ | `lb-column` | A column | `name` on a form control, which field this is |
438
+ | `lb-show` | A column deciding presence | `hidden`, whether an element is shown |
439
+
440
+ The key never appears in the markup. The server declares it with the
441
+ query, `rows("id", run)`, and the hub stamps each live row with its
442
+ value as `lb-key-value`, the way an `<option>` carries a `value` apart
443
+ from its text.
436
444
 
437
445
  A highly simplified page showing
438
446
  an invoice and its lines looks like this:
439
447
 
440
448
  ```html
441
- <section lb-row="invoice">
442
- <h2 lb-cell="number"></h2>
443
- <span lb-cell="customer"></span>
449
+ <section lb-query="invoice">
450
+ <h2 lb-column="number"></h2>
451
+ <span lb-column="customer"></span>
444
452
  </section>
445
453
 
446
454
  <table>
447
- <tbody lb-list="invoiceLines">
448
- <template lb-key="id">
449
- <tr><td lb-cell="item"></td><td lb-cell="amount"></td></tr>
455
+ <tbody lb-query="invoiceLines">
456
+ <template>
457
+ <tr><td lb-column="item"></td><td lb-column="amount"></td></tr>
450
458
  </template>
451
459
  </tbody>
452
460
  </table>
453
461
  ```
454
462
 
463
+ `invoice` is of kind `row`, and with no row template inside it the row
464
+ lands on the `<section>` itself. `invoiceLines` is of kind `rows`, and
465
+ the hub clones the row template once per row.
466
+
455
467
  This decision proved decisive in keeping the browser code robust,
456
468
  expressive, and performant.
457
469
 
@@ -465,45 +477,58 @@ The idea is that user interaction triggers framework browser code
465
477
  that "knows what to do", assembling a request driven purely from
466
478
  attributes.
467
479
 
468
- This requirement is satisfied with a single new attribute, `lb-action`.
469
- Three values are reserved, and any other value is interpreted as the
470
- name of a routine on the server.
480
+ This requirement is satisfied with a single new attribute, `lb-request`.
481
+ Three request names are Loadbare's: `lb-row-insert`, `lb-row-update` and
482
+ `lb-row-delete`. Any other value names a request the page declares on
483
+ the server.
471
484
 
472
- A request is an action and a position. The author writes the action,
473
- and the hub supplies the position from the document, using the same
474
- ancestor rule that decides where a value lands. To add a delete
475
- button to the invoice lines from the previous section, the template
476
- gains one element:
485
+ A request is a name and a position. The author writes the name, and
486
+ the hub supplies the position from the document, using the same
487
+ ancestor rule that decides where a value lands. An element commits the
488
+ way HTML already says it does: a form on `submit`, a control on
489
+ `change`, anything else on `click`. To add a delete button to the
490
+ invoice lines from the previous section, the row template gains one
491
+ element:
477
492
 
478
493
  ```html
479
- <template lb-key="id">
494
+ <template>
480
495
  <tr>
481
- <td lb-cell="item"></td>
482
- <td lb-cell="amount"></td>
483
- <td><button lb-action="lb-row-delete">Remove</button></td>
496
+ <td lb-column="item"></td>
497
+ <td lb-column="amount"></td>
498
+ <td><button lb-request="lb-row-delete">Remove</button></td>
484
499
  </tr>
485
500
  </template>
486
501
  ```
487
502
 
488
- The button names neither the list nor the key. When it is clicked,
489
- the hub reads `invoiceLines` from the nearest list scope and the key
490
- from the live row around the button, and sends:
503
+ The button names neither the query nor the key. When it is clicked,
504
+ the hub reads `invoiceLines` from the nearest ancestor `lb-query` and
505
+ the key from the live row around the button, and sends:
491
506
 
492
507
  ```
493
- { action: "lb-row-delete", list: "invoiceLines", key: "42" }
508
+ { name: "lb-row-delete", query: "invoiceLines", key: "42" }
494
509
  ```
495
510
 
511
+ Every request has that one shape: a name, and `query`, `key` and
512
+ `values` as present. An insert or update gathers `values` from the
513
+ controls around the element that committed: a form's controls, a live
514
+ row's, or the controls whose form owner is the element's.
515
+
496
516
  But we need to be more modern than that. Nowadays we expect the
497
517
  button to be disabled after it is clicked, and to display a wait
498
518
  state in case the network is congested. In other words, the
499
519
  framework browser code needs to notify the event target of its
500
520
  request state.
501
521
 
502
- To do this, framework browser code writes three attributes about a request, `lb-pending`,
503
- `lb-error` and `lb-row-count`. The framework browser code ignores
504
- a button or form performed again while stamped with `lb-pending`,
505
- meaning the disabled state can be represented entirely by CSS, no
506
- JavaScript needed.
522
+ To do this, framework browser code stamps two attributes about a
523
+ request, `lb-request-pending` and `lb-request-error`, and a third about
524
+ a query, `lb-query-row-count`. The framework browser code ignores a
525
+ commit on an element stamped with `lb-request-pending`, meaning the
526
+ disabled state can be represented entirely by CSS, no JavaScript
527
+ needed.
528
+
529
+ The developer writes `lb-*` attributes in markup, and script never
530
+ assigns them. The hub and the builder write only their stamps. That
531
+ rule is what lets the builder check the markup once, at build time.
507
532
 
508
533
  Over many iterations I gradually reduced the core attributes to
509
534
  these, from a larger set that had more overlapping behaviors and edge
@@ -524,24 +549,30 @@ chrome and ships it as `app.html`. With all pages already present
524
549
  in the browser, providing an SPA is fairly simple.
525
550
 
526
551
  The hub catches anchor clicks, and checks if the anchor contains the
527
- attribute `lb-nav-link`. If so, the hub interprets it as in-app
528
- navigation, swaps the anchor's `href` into `<main>`, and sends a request
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.
552
+ attribute `lb-url-link`. If so, the hub interprets a plain primary
553
+ click as in-app navigation, shows the page the `href` names in
554
+ `<main>`, and sends a request to the server for the page data. The
555
+ query string rides along, so the server always knows what URL the
556
+ browser is showing.
531
557
 
532
558
  Anchors without the attribute behave as normal links.
533
559
 
534
560
  #### Synthetic Context
535
561
 
536
562
  Synthetic context emerged as a useful feature near the very end of
537
- development. Realizing that the hub knows about the current
538
- navigation state, we had it publish a syntheic query result,
539
- `lb-navigation`, that contains the URI and, if it can find it,
540
- the anchor text for any link to that URI.
541
-
542
- These can be bound to any HTML, same as any other query sent by
543
- the application, but the resulting values are always supplied by
544
- the hub.
563
+ development. Realizing that the hub knows about the current URL, we
564
+ had it serve a query of its own, `lb-url`, of kind `row` and keyed by
565
+ `lb-path`. Its columns are the path, the page's title as
566
+ `lb-page-label`, whether the path names no page as `lb-page-unknown`,
567
+ and one column per query parm.
568
+
569
+ These can be bound to any HTML, same as any other query the
570
+ application declares, but the hub supplies the row. The hub also
571
+ answers requests for it: a control writing a query parm is
572
+ `<input lb-column="q" lb-request="lb-row-update">` inside
573
+ `lb-query="lb-url"`, an ordinary row update that the hub answers by
574
+ writing the URL and reloading the page's queries. A handler on the
575
+ server moves the URL the same way, by returning `url({ q: "5" })`.
545
576
 
546
577
  There may be more synthetic context in the future.
547
578
 
@@ -562,8 +593,10 @@ files that share a name: `invoice.page.html`, `invoice.queries.ts`
562
593
  and `invoice.requests.ts`. The HTML is the page as the user sees it,
563
594
  the queries provide the data, and the requests are what it allows.
564
595
 
565
- Each query is declared as a row or a list, the same two shapes the
566
- HTML binds to. Each request declares the work it does and the
596
+ Each query is declared with `row(key, run)` or `rows(key, run)`,
597
+ naming its kind and its key. Every response carries response items,
598
+ each naming its query, kind and key beside the row, the rows, or a
599
+ patch. Each request declares the work it does and the
567
600
  queries to run again afterward. Here is the delete button from
568
601
  Requests and Request State, as the server sees it:
569
602
 
@@ -575,9 +608,9 @@ export const requests: Requests = {
575
608
  crud: {
576
609
  invoiceLines: {
577
610
  rowDelete: {
578
- run: async (ctx, where) => {
579
- await ctx.db.deleteLine(where.key);
580
- return { invoiceLines: patch({ drop: [where.key] }) };
611
+ run: async (ctx, { key }) => {
612
+ await ctx.db.deleteLine(key);
613
+ return { invoiceLines: patch({ drop: [key] }) };
581
614
  },
582
615
  refresh: ["invoice"],
583
616
  },
@@ -587,14 +620,15 @@ export const requests: Requests = {
587
620
  ```
588
621
 
589
622
  Deleting a line changes the invoice total, so the entry refreshes
590
- `invoice`. The list itself does not need to be queried again, since
623
+ `invoice`. `invoiceLines` itself does not need to be queried again, since
591
624
  the only change is one row that went away, and `run` says so with a
592
- patch. The response carries both: the refreshed invoice row, and the
593
- patch that removes one line.
625
+ patch. The response carries two response items: the refreshed invoice
626
+ row, and the patch that removes one line.
594
627
 
595
628
  Loadbare, as it turns out, does not have "endpoints" as such. If the
596
- HTML scopes an `lb-row-delete` to `lb-list="invoice_lines"`, then the
597
- server carries a CRUD entry `rowDelete` under object invoiceLines.
629
+ HTML places an `lb-row-delete` inside `lb-query="invoiceLines"`, then the
630
+ server carries a CRUD entry `rowDelete` under object invoiceLines. A
631
+ declared request is the same, keyed by its name under `handlers`.
598
632
  Since the browser and server exist to talk to each other in the same
599
633
  language, no additional abstraction is needed.
600
634
 
@@ -727,7 +761,7 @@ has been made to make them "set and forget".
727
761
 
728
762
  Second, Loadbare is missing entire categories of developer effort,
729
763
  while providing expected benefits. HTML includes without imports,
730
- declarative browser data binding and actions, free tree shaking,
764
+ declarative browser data binding and requests, free tree shaking,
731
765
  extendable with custom elements, a small set of mechanisms, and
732
766
  nothing to learn about the "the Loadbare way for CSS", among many
733
767
  others.
@@ -21,21 +21,28 @@ a page specified, Loadbare displays `index.page.html`.
21
21
 
22
22
  ```html
23
23
  <!-- src/pages/index.page.html -->
24
+ <title>Home</title>
24
25
  <h1>Home</h1>
25
26
  <p>This is the home page.</p>
26
27
  ```
27
28
 
28
29
  ```html
29
30
  <!-- src/pages/about.page.html -->
31
+ <title>About</title>
30
32
  <h1>About</h1>
31
33
  <p>This is the about page.</p>
32
34
  ```
33
35
 
36
+ A page file's `<title>` names the page. The builder removes it from the
37
+ page and keeps its text, and the hub sets the document title to it whenever
38
+ the page is showing. A page without a `<title>` is named by its stub, the
39
+ filename less `.page.html`.
40
+
34
41
  ## Linking between them
35
42
 
36
43
  Add a `<nav>` to the chrome, with one anchor per page.
37
44
 
38
- Also add the `<dialog lb-unknown-page>` element, so the hub `<lb-hub>` can
45
+ Also add a `<dialog lb-url-unknown>` element, so the hub `<lb-hub>` can
39
46
  display something to the user if they type a URL that has no matching page.
40
47
 
41
48
  ```html
@@ -52,32 +59,33 @@ display something to the user if they type a URL that has no matching page.
52
59
  <lb-hub>
53
60
  <nav>
54
61
  <!-- a bare link to "/" goes to page "index" -->
55
- <a href="/" lb-nav-link>Home</a>
56
- <a href="/about" lb-nav-link>About</a>
62
+ <a href="/" lb-url-link>Home</a>
63
+ <a href="/about" lb-url-link>About</a>
57
64
  </nav>
58
65
  <main></main>
59
- <dialog lb-unknown-page lb-row="lb-navigation">
60
- The URL <span lb-cell="page-uri"></span> is not in this app.
66
+ <dialog lb-url-unknown lb-query="lb-url">
67
+ The URL <span lb-column="lb-path"></span> is not in this app.
61
68
  </dialog>
62
69
  </lb-hub>
63
70
  </body>
64
71
  </html>
65
72
  ```
66
73
 
67
- Here we introduce the attribute `lb-nav-link`, which indicates the link
74
+ Here we introduce the attribute `lb-url-link`, which indicates the link
68
75
  should navigate within the app. When the user clicks, the hub `<lb-hub>`
69
76
  intercepts the event and swaps the page's HTML into the `<main>` element.
70
77
  The hub ensures the back button works by calling `history.pushState` on
71
78
  navigation, and catching the browser's `popstate` event.
72
79
 
73
- An anchor without `lb-nav-link` is left alone and behaves like an ordinary
74
- link.
80
+ An anchor without `lb-url-link` is left alone and behaves like an ordinary
81
+ link, and so is a click with a modifier key or a middle click, which opens
82
+ a new tab as usual.
75
83
 
76
- If a `<dialog lb-unknown-page>` element is present in the HTML, it will be
84
+ If a `<dialog lb-url-unknown>` element is present in the HTML, it will be
77
85
  displayed to the user when a URL is entered that has no matching page in the app.
78
- The `lb-row` and `lb-cell` attributes will be explained when we get to data
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 and query string that were
86
+ The `lb-query` and `lb-column` attributes will be explained when we get to data
87
+ binding; for now, know that `lb-url` is a query the hub itself serves, one
88
+ row describing the URL, and its column `lb-path` is the path that was
81
89
  asked for. The
82
90
  widget library ships this dialog ready-made as `<lb-unknown-page>`, which
83
91
  [Using Widget Libraries](./090-using-widget-libraries.md) covers.
@@ -42,6 +42,7 @@ Use the class in the page:
42
42
 
43
43
  ```html
44
44
  <!-- src/pages/about.page.html -->
45
+ <title>About</title>
45
46
  <h1>About</h1>
46
47
  <p class="about-note">This is the about page.</p>
47
48
  ```
@@ -65,11 +66,13 @@ Add the stylesheet link to the chrome:
65
66
  <body hidden>
66
67
  <lb-hub>
67
68
  <nav>
68
- <a href="/" lb-nav-link>Home</a>
69
- <a href="/about" lb-nav-link>About</a>
69
+ <a href="/" lb-url-link>Home</a>
70
+ <a href="/about" lb-url-link>About</a>
70
71
  </nav>
71
72
  <main></main>
72
- <dialog lb-unknown-page>The page <span lb-cell="page"></span> is not in this app.</dialog>
73
+ <dialog lb-url-unknown lb-query="lb-url">
74
+ The URL <span lb-column="lb-path"></span> is not in this app.
75
+ </dialog>
73
76
  </lb-hub>
74
77
  </body>
75
78
  </html>
@@ -33,14 +33,16 @@ element, not yet defined:
33
33
  <lb-hub>
34
34
  <!-- replace this from the previous tutorial...
35
35
  <nav>
36
- <a href="/" lb-nav-link>Home</a>
37
- <a href="/about" lb-nav-link>About</a>
36
+ <a href="/" lb-url-link>Home</a>
37
+ <a href="/about" lb-url-link>About</a>
38
38
  </nav>
39
39
  -->
40
40
  <!-- ...with this empty custom element: -->
41
41
  <app-nav></app-nav>
42
42
  <main></main>
43
- <dialog lb-unknown-page>The page <span lb-cell="page"></span> is not in this app.</dialog>
43
+ <dialog lb-url-unknown lb-query="lb-url">
44
+ The URL <span lb-column="lb-path"></span> is not in this app.
45
+ </dialog>
44
46
  </lb-hub>
45
47
  </body>
46
48
  </html>
@@ -51,8 +53,8 @@ of `<app-nav>` by writing `src/app-nav.html`.
51
53
 
52
54
  ```html
53
55
  <nav>
54
- <a href="/" lb-nav-link>Home</a>
55
- <a href="/about" lb-nav-link>About</a>
56
+ <a href="/" lb-url-link>Home</a>
57
+ <a href="/about" lb-url-link>About</a>
56
58
  </nav>
57
59
  ```
58
60
 
@@ -62,8 +64,8 @@ into the `<app-nav>` custom element, so the actual result will look like this:
62
64
  ```html
63
65
  <app-nav>
64
66
  <nav>
65
- <a href="/" lb-nav-link>Home</a>
66
- <a href="/about" lb-nav-link>About</a>
67
+ <a href="/" lb-url-link>Home</a>
68
+ <a href="/about" lb-url-link>About</a>
67
69
  </nav>
68
70
  </app-nav>
69
71
  ```