@loadbare/app 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/build/assemble.d.ts.map +1 -1
  2. package/dist/build/assemble.js +87 -1
  3. package/dist/build/assemble.js.map +1 -1
  4. package/dist/build/cli.js +2 -1
  5. package/dist/build/cli.js.map +1 -1
  6. package/dist/build/elements.d.ts +9 -1
  7. package/dist/build/elements.d.ts.map +1 -1
  8. package/dist/build/elements.js +50 -0
  9. package/dist/build/elements.js.map +1 -1
  10. package/dist/build/origins.d.ts +2 -0
  11. package/dist/build/origins.d.ts.map +1 -1
  12. package/dist/build/origins.js +1 -1
  13. package/dist/build/origins.js.map +1 -1
  14. package/dist/core/lb-constants.d.ts +34 -1
  15. package/dist/core/lb-constants.d.ts.map +1 -1
  16. package/dist/core/lb-constants.js +105 -0
  17. package/dist/core/lb-constants.js.map +1 -1
  18. package/dist/core/lb-types.d.ts +74 -18
  19. package/dist/core/lb-types.d.ts.map +1 -1
  20. package/dist/core/lb-types.js +74 -11
  21. package/dist/core/lb-types.js.map +1 -1
  22. package/dist/hub/lb-apply.d.ts +57 -18
  23. package/dist/hub/lb-apply.d.ts.map +1 -1
  24. package/dist/hub/lb-apply.js +661 -63
  25. package/dist/hub/lb-apply.js.map +1 -1
  26. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  27. package/dist/hub/lb-hub.browser.js +400 -33
  28. package/dist/hub/lb-hub.browser.js.map +1 -1
  29. package/dist/server/lb-express.d.ts +19 -7
  30. package/dist/server/lb-express.d.ts.map +1 -1
  31. package/dist/server/lb-express.js +62 -26
  32. package/dist/server/lb-express.js.map +1 -1
  33. package/dist/server/lb-server.d.ts +80 -11
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/server/lb-server.js +185 -33
  36. package/dist/server/lb-server.js.map +1 -1
  37. package/docs/TECHREF-1.0.md +584 -111
  38. package/docs/comparison.md +17 -13
  39. package/docs/possible-ideas.md +308 -0
  40. package/docs/reference/chrome.md +7 -1
  41. package/docs/reference/custom-elements.md +134 -39
  42. package/docs/reference/data-binding.md +405 -17
  43. package/docs/reference/page-files.md +101 -19
  44. package/docs/reference/server.md +24 -0
  45. package/docs/reference/widgets.md +118 -25
  46. package/docs/roadmap.md +17 -6
  47. package/docs/testing.md +21 -10
  48. package/package.json +2 -3
  49. package/skills/loadbare-app/SKILL.md +92 -30
  50. package/skills/loadbare-app/references/TECHREF-1.0.md +584 -111
  51. package/skills/loadbare-app/references/chrome.md +7 -1
  52. package/skills/loadbare-app/references/custom-elements.md +134 -39
  53. package/skills/loadbare-app/references/data-binding.md +405 -17
  54. package/skills/loadbare-app/references/page-files.md +101 -19
  55. package/skills/loadbare-app/references/server.md +24 -0
  56. package/skills/loadbare-app/references/widgets.md +118 -25
@@ -48,7 +48,8 @@ page touches that page's files.
48
48
  ## HTML
49
49
 
50
50
  Write the page as a fragment. The fragment lands in `<main>`, which
51
- [chrome.html](./chrome.md) supplies.
51
+ [chrome.html](./chrome.md) supplies. A page carries no `<main>` and no
52
+ `<lb-hub>` of its own; the builder rejects one that does.
52
53
 
53
54
  ```html
54
55
  <!-- src/pages/about.page.html -->
@@ -89,14 +90,18 @@ export const queries: Queries = {
89
90
  page: "directory",
90
91
  count: String(await ctx.db.visitCount()),
91
92
  })),
92
- directory: rows("id", (ctx) => ctx.db.directory()),
93
+ directory: rows("id", (ctx) => ctx.db.directory(), { order: "name" }),
93
94
  };
94
95
  ```
95
96
 
96
- | Declared with | Answers with |
97
- |-------------------|-------------------------------------------|
98
- | `row(key, run)` | One row, an object |
99
- | `rows(key, run)` | All its rows, an array of objects, in order |
97
+ | Declared with | Answers with |
98
+ |----------------------------|-------------------------------------------|
99
+ | `row(key, run)` | One row, an object |
100
+ | `rows(key, run, options)` | All its rows, an array of objects |
101
+
102
+ `options` are a `rows` query's order: `order`, the columns the hub places
103
+ its rows by, and `serverSortedByUrl`, set when the query reads the user's
104
+ order parm itself. See [Order](./data-binding.md#order).
100
105
 
101
106
  `key` names the column that identifies a row. Give every row that column,
102
107
  including a `row` query's: an aggregate row answers with a constant key. The
@@ -109,8 +114,8 @@ page that needs the same data as one row and as a set declares two queries.
109
114
  The hub hands each value to the browser untouched, so what a number, a date
110
115
  or a null looks like is decided here, in the query.
111
116
 
112
- Return the full answer every time, and the rows in the order the page shows
113
- them. Sending only what changed is a request's job — see
117
+ Return the full answer every time. With no `order`, the rows show in the
118
+ order they are returned. Sending only what changed is a request's job — see
114
119
  [refresh and patch](#refresh-and-patch).
115
120
 
116
121
  ## Requests
@@ -120,7 +125,7 @@ optional:
120
125
 
121
126
  | Key | Runs |
122
127
  |---------------|--------------------------------------------------------|
123
- | `onPageEnter` | Before the page's queries, when the page loads |
128
+ | `onPageEnter` | Before the page's queries, when the page loads; may move the URL |
124
129
  | `handlers` | The page's declared requests, by request name |
125
130
  | `crud` | The requests Loadbare provides, by query name |
126
131
 
@@ -132,10 +137,14 @@ name:
132
137
  | `name` | The request name, from `lb-request` |
133
138
  | `query` | The query the request is for |
134
139
  | `key` | The key of the row the request is for |
140
+ | `keys` | The keys of the rows selected, in place of `key` |
141
+ | `rows` | A form's nested rows, by query: each `key` and `values` |
135
142
  | `values` | The gathered control values, by column |
136
143
 
137
144
  The hub sends `query`, `key` and `values` when it finds them; see
138
- [Gathering](./data-binding.md#gathering).
145
+ [Gathering](./data-binding.md#gathering). It sends `keys` for a request that
146
+ acts on the rows the user selected; see
147
+ [Selecting rows](./data-binding.md#selecting-rows).
139
148
 
140
149
  ### onPageEnter
141
150
 
@@ -150,6 +159,12 @@ export const requests: Requests = {
150
159
 
151
160
  Declare no refresh set here. The page's queries run afterward.
152
161
 
162
+ It may return `url()` instead, to move the page to other query parms before
163
+ anything shows, as restoring the filters and order the user last had does.
164
+ The page loads there in the same round trip, and that load does not follow
165
+ `onPageEnter` again. A `url()` leading to the parms already in hand is
166
+ ignored. See [Moving the URL](#moving-the-url).
167
+
153
168
  ### handlers
154
169
 
155
170
  Declare a request under the name the HTML gives `lb-request`. Pair what it
@@ -180,10 +195,34 @@ the query they operate on:
180
195
 
181
196
  | Request name | Runs | `run` receives |
182
197
  |-----------------|-------------|------------------|
183
- | `lb-row-insert` | `rowInsert` | `query`, `values` |
184
- | `lb-row-update` | `rowUpdate` | `query`, `key`, `values` |
198
+ | `lb-row-insert` | `rowInsert` | `query`, `values`, and `rows` from a form |
199
+ | `lb-row-update` | `rowUpdate` | `query`, `key`, `values`, and `rows` from a form |
185
200
  | `lb-row-delete` | `rowDelete` | `query`, `key` |
186
201
 
202
+ An `lb-row-update` or `lb-row-delete` carrying `keys` runs `rowUpdate` or
203
+ `rowDelete` once for each key, with that key, so a handler written for one
204
+ row serves several. The patches the runs return are laid together, and the
205
+ refresh set runs once. A `url()` returned for one of several rows is
206
+ reported and ignored. A declared request receives `keys` as it is.
207
+
208
+ `rows` holds the rows of a query nested in the form the request came from,
209
+ by query name, each with its `key` and the `values` of its own controls; see
210
+ [A form over a set of rows](./data-binding.md#a-form-over-a-set-of-rows).
211
+ The handler writes the master and its details together, deciding what each
212
+ row's values mean, such as an empty amount being no row at all:
213
+
214
+ ```ts
215
+ rowInsert: {
216
+ run: async (ctx, { values, rows }) => {
217
+ const id = await ctx.db.insertPaycheck(values);
218
+ for (const { key, values: row } of rows?.deduction_types ?? []) {
219
+ if (row.amount !== "") await ctx.db.insertDeduction(id, key, row.amount);
220
+ }
221
+ },
222
+ refresh: ["paychecks"],
223
+ },
224
+ ```
225
+
187
226
  ```ts
188
227
  // src/pages/directory.requests.ts
189
228
  import { patch, type Requests } from "@loadbare/app/server";
@@ -240,16 +279,14 @@ queries:
240
279
 
241
280
  Return a patch for a change the request knows the extent of — one row added,
242
281
  one row dropped, one row edited — and leave `refresh` empty. A refreshed
243
- `rows` query sends every row, and the hub places every row again, which moves
244
- each element and takes focus from the control the user is in.
282
+ `rows` query sends every row to say what one row could.
245
283
 
246
284
  A change that seems to need a refresh usually has a patch:
247
285
 
248
- - A placeholder row standing in for an empty set or section: drop its key in
249
- the patch that adds the first real row, and add it back in the patch that
250
- drops the last.
251
- - A row whose position changes: the rows host places it, as
252
- [`lb-table`](./widgets.md#lb-table) does by `data-sort`.
286
+ - A row whose position changes: the hub places it by the query's
287
+ [order](./data-binding.md#order).
288
+ - A group that appears or goes: the hub makes a group with its first row
289
+ and removes it with its last, so no row stands in for an empty one.
253
290
  - A row whose other columns change with the edit: re-read the row and patch
254
291
  it.
255
292
  - Rows the database removes with a deleted one: select their keys before the
@@ -324,3 +361,48 @@ Post/Redirect/Get without the redirect. The refresh set does not run, and
324
361
  nothing else `run` returned is sent, since both answered for the URL the page
325
362
  left. The loaded page reads the new parms through `contextFor`; see
326
363
  [the Express server](./server.md#database-layer).
364
+
365
+ ### Refusing a request
366
+
367
+ A handler refuses a request by throwing `refusal()`, with words written for
368
+ the user:
369
+
370
+ ```ts
371
+ import { refusal, type Requests } from "@loadbare/app/server";
372
+
373
+ export const requests: Requests = {
374
+ handlers: {
375
+ enterPaycheck: {
376
+ run: async (ctx, { values }) => {
377
+ if (Number(values!.amount) <= 0) {
378
+ throw refusal({ amount: "Must be more than zero" });
379
+ }
380
+ const overlap = await ctx.db.overlappingSeries(values!);
381
+ if (overlap) {
382
+ throw refusal(
383
+ `That series already has paychecks from ${overlap.from} through ${overlap.to}.`,
384
+ );
385
+ }
386
+ await ctx.db.enterPaycheck(values!);
387
+ },
388
+ refresh: ["paychecks"],
389
+ },
390
+ },
391
+ };
392
+ ```
393
+
394
+ | Call | The browser shows |
395
+ |----------------------------------|--------------------------------------------|
396
+ | `refusal("message")` | The message, for the request as a whole |
397
+ | `refusal({ column: "message" })` | Each message on the control for its column |
398
+ | `refusal("message", { ... })` | Both |
399
+
400
+ Throw it, from the handler or from anything the handler calls, and whatever
401
+ the application has open rolls back as for any throw. A column names a
402
+ control the request gathered. See [Refusals](./data-binding.md#refusals)
403
+ for how the browser shows them.
404
+
405
+ A refusal is the only error whose words reach the browser. Anything else a
406
+ handler throws is a failure, answered with nothing in it, so a stack or a
407
+ database message never reaches a page.
408
+
@@ -72,6 +72,26 @@ path, so the application cannot be hosted under a subpath such as
72
72
  whole path space through unchanged.
73
73
 
74
74
  Leave `express.json()` to `hubRoutes`, which mounts it on its own routes.
75
+ Pass its options through `hubRoutes` instead.
76
+
77
+ ## Options
78
+
79
+ `hubRoutes` takes an optional third argument, an object with one key per
80
+ concern:
81
+
82
+ | Option | Description |
83
+ |--------|----------------------------------------------------------|
84
+ | `json` | Options for `express.json()`, passed to it unchanged |
85
+
86
+ A request body over 100 KB, the `express.json()` default, is refused with a
87
+ 413 before any handler runs, and the server log names the option that raises
88
+ it. Raise it for an application whose pages send a file's text as a value:
89
+
90
+ ```ts
91
+ app.use(hubRoutes(hub, contextFor, { json: { limit: "5mb" } }));
92
+ ```
93
+
94
+ The limit covers every page's requests.
75
95
 
76
96
  ## Running the server
77
97
 
@@ -91,6 +111,10 @@ TypeScript types itself. `dist/pages.ts` imports each `.requests.ts` and
91
111
  `.queries.ts` file by its full name, extension included, because Node looks
92
112
  for exactly the path an import names.
93
113
 
114
+ The `hub` it exports comes from `createHub`, the only implementation of
115
+ the `Hub` interface. That interface may gain members in any release, so an
116
+ application never implements it.
117
+
94
118
  Enable `allowImportingTsExtensions` in the application's `tsconfig.json`
95
119
  when `tsc` type-checks the server. Without it, `tsc` rejects the `.ts`
96
120
  extensions in `dist/pages.ts`:
@@ -1,7 +1,8 @@
1
1
  # The Basic Widget Library
2
2
 
3
- Six widgets: `lb-input`, `lb-select`, `lb-options`, `lb-picker`, `lb-table`,
4
- `lb-unknown-page`. Every one of them is an ordinary custom element, written
3
+ Eleven widgets: `lb-input`, `lb-select`, `lb-options`, `lb-picker`,
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
6
  against the contracts in [Custom Elements](./custom-elements.md#html) for its
6
7
  definition and [Custom Elements](./custom-elements.md#code) for its class.
7
8
 
@@ -25,7 +26,9 @@ for where a listed package sits in the cascade.
25
26
  `lb-input`, `lb-select`, `lb-options` and `lb-picker` are controls:
26
27
  form-associated custom elements with a `value` property that fire `change`.
27
28
  Each carries `lb-column` and `lb-request` itself, the way an `<input>` does;
28
- see [Being a control](./custom-elements.md#being-a-control).
29
+ see [Being a control](./custom-elements.md#being-a-control). Each passes a
30
+ refusal's message for its column to the native control inside it; see
31
+ [Refusals](./data-binding.md#refusals).
29
32
 
30
33
  ## `lb-input`
31
34
 
@@ -43,6 +46,7 @@ write the same edit twice.
43
46
  | Parameter | Fills |
44
47
  |----------------|----------------------------------|
45
48
  | `exp-label` | The visible `<label>` text |
49
+ | `exp-type` | The input's `type`, `text` when absent |
46
50
  | `exp-readonly` | The input's `readonly` attribute |
47
51
 
48
52
  ## `lb-select`
@@ -67,9 +71,7 @@ writes the row template inside the widget:
67
71
 
68
72
  ```html
69
73
  <lb-options lb-query="statuses" lb-column="status" lb-request="lb-row-update" exp-label="Status">
70
- <template data-group="category">
71
- <option lb-column="label"></option>
72
- </template>
74
+ <template><option lb-column="label"></option></template>
73
75
  </lb-options>
74
76
  ```
75
77
 
@@ -79,9 +81,15 @@ writes the row template inside the widget:
79
81
 
80
82
  - Each option's `value` is its row's key, the `lb-key-value` the hub stamps
81
83
  on the live row, so the widget holds a key and shows a label.
82
- - `data-group` on the row template sections the options into `<optgroup>`s,
83
- one per distinct value of that column, added and removed as rows arrive
84
- and leave.
84
+ - A group template holding an `<optgroup>` puts the options in groups, one
85
+ per run of the query's order's leading term. The widget labels each
86
+ `<optgroup>` from the `lb-group-value` the hub stamps on it:
87
+
88
+ ```html
89
+ <template lb-group>
90
+ <optgroup><template><option lb-column="label"></option></template></optgroup>
91
+ </template>
92
+ ```
85
93
  - Its `value` selects the option with that key, including an option that
86
94
  arrives after the value did.
87
95
 
@@ -100,7 +108,6 @@ one option showing one column:
100
108
  lb-request="lb-row-update"
101
109
  exp-label="Status"
102
110
  exp-column="label"
103
- exp-group="category"
104
111
  ></lb-picker>
105
112
  ```
106
113
 
@@ -108,10 +115,9 @@ one option showing one column:
108
115
  |--------------|-----------------------------------------|
109
116
  | `exp-label` | The visible `<label>` text |
110
117
  | `exp-column` | The column each option shows |
111
- | `exp-group` | The row template's `data-group` |
112
118
 
113
- An option built from two columns, or a row with a second element, is
114
- `lb-options` with the page's own row template.
119
+ An option built from two columns, a row with a second element, or options
120
+ in groups, is `lb-options` with the page's own templates.
115
121
 
116
122
  ## `lb-table`
117
123
 
@@ -123,11 +129,14 @@ row, the row template, and optionally a footer, each as a `<template>`:
123
129
  <template lb-exp-template="head">
124
130
  <tr><th>Date</th><th>Amount</th></tr>
125
131
  </template>
126
- <template data-sort="date" data-group="month">
127
- <tr><td lb-column="date"></td><td lb-column="amount"></td></tr>
132
+ <template lb-group>
133
+ <tr><th colspan="2" lb-column="month"></th></tr>
134
+ <template>
135
+ <tr><td lb-column="date"></td><td lb-column="amount"></td></tr>
136
+ </template>
128
137
  </template>
129
138
  <template lb-exp-template="foot">
130
- <tr lb-query="ledger-total"><td>Total</td><td lb-column="total"></td></tr>
139
+ <tr><td>Total</td><td lb-sum="amount"></td></tr>
131
140
  </template>
132
141
  </lb-table>
133
142
  ```
@@ -140,15 +149,99 @@ row, the row template, and optionally a footer, each as a `<template>`:
140
149
  |-----------------------------------|-----------------------------------------|
141
150
  | `<template lb-exp-template="head">` | The `<thead>` |
142
151
  | `<template lb-exp-template="foot">` | The `<tfoot>` |
143
- | Any other `<template>` | The row template, in the `<tbody>` |
144
-
145
- - `data-group` on the row template sections rows under a heading row, one
146
- per distinct value of that column, spanning every column of the row.
147
- - `data-sort` orders rows within a section, or within the whole body with no
148
- grouping, by the text of that column.
149
- - The footer is not a row of `ledger`. Its `<tr>` names a `row` query of its
150
- own, which lands on the `<tr>` itself. A grand total is a second query over
151
- the same data.
152
+ | Any other `<template>` | The row or group template, in the `<tbody>` |
153
+
154
+ - The hub places the rows by the query's order and builds its groups. The
155
+ rows land in the one `<tbody>`, so a group here is a heading row with its
156
+ rows after it.
157
+ - The footer is not a row of `ledger`. An aggregate there covers every row;
158
+ a figure that is not a sum of the rows, such as a budget, is a `<tr>`
159
+ naming a `row` query of its own.
160
+ - A `<template lb-exp-template="ghost">` is a blank row for entering a new
161
+ one, in a `<tbody>` of its own. The widget joins its controls to a form
162
+ outside the table, since a `<form>` cannot wrap a `<tr>`, and puts the
163
+ cursor back in it once an insert from it succeeds.
164
+ - After rows land, the widget scrolls the row the page's own request created
165
+ or moved into view.
166
+
167
+ ## `lb-confirm`
168
+
169
+ A button that asks before it sends. The page writes the question inside the
170
+ tag, and the request goes on the dialog's confirm button, so nothing reaches
171
+ the hub until the user says yes:
172
+
173
+ ```html
174
+ <lb-confirm exp-label="Delete" exp-request="lb-row-delete">
175
+ Delete <b lb-column="name"></b>?
176
+ </lb-confirm>
177
+ ```
178
+
179
+ | Parameter | Fills |
180
+ |---------------|--------------------------------------------------|
181
+ | `exp-label` | The text of the button that opens the dialog |
182
+ | `exp-request` | The confirm button's `lb-request` |
183
+ | `exp-confirm` | The confirm button's text, `Confirm` when absent |
184
+
185
+ The widget closes the dialog once `lb-request-done` says the request
186
+ succeeded, and leaves it open on a refusal.
187
+
188
+ ## `lb-form`
189
+
190
+ A `<form>` over one row's controls. A row with a key saves each control's
191
+ column on `change`; a row whose key is empty is new, and the hub sends no
192
+ update for it, so its controls wait for a submit. A definition only.
193
+
194
+ | Parameter | Fills |
195
+ |-----------|-----------------|
196
+ | `exp-id` | The form's `id` |
197
+
198
+ ## `lb-insert-dialog`
199
+
200
+ A row entered in a modal dialog, written once outside every row and opened
201
+ with `commandfor` and `command="show-modal"`. Save is the form's
202
+ `lb-row-insert`; on success the widget chooses the new row in the nearest
203
+ `lb-options` around the button that opened it, and closes. The handler
204
+ answers with a patch holding the new row.
205
+
206
+ | Parameter | Fills |
207
+ |-------------|---------------------------------------------------|
208
+ | `exp-id` | The dialog's `id` |
209
+ | `exp-title` | The dialog's heading |
210
+ | `exp-query` | The form's `lb-query`, which it inserts into |
211
+ | `exp-save` | The Save button's text, `Save` when absent |
212
+
213
+ ## `lb-master`
214
+
215
+ A set, one of its rows, and the buttons that act on the set: New, Copy,
216
+ Delete, Save new and Cancel, each shown only for a row on record or a new
217
+ one. A definition only, built from `lb-form` and `lb-confirm`. New sends
218
+ `new` and Copy sends `copy`, requests the page declares; `new` returns
219
+ `url()` emptying the parm that names the row.
220
+
221
+ | Parameter | Fills |
222
+ |---------------|--------------------------------------------|
223
+ | `exp-row` | The `lb-query` of the row the form shows |
224
+ | `exp-form-id` | The form's `id`, `master-form` when absent |
225
+
226
+ ## `lb-checkbox`
227
+
228
+ A checkbox and its label as one element, for an application to style. A
229
+ definition only: a native checkbox is already a control (see
230
+ [Checkboxes](./data-binding.md#checkboxes)), so the widget is the markup, a
231
+ `<label>` holding the `<input type="checkbox">` and a `<span>` with the
232
+ text. The column and request go on the checkbox inside, so they arrive as
233
+ expansion attributes.
234
+
235
+ Unlike `lb-input`, it has no script making the tag the control. A value
236
+ that lands before a widget's script has loaded arrives only as the string
237
+ stamp `lb-column-value`, and a script checkbox would read `"false"` as
238
+ checked. The hub sets a native checkbox's `checked` directly.
239
+
240
+ | Parameter | Fills |
241
+ |---------------|-----------------------------------------------|
242
+ | `exp-column` | The checkbox's `lb-column` |
243
+ | `exp-label` | The label's text |
244
+ | `exp-request` | The checkbox's `lb-request`, none when absent |
152
245
 
153
246
  ## `lb-unknown-page`
154
247
 
package/docs/roadmap.md CHANGED
@@ -29,12 +29,6 @@ expanding row). [Theory](./theory.md) already flags this as possibly load-bearin
29
29
  disallowed. Worth a decision-in-principle the first time a master-detail
30
30
  page is built, even before the mechanism is needed elsewhere.
31
31
 
32
- ### Pending appearance
33
-
34
- A value that hasn't arrived yet is probably derivable from an absent
35
- `lb-column-value` rather than needing a signal of its own. Not yet needed because
36
- nothing currently produces that gap in practice — revisit if one does.
37
-
38
32
  ### Events while a request is in flight
39
33
 
40
34
  The hub ignores a commit on an element carrying `lb-request-pending`.
@@ -108,6 +102,23 @@ Most valuable against a large imported widget library, where an app uses a
108
102
  small fraction of what ships. Revisit when a real app's `app.css` is big
109
103
  enough to measure.
110
104
 
105
+ ### Options on `createHub`
106
+
107
+ Global hooks, transaction wrapping, error handling and a CSRF token each
108
+ want an option on `createHub`, `hubRoutes`, or both. `hubRoutes` already
109
+ takes a trailing options object, one key per concern. `createHub` takes
110
+ the same shape when its first option arrives, which adds a parameter and
111
+ breaks nothing.
112
+
113
+ ### Queries stated by the chrome
114
+
115
+ A custom element in the chrome naming a query forces every page to declare
116
+ that query, since the page load covers one page's set. `lb-url` shows the
117
+ pattern from the framework side, and an application has no equivalent.
118
+
119
+ A chrome-level query set on `createHub` would close this, and it is additive.
120
+ It takes its place in the [options on `createHub`](#options-on-createhub).
121
+
111
122
  ### Data binding utilities
112
123
 
113
124
  A custom element that finds its own nearest ancestor `lb-query`,
package/docs/testing.md CHANGED
@@ -86,6 +86,9 @@ anything ships:
86
86
  `lb-url-unknown` off a `<dialog>` or outside `<lb-hub>`
87
87
  - `lb-show` on a `<template>`, on a row template's root, or with no row
88
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
89
92
 
90
93
  A page file's `<title>` is lifted out and stamped as `lb-page-title`.
91
94
 
@@ -119,6 +122,13 @@ The rest of the build — `assemble`, `elements`, `pages`, `styles`,
119
122
  `package-css` — is tested the same way and in the same tier, since none of it
120
123
  needs a browser either.
121
124
 
125
+ **The command.** `loadbare-app-build` itself runs in a child process against
126
+ a temporary project, which carries a stand-in `@loadbare/app` pointing at
127
+ the hub's source so the test needs no prior build. It is tested for its
128
+ 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
130
+ watch, and surviving a failed build.
131
+
122
132
  ## Tier 2 — The engine
123
133
 
124
134
  `createHub` takes a plain object and returns an object. Nothing in
@@ -163,14 +173,14 @@ evidence here.
163
173
  the row around it, and the columns inside it are its own query's
164
174
  - a query nothing names is reported and skipped; several elements naming one
165
175
  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
176
+ - all rows decide membership, so a key that did not arrive is gone
177
+ - a patch disturbs only what it names, in contents, and a row moves only
178
+ when the order puts it somewhere else
168
179
  - `lb-query-row-count` is counted from the DOM after reconciliation, so all
169
180
  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.
181
+ - the order places all rows and a patch alike, groups come and go with
182
+ their rows to any depth, aggregates follow every landing, and every change
183
+ leaves a stamp a stylesheet can see (`lb-order.test.ts`)
174
184
  - `lb-show` moves an element into a template and back, and landing reaches
175
185
  inside it
176
186
 
@@ -223,6 +233,8 @@ What the hub is tested for here:
223
233
  `lb-request-error` replaces it on failure, and the next request clears
224
234
  it; `aria-busy` comes and goes with `lb-request-pending`, and a commit on
225
235
  a pending element is ignored
236
+ - a round trip the server never answers is aborted at the ten-second
237
+ deadline, under mocked timers, and fails the way any round trip fails
226
238
  - a successful insert gathered from a form resets the form
227
239
  - a path with no page reports, lands `lb-page-unknown`, and opens the
228
240
  `lb-url-unknown` dialog; two pages for one stub report and take the first
@@ -235,7 +247,7 @@ What the hub is tested for here:
235
247
  - an `lb-url` item in a server answer writes the URL and lands the page
236
248
  loaded there
237
249
  - `hidden` comes off the body once a page has landed, including the page
238
- that failed to load
250
+ that failed to load, and a link whose page fails to load marks no element
239
251
 
240
252
  Widgets are small and their logic is local, so jsdom carries them: a value
241
253
  reaches the control the widget owns, the widget is a form-associated control
@@ -266,9 +278,8 @@ No browser test runner is installed and none of the following exists. They
266
278
  are recorded here as the shape of the work, not as coverage:
267
279
 
268
280
  1. a cold load fills `<main>` with no empty flash
269
- 2. the ten-second deadline aborts a request that never answers
270
- 3. the wizard never displays a step the server has not confirmed
271
- 4. the view-source invariant
281
+ 2. the wizard never displays a step the server has not confirmed
282
+ 3. the view-source invariant
272
283
 
273
284
  The last is the one worth a browser. Loadbare's central claim is that no
274
285
  markup exists in the DOM that is not in view-source, and that the difference
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@loadbare/app",
3
3
  "description": "High performance web app framework for server-bound applications",
4
- "version": "0.12.0",
4
+ "version": "0.14.0",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -22,8 +22,7 @@
22
22
  "./constants": "./dist/core/lb-constants.js",
23
23
  "./types": "./dist/core/lb-types.js",
24
24
  "./server": "./dist/server/lb-server.js",
25
- "./express": "./dist/server/lb-express.js",
26
- "./build": "./dist/build/elements.js"
25
+ "./express": "./dist/server/lb-express.js"
27
26
  },
28
27
  "scripts": {
29
28
  "prepublishOnly": "npm run typecheck && npm run test && npm run build",