@loadbare/app 0.10.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.
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +46 -37
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/expand.d.ts +6 -1
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +92 -7
- package/dist/build/expand.js.map +1 -1
- package/dist/core/lb-constants.d.ts +2 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +8 -0
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +31 -16
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +6 -7
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/docs/TECHREF-1.0.md +50 -1
- package/docs/comparison.md +25 -4
- package/docs/reference/custom-elements.md +101 -9
- package/docs/reference/data-binding.md +9 -2
- package/docs/reference/page-files.md +29 -0
- package/docs/what-does-loadbare-extend.md +124 -0
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +27 -5
- package/skills/loadbare-app/references/TECHREF-1.0.md +50 -1
- package/skills/loadbare-app/references/custom-elements.md +101 -9
- package/skills/loadbare-app/references/data-binding.md +9 -2
- package/skills/loadbare-app/references/page-files.md +29 -0
package/docs/TECHREF-1.0.md
CHANGED
|
@@ -109,6 +109,13 @@ But the allowed values may depend on other values in the row.
|
|
|
109
109
|
- **Land the remaining validations.** One `lb-hub` and one empty `<main>`.
|
|
110
110
|
Recommend landing these now, because refusing markup that used to build is
|
|
111
111
|
the kind of change 1.0 gives up.
|
|
112
|
+
- **Decide whether the build validates HTML.** A page can be well formed and
|
|
113
|
+
still break HTML's rules for what an element may hold, such as a `<div>`
|
|
114
|
+
inside a `<p>`. The parser rearranges it silently, the same way at build
|
|
115
|
+
time and in the browser, so nothing reports it. Checking those rules is a
|
|
116
|
+
validator's job, and html-validate is one the builder could run on the
|
|
117
|
+
source or on the expanded output. Deciding it after 1.0 is free if it
|
|
118
|
+
lands as an opt-in, and it may need a configuration key.
|
|
112
119
|
- **Confirm the three output names.** `app.html`, `client.js` and `app.css`
|
|
113
120
|
are about to be fixed in `staticRoutes` as well as in the builder.
|
|
114
121
|
|
|
@@ -251,6 +258,12 @@ A custom element with no element file is left alone. A custom element
|
|
|
251
258
|
with neither an element file nor a `.browser.ts` script is a build error, as
|
|
252
259
|
stated in [Custom elements](#custom-elements).
|
|
253
260
|
|
|
261
|
+
Each file is parsed on its own, and expansion joins them through the DOM,
|
|
262
|
+
which applies none of HTML's rules for what an element may hold. The browser
|
|
263
|
+
reads the shipped text by those rules, so a join it would rearrange, such as
|
|
264
|
+
a definition's `<dialog>` inside a page's `<p>`, is a build error naming the
|
|
265
|
+
file, the custom element and the path to what would move.
|
|
266
|
+
|
|
254
267
|
#### Build time parameters
|
|
255
268
|
|
|
256
269
|
A build time parameter is supplied as an attribute on a custom element,
|
|
@@ -418,6 +431,7 @@ query.
|
|
|
418
431
|
| -------------------- | -------------------------------- |
|
|
419
432
|
| `lb-column-value` | The value it set |
|
|
420
433
|
| `lb-key-value` | The row's key |
|
|
434
|
+
| `lb-row-live` | Nothing; it marks a live row |
|
|
421
435
|
| `lb-query-row-count` | The number of live rows it holds |
|
|
422
436
|
|
|
423
437
|
The markup names a query and the columns it shows. The kind and the key are
|
|
@@ -425,7 +439,8 @@ the server's, and the markup states neither.
|
|
|
425
439
|
|
|
426
440
|
The row template is the first `<template>` among the descendants of an
|
|
427
441
|
element with `lb-query`, outside any nested `lb-query`. A live row is an
|
|
428
|
-
element the hub cloned from a row template for one row
|
|
442
|
+
element the hub cloned from a row template for one row, and carries
|
|
443
|
+
`lb-row-live`.
|
|
429
444
|
|
|
430
445
|
| Kind | Row template | The hub |
|
|
431
446
|
| ------ | ------------ | ----------------------------------- |
|
|
@@ -498,6 +513,13 @@ refresh, and does not need to.
|
|
|
498
513
|
|
|
499
514
|
The hub matches each row to a live row by its key. It stamps
|
|
500
515
|
`lb-key-value` on each live row, and on an element a `row` lands on itself.
|
|
516
|
+
It stamps `lb-row-live` on each live row and nowhere else, so a `row`
|
|
517
|
+
landed inside another query, such as a total in a table's foot, is not one
|
|
518
|
+
of that query's rows. A stylesheet or a custom element selects live rows
|
|
519
|
+
with `[lb-row-live]`, and never with `[lb-key-value]`.
|
|
520
|
+
|
|
521
|
+
A key is unique within a query. Two rows with one key in the same answer,
|
|
522
|
+
or two live rows showing one key, are reported on the console.
|
|
501
523
|
|
|
502
524
|
All rows decide membership and order: every row is placed in the order
|
|
503
525
|
given, and a live row whose key did not arrive is removed. A patch touches
|
|
@@ -1390,9 +1412,33 @@ class NoteField extends HTMLElement {
|
|
|
1390
1412
|
}
|
|
1391
1413
|
```
|
|
1392
1414
|
|
|
1415
|
+
A control that is not yet upgraded when its column lands receives
|
|
1416
|
+
`lb-column-value` alone, since the hub sets `value` only on a control. That
|
|
1417
|
+
happens to an element the builder shipped absent, and to one whose
|
|
1418
|
+
definition loads after the hub lands. A control takes the stamp up the
|
|
1419
|
+
first time it connects, and never again.
|
|
1420
|
+
|
|
1393
1421
|
A custom element that is not a control receives `lb-column-value` and
|
|
1394
1422
|
renders it; its content is never replaced.
|
|
1395
1423
|
|
|
1424
|
+
### Lifecycle
|
|
1425
|
+
|
|
1426
|
+
| The hub | A custom element sees |
|
|
1427
|
+
| ---------------------------------------- | -------------------------------------------- |
|
|
1428
|
+
| Shows a page | `constructor`, `connectedCallback` |
|
|
1429
|
+
| Creates a live row, before placing it | `constructor` |
|
|
1430
|
+
| Places a live row the first time | `connectedCallback` |
|
|
1431
|
+
| Places it again, on every set of all rows | `disconnectedCallback`, `connectedCallback` |
|
|
1432
|
+
| Removes a live row | `disconnectedCallback` |
|
|
1433
|
+
| Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
|
|
1434
|
+
| Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
|
|
1435
|
+
| Turns on an element shipped absent | `constructor`, `connectedCallback` |
|
|
1436
|
+
|
|
1437
|
+
A listener on the element itself goes in the constructor, which reads no
|
|
1438
|
+
attribute and no child. A child is looked up when it is needed.
|
|
1439
|
+
`connectedCallback` runs on every move and is written to run again; work
|
|
1440
|
+
done once per instance goes behind a flag.
|
|
1441
|
+
|
|
1396
1442
|
### Row hooks
|
|
1397
1443
|
|
|
1398
1444
|
| Method | Implemented By | The hub calls it |
|
|
@@ -1505,6 +1551,7 @@ it may define tomorrow.
|
|
|
1505
1551
|
| `lb-exp-template` | Developer | An element file, and a `<template>` | [Slots and templates](#slots-and-templates) |
|
|
1506
1552
|
| `lb-column-value` | Hub | Every element set from a column | [How a column lands](#how-a-column-lands) |
|
|
1507
1553
|
| `lb-key-value` | Hub | A live row, and an element a `row` lands on | [Rows](#rows) |
|
|
1554
|
+
| `lb-row-live` | Hub | A live row | [Rows](#rows) |
|
|
1508
1555
|
| `lb-query-row-count` | Hub | An element with a row template | [Rows](#rows) |
|
|
1509
1556
|
| `lb-request-pending` | Hub | The element that issued a request | [Request state](#request-state) |
|
|
1510
1557
|
| `lb-request-error` | Hub | The element that issued a request | [Request state](#request-state) |
|
|
@@ -1566,6 +1613,8 @@ imports them rather than writing a string.
|
|
|
1566
1613
|
| `ATTR_SHOW` | `lb-show` |
|
|
1567
1614
|
| `ATTR_COLUMN_VALUE` | `lb-column-value` |
|
|
1568
1615
|
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
1616
|
+
| `ATTR_ROW_LIVE` | `lb-row-live` |
|
|
1617
|
+
| `LIVE_ROW` | `[lb-row-live]`, the selector for a live row |
|
|
1569
1618
|
| `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
|
|
1570
1619
|
| `ATTR_REQUEST` | `lb-request` |
|
|
1571
1620
|
| `ATTR_REQUEST_PENDING` | `lb-request-pending` |
|
package/docs/comparison.md
CHANGED
|
@@ -133,7 +133,7 @@ observable, and no client cache of query results.
|
|
|
133
133
|
- A value that has landed exists in the DOM: as `textContent` or `value`, and
|
|
134
134
|
as the `lb-column-value` stamp.
|
|
135
135
|
- The rows of a `rows` query exist as the live rows the hub cloned, each
|
|
136
|
-
stamped with `lb-key-value`.
|
|
136
|
+
stamped with `lb-row-live` and `lb-key-value`.
|
|
137
137
|
- The URL is a row too: the hub serves the query `lb-url`, whose columns are
|
|
138
138
|
the path, the page label and the query parms.
|
|
139
139
|
- An element hidden by `lb-show` exists inside a `<template>` standing where
|
|
@@ -187,7 +187,7 @@ At run time the hub performs a closed set of operations:
|
|
|
187
187
|
| A value for another native element | Set `textContent`, and stamp `lb-column-value` |
|
|
188
188
|
| A value for a custom element that is not a control | Stamp `lb-column-value` |
|
|
189
189
|
| A `row` with no row template | Land it on the element itself, stamp `lb-key-value` |
|
|
190
|
-
| A new key through a row template | Clone the row template, stamp `lb-key-value`, fill it, insert it
|
|
190
|
+
| A new key through a row template | Clone the row template, stamp `lb-row-live` and `lb-key-value`, fill it, insert it |
|
|
191
191
|
| All rows | Place every row in the order given, remove rows whose keys did not arrive |
|
|
192
192
|
| A patch | Upsert the rows named, remove the keys named, leave other rows in place |
|
|
193
193
|
| After rows land | Stamp `lb-query-row-count` |
|
|
@@ -628,7 +628,7 @@ Nothing is scoped, renamed, added or removed.
|
|
|
628
628
|
- Light DOM means every selector reaches every element, in the chrome and in
|
|
629
629
|
widgets.
|
|
630
630
|
- The hub stamps attributes a stylesheet can select on: `lb-column-value`,
|
|
631
|
-
`lb-key-value`, `lb-request-pending`, `lb-request-error`,
|
|
631
|
+
`lb-key-value`, `lb-row-live`, `lb-request-pending`, `lb-request-error`,
|
|
632
632
|
`lb-query-row-count`. An empty `rows` query is styled with
|
|
633
633
|
`[lb-query-row-count="0"]`.
|
|
634
634
|
|
|
@@ -685,6 +685,27 @@ A page's server half:
|
|
|
685
685
|
A query's `run` is `(ctx) => Row` or `(ctx) => Row[]`. An answer whose
|
|
686
686
|
shape disagrees with its declaration is dropped with a warning.
|
|
687
687
|
|
|
688
|
+
A page's queries are that page's view model, computed on the server: a
|
|
689
|
+
backend-for-frontend scoped to one page, whose answers are the values the
|
|
690
|
+
page shows.
|
|
691
|
+
|
|
692
|
+
| | Data layer (repository, ORM, API) | Backend-for-frontend | Loadbare/app page files |
|
|
693
|
+
|------------------|-----------------------------------|----------------------|-------------------------|
|
|
694
|
+
| Serves | Any consumer | One client application | One page |
|
|
695
|
+
| Shaped by | Entities | A client's screens | One page's elements |
|
|
696
|
+
| Answers with | Domain objects, independent of any UI | Data the client still turns into a view | Values as the page shows them, formatted and worded |
|
|
697
|
+
| Client-side work | Mapping, state, view models, components | View models, components | None: the hub lands each column on the element that names it |
|
|
698
|
+
| Keeps the rules | Yes, in code | Usually not | No: the application keeps them beneath, in its database or in code its handlers share |
|
|
699
|
+
| Reused by | Many screens | One application's screens | No other page |
|
|
700
|
+
|
|
701
|
+
A typical stack runs database, ORM or repository, service, API contract,
|
|
702
|
+
client store, view model, component and template. A Loadbare/app page keeps
|
|
703
|
+
three places: the database with the application's rules, the page files that
|
|
704
|
+
say what one page asks and answers, and the markup that says where each
|
|
705
|
+
answer goes. Reuse does not happen through a layer. Two pages showing the
|
|
706
|
+
same data repeat a query's shape or share a database view, and shared
|
|
707
|
+
wording goes in a helper module.
|
|
708
|
+
|
|
688
709
|
The data channel carries no CSRF token. Where an options argument for
|
|
689
710
|
hooks, transaction wrapping, error handling and CSRF would go is an open
|
|
690
711
|
blocker. Serving the static files from a separate origin is a roadmap item.
|
|
@@ -761,7 +782,7 @@ TECHREF-1.0 lists every name Loadbare/app owns in one cross-reference.
|
|
|
761
782
|
| Owned | Count | Names |
|
|
762
783
|
|-------------------------------------|-------|---------------------------------------------------------------|
|
|
763
784
|
| `lb-*` attributes a developer writes | 7 | `lb-query`, `lb-column`, `lb-show`, `lb-request`, `lb-url-link`, `lb-url-push`, `lb-url-unknown` |
|
|
764
|
-
| `lb-*` stamps |
|
|
785
|
+
| `lb-*` stamps | 8 | `lb-column-value`, `lb-key-value`, `lb-row-live`, `lb-query-row-count`, `lb-request-pending`, `lb-request-error`, `lb-page`, `lb-page-title` |
|
|
765
786
|
| `lb-*` build-time attributes | 2 | `lb-exp-slot`, `lb-exp-template` |
|
|
766
787
|
| Requests Loadbare provides | 3 | `lb-row-insert`, `lb-row-update`, `lb-row-delete` |
|
|
767
788
|
| `lb*` methods on custom elements | 2 | `lbPlaceRow`, `lbRowsLanded` |
|
|
@@ -190,6 +190,36 @@ path that closes it.
|
|
|
190
190
|
Expansion runs over `<template>` contents wherever they occur, so a
|
|
191
191
|
definition's row template ships already expanded, `{{placeholder}}` included.
|
|
192
192
|
|
|
193
|
+
### Where a custom element can go
|
|
194
|
+
|
|
195
|
+
HTML limits what an element may hold. A `<p>` holds only phrasing content,
|
|
196
|
+
such as text, `<span>` and `<button>`, and a `<button>` may not hold another
|
|
197
|
+
`<button>`. The parser does not refuse markup that breaks these rules; it
|
|
198
|
+
rearranges it. `<p>Total <div>12</div></p>` becomes a paragraph, then a div,
|
|
199
|
+
then an empty paragraph, with nothing logged.
|
|
200
|
+
|
|
201
|
+
Each file is parsed on its own, so a page file and an element file each get
|
|
202
|
+
the tree the browser would give them. Expansion then places one inside the
|
|
203
|
+
other, and that join is where a rule can break. A definition containing a
|
|
204
|
+
`<div>` or a `<dialog>` cannot go inside a `<p>`, since the browser would
|
|
205
|
+
close the paragraph early and move the rest of the definition out of its
|
|
206
|
+
element. The build refuses such a page, naming the file, the custom element
|
|
207
|
+
and the path to what would move:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
assemble: 'src/pages/ledger.page.html': expand: <lb-confirm> puts <dialog>
|
|
211
|
+
where the browser's parser would not keep it, at p > lb-confirm > dialog.
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Put a custom element whose definition holds block content, such as a
|
|
215
|
+
`<div>`, a `<dialog>` or a `<p>`, in a `<div>`, a `<li>`, a `<td>` or another
|
|
216
|
+
element that holds flow content.
|
|
217
|
+
|
|
218
|
+
Never write a custom element inside a `<select>`, an `<option>` or a
|
|
219
|
+
`<textarea>`. The parser drops the tag inside the first two, and inside a
|
|
220
|
+
`<textarea>` it is text. Both happen when the page file is read, before
|
|
221
|
+
expansion, so the build cannot see the element to report it.
|
|
222
|
+
|
|
193
223
|
### What fails at build time
|
|
194
224
|
|
|
195
225
|
Each of these is reported with the tag and file name, and nothing is
|
|
@@ -204,6 +234,8 @@ shipped:
|
|
|
204
234
|
- a `<template lb-exp-template="name">` naming a destination the definition
|
|
205
235
|
does not have
|
|
206
236
|
- two templates for the same destination
|
|
237
|
+
- a custom element placed where the browser's parser would move its
|
|
238
|
+
contents; see [Where a custom element can go](#where-a-custom-element-can-go)
|
|
207
239
|
|
|
208
240
|
An unfilled `{{placeholder}}` is not among these. That is presence
|
|
209
241
|
propagation working as intended, indistinguishable from an author who left
|
|
@@ -228,6 +260,8 @@ one as a string literal:
|
|
|
228
260
|
| `ATTR_REQUEST` | `lb-request` |
|
|
229
261
|
| `ATTR_COLUMN_VALUE` | `lb-column-value` |
|
|
230
262
|
| `ATTR_KEY_VALUE` | `lb-key-value` |
|
|
263
|
+
| `ATTR_ROW_LIVE` | `lb-row-live` |
|
|
264
|
+
| `LIVE_ROW` | `[lb-row-live]` |
|
|
231
265
|
| `ATTR_QUERY_ROW_COUNT` | `lb-query-row-count` |
|
|
232
266
|
| `ATTR_REQUEST_PENDING` | `lb-request-pending` |
|
|
233
267
|
| `ATTR_REQUEST_ERROR` | `lb-request-error` |
|
|
@@ -244,6 +278,46 @@ writes them in markup, and the hub and the builder write their stamps.
|
|
|
244
278
|
Loadbare reserves method names beginning with `lb` on a custom element, for
|
|
245
279
|
the methods it calls; see [Holding rows](#holding-rows).
|
|
246
280
|
|
|
281
|
+
### When its code runs
|
|
282
|
+
|
|
283
|
+
The hub creates, places and parks elements, and each of those reaches a
|
|
284
|
+
custom element as the browser's lifecycle callbacks. It is the same instance
|
|
285
|
+
from its constructor on, however often it moves:
|
|
286
|
+
|
|
287
|
+
| What the hub does | Callbacks, in order |
|
|
288
|
+
|----------------------------------------------------|---------------------------------------------|
|
|
289
|
+
| Shows a page | `constructor`, `connectedCallback` |
|
|
290
|
+
| Creates a live row, filled before it is placed | `constructor` |
|
|
291
|
+
| Places a live row for the first time | `connectedCallback` |
|
|
292
|
+
| Places it again, as it does on every set of all rows | `disconnectedCallback`, `connectedCallback` |
|
|
293
|
+
| Removes a live row | `disconnectedCallback` |
|
|
294
|
+
| Turns `lb-show` off | `disconnectedCallback`, `adoptedCallback` |
|
|
295
|
+
| Turns `lb-show` on | `adoptedCallback`, `connectedCallback` |
|
|
296
|
+
| Turns on an element the builder shipped absent | `constructor`, `connectedCallback` |
|
|
297
|
+
|
|
298
|
+
The builder ships every `lb-show` element inside its template, and nothing
|
|
299
|
+
in a template is upgraded, so such an element has no instance until its
|
|
300
|
+
column first turns on. See
|
|
301
|
+
[Conditional rendering](./data-binding.md#conditional-rendering).
|
|
302
|
+
|
|
303
|
+
So each kind of work has one place:
|
|
304
|
+
|
|
305
|
+
1. **A listener on the element itself goes in the constructor.** It is
|
|
306
|
+
attached once and survives every move, and a live row gets its own
|
|
307
|
+
because each row is a new instance. An event from a child bubbles to the
|
|
308
|
+
element, so the listener needs no child to exist yet. The constructor
|
|
309
|
+
reads no attribute and no child: an element made with
|
|
310
|
+
`document.createElement` has neither when it runs.
|
|
311
|
+
2. **A child is looked up when it is needed**, in a handler, a getter,
|
|
312
|
+
`lbPlaceRow` or `lbRowsLanded`, and never held from setup.
|
|
313
|
+
3. **`connectedCallback` runs on every move**, so it is written to run
|
|
314
|
+
again. It rearranges the element's own children into a state it checks
|
|
315
|
+
for first, or adds a listener to `document` or `window`, which
|
|
316
|
+
`disconnectedCallback` removes. Work done once per instance that needs
|
|
317
|
+
the element's attributes, such as a control taking up a value that landed
|
|
318
|
+
before it upgraded, goes behind a flag there; see
|
|
319
|
+
[Being a control](#being-a-control).
|
|
320
|
+
|
|
247
321
|
### Being a control
|
|
248
322
|
|
|
249
323
|
A form-associated custom element with a `value` property that fires `change`
|
|
@@ -253,10 +327,20 @@ and commits its `lb-request` on `change`:
|
|
|
253
327
|
|
|
254
328
|
```ts
|
|
255
329
|
// src/star-rating.browser.ts
|
|
330
|
+
import { ATTR_COLUMN_VALUE } from "@loadbare/app/constants";
|
|
331
|
+
|
|
256
332
|
class StarRating extends HTMLElement {
|
|
257
333
|
static formAssociated = true;
|
|
258
334
|
#internals = this.attachInternals();
|
|
259
335
|
#value = "";
|
|
336
|
+
#connected = false;
|
|
337
|
+
|
|
338
|
+
connectedCallback() {
|
|
339
|
+
if (this.#connected) return;
|
|
340
|
+
this.#connected = true;
|
|
341
|
+
const stamped = this.getAttribute(ATTR_COLUMN_VALUE);
|
|
342
|
+
if (stamped !== null) this.value = stamped;
|
|
343
|
+
}
|
|
260
344
|
|
|
261
345
|
get value(): string {
|
|
262
346
|
return this.#value;
|
|
@@ -292,6 +376,14 @@ customElements.define("star-rating", StarRating);
|
|
|
292
376
|
Dispatch `change` with `bubbles: true`, as a native control's does, when the
|
|
293
377
|
user commits an edit. Setting `value` from the hub fires nothing.
|
|
294
378
|
|
|
379
|
+
A control can receive its value before it is a control. An element the
|
|
380
|
+
builder shipped absent, or one whose definition loads after the hub has
|
|
381
|
+
landed, is not upgraded when its column lands, so the hub cannot set its
|
|
382
|
+
`value` and the value arrives as `lb-column-value` alone. Take the stamp up
|
|
383
|
+
the first time the control connects, as `connectedCallback` does above, and
|
|
384
|
+
never again: after that the hub sets `value` itself, and a later move must
|
|
385
|
+
not undo an edit.
|
|
386
|
+
|
|
295
387
|
A form owns a form-associated custom element the way it owns an `<input>`,
|
|
296
388
|
including through the HTML `form` attribute, so a form gathers it on submit.
|
|
297
389
|
Write `formResetCallback` to restore the default value: the hub resets a form
|
|
@@ -330,14 +422,9 @@ List `ATTR_COLUMN_VALUE` in `observedAttributes`. The browser calls
|
|
|
330
422
|
upgrades, as well as for one that changes afterward, so the first render and
|
|
331
423
|
every later one go through the one callback.
|
|
332
424
|
|
|
333
|
-
A custom element carrying `lb-show`, or inside an element that does,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
Going in, it sees `disconnectedCallback` and then `adoptedCallback`; coming
|
|
337
|
-
out, `adoptedCallback` and then `connectedCallback`. It is the same instance
|
|
338
|
-
throughout, and it still receives its column while it is away, so write
|
|
339
|
-
`connectedCallback` to run more than once, as a live row the hub reorders
|
|
340
|
-
already requires.
|
|
425
|
+
A custom element carrying `lb-show`, or inside an element that does, still
|
|
426
|
+
receives its column while it is away, so it returns current; see
|
|
427
|
+
[When its code runs](#when-its-code-runs).
|
|
341
428
|
|
|
342
429
|
### Sending a request
|
|
343
430
|
|
|
@@ -401,7 +488,7 @@ Everything else is the hub's:
|
|
|
401
488
|
| Cloning | Clones the row template once per new key |
|
|
402
489
|
| Matching | Fills the live row already showing that key |
|
|
403
490
|
| Removing | Removes the live rows the response says are gone |
|
|
404
|
-
| Stamping | Stamps each live row with `lb-key-value`
|
|
491
|
+
| Stamping | Stamps each live row with `lb-row-live` and `lb-key-value` |
|
|
405
492
|
| Counting | Stamps `lb-query-row-count` with the number of live rows |
|
|
406
493
|
|
|
407
494
|
`lbPlaceRow` receives the live row already filled and not yet in the
|
|
@@ -411,6 +498,11 @@ template, in arrival order. `lb-options.browser.ts` and
|
|
|
411
498
|
`lb-table.browser.ts` in [`@loadbare/widgets`](./widgets.md) are two
|
|
412
499
|
different placements over the same landing.
|
|
413
500
|
|
|
501
|
+
A custom element that walks its own rows finds them with `LIVE_ROW` from
|
|
502
|
+
`@loadbare/app/constants`, never by `lb-key-value`: an element a `row` lands
|
|
503
|
+
on carries a key too, and may sit among the rows, as a total in a table's
|
|
504
|
+
foot does.
|
|
505
|
+
|
|
414
506
|
Style an empty query against `lb-query-row-count` rather than carrying an
|
|
415
507
|
empty state in the custom element — see
|
|
416
508
|
[Counting rows](./data-binding.md#counting-rows).
|
|
@@ -58,6 +58,7 @@ builder writes a stamp, and a stylesheet or a custom element reads it.
|
|
|
58
58
|
|----------------------|----------------------------------------------------|
|
|
59
59
|
| `lb-column-value` | The value the hub set from a column |
|
|
60
60
|
| `lb-key-value` | The key of a live row, or of a `row` landed on an element |
|
|
61
|
+
| `lb-row-live` | The element is a live row |
|
|
61
62
|
| `lb-query-row-count` | The number of live rows an element holds |
|
|
62
63
|
| `lb-request-pending` | The element's request is in flight |
|
|
63
64
|
| `lb-request-error` | The element's last request failed |
|
|
@@ -86,8 +87,11 @@ any nested `lb-query`:
|
|
|
86
87
|
| `rows` | no | Lands nothing |
|
|
87
88
|
|
|
88
89
|
A live row is an element the hub cloned from a row template for one row.
|
|
89
|
-
The hub stamps it with `lb-key-value`, the row's
|
|
90
|
-
lands on itself carries `lb-key-value` as well
|
|
90
|
+
The hub stamps it with `lb-row-live`, and with `lb-key-value`, the row's
|
|
91
|
+
key. An element a `row` lands on itself carries `lb-key-value` as well, but
|
|
92
|
+
not `lb-row-live`, so a `row` shown inside another query, such as a total in
|
|
93
|
+
a table's foot, is not one of that query's rows. Select live rows with
|
|
94
|
+
`[lb-row-live]`, never with `[lb-key-value]`.
|
|
91
95
|
|
|
92
96
|
Write `lb-column` on each element that shows a column. The element reads
|
|
93
97
|
from its nearest ancestor row: the live row it is in, or the element a
|
|
@@ -153,6 +157,9 @@ membership and order: a live row whose key did not arrive is removed. A
|
|
|
153
157
|
patch changes only the rows it names, and every other live row keeps its
|
|
154
158
|
content and its place.
|
|
155
159
|
|
|
160
|
+
A key is unique within a query. Two rows with one key in the same answer,
|
|
161
|
+
or two live rows showing one key, are reported on the console.
|
|
162
|
+
|
|
156
163
|
A live row's root counts as a column when it carries `lb-column`, which is
|
|
157
164
|
how an `<option>`, whose content is text, shows the column it is.
|
|
158
165
|
|
|
@@ -16,6 +16,35 @@ every other path names the page whose stub it is.
|
|
|
16
16
|
Put the files anywhere under `src/`. The builder pairs them by stub, not by
|
|
17
17
|
directory; `src/pages/` is the convention.
|
|
18
18
|
|
|
19
|
+
## What the page files are
|
|
20
|
+
|
|
21
|
+
A page's queries are that page's view model, answered on the server. Each
|
|
22
|
+
is shaped for the elements that name it, on one page, so it answers in the
|
|
23
|
+
form the page shows: a date formatted, an amount with its separators, and
|
|
24
|
+
the phrase a reader sees, such as "no postings yet" or "12 postings",
|
|
25
|
+
rather than a count for the browser to word. The hub does no conversion,
|
|
26
|
+
so there is no later place for that work to go. The markup says where each
|
|
27
|
+
value goes, and the query says what it is.
|
|
28
|
+
|
|
29
|
+
A value the page reads back, rather than only shows, stays data:
|
|
30
|
+
|
|
31
|
+
| Value | Read back by | So it is |
|
|
32
|
+
|------------------------------------|---------------------------------|-----------------------------------|
|
|
33
|
+
| A column that lands on a control | The request that gathers it | What the handler expects to receive |
|
|
34
|
+
| The key column | Every request from the row | What the handler looks the row up by |
|
|
35
|
+
| A column `lb-show` names | The hub | `null` or `false` for off |
|
|
36
|
+
| A column a stylesheet selects on | `[lb-column-value="…"]` | A fixed token, not wording |
|
|
37
|
+
|
|
38
|
+
The page files are not a data layer; they sit on one. The application
|
|
39
|
+
opens its own database and keeps its rules there, or in code its handlers
|
|
40
|
+
share, and the page files stay thin because integrity is not their job.
|
|
41
|
+
|
|
42
|
+
What two pages share goes beneath them, not between. Two pages showing the
|
|
43
|
+
same data each write the query, or both read a database view. Shared
|
|
44
|
+
wording and shared parm reading go in a helper module the queries import.
|
|
45
|
+
Everything that belongs to one page stays in one place, so a change to a
|
|
46
|
+
page touches that page's files.
|
|
47
|
+
|
|
19
48
|
## HTML
|
|
20
49
|
|
|
21
50
|
Write the page as a fragment. The fragment lands in `<main>`, which
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# What Does Loadbare Extend?
|
|
2
|
+
|
|
3
|
+
> **LLM-authored, not yet revised by a person.** Drafted by Claude on
|
|
4
|
+
> 2026-09-27 against `@loadbare/app` 0.10.0.
|
|
5
|
+
|
|
6
|
+
The question: what is `@loadbare/app`'s foundation, the thing it adds to
|
|
7
|
+
rather than replaces, and does the package stand on that foundation the way
|
|
8
|
+
`@loadbare/db` stands on Postgres?
|
|
9
|
+
|
|
10
|
+
The short answer: the browser half of `@loadbare/app` is honestly "HTML+", in
|
|
11
|
+
the same way `@loadbare/db` is "Postgres+". The package as a whole is not.
|
|
12
|
+
The framing that covers all of it, and ties it to db, is relational rather
|
|
13
|
+
than HTML.
|
|
14
|
+
|
|
15
|
+
## The precise reading
|
|
16
|
+
|
|
17
|
+
### Where "extension to HTML" is literally true
|
|
18
|
+
|
|
19
|
+
- **It uses the platform's own extension point.** Custom elements are how
|
|
20
|
+
HTML is meant to be extended. The whole browser runtime is one custom
|
|
21
|
+
element, `<lb-hub>`. Custom elements are also the only way Loadbare lets an
|
|
22
|
+
application split up markup or ship behaviour. There are no components, no
|
|
23
|
+
JSX, and no expressions evaluated at run time.
|
|
24
|
+
- **The attributes copy HTML's own ideas.** [Theory](./theory.md#data-binding)
|
|
25
|
+
already sets this out in a table:
|
|
26
|
+
- `lb-column` works like `name`.
|
|
27
|
+
- `lb-query` works like `<form>` or `<select>`, a container of records.
|
|
28
|
+
- `lb-show` works like `hidden`.
|
|
29
|
+
- `lb-key-value` works like an `<option>`'s `value` sitting apart from its
|
|
30
|
+
text.
|
|
31
|
+
- **HTML decides behaviour wherever it already has an opinion.** Commits
|
|
32
|
+
follow HTML's events: a form on submit, a control on change, anything else
|
|
33
|
+
on click. Gathering follows the form owner, including `form="…"`. Resets
|
|
34
|
+
go through `formResetCallback`. Busy state uses `aria-busy`. The unknown
|
|
35
|
+
page is a real `<dialog>`, and links are real `<a href>`.
|
|
36
|
+
- **What ships is plain HTML.** It can be read in view-source, it uses light
|
|
37
|
+
DOM, and CSS is left untouched. A page that shows no data is just an HTML
|
|
38
|
+
file.
|
|
39
|
+
- **There is historical precedent.** IE4's `datasrc`/`datafld` really was
|
|
40
|
+
data binding added to HTML, and [Prior art](./prior-art.md) finds its
|
|
41
|
+
vocabulary maps almost one to one onto Loadbare's.
|
|
42
|
+
|
|
43
|
+
### Where it is inaccurate or strained
|
|
44
|
+
|
|
45
|
+
- **The attributes don't conform to the spec.** HTML sets aside `data-*` for
|
|
46
|
+
author attributes, and a validator will flag `lb-*`. htmx and Alpine have
|
|
47
|
+
the same problem, so it's defensible. But a literal claim of "extension to
|
|
48
|
+
HTML" is only half true: Loadbare uses one of the sanctioned extension
|
|
49
|
+
points (custom elements) fully and skips the other (`data-*`).
|
|
50
|
+
- **Build-time expansion is a preprocessor.** `exp-` parameters and `{{…}}`
|
|
51
|
+
are a small template language that runs before HTML exists. The output is
|
|
52
|
+
HTML; the source a developer writes is not quite HTML.
|
|
53
|
+
- **Navigation overrides HTML's model on purpose.** HTML's model is one
|
|
54
|
+
document per URL. Loadbare ships every page as a `<template>` in one
|
|
55
|
+
document and answers every route with `app.html`, with no 404. Keeping the
|
|
56
|
+
address bar truthful softens this, but it is still a replacement.
|
|
57
|
+
- **The wire is JSON, not hypermedia.** In the ecosystem, "extends HTML"
|
|
58
|
+
usually means htmx's argument that HTML is unfinished hypertext. Loadbare
|
|
59
|
+
rejects that model after the first load. Calling it an HTML extension
|
|
60
|
+
invites exactly the comparison [Theory](./theory.md) spends paragraphs
|
|
61
|
+
getting out of.
|
|
62
|
+
- **Half of what a developer writes isn't HTML.** `queries.ts` and
|
|
63
|
+
`requests.ts`, with `row`/`rows`/`patch`, crud entries and refresh lists,
|
|
64
|
+
have no HTML counterpart at all.
|
|
65
|
+
|
|
66
|
+
## The db comparison
|
|
67
|
+
|
|
68
|
+
db earns "Postgres+" because it passes four tests:
|
|
69
|
+
|
|
70
|
+
1. What it builds is plain Postgres, inspectable with psql.
|
|
71
|
+
2. Its vocabulary extends Postgres's own concepts: the foreign key becomes the
|
|
72
|
+
channel for values flowing down and aggregates flowing up.
|
|
73
|
+
3. A developer can drop to plain SQL anywhere.
|
|
74
|
+
4. Postgres does the heavy lifting.
|
|
75
|
+
|
|
76
|
+
The browser half of app passes all four:
|
|
77
|
+
|
|
78
|
+
1. The output is plain HTML.
|
|
79
|
+
2. The vocabulary extends `name`, `form` and `hidden`.
|
|
80
|
+
3. Any HTML works, and a custom element is still just a custom element.
|
|
81
|
+
4. The platform does the work: `cloneNode` of templates, the selector engine,
|
|
82
|
+
form owners, `showModal`, the History API.
|
|
83
|
+
|
|
84
|
+
The rule behind it is the same as db's: **defer where the foundation has an
|
|
85
|
+
answer, and add only where it is silent.** HTML is silent about data, and
|
|
86
|
+
that's where the seven attributes live.
|
|
87
|
+
|
|
88
|
+
The difference is that db never overrides Postgres. It adds to Postgres and
|
|
89
|
+
replaces the application's logic layer. app overrides HTML twice, in
|
|
90
|
+
navigation and on the wire, both for the 300ms budget. Those are deliberate
|
|
91
|
+
departures, not add-ons.
|
|
92
|
+
|
|
93
|
+
## How it feels
|
|
94
|
+
|
|
95
|
+
- **Writing a page** feels like writing HTML: forms and tables with a few
|
|
96
|
+
attributes, much like data-bound HTML in 1999 without the ActiveX.
|
|
97
|
+
- **Writing a widget** feels like writing web components.
|
|
98
|
+
- **Writing `requests.ts`** feels like neither. A nested declaration such as
|
|
99
|
+
`crud.invoiceLines.rowDelete.{run, refresh}` feels like a framework's
|
|
100
|
+
configuration map. db has no equivalent seam, because its DSL reads as DDL
|
|
101
|
+
all the way down. That file is where the "HTML+" feel breaks.
|
|
102
|
+
|
|
103
|
+
## A better framing
|
|
104
|
+
|
|
105
|
+
The idea that runs through every layer of app is not HTML. It is the
|
|
106
|
+
**row**: query, row, rows, column and key. That's where the scope decision in
|
|
107
|
+
[Theory](./theory.md#data-binding) lands, and it's what the protocol carries.
|
|
108
|
+
HTML is the surface, and relations are the substance.
|
|
109
|
+
|
|
110
|
+
Framed that way, the two packages form one story rather than two:
|
|
111
|
+
|
|
112
|
+
- **db** is the relational model with derived values, built on Postgres.
|
|
113
|
+
- **app** is the relational model shown and edited on screen, with HTML as its
|
|
114
|
+
surface.
|
|
115
|
+
- **The row** is the only thing that crosses between them.
|
|
116
|
+
|
|
117
|
+
So "Relational+ from end to end, with Postgres at one end and HTML at the
|
|
118
|
+
other" is accurate everywhere. "HTML+" is accurate only for page and widget
|
|
119
|
+
authoring. It also explains the server half: `queries.ts` and `requests.ts`
|
|
120
|
+
aren't HTML because they're the relational half of the contract.
|
|
121
|
+
|
|
122
|
+
"HTML+" still works as a description of how page authoring feels. It fails
|
|
123
|
+
as a statement of what the package is, and it pulls readers toward the
|
|
124
|
+
hypermedia comparison Loadbare would rather avoid.
|
package/package.json
CHANGED
|
@@ -219,8 +219,14 @@ for rows added or edited, `patch({ drop })` for keys removed, with
|
|
|
219
219
|
`refresh: []`. List a query in `refresh` only when its membership or order
|
|
220
220
|
changed in a way the handler cannot name.
|
|
221
221
|
|
|
222
|
-
**
|
|
223
|
-
|
|
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.
|
|
224
230
|
|
|
225
231
|
**A URL's path names a page, never a resource.** Do not design
|
|
226
232
|
`/accounts/42`. A row is addressed by `query` and `key`, taken from where
|
|
@@ -279,6 +285,13 @@ element (`static formAssociated = true`) with a `value` property that fires
|
|
|
279
285
|
on `change`. Any other custom element keeps its content and receives the
|
|
280
286
|
column as its `lb-column-value` attribute.
|
|
281
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
|
+
|
|
282
295
|
**Name a custom element script `<tag>.browser.ts`.** A plain `<tag>.ts`
|
|
283
296
|
stays on the server and the tag goes unregistered.
|
|
284
297
|
|
|
@@ -296,6 +309,13 @@ A custom element is `<tag>.html`, `<tag>.browser.ts`, or both, and a tag
|
|
|
296
309
|
with neither is a build error. The HTML is expanded at build time into the
|
|
297
310
|
tag's children; the class is an ordinary custom element with no base class.
|
|
298
311
|
|
|
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.
|
|
318
|
+
|
|
299
319
|
Reach for one only when plain HTML cannot do the job. A set of rows, a form
|
|
300
320
|
and a condition need none. A custom element exists to be a control of its
|
|
301
321
|
own, or to place and scaffold rows through the two hooks the hub calls,
|
|
@@ -322,10 +342,12 @@ with the package, so no network is needed.
|
|
|
322
342
|
- [`references/data-binding.md`](references/data-binding.md) — landing,
|
|
323
343
|
gathering, requests, conditions and request state. Start here for
|
|
324
344
|
anything in a page.
|
|
325
|
-
- [`references/page-files.md`](references/page-files.md) —
|
|
326
|
-
`onPageEnter`, handlers, `crud`, refresh and patch,
|
|
345
|
+
- [`references/page-files.md`](references/page-files.md) — what page
|
|
346
|
+
files are, queries, `onPageEnter`, handlers, `crud`, refresh and patch,
|
|
347
|
+
and `url()`.
|
|
327
348
|
- [`references/custom-elements.md`](references/custom-elements.md) —
|
|
328
|
-
expansion, parameters, slots, destinations, and custom element code
|
|
349
|
+
expansion, parameters, slots, destinations, and custom element code and
|
|
350
|
+
when it runs.
|
|
329
351
|
- [`references/chrome.md`](references/chrome.md) — the chrome, and the URL
|
|
330
352
|
as the query `lb-url`.
|
|
331
353
|
- [`references/server.md`](references/server.md) — the Express server and
|