@loadbare/app 0.9.0 → 0.11.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 (82) hide show
  1. package/README.md +3 -3
  2. package/dist/build/assemble.d.ts.map +1 -1
  3. package/dist/build/assemble.js +107 -90
  4. package/dist/build/assemble.js.map +1 -1
  5. package/dist/build/expand.d.ts +6 -1
  6. package/dist/build/expand.d.ts.map +1 -1
  7. package/dist/build/expand.js +112 -26
  8. package/dist/build/expand.js.map +1 -1
  9. package/dist/build/locations.d.ts +2 -3
  10. package/dist/build/locations.d.ts.map +1 -1
  11. package/dist/build/locations.js +2 -3
  12. package/dist/build/locations.js.map +1 -1
  13. package/dist/build/pages.d.ts +3 -4
  14. package/dist/build/pages.d.ts.map +1 -1
  15. package/dist/build/pages.js +3 -4
  16. package/dist/build/pages.js.map +1 -1
  17. package/dist/core/lb-constants.d.ts +27 -24
  18. package/dist/core/lb-constants.d.ts.map +1 -1
  19. package/dist/core/lb-constants.js +103 -168
  20. package/dist/core/lb-constants.js.map +1 -1
  21. package/dist/core/lb-types.d.ts +64 -77
  22. package/dist/core/lb-types.d.ts.map +1 -1
  23. package/dist/core/lb-types.js +40 -7
  24. package/dist/core/lb-types.js.map +1 -1
  25. package/dist/hub/lb-apply.d.ts +47 -37
  26. package/dist/hub/lb-apply.d.ts.map +1 -1
  27. package/dist/hub/lb-apply.js +195 -199
  28. package/dist/hub/lb-apply.js.map +1 -1
  29. package/dist/hub/lb-hub.browser.d.ts +1 -1
  30. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  31. package/dist/hub/lb-hub.browser.js +410 -449
  32. package/dist/hub/lb-hub.browser.js.map +1 -1
  33. package/dist/server/lb-express.d.ts +5 -5
  34. package/dist/server/lb-express.d.ts.map +1 -1
  35. package/dist/server/lb-express.js +35 -66
  36. package/dist/server/lb-express.js.map +1 -1
  37. package/dist/server/lb-server.d.ts +77 -135
  38. package/dist/server/lb-server.d.ts.map +1 -1
  39. package/dist/server/lb-server.js +132 -79
  40. package/dist/server/lb-server.js.map +1 -1
  41. package/docs/TECHREF-1.0.md +908 -585
  42. package/docs/comparison.md +243 -185
  43. package/docs/prior-art.md +15 -14
  44. package/docs/reference/builder.md +9 -3
  45. package/docs/reference/chrome.md +107 -56
  46. package/docs/reference/custom-elements.md +291 -173
  47. package/docs/reference/data-binding.md +381 -374
  48. package/docs/reference/overview.md +12 -10
  49. package/docs/reference/page-files.md +164 -99
  50. package/docs/reference/server.md +2 -2
  51. package/docs/reference/widgets.md +104 -110
  52. package/docs/roadmap.md +32 -39
  53. package/docs/terms-of-art.md +57 -0
  54. package/docs/testing.md +97 -68
  55. package/docs/theory.md +92 -58
  56. package/docs/tutorials/010-pages-and-navigation.md +20 -12
  57. package/docs/tutorials/020-css.md +6 -3
  58. package/docs/tutorials/030-html-decomposition.md +9 -7
  59. package/docs/tutorials/040-displaying-data.md +30 -13
  60. package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
  61. package/docs/tutorials/060-custom-element-code.md +17 -16
  62. package/docs/tutorials/065-conditional-rendering.md +34 -23
  63. package/docs/tutorials/070-displaying-a-list.md +29 -21
  64. package/docs/tutorials/072-inserting-into-a-list.md +24 -16
  65. package/docs/tutorials/074-deleting-from-a-list.md +9 -7
  66. package/docs/tutorials/076-updating-a-list-item.md +11 -10
  67. package/docs/tutorials/080-widget-requests.md +71 -43
  68. package/docs/tutorials/090-using-widget-libraries.md +22 -22
  69. package/docs/what-does-loadbare-extend.md +124 -0
  70. package/package.json +1 -1
  71. package/skills/loadbare-app/SKILL.md +201 -123
  72. package/skills/loadbare-app/references/TECHREF-1.0.md +908 -585
  73. package/skills/loadbare-app/references/builder.md +9 -3
  74. package/skills/loadbare-app/references/chrome.md +107 -56
  75. package/skills/loadbare-app/references/custom-elements.md +291 -173
  76. package/skills/loadbare-app/references/data-binding.md +381 -374
  77. package/skills/loadbare-app/references/overview.md +12 -10
  78. package/skills/loadbare-app/references/page-files.md +164 -99
  79. package/skills/loadbare-app/references/server.md +2 -2
  80. package/skills/loadbare-app/references/widgets.md +104 -110
  81. package/docs/analysis-accidental-complexity.md +0 -149
  82. package/docs/analysis-closed-set.md +0 -210
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: loadbare-app
3
- description: Build server-bound web applications with Loadbare/app. Covers the file conventions the builder finds by name (`chrome.html`, `*.page.html`, `*.queries.ts`, `*.requests.ts`, `imports.ts`, `<tag>.html`, `<tag>.browser.ts`); the `lb-*` attribute vocabulary that binds HTML to server data and sends requests (`lb-list`, `lb-row`, `lb-cell`, `lb-key`, `lb-show`, `lb-action`); query parms in the URL with `lb-query-parm`; build-time widget expansion with `exp-*` parameters, `lb-slot` and `lb-template`; custom element code; the Express wiring with `hubRoutes`; and the `@loadbare/widgets` library. Use whenever a task involves `@loadbare/app`, `@loadbare/widgets`, `loadbare-app-build`, an `lb-` attribute, or a page, query, request or widget file in a Loadbare application. The model is not React, htmx or REST, and cannot be inferred from them.
3
+ description: Build server-bound web applications with Loadbare/app. Covers the file conventions the builder finds by name (`chrome.html`, `*.page.html`, `*.queries.ts`, `*.requests.ts`, `imports.ts`, `<tag>.html`, `<tag>.browser.ts`); the `lb-*` attribute vocabulary that lands query rows on HTML and issues requests (`lb-query`, `lb-column`, `lb-show`, `lb-request`); queries declared with `row(key, run)` and `rows(key, run)`; the URL as the hub's own query `lb-url`, with `lb-url-link`, `lb-url-push` and `lb-url-unknown`; build-time expansion with `exp-*` parameters, `lb-exp-slot` and `lb-exp-template`; custom elements as form-associated controls and row hosts; the Express wiring with `hubRoutes`; and the `@loadbare/widgets` library. Use whenever a task involves `@loadbare/app`, `@loadbare/widgets`, `loadbare-app-build`, an `lb-` attribute, or a page, query, request or custom element file in a Loadbare application. The model is not React, htmx or REST, and cannot be inferred from them.
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  package: "@loadbare/app"
@@ -11,17 +11,19 @@ metadata:
11
11
 
12
12
  Loadbare/app builds a whole application into one HTML document, one client
13
13
  script and one stylesheet. The server answers named queries with rows and
14
- performs named requests; the browser lands those rows on elements that name
15
- them. No component renders anything, and no page fetches anything.
14
+ runs named requests; the hub in the browser lands those rows on the elements
15
+ that name them. No component renders anything, and no page fetches
16
+ anything.
16
17
 
17
- The wire is relational. A name answers with one row or a set of rows, a
18
- row holds cells, and a cell holds one value. Hold on to that: most wrong
19
- designs come from sending a shape a relational answer cannot have.
18
+ Everything is a query. A query is a name for rows, of kind `row` or `rows`,
19
+ with a key column. A row holds columns, and a column holds one value. The
20
+ URL itself is a query, `lb-url`, that the hub serves. Hold on to that: most
21
+ wrong designs come from sending a shape a relational answer cannot have.
20
22
 
21
23
  ## Build every change before handing it over
22
24
 
23
- `loadbare-app-build` is the checker. It expands every page and widget and
24
- refuses what it cannot ship, naming the tag and the file:
25
+ `loadbare-app-build` is the checker. It expands every page and custom
26
+ element and refuses what it cannot ship, naming the tag and the file:
25
27
 
26
28
  ```bash
27
29
  loadbare-app-build --src src --out dist
@@ -40,9 +42,9 @@ tsc --noEmit
40
42
  `dist/pages.ts` imports page files with their `.ts` extensions, so the
41
43
  application's `tsconfig.json` needs `allowImportingTsExtensions`.
42
44
 
43
- **Write, build, fix, repeat until it is clean.** Then start the server:
44
- `createHub` refuses a query that answers with the wrong shape and a page
45
- that declares a reserved name, and those refusals only appear at run time.
45
+ Write, build, fix, and repeat until the build is clean. Then start the
46
+ server: `createHub` refuses a page that declares a reserved name at startup,
47
+ and the server console reports a query that answers with the wrong kind.
46
48
  Restart the server after adding or changing a `.queries.ts` or
47
49
  `.requests.ts` file.
48
50
 
@@ -58,9 +60,9 @@ src/
58
60
  pages/
59
61
  index.page.html the page for /
60
62
  members.page.html the page for /members
61
- members.queries.ts what members displays
62
- members.requests.ts what members may be asked to do
63
- widgets/
63
+ members.queries.ts the queries members shows
64
+ members.requests.ts the requests members answers
65
+ elements/
64
66
  note-card.html the markup <note-card> expands into
65
67
  visit-count.browser.ts the class <visit-count> registers
66
68
  server.ts
@@ -74,18 +76,22 @@ A page is an HTML fragment that lands in `<main>`:
74
76
 
75
77
  ```html
76
78
  <!-- src/pages/members.page.html -->
77
- <p lb-row="dues">Collected this year: <span lb-cell="total"></span></p>
79
+ <title>Members</title>
80
+ <h1>Members</h1>
78
81
 
79
- <section lb-list="roster">
80
- <form lb-action="lb-row-insert">
81
- <input lb-cell="name" />
82
- <button type="submit">Add</button>
82
+ <p lb-query="dues">Dues collected this year: <span lb-column="total"></span></p>
83
+
84
+ <section lb-query="roster">
85
+ <form lb-request="lb-row-insert">
86
+ <input lb-column="name" placeholder="Name" />
87
+ <button type="submit">Add member</button>
83
88
  </form>
89
+
84
90
  <ul>
85
- <template lb-key="id">
91
+ <template>
86
92
  <li>
87
- <span lb-cell="name"></span>
88
- <button lb-action="lb-row-delete">Remove</button>
93
+ <input lb-column="name" lb-request="lb-row-update" />
94
+ <button lb-request="lb-row-delete">Remove</button>
89
95
  </li>
90
96
  </template>
91
97
  </ul>
@@ -94,11 +100,11 @@ A page is an HTML fragment that lands in `<main>`:
94
100
 
95
101
  ```ts
96
102
  // src/pages/members.queries.ts
97
- import { list, row, type Queries } from "@loadbare/app/server";
103
+ import { row, rows, type Queries } from "@loadbare/app/server";
98
104
 
99
105
  export const queries: Queries = {
100
- dues: row((ctx) => ctx.db.duesTotal()),
101
- roster: list((ctx) => ctx.db.members()),
106
+ dues: row("year", (ctx) => ctx.db.duesTotal()),
107
+ roster: rows("id", (ctx) => ctx.db.members()),
102
108
  };
103
109
  ```
104
110
 
@@ -115,6 +121,12 @@ export const requests: Requests = {
115
121
  }),
116
122
  refresh: ["dues"],
117
123
  },
124
+ rowUpdate: {
125
+ run: async (ctx, { key, values }) => ({
126
+ roster: patch({ rows: [await ctx.db.updateMember(key, values)] }),
127
+ }),
128
+ refresh: [],
129
+ },
118
130
  rowDelete: {
119
131
  run: async (ctx, { key }) => {
120
132
  await ctx.db.removeMember(key);
@@ -133,122 +145,186 @@ Re-read this list when a build fails or a design will not fit. Each item is
133
145
  a place a reasonable instinct from React, Vue, htmx or REST produces an
134
146
  error or a dead end.
135
147
 
148
+ **The server declares every query's kind and key.** Declare each query with
149
+ `row(key, run)` or `rows(key, run)`, key column first. The markup names the
150
+ query with `lb-query` and the columns it shows with `lb-column`, and repeats
151
+ neither the kind nor the key. A page that needs the same data as one row
152
+ and as a set declares two queries. Every query has a key; an aggregate row
153
+ answers with a constant one.
154
+
136
155
  **There is no template language.** No `{#if}`, no `v-for`, no `map()`. A
137
- list is an element carrying `lb-list` with a `<template lb-key="...">`
138
- inside it, and the hub clones the template once per row. A condition is
139
- `lb-show="<column>"`: write every possibility into the page and let a column
140
- decide which is present. The column must be a boolean or null; the string
141
- `"false"` counts as present.
156
+ set of rows is an element carrying `lb-query` with a `<template>` inside it,
157
+ the row template, which the hub clones once per row. A `row` query with no
158
+ row template lands on the element itself. A `rows` query with no row
159
+ template lands nothing, which is what an insert form naming its query is. A
160
+ condition is `lb-show="<column>"`: write every possibility into the page and
161
+ let a column decide which is present. The column is a boolean or null; the
162
+ string `"false"` counts as present.
142
163
 
143
164
  **`{{placeholder}}` is build time only.** It reads an `exp-` attribute on
144
165
  the tag, never data. Write it as an entire attribute value or an entire
145
166
  text node. Name parameters in lowercase with hyphens: HTML lowercases
146
167
  `exp-inputClass` before expansion sees it.
147
168
 
148
- **Scope comes from ancestry, not props.** An `lb-cell` binds to the row on
149
- its nearest ancestor carrying `lb-row` or a live list row. An element
150
- outside every scope displays nothing. A value of `lb-cell` is a column name.
169
+ **Position comes from ancestry, not props.** `lb-column` and `lb-show` read
170
+ from the nearest ancestor row: a live row, or an element a `row` query
171
+ landed on. A nested `lb-query` begins a new query. An element with
172
+ `lb-column` and no ancestor row receives nothing, and the hub still gathers
173
+ from it.
174
+
175
+ **A column never holds rows.** Master-detail is a `row` query and a `rows`
176
+ query under two names. Many masters with their details is one `rows` query
177
+ of joined rows, grouped for display by a custom element's `lbPlaceRow`. A
178
+ query nested inside another query's row template receives the same rows in
179
+ every outer row; use it for a picker, never for per-row detail.
180
+
181
+ **Every request has one shape.** `lb-request` names the request, and the
182
+ hub sends `{ name, query, key, values }` as present: `query` from the
183
+ nearest ancestor `lb-query`, `key` from the nearest ancestor row of that
184
+ query, and `values` gathered from controls by their `lb-column`.
185
+
186
+ **An element commits on its own event.** A form on `submit`, a control on
187
+ `change`, anything else on `click`. A click on interactive content inside
188
+ the element (a link, a button, a control, a label) belongs to that content
189
+ and commits nothing. A submit button never commits on click;
190
+ on submit, the submitter's `lb-request` wins over the form's. A native
191
+ `<input lb-column="name" lb-request="lb-row-update">` saves that one column
192
+ on `change`. Inside a form, leave `lb-request` off the controls and put it
193
+ on the form.
194
+
195
+ **Gathering follows the form, the live row, or the form owner.** An element
196
+ with `lb-column` gathers its own value alone. A `<form>` gathers every
197
+ control whose form owner it is. An element in a live row gathers that live
198
+ row. Anything else gathers its form owner's controls. A `<form>` cannot
199
+ wrap a `<tr>`, so an insert row in a table puts its controls in a form
200
+ elsewhere with the HTML `form` attribute. Checkboxes, radio buttons and
201
+ file inputs are not controls here: they receive no value and are not
202
+ gathered.
203
+
204
+ **Three requests are Loadbare's.** `lb-row-insert` needs `query` and
205
+ `values`, `lb-row-update` needs `query`, `key` and `values`, and
206
+ `lb-row-delete` needs `query` and `key`. The page permits each under
207
+ `crud`, keyed by query name, as `rowInsert`, `rowUpdate` and `rowDelete`.
208
+ Each works on a `row` query as well as a `rows` query, since the element a
209
+ `row` lands on carries its key. Every other request name is a declared
210
+ request, under `handlers`. Anything undeclared is refused.
211
+
212
+ **Write `rowUpdate` as a partial update.** A form sends every control it
213
+ owns; a single control sends its one column. Set the columns `values`
214
+ names, leave the rest alone, and check the names against the columns the
215
+ page may edit.
151
216
 
152
- **One name, one cardinality.** Declare every query with `row()` or
153
- `list()`. A page that needs the same data as a row and as a set declares
154
- two queries.
217
+ **Return a patch when the change has a known extent.** `patch({ rows })`
218
+ for rows added or edited, `patch({ drop })` for keys removed, with
219
+ `refresh: []`. List a query in `refresh` only when its membership or order
220
+ changed in a way the handler cannot name.
155
221
 
156
- **A cell never holds rows.** Master-detail is a row and a list under two
157
- names. Many masters with their details is one list of joined rows, grouped
158
- for display by a widget's `lbPlaceRow`. A list nested inside another list's
159
- rows receives the same rows in every outer row; use it for a picker, never
160
- for per-row detail.
222
+ **A page's queries are its view model, not a data layer.** Each answers
223
+ one page's elements in the form they show: formatted dates and amounts, and
224
+ the words a reader sees, such as "12 postings". The hub does no type
225
+ conversion, so what a number, date or null looks like is decided on the
226
+ server. A value the page reads back stays data: a column on a control, the
227
+ key, an `lb-show` condition, a value a stylesheet selects on. What pages
228
+ share goes beneath them, in a database view or a helper module, never in a
229
+ layer between the page and its queries.
161
230
 
162
231
  **A URL's path names a page, never a resource.** Do not design
163
- `/accounts/42`. A row is addressed by `list` and `key`, taken from where the
164
- element sits.
232
+ `/accounts/42`. A row is addressed by `query` and `key`, taken from where
233
+ the element sits.
165
234
 
166
235
  **The query string is what the page has on screen.** Which record a page
167
236
  shows, a filter, a date range: each is a query parm,
168
237
  `/accounts?acct=23&from=2026-09-09`, so a reload, a bookmark or a mailed link
169
- shows the same thing. A control writes one with `lb-query-parm="acct"`, which
170
- replaces the history entry, reloads the page at the new URL, and sends no
171
- request. A link writes several with an ordinary `lb-nav-link` href. Queries
172
- still take no arguments: `contextFor(req, parms)` puts parms onto `ctx`, and
173
- a query reads them there. Read them from `parms`, never `req.query`, because
174
- `contextFor` may be called twice for one request. A parm is user input, so
175
- validate it where you read it. Do not keep a selection in server state set
176
- by an action; that forces `refresh: []` and a hand-written re-answer of
177
- everything the selection touches.
178
-
179
- **When a server response changes the query parms.** After an insert, only
180
- the server knows the new key. After a delete, only the server knows the parm
181
- should go. Have `run` return `queryParms({ acct: String(id) })`, or
182
- `queryParms({ acct: "" })` to remove the parm. The page loads at the new
183
- query string in the same round trip, and the history entry is replaced to
184
- match. This is Post/Redirect/Get without the redirect: the path never
185
- changes, and there is no second request. Do not reach for a redirect, a
186
- hand-written refresh of every query, or server state holding the selection.
187
- See [refresh and patch](references/page-files.md#refresh-and-patch).
238
+ shows the same thing. The hub serves the query `lb-url`, of kind `row`,
239
+ keyed by `lb-path`, with `lb-page-label`, `lb-page-unknown`, and one column
240
+ per query parm. A control inside `lb-query="lb-url"` carrying
241
+ `lb-request="lb-row-update"` writes its parm; the hub replaces the history
242
+ entry, or pushes one when the control carries `lb-url-push`, and reloads the
243
+ page's queries with no request to the server. An `<a lb-url-link>` moves
244
+ to its `href`. Queries still take no arguments: `contextFor(req, parms)`
245
+ puts parms onto `ctx`, and a query reads them there. Read them from
246
+ `parms`, never `req.query`, because `contextFor` may be called twice for one
247
+ request. A parm is user input, so validate it where it is read. Do not
248
+ keep a selection in server state set by a handler.
249
+
250
+ **A handler moves the URL with `url()`.** After an insert, only the server
251
+ knows the new key; after a delete, only the server knows the parm should
252
+ go. Have `run` return `url({ acct: String(id) })`, or `url({ acct: "" })`
253
+ to remove the parm, or `url({ "lb-path": "/accounts", acct: "5" })` to
254
+ enter another page with only the parms named. The page loads at the new URL
255
+ in the same round trip. This is Post/Redirect/Get without the redirect. See
256
+ [moving the URL](references/page-files.md#moving-the-url).
257
+
258
+ **A page names itself with `<title>`.** The builder lifts a page file's
259
+ `<title>` and the hub shows it as `lb-page-label` and as the document title.
260
+ A page without one is labeled by its stub.
188
261
 
189
262
  **Loadbare is for applications, not sites.** Every route is answered with
190
263
  `app.html`, and a path that names no page is found in the browser, not
191
- answered with a 404. That is the design, not a defect to report.
264
+ answered with a 404. The hub opens a `<dialog lb-url-unknown
265
+ lb-query="lb-url">` in the chrome for it.
192
266
 
193
267
  **There are no endpoints to write.** `hubRoutes(hub, contextFor)` is the
194
- whole data channel. Declare every action under `actions` and every
195
- permitted operation under `crud`; anything undeclared is refused.
196
-
197
- **All three CRUD operations are list operations.** Each needs a key, and a
198
- key exists only on a live row inside `lb-list`. An `lb-row` scope is
199
- read-only; give a writable single row a list that answers with one row.
200
-
201
- **Write `rowUpdate` as a partial update.** A form sends every cell it
202
- holds; a widget carrying `lb-cell` and `lb-action="lb-row-update"` sends its
203
- one cell. Set the columns `values` names, leave the rest alone, and check
204
- the names against the columns the page may edit.
205
-
206
- **Put `lb-row-insert` and `lb-row-update` on a `<form>` or a button.** The
207
- hub gathers the nearest `<form>`, `<tr>` or live row. Cells in a bare
208
- `<div>` belong to no row and the request is refused. A native `<input>`
209
- carrying `lb-row-update` is refused; commit one cell with a widget such as
210
- `<lb-input>`. Inside a form, leave `lb-action` off the widgets.
211
-
212
- **Return a patch when the change has a known extent.** `patch({ rows })`
213
- for rows added or edited, `patch({ drop })` for keys removed, with
214
- `refresh: []`. List a query in `refresh` only when its membership or order
215
- changed in a way the operation cannot name.
216
-
217
- **Format values in the query.** The hub does no type conversion. What a
218
- number, date or null looks like is decided on the server.
219
-
220
- **Checkboxes, radio buttons and file inputs are not bound.** They receive
221
- no value and are not gathered. This is an open item, not a mistake in the
222
- page.
223
-
224
- **`lb-` belongs to Loadbare.** Invent no `lb-` attribute, and name no
225
- query or action with the prefix. Import attribute names in widget code from
226
- `@loadbare/app/constants`; never write them as string literals.
227
-
228
- **Name a widget script `<tag>.browser.ts`.** A plain `<tag>.ts` stays on
229
- the server and the tag goes unregistered.
230
-
231
- **Light DOM, global CSS.** No shadow root, no scoping. Style empty lists
232
- with `[lb-row-count="0"]`, pending requests with `[lb-pending]`, and failed
233
- ones with `[lb-error]`.
268
+ whole data channel.
269
+
270
+ **The developer writes `lb-*` attributes in markup, and script never
271
+ assigns them.** The builder refuses any `lb-` attribute other than
272
+ `lb-query`, `lb-column`, `lb-show`, `lb-request`, `lb-url-link`,
273
+ `lb-url-push` and `lb-url-unknown`. Everything else with the prefix is a
274
+ stamp the hub or the builder writes.
275
+
276
+ **`lb-` belongs to Loadbare.** The application should never name anything
277
+ with the `lb-` prefix anywhere: no attribute, query, column, request, custom
278
+ element, event, or custom element method beginning `lb`. Import attribute
279
+ names in custom element code from `@loadbare/app/constants`; never write
280
+ them as string literals.
281
+
282
+ **A custom element takes part as a control.** A form-associated custom
283
+ element (`static formAssociated = true`) with a `value` property that fires
284
+ `change` is a control: the hub sets its `value`, gathers it, and commits it
285
+ on `change`. Any other custom element keeps its content and receives the
286
+ column as its `lb-column-value` attribute.
287
+
288
+ **A custom element connects many times.** It is moved, never rebuilt: the
289
+ hub places every live row again on each set of all rows, and parks an
290
+ `lb-show` element in a template while it is off, so `connectedCallback` runs
291
+ on every move. Listen on the element itself in the constructor, look a
292
+ child up when it is needed, and in a control take up `lb-column-value` once,
293
+ on first connect, since a value can land before the element upgrades.
294
+
295
+ **Name a custom element script `<tag>.browser.ts`.** A plain `<tag>.ts`
296
+ stays on the server and the tag goes unregistered.
297
+
298
+ **Light DOM, global CSS.** No shadow root, no scoping. Style an empty
299
+ query with `[lb-query-row-count="0"]`, a request in flight with
300
+ `[lb-request-pending]`, a failed one with `[lb-request-error]`, and a value
301
+ with `[lb-column-value="overdue"]`.
234
302
 
235
303
  **Host at the origin root.** The hub reaches its route by absolute path, so
236
304
  a subpath such as `example.com/myapp/` does not work.
237
305
 
238
- ## Widgets
306
+ ## Custom elements
307
+
308
+ A custom element is `<tag>.html`, `<tag>.browser.ts`, or both, and a tag
309
+ with neither is a build error. The HTML is expanded at build time into the
310
+ tag's children; the class is an ordinary custom element with no base class.
239
311
 
240
- A widget is `<tag>.html`, `<tag>.browser.ts`, or both, and a tag with
241
- neither is a build error. The HTML is expanded at build time into the tag's
242
- children; the class is an ordinary custom element with no base class.
312
+ Place a custom element where HTML allows what its definition holds. One
313
+ whose definition has a `<div>` or a `<dialog>` cannot sit inside a `<p>`, and
314
+ the build refuses it; see
315
+ [Where a custom element can go](references/custom-elements.md#where-a-custom-element-can-go).
316
+ Never write one inside `<select>`, `<option>` or `<textarea>`, where the
317
+ parser loses the tag before the build can report it.
243
318
 
244
- Reach for a widget only when plain HTML cannot do the job. A list, a form
245
- and a condition need none. A widget exists to wrap a control that decides
246
- its own moment to send (`<lb-input>` on `change`), or to place and scaffold
247
- rows (`lbPlaceRow`, `lbRowsLanded`).
319
+ Reach for one only when plain HTML cannot do the job. A set of rows, a form
320
+ and a condition need none. A custom element exists to be a control of its
321
+ own, or to place and scaffold rows through the two hooks the hub calls,
322
+ `lbPlaceRow` and `lbRowsLanded` (the `RowsHost` interface).
248
323
 
249
324
  Before writing one, check `@loadbare/widgets`: `lb-input`, `lb-select`,
250
- `lb-options`, `lb-table`, `lb-picker`, `lb-unknown-page`. Install it and
251
- list it in `src/imports.ts`:
325
+ `lb-options`, `lb-picker`, `lb-table`, `lb-unknown-page`. The first four
326
+ are form-associated controls that carry `lb-column` and `lb-request`
327
+ themselves. Install the package and list it in `src/imports.ts`:
252
328
 
253
329
  ```ts
254
330
  export default ["@loadbare/widgets"];
@@ -263,15 +339,17 @@ with the package, so no network is needed.
263
339
 
264
340
  - [`references/overview.md`](references/overview.md) — the map of the
265
341
  reference, by part of the application.
266
- - [`references/data-binding.md`](references/data-binding.md) — every `lb-`
267
- attribute, requests, forms, query parms, conditions and request state.
268
- Start here for anything in a page.
269
- - [`references/page-files.md`](references/page-files.md) — queries,
270
- `onPageEnter`, actions, CRUD, refresh and patch.
342
+ - [`references/data-binding.md`](references/data-binding.md) — landing,
343
+ gathering, requests, conditions and request state. Start here for
344
+ anything in a page.
345
+ - [`references/page-files.md`](references/page-files.md) — what page
346
+ files are, queries, `onPageEnter`, handlers, `crud`, refresh and patch,
347
+ and `url()`.
271
348
  - [`references/custom-elements.md`](references/custom-elements.md) —
272
- expansion, parameters, slots, destinations, and widget code.
273
- - [`references/chrome.md`](references/chrome.md) — the chrome,
274
- navigation and `lb-navigation`.
349
+ expansion, parameters, slots, destinations, and custom element code and
350
+ when it runs.
351
+ - [`references/chrome.md`](references/chrome.md) — the chrome, and the URL
352
+ as the query `lb-url`.
275
353
  - [`references/server.md`](references/server.md) — the Express server and
276
354
  the request context.
277
355
  - [`references/builder.md`](references/builder.md) — the builder's options,
@@ -282,5 +360,5 @@ with the package, so no network is needed.
282
360
  - [`references/TECHREF-1.0.md`](references/TECHREF-1.0.md) — the reserved
283
361
  names, what a URL names and
284
362
  [query parms](references/TECHREF-1.0.md#query-parms), and the open items
285
- that block 1.0. Read it before designing around
286
- something the other references do not mention.
363
+ that block 1.0. Read it before designing around something the other
364
+ references do not mention.