@loadbare/app 0.18.1 → 0.19.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/README.md +17 -17
  2. package/dist/build/assemble.d.ts +7 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +81 -11
  5. package/dist/build/assemble.js.map +1 -1
  6. package/dist/build/cli.d.ts +3 -0
  7. package/dist/build/cli.d.ts.map +1 -1
  8. package/dist/build/cli.js +6 -1
  9. package/dist/build/cli.js.map +1 -1
  10. package/dist/build/declared.d.ts +25 -0
  11. package/dist/build/declared.d.ts.map +1 -0
  12. package/dist/build/declared.js +62 -0
  13. package/dist/build/declared.js.map +1 -0
  14. package/dist/build/pages.d.ts +6 -0
  15. package/dist/build/pages.d.ts.map +1 -1
  16. package/dist/build/pages.js +32 -12
  17. package/dist/build/pages.js.map +1 -1
  18. package/dist/core/lb-types.d.ts +6 -3
  19. package/dist/core/lb-types.d.ts.map +1 -1
  20. package/dist/core/lb-types.js.map +1 -1
  21. package/dist/hub/lb-apply.d.ts.map +1 -1
  22. package/dist/hub/lb-apply.js +10 -3
  23. package/dist/hub/lb-apply.js.map +1 -1
  24. package/dist/server/lb-server.d.ts +5 -2
  25. package/dist/server/lb-server.d.ts.map +1 -1
  26. package/dist/server/lb-server.js +12 -3
  27. package/dist/server/lb-server.js.map +1 -1
  28. package/docs/TECHREF-1.0.md +33 -85
  29. package/docs/comparison.md +695 -482
  30. package/docs/departures.md +63 -0
  31. package/docs/miswarts.md +140 -0
  32. package/docs/opinions.md +155 -0
  33. package/docs/reference/builder.md +9 -2
  34. package/docs/reference/chrome.md +2 -1
  35. package/docs/reference/css.md +163 -0
  36. package/docs/reference/custom-elements.md +1 -1
  37. package/docs/reference/data-binding.md +44 -10
  38. package/docs/reference/page-files.md +38 -5
  39. package/docs/reference/widgets.md +24 -2
  40. package/docs/roadmap.md +55 -43
  41. package/docs/testing.md +62 -28
  42. package/docs/theory.md +348 -108
  43. package/package.json +1 -1
  44. package/skills/loadbare-app/SKILL.md +16 -9
  45. package/skills/loadbare-app/references/TECHREF-1.0.md +33 -85
  46. package/skills/loadbare-app/references/builder.md +9 -2
  47. package/skills/loadbare-app/references/chrome.md +2 -1
  48. package/skills/loadbare-app/references/css.md +163 -0
  49. package/skills/loadbare-app/references/custom-elements.md +1 -1
  50. package/skills/loadbare-app/references/data-binding.md +44 -10
  51. package/skills/loadbare-app/references/miswarts.md +140 -0
  52. package/skills/loadbare-app/references/page-files.md +38 -5
  53. package/skills/loadbare-app/references/widgets.md +24 -2
  54. package/docs/possible-ideas.md +0 -314
  55. package/docs/theory-old.md +0 -839
  56. package/docs/what-does-loadbare-extend.md +0 -124
@@ -2,7 +2,7 @@
2
2
 
3
3
  A page shows server data by naming queries and columns in its HTML, and asks
4
4
  the server to change that data by naming requests. The developer writes
5
- nine `lb-` attributes. The server declares every query a page may show and
5
+ sixteen `lb-` attributes. The server declares every query a page may show and
6
6
  every request it may send, and refuses anything it has not declared.
7
7
 
8
8
  Everything is a query. A query is a name for rows, of kind `row` or `rows`,
@@ -48,6 +48,10 @@ each with its key, and permits the three requests on `roster`; see
48
48
  | `lb-show` | Any element but `<template>` | The column that decides whether it is present |
49
49
  | `lb-order` | An element with `lb-query` | The choice holding the user's order; see [Order](#order) |
50
50
  | `lb-filter` | An element with `lb-query` | The columns a choice filters; see [Filters](#filters) |
51
+ | `lb-group` | A `<template>` | Nothing: it marks a group template; see [Groups](#groups) |
52
+ | `lb-count` | Any element | Nothing: it shows a count of rows; see [Aggregates](#aggregates) |
53
+ | `lb-sum`, `lb-avg`, `lb-min`, `lb-max` | Any element | The column aggregated; see [Aggregates](#aggregates) |
54
+ | `lb-select` | A checkbox in a row template | Nothing: it selects its live row; see [Selecting rows](#selecting-rows) |
51
55
  | `lb-request` | Any element | The request it sends when it commits |
52
56
  | `lb-url-link` | An `<a>` | A link between pages; see [chrome](./chrome.md#links) |
53
57
  | `lb-url-push` | An element with `lb-request` | A URL change that pushes a history entry |
@@ -62,8 +66,14 @@ builder writes a stamp, and a stylesheet or a custom element reads it.
62
66
  | `lb-key-value` | The key of a live row, or of a `row` landed on an element |
63
67
  | `lb-row-live` | The element is a live row |
64
68
  | `lb-query-row-count` | The number of live rows an element shows |
69
+ | `lb-group-live`, `lb-group-column`, `lb-group-value` | A group, the column it breaks on, and its value; see [Groups](#groups) |
70
+ | `lb-row-created`, `lb-row-changed`, `lb-row-moved`, `lb-row-requested` | What happened to a row; see [What happened to a row](#what-happened-to-a-row) |
71
+ | `lb-row-leaving`, `lb-group-leaving` | A row or group on its way out |
72
+ | `lb-row-editing` | The row a form is pointed at; see [Editing a row in one form](#editing-a-row-in-one-form) |
73
+ | `lb-row-selected` | A row whose `lb-select` is ticked |
65
74
  | `lb-request-pending` | The element's request is in flight |
66
75
  | `lb-request-error` | The element's last request failed |
76
+ | `lb-request-refused` | The handler refused it; see [Refusals](#refusals) |
67
77
  | `lb-page` | The stub of a page, on its `<template>` |
68
78
  | `lb-page-title` | The text of a page file's `<title>` |
69
79
 
@@ -95,6 +105,10 @@ not `lb-row-live`, so a `row` shown inside another query, such as a total in
95
105
  a table's foot, is not one of that query's rows. Select live rows with
96
106
  `[lb-row-live]`, never with `[lb-key-value]`.
97
107
 
108
+ A `row` query answering `null` found no row. The hub lands every column the
109
+ element names as null and removes its `lb-key-value`; under a row template,
110
+ the live row leaves.
111
+
98
112
  Write `lb-column` on each element that shows a column. The element reads
99
113
  from its nearest ancestor row: the live row it is in, or the element a
100
114
  `row` landed on. A nested `lb-query` begins a new query, and what is inside
@@ -139,12 +153,16 @@ from its element file, and renders `lb-column-value` itself; see
139
153
  A file input receives nothing, and the hub reports it to the console.
140
154
 
141
155
  The hub hands every value to the browser as the server sent it. What a
142
- number, a date or a null looks like is decided in the query.
156
+ number, a date or a null looks like is decided in the query, or by a custom
157
+ element that formats it, such as
158
+ [`lb-number` and `lb-date`](./widgets.md#lb-number-and-lb-date). Send a
159
+ column the user sorts or filters raw, and format it in the element.
143
160
 
144
161
  ### Checkboxes
145
162
 
146
163
  A checkbox is a control. It is checked when its column is anything but
147
- `false` or null, and it sends `"true"` or `"false"`:
164
+ `false` or null, and it sends `"true"` or `"false"`
165
+ ([miswart](../miswarts.md#checkboxes-and-radio-groups-always-gather-a-value)):
148
166
 
149
167
  ```html
150
168
  <input type="checkbox" lb-column="active" lb-request="lb-row-update" />
@@ -169,8 +187,9 @@ when none is:
169
187
 
170
188
  In a row template, the hub stamps each radio button's `name` with the
171
189
  `name` written, or its column when none is, followed by `-` and the live
172
- row's key, so each live row holds a group of its own. Rows not yet entered
173
- share the empty key, and so share one group.
190
+ row's key, so each live row holds a group of its own
191
+ ([miswart](../miswarts.md#radio-button-names-in-a-row-template)). Rows not yet
192
+ entered share the empty key, and so share one group.
174
193
 
175
194
  A radio button in a query's row template answers to that query's row, as
176
195
  any element there does. A choice among a query's rows is better made with
@@ -273,7 +292,8 @@ its rows the user may filter, separated by commas. The column of
273
292
  ```
274
293
 
275
294
  A row that does not pass every filter is `hidden`, and a ticked `lb-select`
276
- in it is cleared. A group whose rows are all hidden is hidden too. An empty
295
+ in it is cleared. A group whose rows are all hidden is hidden too
296
+ ([miswart](../miswarts.md#rows-hidden-by-a-filter)). An empty
277
297
  choice filters nothing.
278
298
 
279
299
  A filter is one or more terms, each an operator followed by a value:
@@ -364,8 +384,9 @@ Every change to a list is one a stylesheet can see:
364
384
  | `lb-row-leaving` | A row on its way out |
365
385
  | `lb-group-leaving` | A group on its way out |
366
386
 
367
- A row that leaves loses `lb-row-live`, takes `inert`, and is removed once
368
- its animations finish, at once when there are none. `lb-row-requested` is
387
+ A row that leaves loses `lb-row-live`, takes `inert`
388
+ ([miswart](../miswarts.md#leaving-rows-are-inert)), and is removed once its
389
+ animations finish, at once when there are none. `lb-row-requested` is
369
390
  cleared at every landing; the others when the hub next touches the row.
370
391
 
371
392
  A row is moved when its values changed and the rows whose values did not
@@ -920,7 +941,8 @@ The hub stamps the element that committed:
920
941
 
921
942
  `lb-request-error` covers a non-2xx response, a network failure and a
922
943
  timeout alike. The hub aborts a round trip after ten seconds, and sets
923
- `aria-busy="true"` for as long as `lb-request-pending` is present.
944
+ `aria-busy="true"` for as long as `lb-request-pending` is present
945
+ ([miswart](../miswarts.md#busy-elements-carry-aria-busy)).
924
946
 
925
947
  The hub ignores a commit on an element carrying `lb-request-pending`, so a
926
948
  pending button is already disabled and a stylesheet only shows it:
@@ -1034,8 +1056,16 @@ The developer writes `lb-` attributes in markup, and script never assigns
1034
1056
  them. The builder checks the markup of the chrome and of every page, after
1035
1057
  expansion, and refuses to build:
1036
1058
 
1037
- - an `lb-` attribute that is not one of the nine the developer writes
1059
+ - an `lb-` attribute that is not one of the sixteen the developer writes
1038
1060
  - `lb-request` naming an `lb-` request other than the three Loadbare provides
1061
+ - `lb-query` naming a query the page does not declare; every page and the
1062
+ chrome may name `lb-url` and `lb-choices`, and the chrome names no other
1063
+ - `lb-request` naming a request the page does not declare under `handlers`;
1064
+ the chrome sends none
1065
+ - one of the three requests for a query whose `crud` entry does not declare
1066
+ it, or any but `lb-row-update` for `lb-url` or `lb-choices`; the query is
1067
+ a form's own when the form carries `lb-query`, and otherwise the nearest
1068
+ `lb-query` around the element
1039
1069
  - `lb-url-link` on anything but an `<a>`
1040
1070
  - `lb-url-push` on an element without `lb-request`
1041
1071
  - `lb-url-unknown` on anything but a `<dialog>`, or outside `<lb-hub>`
@@ -1048,3 +1078,7 @@ expansion, and refuses to build:
1048
1078
  template, or beside `lb-column` or `lb-request`
1049
1079
  - a `command` beginning `--lb-` that is not one of the three commands
1050
1080
  - one of the three commands with no `commandfor`
1081
+
1082
+ To know what each page declares, the builder loads its `.queries.ts` and
1083
+ `.requests.ts` modules, so their import-time code runs during the build, as
1084
+ it does when the server starts.
@@ -96,7 +96,7 @@ export const queries: Queries = {
96
96
 
97
97
  | Declared with | Answers with |
98
98
  |----------------------------|-------------------------------------------|
99
- | `row(key, run)` | One row, an object |
99
+ | `row(key, run)` | One row, an object, or `null` for none |
100
100
  | `rows(key, run, options)` | All its rows, an array of objects |
101
101
 
102
102
  `options` are a `rows` query's order and column types. `order` is the
@@ -229,8 +229,9 @@ export const requests: Requests = {
229
229
  };
230
230
  ```
231
231
 
232
- Declare every request the page allows. The server refuses a name the page
233
- does not declare.
232
+ Declare every request the page allows. The builder refuses markup naming a
233
+ request the page does not declare, and the server refuses one that arrives
234
+ anyway; see [The markup checks](./data-binding.md#the-markup-checks).
234
235
 
235
236
  ### crud
236
237
 
@@ -293,8 +294,9 @@ export const requests: Requests = {
293
294
  };
294
295
  ```
295
296
 
296
- Declare every request the query permits. The server refuses a request a
297
- query does not declare, and a query with no `crud` entry permits none.
297
+ Declare every request the query permits. The builder refuses markup sending
298
+ a request a query does not declare, and the server refuses one that arrives
299
+ anyway. A query with no `crud` entry permits none.
298
300
 
299
301
  Write `rowUpdate` to set the columns `values` names and leave every other
300
302
  column as it is. A form or a live row sends every control it gathers, and a
@@ -303,6 +305,36 @@ columns the page may edit.
303
305
 
304
306
  Every value in `values` is a string, as a control's value is.
305
307
 
308
+ Two users writing the same row overlap only on the columns their requests
309
+ carry, and the last write wins. A single control sends its own column, so
310
+ two users editing different columns of a row do not overwrite each other.
311
+ A form sends every control it gathers, so two forms saved over one row
312
+ overlap on all of their columns.
313
+
314
+ A write whose outcome matters states its precondition in its own SQL, and
315
+ refuses when no row was updated:
316
+
317
+ ```ts
318
+ handlers: {
319
+ approve: {
320
+ run: async (ctx, { key }) => {
321
+ const updated = await ctx.db.run(
322
+ "UPDATE invoice SET status = 'approved' WHERE id = $1 AND status = 'pending'",
323
+ [key],
324
+ );
325
+ if (updated === 0) {
326
+ throw refusal("Someone else has already decided this invoice.");
327
+ }
328
+ },
329
+ refresh: ["invoice"],
330
+ },
331
+ },
332
+ ```
333
+
334
+ The request's name says what the user meant, and the precondition follows
335
+ from it, so no earlier value is needed. See
336
+ [Refusing a request](#refusing-a-request).
337
+
306
338
  ### refresh and patch
307
339
 
308
340
  List in `refresh` every query whose whole answer the request changed.
@@ -317,6 +349,7 @@ queries:
317
349
  | Answer | States |
318
350
  |--------------------------|---------------------------------------------|
319
351
  | `{ ... }` | The row of a `row` query |
352
+ | `null` | A `row` query found no row |
320
353
  | `[...]` | All rows of a `rows` query, and their order |
321
354
  | `patch({ rows: [...] })` | These rows arrive or change; the rest stand |
322
355
  | `patch({ drop: [...] })` | These keys are gone; the rest stand |
@@ -1,8 +1,8 @@
1
1
  # The Basic Widget Library
2
2
 
3
- Eleven widgets: `lb-input`, `lb-select`, `lb-options`, `lb-picker`,
3
+ Thirteen widgets: `lb-input`, `lb-select`, `lb-options`, `lb-picker`,
4
4
  `lb-table`, `lb-unknown-page`, `lb-confirm`, `lb-form`, `lb-insert-dialog`,
5
- `lb-master` and `lb-checkbox`. Every one of them is an ordinary custom element, written
5
+ `lb-master`, `lb-checkbox`, `lb-number` and `lb-date`. Every one of them is an ordinary custom element, written
6
6
  against the contracts in [Custom Elements](./custom-elements.md#html) for its
7
7
  definition and [Custom Elements](./custom-elements.md#code) for its class.
8
8
 
@@ -258,6 +258,28 @@ checked. The hub sets a native checkbox's `checked` directly.
258
258
  | `exp-label` | The label's text |
259
259
  | `exp-request` | The checkbox's `lb-request`, none when absent |
260
260
 
261
+ ## `lb-number` and `lb-date`
262
+
263
+ A number shown by the browser's `Intl.NumberFormat`, and a date or time
264
+ shown by `Intl.DateTimeFormat`. Neither is a control: `lb-column` goes on
265
+ the tag, and the widget renders the `lb-column-value` the hub stamps; see
266
+ [Receiving a value](./custom-elements.md#receiving-a-value).
267
+
268
+ ```html
269
+ <lb-number lb-column="balance" exp-style="currency" exp-currency="USD"></lb-number>
270
+ <lb-date lb-column="due" exp-date-style="medium"></lb-date>
271
+ ```
272
+
273
+ Each parameter is one of Intl's options in kebab-case, as `exp-currency-sign`
274
+ for `currencySign`, plus `exp-locale`. A value that is no number or date
275
+ shows as it arrived, and a date alone, `YYYY-MM-DD`, shows as that day in
276
+ every time zone. The stamp keeps the raw value, so a stylesheet selects on
277
+ it:
278
+
279
+ ```css
280
+ lb-number[lb-column-value^="-"] { color: var(--negative); }
281
+ ```
282
+
261
283
  ## `lb-unknown-page`
262
284
 
263
285
  The chrome's dialog for a URL that names no page, as one tag:
package/docs/roadmap.md CHANGED
@@ -15,13 +15,6 @@ mechanism Release 1.0 already ships. Each is here because deciding it
15
15
  speculatively, before a real page forces the question, risks designing the
16
16
  wrong thing. Revisit when the described symptom actually shows up.
17
17
 
18
- ### Staleness and concurrent writers
19
-
20
- Two tabs, or two users, updating the same rows at once. A solo developer
21
- testing in one browser will not produce this by accident, and retrofitting a
22
- version or conflict check onto every row after the fact touches every widget
23
- that writes.
24
-
25
18
  ### A query that pages or limits its rows
26
19
 
27
20
  The user's order and filters apply to the rows the browser holds. A query
@@ -49,6 +42,31 @@ end up living in the widget while the server remains authoritative for the
49
42
  same field. Not yet designed — revisit the first time an application needs
50
43
  inline validation.
51
44
 
45
+ ### Compound foreign keys
46
+
47
+ A picker is the UI for a foreign key. `<select lb-query="statuses"
48
+ lb-column="status">` says the row's `status` references a row of
49
+ `statuses`, and offers every one of them. For a single-column foreign key
50
+ that is exactly right, which is why a query in a row template answers the
51
+ same rows in every live row.
52
+
53
+ A compound foreign key cannot be expressed. When `(type, status)`
54
+ references `allowed_statuses(type, status)`, the choices for `status`
55
+ depend on the row's own `type`, and every row is offered every status.
56
+ The database knows the constraint, and the UI cannot say it, so a user is
57
+ offered choices the server will refuse.
58
+
59
+ A likely direction keeps one query and one answer: the lookup answers every
60
+ allowed pair as ordinary rows, the picker names the columns of its row that
61
+ restrict it, and the hub shows each picker only the rows that match, as it
62
+ already filters the rows it holds. Two questions come with it:
63
+
64
+ - Whether the restricting value is the one that landed or the control's
65
+ current value, so the choices follow an edit before it is saved.
66
+ - What becomes of a chosen value the restriction no longer allows.
67
+
68
+ Revisit when an application needs a choice restricted by its row.
69
+
52
70
  ## Release 1.1 candidates
53
71
 
54
72
  Net-new scope, not gaps in 1.0. Release 1.1 will optimize for operational
@@ -128,9 +146,10 @@ breaks nothing.
128
146
 
129
147
  ### Queries stated by the chrome
130
148
 
131
- A custom element in the chrome naming a query forces every page to declare
132
- that query, since the page load covers one page's set. `lb-url` shows the
133
- pattern from the framework side, and an application has no equivalent.
149
+ The chrome belongs to no page, so the builder refuses an application query
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.
134
153
 
135
154
  A chrome-level query set on `createHub` would close this, and it is additive.
136
155
  It takes its place in the [options on `createHub`](#options-on-createhub).
@@ -159,39 +178,6 @@ A query parm write replaces the history entry, and `lb-url-push` pushes
159
178
  one. Which of the two a user wants may be the user's to say, rather
160
179
  than the page's.
161
180
 
162
- ### Build-time checking of `lb-request` against the declared requests
163
-
164
- Release 1.0 resolves every `lb-request` value at request time. A value naming
165
- no entry under `handlers`, or one of the requests Loadbare provides for a
166
- query with no entry under `crud`, is found on the first commit: a server
167
- console warning and an empty answer, described in
168
- [Page files](./reference/page-files.md). A typo builds cleanly.
169
-
170
- The builder already expands each page and walks the finished document in
171
- `walkMarkup`, tracking the enclosing `lb-query`, so collecting every request
172
- name together with the query it is issued for needs no new machinery. What
173
- it lacks is the other half of the comparison: it knows the path of a
174
- `.requests.ts` file and never reads its contents.
175
-
176
- Two routes to the declared names, and the choice is the decision:
177
-
178
- - **Load the built module.** Bundle the generated `pages.ts`, import it, and
179
- read the keys off the finished objects. Exact, and indifferent to how the
180
- object was written. The build then executes application import-time code,
181
- and a builder that only reads files stops being that.
182
- - **Parse the source.** Read the keys statically with the TypeScript compiler
183
- API. No application code runs, at the price of a real dependency and of
184
- guessing wrong on anything not written as a plain object literal.
185
-
186
- Two smaller calls come with either route. The chrome belongs to no page, so
187
- a request there can only be required of every page or forbidden. A page with
188
- no requests file makes any request on it an error. The check is sound
189
- because the developer writes `lb-*` attributes in markup and script never
190
- assigns them.
191
-
192
- The same machinery would then also let `lb-query` names be checked against
193
- the declared queries.
194
-
195
181
  ### A language server
196
182
 
197
183
  ...for Loadbare HTML.
@@ -203,6 +189,23 @@ expansion allows a strings file. We could either preserve the fully
203
189
  static build-time system and create multiple versions of `app.html`, or we
204
190
  could add label hydration to the page navigation stage.
205
191
 
192
+ ### Version checks on update and delete
193
+
194
+ The last write to a row wins, at the grain of the columns each request
195
+ carries, and a write whose outcome matters states its precondition in its
196
+ own SQL; see [crud](./reference/page-files.md#crud). A plain edit has no
197
+ such precondition: only the value the user saw, or a version standing for
198
+ it, tells that the row changed in between, and the server remembers
199
+ nothing.
200
+
201
+ A query would declare its version column. The hub keeps every query's
202
+ last answer, patches included, so it would send the version it holds with
203
+ each update and delete for that query, and the handler would put it in its
204
+ `WHERE`. No widget changes, and a control committing alone takes part.
205
+
206
+ Revisit when an application needs a write refused because the row changed
207
+ since the user saw it.
208
+
206
209
  ## Decided against
207
210
 
208
211
  Settled questions, kept here so they are not reopened. Each names what was
@@ -222,3 +225,12 @@ attribute value, with no escape for either.
222
225
 
223
226
  Text has a workaround: `<div>Hello, <span>{{world}}</span></div>` places the
224
227
  placeholder in a text node of its own. Attribute values have none.
228
+
229
+ ### Serving the static files
230
+
231
+ A router beside `hubRoutes` that serves `client.js`, `app.css` and
232
+ `app.html` would save an application three routes. It would also decide
233
+ for every application how those files are read, cached and placed among
234
+ its own routes, and whether a path naming no page answers `app.html`.
235
+ Loadbare/app ships no server, and serving the build's output is the
236
+ application's choice, as the rest of its server is.
package/docs/testing.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Testing
2
2
 
3
+ > LLM-authored and LLM-maintained. This is acceptable because the
4
+ > document is fact-based, describing tests that exist in this package, and
5
+ > is written to be consumed by an LLM working on the package.
6
+
3
7
  A contributor's document, and a companion to [theory.md](./theory.md). It
4
8
  describes how this package tests itself.
5
9
 
@@ -26,6 +30,23 @@ All four tiers run under `npm test` today and need no dependency that is not
26
30
  already installed. A fifth environment — a real browser — is discussed at the
27
31
  end and is not built.
28
32
 
33
+ ## What the tests are evidence for
34
+
35
+ [Theory](./theory.md) makes three claims that can be checked, and this suite
36
+ carries evidence for two of them.
37
+
38
+ - **Correctness.** Most of what Theory's correctness section names is
39
+ tested here: rows landed by key, an unchanged row left alone with what the
40
+ user typed, an answer in the wrong shape left out, a write sent once, a
41
+ refusal shown on the controls it names, and a server that refuses what a
42
+ page did not declare.
43
+ - **The vocabulary rule.** The markup checks in tier 1 enforce it at build
44
+ time: an `lb-` attribute outside the set the developer writes is a build
45
+ that fails.
46
+ - **Performance.** No tier measures the 300ms round trip. Node and jsdom
47
+ have no network and no paint, so they are not evidence for it, and this
48
+ suite does not try.
49
+
29
50
  ## The runner
30
51
 
31
52
  `node:test`, run through `tsx`:
@@ -78,17 +99,14 @@ the most of what Loadbare promises, so it is first.
78
99
  - more than one `lb-exp-slot`
79
100
 
80
101
  **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
- - a chrome without exactly one `<lb-hub>` and one empty `<main>` inside it,
90
- and a page carrying either
91
- - a widget script declaring an `lb` method Loadbare does not define
102
+ anything ships, against the list in
103
+ [The markup checks](./reference/data-binding.md#the-markup-checks). Each one
104
+ is a build that must not produce output. The checks of `lb-query` and
105
+ `lb-request` names take each page's declarations as `loadDeclared` reads
106
+ them, and `loadDeclared` is tested against real page modules: names read
107
+ off objects built with spreads and computed keys, nothing declared for a
108
+ page with no modules, nothing left in the output directory, and a module
109
+ that throws named in the error.
92
110
 
93
111
  A page file's `<title>` is lifted out and stamped as `lb-page-title`.
94
112
 
@@ -118,7 +136,7 @@ Review the golden files rather than blessing them. `npm run test:golden -w
118
136
  @loadbare/demo` rewrites them; the diff is the point, and a diff nobody read
119
137
  is a test nobody ran.
120
138
 
121
- The rest of the build — `assemble`, `elements`, `pages`, `styles`,
139
+ The rest of the build — `assemble`, `declared`, `elements`, `pages`, `styles`,
122
140
  `package-css` — is tested the same way and in the same tier, since none of it
123
141
  needs a browser either.
124
142
 
@@ -126,7 +144,7 @@ needs a browser either.
126
144
  a temporary project, which carries a stand-in `@loadbare/app` pointing at
127
145
  the hub's source so the test needs no prior build. It is tested for its
128
146
  default `src` and `dist`, every file it writes, `--minify`, a failed build's
129
- exit code, and `--watch` rebuilding on a change, skipping a file it does not
147
+ exit code, a name the page does not declare, and `--watch` rebuilding on a change, skipping a file it does not
130
148
  watch, and surviving a failed build.
131
149
 
132
150
  ## Tier 2 — The engine
@@ -149,11 +167,22 @@ watch, and surviving a failed build.
149
167
  operator away from silently reversing
150
168
  - a handler returning `url()` answers with the `lb-url` item alone, and runs
151
169
  no refresh set
170
+ - a `row` query that finds no row answers `null`
171
+ - at startup, a malformed order, an unknown column type, and a crud request
172
+ that refreshes its own `rows` query are refused
173
+ - an update or delete for several rows runs once per key, lays the patches
174
+ together, and refreshes once
175
+ - `onPageEnter` may return `url()` or `choices()`, and `onChoices` hears
176
+ every choice the user makes
177
+ - a refusal carries a message, columns or both, and passes through the
178
+ engine untouched
152
179
 
153
180
  `tests/lb-express.test.ts` runs against a listening app: the page comes from
154
181
  the path, the query parms reach `contextFor`, a `url()` answer is followed by
155
- the page loaded at the new URL, an undeclared request is refused, and a
156
- malformed body does not throw.
182
+ the page loaded at the new URL, an undeclared request is refused, a
183
+ malformed body does not throw, an oversized body is refused, a refusal
184
+ answers 422 with its words, and any other throw answers 500 with nothing in
185
+ it.
157
186
 
158
187
  ## Tier 3 — Landing
159
188
 
@@ -187,6 +216,10 @@ evidence here.
187
216
  groups and leaving them out of counts and aggregates (`lb-choices.test.ts`)
188
217
  - `lb-show` moves an element into a template and back, and landing reaches
189
218
  inside it
219
+ - a row whose values arrive as it already shows them is not filled again,
220
+ so what the user typed into it stays
221
+ - a `row` query answered with no row empties the columns it names and drops
222
+ its key
190
223
 
191
224
  Two properties, written as loops rather than with a library. Applying the
192
225
  same rows twice is applying them once. And rows reached through any sequence
@@ -252,6 +285,17 @@ What the hub is tested for here:
252
285
  loaded there
253
286
  - `hidden` comes off the body once a page has landed, including the page
254
287
  that failed to load, and a link whose page fails to load marks no element
288
+ - an answer that returns after the user has moved to another URL does not
289
+ land
290
+ - focus goes to the first element the user can operate once a page's
291
+ queries land
292
+ - a form pointed at a row by `commandfor` fills from the row the hub holds,
293
+ and a row not yet entered sends no update or delete
294
+ - ticked `lb-select` rows travel as `keys`, and the selection clears once
295
+ the request succeeds
296
+ - a refusal lands its message on the element that committed and on each
297
+ control it names
298
+ - `lb-request-done` reaches the element that committed and its ancestors
255
299
 
256
300
  Widgets are small and their logic is local, so jsdom carries them: a value
257
301
  reaches the control the widget owns, the widget is a form-associated control
@@ -282,18 +326,8 @@ No browser test runner is installed and none of the following exists. They
282
326
  are recorded here as the shape of the work, not as coverage:
283
327
 
284
328
  1. a cold load fills `<main>` with no empty flash
285
- 2. the wizard never displays a step the server has not confirmed
286
- 3. the view-source invariant
287
-
288
- The last is the one worth a browser. Loadbare's central claim is that no
289
- markup exists in the DOM that is not in view-source, and that the difference
290
- between the two is exactly the dynamic half. That is a property, it is stated
291
- precisely in [theory.md](./theory.md), and it should be enforced mechanically
292
- rather than believed: fetch the shell as text, compare the element set
293
- against the live document after interaction, and assert that the only
294
- additions are clones of row templates.
295
-
296
- See [TODO.md](../TODO.md) for this and the rest of the open work.
329
+ 2. the wizard page in `@loadbare/demo` never displays a step the server has
330
+ not confirmed
297
331
 
298
332
  ## Continuous integration
299
333
 
@@ -307,7 +341,7 @@ tiers here stay fast enough to run on every save.
307
341
  ## One change the tests want
308
342
 
309
343
  The client reports through `console.error` and `console.warn` directly, from
310
- roughly twenty places across `hub/` and `@loadbare/widgets`. Routing those through a
344
+ more than twenty places across `hub/` and `@loadbare/widgets`. Routing those through a
311
345
  single `report()` in `core/` would give the tests one seam to observe instead
312
346
  of a global to mock per file, and would put the framework's error channel in
313
347
  the same file as the rest of its vocabulary. It is a small change and it is