@loadbare/app 0.12.0 → 0.13.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 +57 -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 +24 -1
  15. package/dist/core/lb-constants.d.ts.map +1 -1
  16. package/dist/core/lb-constants.js +71 -0
  17. package/dist/core/lb-constants.js.map +1 -1
  18. package/dist/core/lb-types.d.ts +47 -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 +37 -15
  23. package/dist/hub/lb-apply.d.ts.map +1 -1
  24. package/dist/hub/lb-apply.js +598 -56
  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 +61 -14
  28. package/dist/hub/lb-hub.browser.js.map +1 -1
  29. package/dist/server/lb-express.d.ts +6 -4
  30. package/dist/server/lb-express.d.ts.map +1 -1
  31. package/dist/server/lb-express.js +32 -14
  32. package/dist/server/lb-express.js.map +1 -1
  33. package/dist/server/lb-server.d.ts +49 -10
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/server/lb-server.js +76 -34
  36. package/dist/server/lb-server.js.map +1 -1
  37. package/docs/TECHREF-1.0.md +292 -92
  38. package/docs/comparison.md +11 -9
  39. package/docs/possible-ideas.md +188 -0
  40. package/docs/reference/chrome.md +7 -1
  41. package/docs/reference/custom-elements.md +37 -38
  42. package/docs/reference/data-binding.md +102 -13
  43. package/docs/reference/page-files.md +25 -16
  44. package/docs/reference/server.md +4 -0
  45. package/docs/reference/widgets.md +94 -24
  46. package/docs/roadmap.md +10 -6
  47. package/docs/testing.md +21 -10
  48. package/package.json +2 -3
  49. package/skills/loadbare-app/SKILL.md +43 -24
  50. package/skills/loadbare-app/references/TECHREF-1.0.md +292 -92
  51. package/skills/loadbare-app/references/chrome.md +7 -1
  52. package/skills/loadbare-app/references/custom-elements.md +37 -38
  53. package/skills/loadbare-app/references/data-binding.md +102 -13
  54. package/skills/loadbare-app/references/page-files.md +25 -16
  55. package/skills/loadbare-app/references/server.md +4 -0
  56. package/skills/loadbare-app/references/widgets.md +94 -24
@@ -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
+ Ten widgets: `lb-input`, `lb-select`, `lb-options`, `lb-picker`, `lb-table`,
4
+ `lb-unknown-page`, `lb-confirm`, `lb-form`, `lb-insert-dialog` and
5
+ `lb-master`. 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
 
@@ -43,6 +44,7 @@ write the same edit twice.
43
44
  | Parameter | Fills |
44
45
  |----------------|----------------------------------|
45
46
  | `exp-label` | The visible `<label>` text |
47
+ | `exp-type` | The input's `type`, `text` when absent |
46
48
  | `exp-readonly` | The input's `readonly` attribute |
47
49
 
48
50
  ## `lb-select`
@@ -67,9 +69,7 @@ writes the row template inside the widget:
67
69
 
68
70
  ```html
69
71
  <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>
72
+ <template><option lb-column="label"></option></template>
73
73
  </lb-options>
74
74
  ```
75
75
 
@@ -79,9 +79,15 @@ writes the row template inside the widget:
79
79
 
80
80
  - Each option's `value` is its row's key, the `lb-key-value` the hub stamps
81
81
  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.
82
+ - A group template holding an `<optgroup>` puts the options in groups, one
83
+ per run of the query's order's leading term. The widget labels each
84
+ `<optgroup>` from the `lb-group-value` the hub stamps on it:
85
+
86
+ ```html
87
+ <template lb-group>
88
+ <optgroup><template><option lb-column="label"></option></template></optgroup>
89
+ </template>
90
+ ```
85
91
  - Its `value` selects the option with that key, including an option that
86
92
  arrives after the value did.
87
93
 
@@ -100,7 +106,6 @@ one option showing one column:
100
106
  lb-request="lb-row-update"
101
107
  exp-label="Status"
102
108
  exp-column="label"
103
- exp-group="category"
104
109
  ></lb-picker>
105
110
  ```
106
111
 
@@ -108,10 +113,9 @@ one option showing one column:
108
113
  |--------------|-----------------------------------------|
109
114
  | `exp-label` | The visible `<label>` text |
110
115
  | `exp-column` | The column each option shows |
111
- | `exp-group` | The row template's `data-group` |
112
116
 
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.
117
+ An option built from two columns, a row with a second element, or options
118
+ in groups, is `lb-options` with the page's own templates.
115
119
 
116
120
  ## `lb-table`
117
121
 
@@ -123,11 +127,14 @@ row, the row template, and optionally a footer, each as a `<template>`:
123
127
  <template lb-exp-template="head">
124
128
  <tr><th>Date</th><th>Amount</th></tr>
125
129
  </template>
126
- <template data-sort="date" data-group="month">
127
- <tr><td lb-column="date"></td><td lb-column="amount"></td></tr>
130
+ <template lb-group>
131
+ <tr><th colspan="2" lb-column="month"></th></tr>
132
+ <template>
133
+ <tr><td lb-column="date"></td><td lb-column="amount"></td></tr>
134
+ </template>
128
135
  </template>
129
136
  <template lb-exp-template="foot">
130
- <tr lb-query="ledger-total"><td>Total</td><td lb-column="total"></td></tr>
137
+ <tr><td>Total</td><td lb-sum="amount"></td></tr>
131
138
  </template>
132
139
  </lb-table>
133
140
  ```
@@ -140,15 +147,78 @@ row, the row template, and optionally a footer, each as a `<template>`:
140
147
  |-----------------------------------|-----------------------------------------|
141
148
  | `<template lb-exp-template="head">` | The `<thead>` |
142
149
  | `<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.
150
+ | Any other `<template>` | The row or group template, in the `<tbody>` |
151
+
152
+ - The hub places the rows by the query's order and builds its groups. The
153
+ rows land in the one `<tbody>`, so a group here is a heading row with its
154
+ rows after it.
155
+ - The footer is not a row of `ledger`. An aggregate there covers every row;
156
+ a figure that is not a sum of the rows, such as a budget, is a `<tr>`
157
+ naming a `row` query of its own.
158
+ - A `<template lb-exp-template="ghost">` is a blank row for entering a new
159
+ one, in a `<tbody>` of its own. The widget joins its controls to a form
160
+ outside the table, since a `<form>` cannot wrap a `<tr>`, and puts the
161
+ cursor back in it once an insert from it succeeds.
162
+ - After rows land, the widget scrolls the row the page's own request created
163
+ or moved into view.
164
+
165
+ ## `lb-confirm`
166
+
167
+ A button that asks before it sends. The page writes the question inside the
168
+ tag, and the request goes on the dialog's confirm button, so nothing reaches
169
+ the hub until the user says yes:
170
+
171
+ ```html
172
+ <lb-confirm exp-label="Delete" exp-request="lb-row-delete">
173
+ Delete <b lb-column="name"></b>?
174
+ </lb-confirm>
175
+ ```
176
+
177
+ | Parameter | Fills |
178
+ |---------------|--------------------------------------------------|
179
+ | `exp-label` | The text of the button that opens the dialog |
180
+ | `exp-request` | The confirm button's `lb-request` |
181
+ | `exp-confirm` | The confirm button's text, `Confirm` when absent |
182
+
183
+ The widget closes the dialog once `lb-request-done` says the request
184
+ succeeded, and leaves it open on a refusal.
185
+
186
+ ## `lb-form`
187
+
188
+ A `<form>` over one row's controls. A row with a key saves each control's
189
+ column on `change`; a row whose key is empty is new, and the widget stops an
190
+ `lb-row-update` that carries no key, so its controls wait for a submit.
191
+
192
+ | Parameter | Fills |
193
+ |-----------|-----------------|
194
+ | `exp-id` | The form's `id` |
195
+
196
+ ## `lb-insert-dialog`
197
+
198
+ A row entered in a modal dialog, written once outside every row and opened
199
+ with `commandfor` and `command="show-modal"`. Save is the form's
200
+ `lb-row-insert`; on success the widget chooses the new row in the nearest
201
+ `lb-options` around the button that opened it, and closes. The handler
202
+ answers with a patch holding the new row.
203
+
204
+ | Parameter | Fills |
205
+ |-------------|---------------------------------------------------|
206
+ | `exp-id` | The dialog's `id` |
207
+ | `exp-title` | The dialog's heading |
208
+ | `exp-query` | The form's `lb-query`, which it inserts into |
209
+ | `exp-save` | The Save button's text, `Save` when absent |
210
+
211
+ ## `lb-master`
212
+
213
+ A set, one of its rows, and the buttons that act on the set: Copy, Delete,
214
+ Save new and Cancel, each shown only for a row on record or a new one. A
215
+ definition only, built from `lb-form` and `lb-confirm`. Copy sends `copy`, a
216
+ request the page declares.
217
+
218
+ | Parameter | Fills |
219
+ |---------------|--------------------------------------------|
220
+ | `exp-row` | The `lb-query` of the row the form shows |
221
+ | `exp-form-id` | The form's `id`, `master-form` when absent |
152
222
 
153
223
  ## `lb-unknown-page`
154
224
 
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,16 @@ 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
+ ### Queries stated by the chrome
106
+
107
+ A custom element in the chrome naming a query forces every page to declare
108
+ that query, since the page load covers one page's set. `lb-url` shows the
109
+ pattern from the framework side, and an application has no equivalent.
110
+
111
+ A chrome-level query set on `createHub` would close this, and it is additive.
112
+ It takes its place in the options argument whose shape is a 1.0 blocker in
113
+ [TECHREF-1.0](./TECHREF-1.0.md#the-server-api).
114
+
111
115
  ### Data binding utilities
112
116
 
113
117
  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.13.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",
@@ -176,12 +176,35 @@ from it.
176
176
 
177
177
  **A column never holds rows.** Master-detail is a `row` query and a `rows`
178
178
  query under two names. Many masters with their details is one `rows` query
179
- of joined rows, grouped for display by a custom element's `lbPlaceRow`. A
179
+ of joined rows, ordered by the master first and shown in groups. A
180
180
  query nested inside another query's row template receives the same rows in
181
181
  every outer row; use it for a picker, never for per-row detail. A new outer
182
182
  row is filled from the nested query's last answer, whatever order they land
183
183
  in.
184
184
 
185
+ **A `rows` query declares its order, and the hub places every row by it.**
186
+ `rows("id", run, { order: "category_order,name" })`; a column after `-` sorts
187
+ descending, and values compare by JSON type, so a numeric sort column is a
188
+ JSON number. The parm `lb-order-<query>` replaces the order, and a control
189
+ writing it inside `lb-query="lb-url"` re-sorts with no round trip. A query
190
+ that pages or limits reads the parm itself and declares
191
+ `serverSortedByUrl: true`. Never sort in a widget, and never send a
192
+ placeholder row to hold a place.
193
+
194
+ **Groups are templates.** A `<template lb-group>` holds a heading and one
195
+ nested `<template>`, the next level or the row template; the nth breaks on
196
+ the order's nth term, to any depth. Contents land before the nested
197
+ template, so put it inside the heading's element for a container and beside
198
+ the heading for a heading row. `<tbody>` and `<optgroup>` do not nest. The
199
+ heading is filled from the group's first row. `lb-count`, `lb-sum="col"`,
200
+ `lb-avg`, `lb-min` and `lb-max` total the nearest group, or the whole list
201
+ outside any.
202
+
203
+ **Animate with CSS.** The hub stamps `lb-row-created`, `lb-row-changed`,
204
+ `lb-row-moved`, and `lb-row-requested` for this page's request, and marks a
205
+ leaving row or group `lb-row-leaving` or `lb-group-leaving`, removing it once
206
+ its animations end. Scrolling to a row is script, in `lbRowsLanded`.
207
+
185
208
  **Every request has one shape.** `lb-request` names the request, and the
186
209
  hub sends `{ name, query, key, values }` as present: `query` from the
187
210
  nearest ancestor `lb-query`, `key` from the nearest ancestor row of that
@@ -226,19 +249,16 @@ page may edit.
226
249
 
227
250
  **Return a patch when the change has a known extent.** `patch({ rows })`
228
251
  for rows added or edited, `patch({ drop })` for keys removed, with
229
- `refresh: []`. A refreshed `rows` query sends every row, and the hub places
230
- every row again, which moves each element and takes focus from the control
231
- the user is in. List a query in `refresh` only when its membership or order
232
- changed in a way the handler cannot name. Never list one only to fill the
252
+ `refresh: []`. A refreshed `rows` query sends every row to say what one row
253
+ could. List a query in `refresh` only when its membership changed in a way
254
+ the handler cannot name. Never list one only to fill the
233
255
  picker in a row the request adds: its answer did not change, and the hub
234
256
  fills a new row from the last one.
235
257
 
236
258
  **The usual reasons for a refresh each have a patch.**
237
- - A placeholder row standing in for an empty set or section: drop its key
238
- in the patch that adds the first real row, and add it back in the patch
239
- that drops the last.
240
- - A row whose position changes: the rows host places it, as `lb-table` does
241
- by `data-sort`.
259
+ - A row whose position changes: the hub places it by the query's order.
260
+ - A group that appears or goes: the hub makes it with its first row and
261
+ removes it with its last.
242
262
  - A row whose other columns change with the edit: re-read the row and patch
243
263
  it.
244
264
  - Rows the database removes with a deleted one: select their keys before
@@ -247,10 +267,8 @@ fills a new row from the last one.
247
267
  An update or a delete names its row by key, and removing a row never
248
268
  reorders the rest, so each always has a patch: `createHub` refuses at
249
269
  startup a `rowUpdate` or `rowDelete` whose `refresh` names its own `rows`
250
- query. An insert may refresh its own query, and should only when the new
251
- row's place matters and nothing places it: a patched row lands where its
252
- host puts it, in order under `lb-table` with `data-sort`, last in plain
253
- markup and in `lb-options`.
270
+ query. An insert may refresh its own query, though a patch of the new row
271
+ lands in its place by the order.
254
272
  Each handler's `refresh` is its own, and every query in it needs its own
255
273
  reason. A list shared by several handlers is a warning sign.
256
274
 
@@ -327,24 +345,24 @@ on `change`. Any other custom element keeps its content and receives the
327
345
  column as its `lb-column-value` attribute.
328
346
 
329
347
  **A custom element connects many times.** It is moved, never rebuilt: the
330
- hub places every live row again on each set of all rows, and parks an
331
- `lb-show` element in a template while it is off, so `connectedCallback` runs
332
- on every move. Listen on the element itself in the constructor, look a
348
+ hub moves a live row the order puts elsewhere, and parks an `lb-show`
349
+ element in a template while it is off, so `connectedCallback` runs on every
350
+ move. Listen on the element itself in the constructor, look a
333
351
  child up when it is needed, and in a control take up `lb-column-value` once,
334
352
  on first connect, since a value can land before the element upgrades.
335
353
 
336
354
  **The hub and a custom element talk in four ways, one per kind of
337
355
  message.** State the hub gives an element is an attribute stamp, or `value`
338
356
  on a control. Work the hub needs done while landing is an optional `lb`
339
- method: `lbPlaceRow`, `lbRowsLanded`. A moment is a bubbling event: an
357
+ method: `lbRowsLanded`. A moment is a bubbling event: an
340
358
  element sends `lb-request`, and the hub answers on the same element with
341
359
  `lb-request-done`, carrying the request and the response items that landed,
342
360
  or the error. Listen for `lb-request-done` on whichever ancestor needs the
343
361
  outcome, such as a dialog choosing the row its form just created.
344
362
 
345
363
  **A widget that holds rows may sit under `lb-show`.** Rows that land while
346
- it is away are placed by its `lbPlaceRow` once it first appears, so hide a
347
- table with `lb-show`, not with a stylesheet rule.
364
+ it is away are placed by the hub, and its `lbRowsLanded` runs once it first
365
+ appears, so hide a table with `lb-show`, not with a stylesheet rule.
348
366
 
349
367
  **Name a custom element script `<tag>.browser.ts`.** A plain `<tag>.ts`
350
368
  stays on the server and the tag goes unregistered.
@@ -372,12 +390,13 @@ parser loses the tag before the build can report it.
372
390
 
373
391
  Reach for one only when plain HTML cannot do the job. A set of rows, a form
374
392
  and a condition need none. A custom element exists to be a control of its
375
- own, or to place and scaffold rows through the two hooks the hub calls,
376
- `lbPlaceRow` and `lbRowsLanded` (the `RowsHost` interface).
393
+ own, or to act on rows once they land through the hook the hub calls,
394
+ `lbRowsLanded` (the `RowsHost` interface).
377
395
 
378
396
  Before writing one, check `@loadbare/widgets`: `lb-input`, `lb-select`,
379
- `lb-options`, `lb-picker`, `lb-table`, `lb-unknown-page`. The first four
380
- are form-associated controls that carry `lb-column` and `lb-request`
397
+ `lb-options`, `lb-picker`, `lb-table`, `lb-unknown-page`, `lb-confirm`,
398
+ `lb-form`, `lb-insert-dialog`, `lb-master`. The first four are
399
+ form-associated controls that carry `lb-column` and `lb-request`
381
400
  themselves. Install the package and list it in `src/imports.ts`:
382
401
 
383
402
  ```ts