@loadbare/app 0.7.2 → 0.7.4
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 +67 -6
- package/dist/build/assemble.js.map +1 -1
- package/dist/core/lb-constants.d.ts +1 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +15 -2
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +6 -0
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +112 -15
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.js +46 -26
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/docs/TECHREF-1.0.md +138 -98
- package/docs/analysis-closed-set.md +203 -0
- package/docs/comparison.md +1123 -0
- package/docs/prior-art.md +216 -0
- package/docs/reference/custom-elements.md +18 -3
- package/docs/reference/data-binding.md +111 -41
- package/docs/testing.md +10 -0
- package/docs/theory.md +737 -399
- package/package.json +1 -1
package/docs/TECHREF-1.0.md
CHANGED
|
@@ -10,8 +10,8 @@ to be true, then revise the code, docs, and tests to ensure it is true.
|
|
|
10
10
|
We cannot declare 1.0 until we determine if these blockers can be
|
|
11
11
|
added later w/o breaking changes. If we are reasonably confident
|
|
12
12
|
that they can be decided later w/o breaking changes, then we rename
|
|
13
|
-
them to "Out of Scope". Otherwise,
|
|
14
|
-
we declare 1.0.
|
|
13
|
+
them to "Out of Scope" or move them to the roadmap. Otherwise,
|
|
14
|
+
they must be resolved before we declare 1.0.
|
|
15
15
|
|
|
16
16
|
### Data types
|
|
17
17
|
|
|
@@ -59,6 +59,15 @@ That decision is firm; what the set contains is not.
|
|
|
59
59
|
|
|
60
60
|
### Lists
|
|
61
61
|
|
|
62
|
+
Imagine we have an HTML `<select>` that displays a fixed list for all
|
|
63
|
+
rows in a table, such as `customer_type` for a table of customers. When
|
|
64
|
+
all customers can be any customer type, our system as written today
|
|
65
|
+
is fine, because all HTML `<select>` elements get the same list.
|
|
66
|
+
|
|
67
|
+
But it may be that the list of allowed values is dependent on other values
|
|
68
|
+
in the row.
|
|
69
|
+
|
|
70
|
+
|
|
62
71
|
- **Decide how a new row fills a nested list.** A row added to the outer
|
|
63
72
|
list starts with an empty nested list, which fills only when the nested
|
|
64
73
|
list's name lands again. See [Master-detail](#master-detail) for what a
|
|
@@ -210,7 +219,7 @@ the whole of `--src` is one flat namespace, as stated in
|
|
|
210
219
|
Three kinds of file hold HTML.
|
|
211
220
|
|
|
212
221
|
| File | Holds | Found in |
|
|
213
|
-
|
|
222
|
+
| ------------------ | ------------------------------------ | ------------ |
|
|
214
223
|
| `chrome.html` | The one HTML document | `--src` |
|
|
215
224
|
| `<stub>.page.html` | One page's markup, as a fragment | `--src` |
|
|
216
225
|
| `<tag-name>.html` | One widget definition, as a fragment | Every origin |
|
|
@@ -277,13 +286,13 @@ A definition states a default with `{{name|default}}`, as in
|
|
|
277
286
|
`{{button-label|OK}}`. Whitespace around the name and around the default is
|
|
278
287
|
discarded. The default is a literal.
|
|
279
288
|
|
|
280
|
-
| The attribute is assigned | Expansion defines
|
|
281
|
-
|
|
282
|
-
| `exp-name="text"` | `{{name}}`
|
|
283
|
-
| `exp-name=""` | `{{name}}`
|
|
284
|
-
| Nothing | `{{name
|
|
285
|
-
| Nothing | `{{name}}`
|
|
286
|
-
| `exp-name` | No `{{name}}`
|
|
289
|
+
| The attribute is assigned | Expansion defines | Result |
|
|
290
|
+
| ------------------------- | ------------------- | -------------------------------- |
|
|
291
|
+
| `exp-name="text"` | `{{name}}` | `text` |
|
|
292
|
+
| `exp-name=""` | `{{name}}` | An empty string |
|
|
293
|
+
| Nothing | `{{name\|default}}` | `default` |
|
|
294
|
+
| Nothing | `{{name}}` | Nothing, and the attribute drops |
|
|
295
|
+
| `exp-name` | No `{{name}}` | A build error |
|
|
287
296
|
|
|
288
297
|
Attributes without the `exp-` prefix are ignored during expansion.
|
|
289
298
|
|
|
@@ -370,20 +379,19 @@ These are build errors:
|
|
|
370
379
|
|
|
371
380
|
### Binding
|
|
372
381
|
|
|
373
|
-
---- UNEDITED ----
|
|
374
|
-
|
|
375
382
|
Binding attributes determine how the hub updates the DOM, either by
|
|
376
383
|
directly updating a plain HTML element or instructing
|
|
377
384
|
custom widgets to update themselves.
|
|
378
385
|
|
|
379
386
|
|
|
380
|
-
| Attribute | Assigned By | Behavior
|
|
381
|
-
|
|
382
|
-
| lb-list | Developer | Scopes DOM children to a named set of rows; a nested lb-list or lb-row begins a new scope
|
|
383
|
-
| lb-row | Developer | Scopes DOM children to one named row; a nested lb-list or lb-row begins a new scope
|
|
384
|
-
| lb-cell | Developer | This DOM node displays this column of the row in scope
|
|
385
|
-
| lb-
|
|
386
|
-
| lb-key
|
|
387
|
+
| Attribute | Assigned By | Behavior |
|
|
388
|
+
| ------------ | ----------- | ----------------------------------------------------------------------------------------------- |
|
|
389
|
+
| lb-list | Developer | Scopes DOM children to a named set of rows; a nested lb-list or lb-row begins a new scope |
|
|
390
|
+
| lb-row | Developer | Scopes DOM children to one named row; a nested lb-list or lb-row begins a new scope |
|
|
391
|
+
| lb-cell | Developer | This DOM node displays this column of the row in scope |
|
|
392
|
+
| lb-show | Developer | This DOM node is present when this column of the row in scope is neither null nor false |
|
|
393
|
+
| lb-key | Developer | Names the column that identifies a row, on the row template inside an lb-list |
|
|
394
|
+
| lb-key-value | Hub | Stamped on a live row: that row's value of `lb-key` |
|
|
387
395
|
| lb-value | Hub | The value that landed on a cell; a widget updates itself from it and a stylesheet selects on it |
|
|
388
396
|
|
|
389
397
|
#### How a value lands
|
|
@@ -391,11 +399,11 @@ custom widgets to update themselves.
|
|
|
391
399
|
A cell lands one of three ways, and the element decides which. Every cell
|
|
392
400
|
that receives a value also carries it as `lb-value`.
|
|
393
401
|
|
|
394
|
-
| Element
|
|
395
|
-
|
|
396
|
-
| A custom element, tag hyphenated
|
|
397
|
-
| `<select>`, `<textarea>`, or `<input>`
|
|
398
|
-
| Any other native element
|
|
402
|
+
| Element | Receives the value as |
|
|
403
|
+
| -------------------------------------- | --------------------------------- |
|
|
404
|
+
| A custom element, tag hyphenated | Its `lb-value` attribute |
|
|
405
|
+
| `<select>`, `<textarea>`, or `<input>` | Its `value`, and `lb-value` |
|
|
406
|
+
| Any other native element | Its `textContent`, and `lb-value` |
|
|
399
407
|
|
|
400
408
|
A widget owns whatever control it wraps, so it is handed the value and
|
|
401
409
|
renders it itself. A form control shows its state as its `value`, so a
|
|
@@ -433,30 +441,44 @@ A widget sees `attributeChangedCallback` for an `lb-value` already present
|
|
|
433
441
|
when it upgrades, so a widget cannot tell a first landing from a refresh,
|
|
434
442
|
and does not need to.
|
|
435
443
|
|
|
436
|
-
#### Displaying by
|
|
444
|
+
#### Displaying by condition
|
|
437
445
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
446
|
+
`lb-show` names the column that decides whether an element is present.
|
|
447
|
+
`null` and `false` are off, and every other value is on. No string is read,
|
|
448
|
+
so `"false"` is on, and a query spells a condition as a boolean or a null.
|
|
441
449
|
|
|
442
450
|
```html
|
|
443
451
|
<template lb-key="id">
|
|
444
452
|
<tr>
|
|
445
453
|
<td lb-cell="name"></td>
|
|
446
|
-
<td lb-
|
|
447
|
-
<td><button lb-action="lb-row-delete">Remove</button></td>
|
|
454
|
+
<td><button lb-action="lb-row-delete" lb-show="removable">Remove</button></td>
|
|
448
455
|
</tr>
|
|
449
456
|
</template>
|
|
450
457
|
```
|
|
451
458
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
459
|
+
It binds as `lb-cell` does, to the row on the nearest scoped ancestor, and on
|
|
460
|
+
an element that is itself a scope the column belongs to the row around it. A row
|
|
461
|
+
that does not carry the column leaves the element as it is.
|
|
455
462
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
463
|
+
An element that is off is moved into a `<template lb-show="column">` standing
|
|
464
|
+
where it stood, and moved back out when the column turns on. It is not
|
|
465
|
+
rendered, focused, clicked, announced or gathered. It is moved and never
|
|
466
|
+
rebuilt, so a widget keeps its instance. Landing reaches into that template,
|
|
467
|
+
and nothing else does, so the element and every cell and scope inside it
|
|
468
|
+
return current. A custom element sees `disconnectedCallback` then
|
|
469
|
+
`adoptedCallback` going in, and `adoptedCallback` then `connectedCallback`
|
|
470
|
+
coming out, and still receives `lb-value` while it is away.
|
|
471
|
+
|
|
472
|
+
The builder ships every `lb-show` element already inside its template, so
|
|
473
|
+
nothing conditional shows until its row lands. An absent element's template
|
|
474
|
+
keeps its place among its siblings, and a position selector counts it.
|
|
475
|
+
|
|
476
|
+
These are build errors: `lb-show` on a row template's root, with no row
|
|
477
|
+
around it (outside every scope, on a scope with none around it, or in a list
|
|
478
|
+
scope outside its row template), or on a `<template>`.
|
|
479
|
+
|
|
480
|
+
Hiding is presentation, and the server still refuses what a request may not
|
|
481
|
+
do.
|
|
460
482
|
|
|
461
483
|
#### Master-detail
|
|
462
484
|
|
|
@@ -504,10 +526,10 @@ name, the same test that decides how a value lands — see
|
|
|
504
526
|
[How a value lands](#how-a-value-lands).
|
|
505
527
|
|
|
506
528
|
| lb-action | Written on |
|
|
507
|
-
|
|
508
|
-
| lb-row-insert | a `<form>`, or a button
|
|
529
|
+
| -------------- | ------------------------------------------------- |
|
|
530
|
+
| lb-row-insert | a `<form>`, or a button in a `<tr>`, in a list |
|
|
509
531
|
| lb-row-delete | anything inside a live row |
|
|
510
|
-
| lb-row-update | a `<form>`, or a button
|
|
532
|
+
| lb-row-update | a `<form>`, or a button in a live row |
|
|
511
533
|
| lb-cell-change | a widget wrapping one control |
|
|
512
534
|
| anything else | must be a named routine in the page's server code |
|
|
513
535
|
|
|
@@ -520,13 +542,13 @@ dispatched it. It reads the scope from the dispatching element before any
|
|
|
520
542
|
ancestor sees the event, and never overwrites a field the request already
|
|
521
543
|
carries.
|
|
522
544
|
|
|
523
|
-
| `action` | Filled from scope | Required
|
|
524
|
-
|
|
525
|
-
| a declared name | `list` or `row`, `key`, `cell` | nothing
|
|
526
|
-
| `lb-cell-change` | `list`, `key`, `cell` | all three, and `value`
|
|
527
|
-
| `lb-row-insert` | `list` | `list`, and `values`
|
|
528
|
-
| `lb-row-delete` | `list`, `key` | both
|
|
529
|
-
| `lb-row-update` | `list`, `key` | both, and `values`
|
|
545
|
+
| `action` | Filled from scope | Required |
|
|
546
|
+
| ---------------- | ------------------------------ | ---------------------- |
|
|
547
|
+
| a declared name | `list` or `row`, `key`, `cell` | nothing |
|
|
548
|
+
| `lb-cell-change` | `list`, `key`, `cell` | all three, and `value` |
|
|
549
|
+
| `lb-row-insert` | `list` | `list`, and `values` |
|
|
550
|
+
| `lb-row-delete` | `list`, `key` | both |
|
|
551
|
+
| `lb-row-update` | `list`, `key` | both, and `values` |
|
|
530
552
|
|
|
531
553
|
An element's own `lb-list` or `lb-row` names what it displays, never where
|
|
532
554
|
its request goes. A request belongs to the scope around the element, the
|
|
@@ -534,14 +556,21 @@ way a control belongs to the form around it. `list` or `row` comes from the
|
|
|
534
556
|
nearest ancestor scope, `key` from the nearest live row inside that scope,
|
|
535
557
|
and `cell` from the dispatching element's own `lb-cell`.
|
|
536
558
|
A request missing a required field is not sent. The hub never fills
|
|
537
|
-
`value
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
559
|
+
`value`. An `lb-row-insert` or `lb-row-update` gathers the row it belongs
|
|
560
|
+
to: `values` holds every `lb-cell` with a control to read in the nearest
|
|
561
|
+
`<form>`, `<tr>` or live row around the dispatching element, itself
|
|
562
|
+
included, inside its scope. The cells are found the way a row lands, so a
|
|
563
|
+
cell inside a scope nested in the row is that scope's and is not gathered,
|
|
564
|
+
while an element carrying a scope and `lb-cell` both is the row's cell.
|
|
565
|
+
This is a button's form owner: what else sits
|
|
566
|
+
beside the element never changes what is sent. A `<tr>` counts because a
|
|
567
|
+
form cannot go around a table row's controls, so a new row in a table is its
|
|
568
|
+
own form; a live row counts because it is the row the key names. The scope
|
|
569
|
+
itself is never the row, since its cells belong to other rows. A request
|
|
570
|
+
from an element in no form or row is not sent, and the hub reports that its
|
|
571
|
+
cells belong in a `<form>`. A request that already carries `values` keeps
|
|
572
|
+
them, and one whose row has no cell to read is not sent. A click inside a
|
|
573
|
+
native element that carries either operation and
|
|
545
574
|
holds cells itself is not sent, since clicking into one of its controls
|
|
546
575
|
would send the row: put the action on a form or on a button.
|
|
547
576
|
|
|
@@ -617,7 +646,7 @@ other link, so leaving the application is the default and staying in it is
|
|
|
617
646
|
the opt-in.
|
|
618
647
|
|
|
619
648
|
| Attribute | Assigned By | Behavior |
|
|
620
|
-
|
|
649
|
+
| ----------- | ----------- | --------------------------------------------------------------------------------------- |
|
|
621
650
|
| lb-nav-link | Developer | On an `<a>`: the hub shows the page the anchor's path names, without loading a document |
|
|
622
651
|
|
|
623
652
|
The attribute takes no value. The hub looks for the nearest ancestor
|
|
@@ -670,7 +699,7 @@ Current page, published by the hub as a row. Can be bound anywhere just
|
|
|
670
699
|
like a server-produced result.
|
|
671
700
|
|
|
672
701
|
| Cell | Holds |
|
|
673
|
-
|
|
702
|
+
| ------------ | ------------------------------------------------------------------- |
|
|
674
703
|
| `page-label` | The text of the link to the current page, empty if no link names it |
|
|
675
704
|
| `page-uri` | The path as the browser has it, such as `/members` |
|
|
676
705
|
|
|
@@ -792,7 +821,7 @@ client script, the stylesheet, the hub's data channel, and the one HTML
|
|
|
792
821
|
document.
|
|
793
822
|
|
|
794
823
|
| Handle | With |
|
|
795
|
-
|
|
824
|
+
| ---------------------------- | ------------------- |
|
|
796
825
|
| `/client.js` | `dist/client.js` |
|
|
797
826
|
| `/app.css` | `dist/app.css` |
|
|
798
827
|
| `hubRoutes(hub, contextFor)` | The hub's own route |
|
|
@@ -881,7 +910,7 @@ as explained in the next section.
|
|
|
881
910
|
It has three optional keys.
|
|
882
911
|
|
|
883
912
|
| Key | Keyed by | Answers |
|
|
884
|
-
|
|
913
|
+
| ------------- | --------------------- | -------------------------------------------------- |
|
|
885
914
|
| `actions` | The `lb-action` value | Anything the page chooses to declare |
|
|
886
915
|
| `crud` | A query name | The four reserved `lb-action` values |
|
|
887
916
|
| `onPageEnter` | Nothing | Runs once on entering the page, before its queries |
|
|
@@ -938,7 +967,7 @@ are one vocabulary. All four operate on a list, because each needs a key and
|
|
|
938
967
|
a key exists only on a live row.
|
|
939
968
|
|
|
940
969
|
| `lb-action` | Key under `crud` | `where` carries |
|
|
941
|
-
|
|
970
|
+
| ---------------- | ---------------- | ---------------------- |
|
|
942
971
|
| `lb-cell-change` | `cellChange` | `key`, `cell`, `value` |
|
|
943
972
|
| `lb-row-delete` | `rowDelete` | `key` |
|
|
944
973
|
| `lb-row-insert` | `rowInsert` | `values` |
|
|
@@ -981,21 +1010,31 @@ The hub stamps these on the element that dispatched a request, which is the
|
|
|
981
1010
|
widget itself when a widget fired it. A widget observes them and reacts; it
|
|
982
1011
|
must name them in `observedAttributes` to see them change.
|
|
983
1012
|
|
|
984
|
-
| Attribute | Written on | Holds
|
|
985
|
-
|
|
986
|
-
| `lb-pending` | The dispatching element | The round trip is in flight
|
|
1013
|
+
| Attribute | Written on | Holds |
|
|
1014
|
+
| -------------- | ----------------------- | --------------------------------------------------------------------------------------- |
|
|
1015
|
+
| `lb-pending` | The dispatching element | The round trip is in flight |
|
|
987
1016
|
| `lb-error` | The dispatching element | The last round trip failed, cleared on the next — see [The round trip](#the-round-trip) |
|
|
988
|
-
| `lb-row-count` | A list scope | How many rows the scope is showing
|
|
1017
|
+
| `lb-row-count` | A list scope | How many rows the scope is showing |
|
|
1018
|
+
|
|
1019
|
+
They are also stamped on plain HTML, where a stylesheet is the only
|
|
1020
|
+
consumer: dim a pending button, mark a failed one, and style an empty list
|
|
1021
|
+
against `lb-row-count` rather than carrying an empty-state element.
|
|
1022
|
+
|
|
1023
|
+
The hub reads one of them. A native `lb-action` element, a button or a
|
|
1024
|
+
form, that is performed again while it carries `lb-pending` is ignored: the
|
|
1025
|
+
click or submit is cancelled and nothing is sent. A pending native action is
|
|
1026
|
+
therefore disabled in fact, and a stylesheet only has to show it. A widget is
|
|
1027
|
+
not held back this way, because a widget that sends on change must have its
|
|
1028
|
+
latest value sent rather than dropped.
|
|
989
1029
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
carrying an empty-state element.
|
|
1030
|
+
Alongside `lb-pending` the hub sets `aria-busy="true"` on the same element and
|
|
1031
|
+
removes it when the round trip settles, so assistive technology hears the
|
|
1032
|
+
state a stylesheet shows.
|
|
994
1033
|
|
|
995
1034
|
#### Row hooks
|
|
996
1035
|
|
|
997
1036
|
| Name | Implemented By | Behavior |
|
|
998
|
-
|
|
1037
|
+
| ------------------------------- | -------------- | ----------------------------------------------------------- |
|
|
999
1038
|
| `applyRow(root, row)` | Loadbare | Fills one scope from one row |
|
|
1000
1039
|
| `lbPlaceRow(el, row, template)` | Developer | Optional on a list scope: where a row goes |
|
|
1001
1040
|
| `lbRowsLanded()` | Developer | Optional on a list scope: scaffolding derived from the rows |
|
|
@@ -1020,7 +1059,7 @@ that defines it. The definition lives there and only there.
|
|
|
1020
1059
|
Loadbare/app owns every flag in this table.
|
|
1021
1060
|
|
|
1022
1061
|
| Flag | Default | Names | Defined in |
|
|
1023
|
-
|
|
1062
|
+
| ---------- | ------- | ----------------------------------------- | ------------------------------------------------- |
|
|
1024
1063
|
| `--src` | `src` | The application's own tree, scanned whole | [What the builder scans](#what-the-builder-scans) |
|
|
1025
1064
|
| `--out` | `dist` | Where the builder writes | [Running the builder](#running-the-builder) |
|
|
1026
1065
|
| `--watch` | off | Rebuild on change under `--src` | [Running the builder](#running-the-builder) |
|
|
@@ -1033,7 +1072,7 @@ named carries its meaning wherever it sits in the `--src` tree, so an
|
|
|
1033
1072
|
application must not use one of these names for anything else.
|
|
1034
1073
|
|
|
1035
1074
|
| File | How many | Holds | Defined in |
|
|
1036
|
-
|
|
1075
|
+
| ----------------------- | ---------------------- | -------------------------------------------------------- | ------------------------------------------------------- |
|
|
1037
1076
|
| `chrome.html` | Exactly one | The application's one HTML document | [Chrome](#chrome) |
|
|
1038
1077
|
| `imports.ts` | Zero or one | Default-exports an array of widget library package names | [Imported widget libraries](#imported-widget-libraries) |
|
|
1039
1078
|
| `<stub>.page.html` | One per page | One page's markup, as a fragment | [Pages](#pages) |
|
|
@@ -1051,7 +1090,7 @@ only when some origin has a stylesheet, `pages.ts` only when some page has a
|
|
|
1051
1090
|
`.queries.ts` or a `.requests.ts`.
|
|
1052
1091
|
|
|
1053
1092
|
| Path | Written | Holds | Read by | Defined in |
|
|
1054
|
-
|
|
1093
|
+
| ------------------ | ----------------- | ------------------------------------------------------------------ | ---------- | ------------------------------------------- |
|
|
1055
1094
|
| `/app.html` | Always | The chrome, built | The server | [Chrome](#chrome) |
|
|
1056
1095
|
| `/client.js` | Always | `<lb-hub>` and every widget the application uses | The chrome | [Chrome](#chrome) |
|
|
1057
1096
|
| `/client-entry.ts` | Always | The generated entry `client.js` is bundled from | Nothing | [Running the builder](#running-the-builder) |
|
|
@@ -1064,7 +1103,7 @@ Loadbare/app owns every key in this table. A widget library declares them.
|
|
|
1064
1103
|
An application never does.
|
|
1065
1104
|
|
|
1066
1105
|
| Key | Value | Means | Defined in |
|
|
1067
|
-
|
|
1106
|
+
| ------------------ | ------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
1068
1107
|
| `loadbare.widgets` | A path inside the package | Where this package's widgets are. Absent means the whole installed directory | [Imported widget libraries](#imported-widget-libraries) |
|
|
1069
1108
|
|
|
1070
1109
|
### Reserved namespaces
|
|
@@ -1072,11 +1111,11 @@ An application never does.
|
|
|
1072
1111
|
Loadbare/app owns every namespace in this table. Ownership of a name and
|
|
1073
1112
|
ownership of its behavior are stated separately, because they differ.
|
|
1074
1113
|
|
|
1075
|
-
| Namespace | Applies to | Loadbare owns | Defined in
|
|
1076
|
-
|
|
1077
|
-
| `lb-*` | HTML attributes | The names and their behavior | [The lb-* namespace](#the-lb--namespace)
|
|
1078
|
-
| `exp-*` | HTML attributes | The behavior; widget authors pick the values | [Build time parameters](#build-time-parameters)
|
|
1079
|
-
| `lb*` | Methods on custom elements | The names and their behavior | [Row hooks](#row-hooks)
|
|
1114
|
+
| Namespace | Applies to | Loadbare owns | Defined in |
|
|
1115
|
+
| --------- | -------------------------- | -------------------------------------------- | ----------------------------------------------- |
|
|
1116
|
+
| `lb-*` | HTML attributes | The names and their behavior | [The lb-* namespace](#the-lb--namespace) |
|
|
1117
|
+
| `exp-*` | HTML attributes | The behavior; widget authors pick the values | [Build time parameters](#build-time-parameters) |
|
|
1118
|
+
| `lb*` | Methods on custom elements | The names and their behavior | [Row hooks](#row-hooks) |
|
|
1080
1119
|
|
|
1081
1120
|
#### The lb-* namespace
|
|
1082
1121
|
|
|
@@ -1090,23 +1129,24 @@ actions on the wire — see [Page Queries](#page-queries).
|
|
|
1090
1129
|
|
|
1091
1130
|
Each attribute is defined in one section, and this table says which.
|
|
1092
1131
|
|
|
1093
|
-
| Attribute
|
|
1094
|
-
|
|
1095
|
-
| `lb-list`
|
|
1096
|
-
| `lb-row`
|
|
1097
|
-
| `lb-cell`
|
|
1098
|
-
| `lb-
|
|
1099
|
-
| `lb-key
|
|
1100
|
-
| `lb-value`
|
|
1101
|
-
| `lb-
|
|
1102
|
-
| `lb-
|
|
1103
|
-
| `lb-
|
|
1104
|
-
| `lb-
|
|
1105
|
-
| `lb-
|
|
1106
|
-
| `lb-
|
|
1107
|
-
| `lb-
|
|
1108
|
-
| `lb-
|
|
1109
|
-
| `lb-
|
|
1132
|
+
| Attribute | Written by | Defined in |
|
|
1133
|
+
| ----------------- | ------------- | --------------------------------------------------- |
|
|
1134
|
+
| `lb-list` | Developer | [Binding](#binding) |
|
|
1135
|
+
| `lb-row` | Developer | [Binding](#binding) |
|
|
1136
|
+
| `lb-cell` | Developer | [Binding](#binding) |
|
|
1137
|
+
| `lb-show` | Developer | [Displaying by condition](#displaying-by-condition) |
|
|
1138
|
+
| `lb-key` | Developer | [Binding](#binding) |
|
|
1139
|
+
| `lb-key-value` | Hub | [Binding](#binding) |
|
|
1140
|
+
| `lb-value` | Hub | [How a value lands](#how-a-value-lands) |
|
|
1141
|
+
| `lb-action` | Developer | [Requests](#requests) |
|
|
1142
|
+
| `lb-nav-link` | Developer | [Links](#links) |
|
|
1143
|
+
| `lb-pending` | Hub | [Request state](#request-state) |
|
|
1144
|
+
| `lb-error` | Hub | [Request state](#request-state) |
|
|
1145
|
+
| `lb-row-count` | Hub | [Request state](#request-state) |
|
|
1146
|
+
| `lb-unknown-page` | Developer | [Chrome](#chrome) |
|
|
1147
|
+
| `lb-slot` | Widget author | [Slots and templates](#slots-and-templates) |
|
|
1148
|
+
| `lb-template` | Widget author | [Slots and templates](#slots-and-templates) |
|
|
1149
|
+
| `lb-page` | Builder | [Pages](#pages) |
|
|
1110
1150
|
|
|
1111
1151
|
An attribute the builder does not recognize is left alone today. Refusing
|
|
1112
1152
|
one is on the list of validations still to land — see
|
|
@@ -1118,7 +1158,7 @@ Loadbare/app owns every tag in this table. An application writes them and
|
|
|
1118
1158
|
never defines them.
|
|
1119
1159
|
|
|
1120
1160
|
| Tag | Written in | Means | Defined in |
|
|
1121
|
-
|
|
1161
|
+
| ---------- | ---------- | ------------------------------ | ----------------- |
|
|
1122
1162
|
| `<lb-hub>` | The chrome | The application's live element | [Chrome](#chrome) |
|
|
1123
1163
|
|
|
1124
1164
|
### Reserved events
|
|
@@ -1127,7 +1167,7 @@ Loadbare/app owns every DOM event in this table, both the name and what its
|
|
|
1127
1167
|
`detail` carries.
|
|
1128
1168
|
|
|
1129
1169
|
| Event | Dispatched from | Bubbles | Cancelable | Defined in |
|
|
1130
|
-
|
|
1170
|
+
| ------------ | ---------------------- | ------- | ---------- | --------------------------------------- |
|
|
1131
1171
|
| `lb-request` | The element that acted | Yes | No | [The request event](#the-request-event) |
|
|
1132
1172
|
|
|
1133
1173
|
### Reserved attributes
|
|
@@ -1136,6 +1176,6 @@ Loadbare/app owns every attribute in this table, both the name and its
|
|
|
1136
1176
|
behavior.
|
|
1137
1177
|
|
|
1138
1178
|
| Attribute | Written on | Takes a value | Defined in |
|
|
1139
|
-
|
|
1179
|
+
| ----------------- | ----------------------------------- | ------------- | ----------------- |
|
|
1140
1180
|
| `lb-unknown-page` | A `<dialog>` in chrome | No | [Chrome](#chrome) |
|
|
1141
1181
|
| `lb-page` | A `<template>`, by the builder only | Yes | [Pages](#pages) |
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# A closed set for the Loadbare/app vocabulary
|
|
2
|
+
|
|
3
|
+
> **LLM-authored, not yet revised by a person.** Drafted by Claude on
|
|
4
|
+
> 2026-09-13, from a design session against `@loadbare/app` 0.7.3 while
|
|
5
|
+
> building a chart of accounts page. Treat it as a proposal, not as the
|
|
6
|
+
> author's statement.
|
|
7
|
+
|
|
8
|
+
## The question
|
|
9
|
+
|
|
10
|
+
Relational algebra is closed: every operation takes relations and returns a
|
|
11
|
+
relation. CRUD is closed in the same way on the write side. Closure is what
|
|
12
|
+
makes both easy to program against. Combinations do not explode, the cases
|
|
13
|
+
are finite, and nothing falls outside the model.
|
|
14
|
+
|
|
15
|
+
Loadbare/app maps three data shapes (cell, row, list) and three relational
|
|
16
|
+
operations (insert, update, delete) into HTML. This document asks whether
|
|
17
|
+
recent iterations have shown enough to state a **closed** vocabulary and set
|
|
18
|
+
of behaviors: one where every combination of shape, operation, position,
|
|
19
|
+
nesting and timing is defined, so application code never meets an edge case
|
|
20
|
+
outside the model.
|
|
21
|
+
|
|
22
|
+
Author note: a page can of course define invalid combinations, but their
|
|
23
|
+
lack of validity must be intelligibly reported as a failure to stick to the
|
|
24
|
+
closed set.
|
|
25
|
+
|
|
26
|
+
"Closed" is tested four ways:
|
|
27
|
+
|
|
28
|
+
1. **Shapes are closed.** Everything a page receives is one of the shapes,
|
|
29
|
+
or a patch on a list.
|
|
30
|
+
2. **Operations are closed over shapes.** Each operation yields a defined
|
|
31
|
+
result in the same vocabulary.
|
|
32
|
+
3. **Composition is closed.** Nesting, many scopes bound to one name, scopes
|
|
33
|
+
that appear late, and several names in one response all stay inside the
|
|
34
|
+
rules.
|
|
35
|
+
4. **Anything outside the spine is justified.** It either reduces to the
|
|
36
|
+
spine or is a separate, deliberately bounded axis.
|
|
37
|
+
|
|
38
|
+
## The model in one sentence
|
|
39
|
+
|
|
40
|
+
> **A page holds named relations. Every read and every write answers with
|
|
41
|
+
> new values for those names. Every scope is a view of one name.**
|
|
42
|
+
|
|
43
|
+
Every rule below follows from it.
|
|
44
|
+
|
|
45
|
+
## 1. Shapes: three, plus identity
|
|
46
|
+
|
|
47
|
+
| Shape | Attribute | Relational |
|
|
48
|
+
| -------- | ------------------------------------------- | ----------------------------------------------------------------- |
|
|
49
|
+
| list | `lb-list` | a relation, as a variable that holds rows |
|
|
50
|
+
| row | `lb-row` | a single row, read-only; a writable single row is a list of one |
|
|
51
|
+
| cell | `lb-cell` | a column value |
|
|
52
|
+
| identity | `lb-key` written, `lb-key-value` stamped | the primary key |
|
|
53
|
+
|
|
54
|
+
## 2. What every answer is made of: three forms
|
|
55
|
+
|
|
56
|
+
Every query, CRUD operation, declared action and refresh answers with
|
|
57
|
+
`{ name: result }`, and each result is one of:
|
|
58
|
+
|
|
59
|
+
- **a whole list** (an array), which replaces the relation, membership and
|
|
60
|
+
order included;
|
|
61
|
+
- **a patch** (`{ rows, drop }`), which upserts rows by key and drops keys;
|
|
62
|
+
- **a row** (an object), which replaces the single row.
|
|
63
|
+
|
|
64
|
+
This is the closure property. No operation answers with HTML, a redirect, or
|
|
65
|
+
instructions. Declared actions are open-ended, like stored procedures, but
|
|
66
|
+
their results are still in these three forms, so the landing side stays
|
|
67
|
+
closed.
|
|
68
|
+
|
|
69
|
+
## 3. Operations: three, plus one open door
|
|
70
|
+
|
|
71
|
+
| Operation | Takes from position | Carries | Answers |
|
|
72
|
+
| ---------------- | ------------------- | -------------------------------------------- | ------------ |
|
|
73
|
+
| `lb-row-insert` | list | values, gathered from the record | patch rows |
|
|
74
|
+
| `lb-row-update` | list, key | values, gathered or supplied by a widget | patch rows |
|
|
75
|
+
| `lb-row-delete` | list, key | nothing | patch drop |
|
|
76
|
+
| a declared name | list or row, key, cell | value | any names |
|
|
77
|
+
|
|
78
|
+
### Proposed: fold `lb-cell-change` into `lb-row-update`
|
|
79
|
+
|
|
80
|
+
A cell change is an update whose `values` holds one entry, and the hub
|
|
81
|
+
already keeps `values` a widget supplies. SQL has one UPDATE whether it sets
|
|
82
|
+
one column or many. On the server, `cellChange` and `rowUpdate` merge into
|
|
83
|
+
one `update(key, values)`, where a column absent from `values` is left as it
|
|
84
|
+
is.
|
|
85
|
+
|
|
86
|
+
### Reordering is not an operation
|
|
87
|
+
|
|
88
|
+
Position is not a relational concept; order is a column. A drag is an update
|
|
89
|
+
to an ordering column such as `display_order`, which the wire already
|
|
90
|
+
carries in `values`. No move operation is needed, and no field for "where
|
|
91
|
+
the row went".
|
|
92
|
+
|
|
93
|
+
## 4. Position: taken from the page, never written in the request
|
|
94
|
+
|
|
95
|
+
- An element's own attributes say what it displays. Its ancestors say where
|
|
96
|
+
it belongs.
|
|
97
|
+
- A nested scope begins a new scope.
|
|
98
|
+
- A request is an action plus a position. The hub supplies the position;
|
|
99
|
+
only an interaction supplies a value.
|
|
100
|
+
|
|
101
|
+
These are the rules [Theory](./theory.md) already states. They are listed
|
|
102
|
+
here because closure depends on them.
|
|
103
|
+
|
|
104
|
+
## 5. Landing: every combination defined
|
|
105
|
+
|
|
106
|
+
| Result → target | list scope | row scope | no scope yet |
|
|
107
|
+
| --------------- | ------------ | --------------------------------- | -------------------------- |
|
|
108
|
+
| whole list | reconcile | error: one name has one shape | stored |
|
|
109
|
+
| patch | upsert, drop | error | applied to the stored copy |
|
|
110
|
+
| row | error | fill | stored |
|
|
111
|
+
|
|
112
|
+
A result lands on every scope bound to its name, at any depth of nesting.
|
|
113
|
+
0.7.3 already does this: a patch to `groups` reaches a picker in every row of
|
|
114
|
+
an outer table, and the outer list's reconciliation does not mistake the
|
|
115
|
+
picker's rows for its own.
|
|
116
|
+
|
|
117
|
+
### The missing rule: timing
|
|
118
|
+
|
|
119
|
+
In 0.7.3 a scope that appears after its name has landed stays empty. A row
|
|
120
|
+
added to an outer list by a patch starts with every nested list empty, and a
|
|
121
|
+
widget's scaffolding built during landing, such as a ghost row holding a
|
|
122
|
+
picker, starts empty too. Pages work around it by ordering their queries so
|
|
123
|
+
the outer list lands first, which only covers the first load.
|
|
124
|
+
|
|
125
|
+
The closed rule:
|
|
126
|
+
|
|
127
|
+
> **The hub holds each name's current value. A scope shows that value
|
|
128
|
+
> whenever it appears.**
|
|
129
|
+
|
|
130
|
+
In relational terms a name is a relation variable, results are assignments to
|
|
131
|
+
it, and scopes are views of it. Whether a scope existed before or after its
|
|
132
|
+
data arrived stops mattering.
|
|
133
|
+
|
|
134
|
+
It also settles several things that were not worked on directly:
|
|
135
|
+
|
|
136
|
+
- The blocker in [TECHREF-1.0](./TECHREF-1.0.md), "Decide how a new row
|
|
137
|
+
fills a nested list".
|
|
138
|
+
- Pickers in newly created rows, and pickers in ghost rows built after the
|
|
139
|
+
first load.
|
|
140
|
+
- Query order in a page's queries file stops mattering.
|
|
141
|
+
- **What a partial patch row means.** A patch row upserts by key, and a
|
|
142
|
+
column it does not name is left unchanged, as an SQL UPDATE leaves it.
|
|
143
|
+
The reference is silent on this today.
|
|
144
|
+
- "No scope for a name" becomes an ordinary state rather than a console
|
|
145
|
+
warning, since a scope may simply not exist yet.
|
|
146
|
+
|
|
147
|
+
#### Mechanism
|
|
148
|
+
|
|
149
|
+
- The hub fills a cloned row, nested scopes included, before inserting it.
|
|
150
|
+
This extends the rule it already follows of filling before insertion.
|
|
151
|
+
- After each landing, it sweeps for scopes that have never received data.
|
|
152
|
+
For a list, an absent `lb-row-count` already marks one. A row scope would
|
|
153
|
+
need an equivalent mark.
|
|
154
|
+
- A widget that builds scopes outside a landing, at some later time, would
|
|
155
|
+
need a `MutationObserver`. No widget built so far does that.
|
|
156
|
+
|
|
157
|
+
#### What would prove it wrong
|
|
158
|
+
|
|
159
|
+
A case where the stored copy and the document legitimately disagree. The
|
|
160
|
+
only candidate is a control holding an edit not yet sent, and landing already
|
|
161
|
+
overwrites that in 0.7.3, so the rule introduces nothing new.
|
|
162
|
+
|
|
163
|
+
## 6. State attributes: four, stamped by the hub, read by CSS
|
|
164
|
+
|
|
165
|
+
`lb-value`, `lb-row-count`, `lb-pending`, `lb-error`.
|
|
166
|
+
|
|
167
|
+
## 7. Outside the algebra, deliberately bounded
|
|
168
|
+
|
|
169
|
+
- **Composition at build time:** `lb-slot`, `lb-template`.
|
|
170
|
+
- **The host channel:** `lb-page`, `lb-nav-link`, `lb-unknown-page`, and the
|
|
171
|
+
hub's own `lb-navigation` row. That row already shows the hub's own state
|
|
172
|
+
landing through the same three forms.
|
|
173
|
+
- **The widget protocol:** the `lb-request` event, which an ancestor may
|
|
174
|
+
stop, and the `lbPlaceRow` and `lbRowsLanded` hooks.
|
|
175
|
+
|
|
176
|
+
## What this changes in 0.7.3
|
|
177
|
+
|
|
178
|
+
1. The hub holds each name's current value and fills scopes that appear
|
|
179
|
+
late.
|
|
180
|
+
2. `lb-cell-change` folds into `lb-row-update`, with widget-supplied
|
|
181
|
+
`values`, and the server's `cellChange` and `rowUpdate` merge.
|
|
182
|
+
3. The reference states the patch row rule: upsert by key, unnamed columns
|
|
183
|
+
unchanged.
|
|
184
|
+
|
|
185
|
+
Everything else is the current vocabulary, now justified as closed.
|
|
186
|
+
|
|
187
|
+
## What the evidence has not settled
|
|
188
|
+
|
|
189
|
+
Everything above is backed by pages already built. These are not:
|
|
190
|
+
|
|
191
|
+
1. **Parameters and view state:** a filter, a selection, a collapsed
|
|
192
|
+
section. The likely closed answer is that URL query parameters become
|
|
193
|
+
columns of the `lb-navigation` row and reach queries as arguments, as
|
|
194
|
+
`<form method="get">` puts its fields in the query string. No page needs
|
|
195
|
+
it yet, so this is a prediction rather than evidence.
|
|
196
|
+
2. **Checkboxes and data types:** probably HTML's presence-or-absence
|
|
197
|
+
convention, still undecided.
|
|
198
|
+
3. **Concurrent writers:** staleness is a property of the stored copy, so
|
|
199
|
+
the rule in section 5 makes this question easier to state without
|
|
200
|
+
answering it.
|
|
201
|
+
4. **Error content:** `lb-error` records that a round trip failed, not why.
|
|
202
|
+
A closed answer would deliver the message as a result like any other,
|
|
203
|
+
perhaps as a hub row. Untested.
|