@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.
- package/README.md +17 -17
- package/dist/build/assemble.d.ts +7 -1
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +81 -11
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/cli.d.ts +3 -0
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +6 -1
- package/dist/build/cli.js.map +1 -1
- package/dist/build/declared.d.ts +25 -0
- package/dist/build/declared.d.ts.map +1 -0
- package/dist/build/declared.js +62 -0
- package/dist/build/declared.js.map +1 -0
- package/dist/build/pages.d.ts +6 -0
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +32 -12
- package/dist/build/pages.js.map +1 -1
- package/dist/core/lb-types.d.ts +6 -3
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +10 -3
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/server/lb-server.d.ts +5 -2
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +12 -3
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +33 -85
- package/docs/comparison.md +695 -482
- package/docs/departures.md +63 -0
- package/docs/miswarts.md +140 -0
- package/docs/opinions.md +155 -0
- package/docs/reference/builder.md +9 -2
- package/docs/reference/chrome.md +2 -1
- package/docs/reference/css.md +163 -0
- package/docs/reference/custom-elements.md +1 -1
- package/docs/reference/data-binding.md +44 -10
- package/docs/reference/page-files.md +38 -5
- package/docs/reference/widgets.md +24 -2
- package/docs/roadmap.md +55 -43
- package/docs/testing.md +62 -28
- package/docs/theory.md +348 -108
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +16 -9
- package/skills/loadbare-app/references/TECHREF-1.0.md +33 -85
- package/skills/loadbare-app/references/builder.md +9 -2
- package/skills/loadbare-app/references/chrome.md +2 -1
- package/skills/loadbare-app/references/css.md +163 -0
- package/skills/loadbare-app/references/custom-elements.md +1 -1
- package/skills/loadbare-app/references/data-binding.md +44 -10
- package/skills/loadbare-app/references/miswarts.md +140 -0
- package/skills/loadbare-app/references/page-files.md +38 -5
- package/skills/loadbare-app/references/widgets.md +24 -2
- package/docs/possible-ideas.md +0 -314
- package/docs/theory-old.md +0 -839
- 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
|
-
|
|
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
|
|
173
|
-
|
|
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
|
|
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
|
|
368
|
-
|
|
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
|
|
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
|
|
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
|
|
297
|
-
query does not declare, and
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
pattern from the framework side,
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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,
|
|
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
|
|
286
|
-
|
|
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
|
-
|
|
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
|