@loadbare/app 0.8.2 → 0.10.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/README.md +3 -3
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +74 -66
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +20 -19
- package/dist/build/expand.js.map +1 -1
- package/dist/build/locations.d.ts +2 -3
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +2 -3
- package/dist/build/locations.js.map +1 -1
- package/dist/build/pages.d.ts +3 -4
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +3 -4
- package/dist/build/pages.js.map +1 -1
- package/dist/core/lb-constants.d.ts +25 -23
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +95 -158
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/core/lb-types.d.ts +73 -75
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +58 -5
- package/dist/core/lb-types.js.map +1 -1
- package/dist/hub/lb-apply.d.ts +47 -37
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +174 -193
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +419 -415
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/dist/server/lb-express.d.ts +20 -13
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +50 -52
- package/dist/server/lb-express.js.map +1 -1
- package/dist/server/lb-server.d.ts +81 -116
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +151 -48
- package/dist/server/lb-server.js.map +1 -1
- package/docs/TECHREF-1.0.md +893 -558
- package/docs/comparison.md +222 -185
- package/docs/prior-art.md +15 -14
- package/docs/reference/builder.md +9 -3
- package/docs/reference/chrome.md +107 -56
- package/docs/reference/custom-elements.md +199 -173
- package/docs/reference/data-binding.md +375 -370
- package/docs/reference/overview.md +12 -10
- package/docs/reference/page-files.md +161 -86
- package/docs/reference/server.md +13 -7
- package/docs/reference/widgets.md +104 -110
- package/docs/roadmap.md +36 -31
- package/docs/terms-of-art.md +57 -0
- package/docs/testing.md +97 -68
- package/docs/theory.md +92 -58
- package/docs/tutorials/010-pages-and-navigation.md +20 -12
- package/docs/tutorials/020-css.md +6 -3
- package/docs/tutorials/030-html-decomposition.md +9 -7
- package/docs/tutorials/040-displaying-data.md +30 -13
- package/docs/tutorials/{050-actions.md → 050-requests.md} +25 -15
- package/docs/tutorials/060-custom-element-code.md +17 -16
- package/docs/tutorials/065-conditional-rendering.md +34 -23
- package/docs/tutorials/070-displaying-a-list.md +29 -21
- package/docs/tutorials/072-inserting-into-a-list.md +24 -16
- package/docs/tutorials/074-deleting-from-a-list.md +9 -7
- package/docs/tutorials/076-updating-a-list-item.md +11 -10
- package/docs/tutorials/080-widget-requests.md +71 -43
- package/docs/tutorials/090-using-widget-libraries.md +22 -22
- package/package.json +1 -1
- package/skills/loadbare-app/SKILL.md +178 -111
- package/skills/loadbare-app/references/TECHREF-1.0.md +893 -558
- package/skills/loadbare-app/references/builder.md +9 -3
- package/skills/loadbare-app/references/chrome.md +107 -56
- package/skills/loadbare-app/references/custom-elements.md +199 -173
- package/skills/loadbare-app/references/data-binding.md +375 -370
- package/skills/loadbare-app/references/overview.md +12 -10
- package/skills/loadbare-app/references/page-files.md +161 -86
- package/skills/loadbare-app/references/server.md +13 -7
- package/skills/loadbare-app/references/widgets.md +104 -110
- package/docs/analysis-accidental-complexity.md +0 -149
- package/docs/analysis-closed-set.md +0 -210
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# The Basic Widget Library
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`lb-
|
|
5
|
-
|
|
6
|
-
definition
|
|
7
|
-
nothing here is special-cased machinery.
|
|
3
|
+
Six widgets: `lb-input`, `lb-select`, `lb-options`, `lb-picker`, `lb-table`,
|
|
4
|
+
`lb-unknown-page`. Every one of them is an ordinary custom element, written
|
|
5
|
+
against the contracts in [Custom Elements](./custom-elements.md#html) for its
|
|
6
|
+
definition and [Custom Elements](./custom-elements.md#code) for its class.
|
|
8
7
|
|
|
9
8
|
They ship compiled, in `@loadbare/widgets`, a package the builder resolves the
|
|
10
|
-
way it resolves
|
|
9
|
+
way it resolves any other: it declares `"loadbare": { "widgets": "./dist" }`
|
|
11
10
|
and the builder scans that. Install it and list it:
|
|
12
11
|
|
|
13
12
|
```
|
|
@@ -23,140 +22,133 @@ See [Widgets from packages](./builder.md#widgets-from-packages) for what
|
|
|
23
22
|
listing a package does, and [The Builder](./builder.md#where-the-builder-looks)
|
|
24
23
|
for where a listed package sits in the cascade.
|
|
25
24
|
|
|
25
|
+
`lb-input`, `lb-select`, `lb-options` and `lb-picker` are controls:
|
|
26
|
+
form-associated custom elements with a `value` property that fire `change`.
|
|
27
|
+
Each carries `lb-column` and `lb-request` itself, the way an `<input>` does;
|
|
28
|
+
see [Being a control](./custom-elements.md#being-a-control).
|
|
29
|
+
|
|
26
30
|
## `lb-input`
|
|
27
31
|
|
|
28
|
-
Wraps an `<input>`. `
|
|
29
|
-
|
|
30
|
-
reserved `lb-row-update` saves the input's own cell; any other name sends that
|
|
31
|
-
action. Either carries the input's value, and the hub adds the scope the
|
|
32
|
-
input sits in.
|
|
32
|
+
Wraps an `<input>`. The hub sets its `value` from the column `lb-column`
|
|
33
|
+
names, and `lb-request` names what the widget sends on `change`:
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
```html
|
|
36
|
+
<lb-input lb-column="name" lb-request="lb-row-update" exp-label="Name"></lb-input>
|
|
37
|
+
```
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
| `exp-readonly` | the input's `readonly` attribute |
|
|
39
|
+
Inside a form, leave `lb-request` off. The form gathers the widget on submit
|
|
40
|
+
like any other control, so a widget that also sent its own request would
|
|
41
|
+
write the same edit twice.
|
|
42
42
|
|
|
43
|
-
|
|
|
44
|
-
|
|
45
|
-
| `
|
|
43
|
+
| Parameter | Fills |
|
|
44
|
+
|----------------|----------------------------------|
|
|
45
|
+
| `exp-label` | The visible `<label>` text |
|
|
46
|
+
| `exp-readonly` | The input's `readonly` attribute |
|
|
46
47
|
|
|
47
48
|
## `lb-select`
|
|
48
49
|
|
|
49
|
-
Wraps a `<select>` whose `<option>`s the
|
|
50
|
-
`lb-slot`). `lb-value` sets the select's `.value`; a `change` sends the
|
|
51
|
-
action named by `lb-action`, with the select's `.value` as the request's
|
|
52
|
-
`value` — the choice is the interaction, so this is the case where an
|
|
53
|
-
action carries a value.
|
|
50
|
+
Wraps a `<select>` whose `<option>`s the page writes inside the tag:
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
```html
|
|
53
|
+
<lb-select lb-column="team" lb-request="lb-row-update" exp-label="Team">
|
|
54
|
+
<option value="">No team</option>
|
|
55
|
+
<option value="Engines">Engines</option>
|
|
56
|
+
</lb-select>
|
|
57
|
+
```
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
| Parameter | Fills |
|
|
60
|
+
|-------------|----------------------------|
|
|
61
|
+
| `exp-label` | The visible `<label>` text |
|
|
61
62
|
|
|
62
63
|
## `lb-options`
|
|
63
64
|
|
|
64
|
-
A `<select>` whose `<option>`s
|
|
65
|
-
|
|
66
|
-
`lb-slot`), same as any list widget:
|
|
65
|
+
A `<select>` whose `<option>`s are the rows of the query it names. The page
|
|
66
|
+
writes the row template inside the widget:
|
|
67
67
|
|
|
68
68
|
```html
|
|
69
|
-
<lb-options lb-
|
|
70
|
-
<template
|
|
71
|
-
<option lb-
|
|
69
|
+
<lb-options lb-query="statuses" lb-column="status" lb-request="lb-row-update" exp-label="Status">
|
|
70
|
+
<template data-group="category">
|
|
71
|
+
<option lb-column="label"></option>
|
|
72
72
|
</template>
|
|
73
73
|
</lb-options>
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
| Parameter
|
|
77
|
-
|
|
78
|
-
| `exp-label` |
|
|
76
|
+
| Parameter | Fills |
|
|
77
|
+
|-------------|----------------------------|
|
|
78
|
+
| `exp-label` | The visible `<label>` text |
|
|
79
79
|
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
once, with `lb-key`.
|
|
80
|
+
- Each option's `value` is its row's key, the `lb-key-value` the hub stamps
|
|
81
|
+
on the live row, so the widget holds a key and shows a label.
|
|
83
82
|
- `data-group` on the row template sections the options into `<optgroup>`s,
|
|
84
|
-
one per distinct value,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
one per distinct value of that column, added and removed as rows arrive
|
|
84
|
+
and leave.
|
|
85
|
+
- Its `value` selects the option with that key, including an option that
|
|
86
|
+
arrives after the value did.
|
|
87
|
+
|
|
88
|
+
`lb-picker` is the same class with the row template supplied by its
|
|
89
|
+
definition.
|
|
90
|
+
|
|
91
|
+
## `lb-picker`
|
|
92
|
+
|
|
93
|
+
`lb-options`, with the row template built in, for a list where every row is
|
|
94
|
+
one option showing one column:
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<lb-picker
|
|
98
|
+
lb-query="statuses"
|
|
99
|
+
lb-column="status"
|
|
100
|
+
lb-request="lb-row-update"
|
|
101
|
+
exp-label="Status"
|
|
102
|
+
exp-column="label"
|
|
103
|
+
exp-group="category"
|
|
104
|
+
></lb-picker>
|
|
105
|
+
```
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
| Parameter | Fills |
|
|
108
|
+
|--------------|-----------------------------------------|
|
|
109
|
+
| `exp-label` | The visible `<label>` text |
|
|
110
|
+
| `exp-column` | The column each option shows |
|
|
111
|
+
| `exp-group` | The row template's `data-group` |
|
|
112
|
+
|
|
113
|
+
An option built from two columns, or a row with a second element, is
|
|
114
|
+
`lb-options` with the page's own row template.
|
|
93
115
|
|
|
94
116
|
## `lb-table`
|
|
95
117
|
|
|
96
|
-
A `<table>` that supplies its own scaffolding
|
|
97
|
-
|
|
98
|
-
`<template>` matched to a destination:
|
|
118
|
+
A `<table>` that supplies its own scaffolding. The page supplies the heading
|
|
119
|
+
row, the row template, and optionally a footer, each as a `<template>`:
|
|
99
120
|
|
|
100
121
|
```html
|
|
101
|
-
<lb-table lb-
|
|
102
|
-
<template lb-template="head">
|
|
122
|
+
<lb-table lb-query="ledger" exp-caption="Ledger">
|
|
123
|
+
<template lb-exp-template="head">
|
|
103
124
|
<tr><th>Date</th><th>Amount</th></tr>
|
|
104
125
|
</template>
|
|
105
|
-
<template
|
|
106
|
-
<tr><td lb-
|
|
126
|
+
<template data-sort="date" data-group="month">
|
|
127
|
+
<tr><td lb-column="date"></td><td lb-column="amount"></td></tr>
|
|
107
128
|
</template>
|
|
108
|
-
<template lb-template="foot">
|
|
109
|
-
<tr lb-
|
|
129
|
+
<template lb-exp-template="foot">
|
|
130
|
+
<tr lb-query="ledger-total"><td>Total</td><td lb-column="total"></td></tr>
|
|
110
131
|
</template>
|
|
111
132
|
</lb-table>
|
|
112
133
|
```
|
|
113
134
|
|
|
114
|
-
| Parameter
|
|
115
|
-
|
|
116
|
-
| `exp-caption` |
|
|
117
|
-
|
|
118
|
-
| Destination | Fills |
|
|
119
|
-
| ------------ | ----- |
|
|
120
|
-
| `head` (`lb-template="head"`) | the `<thead>` content |
|
|
121
|
-
| `foot` (`lb-template="foot"`) | the `<tfoot>` content |
|
|
122
|
-
| slot (no `lb-template`) | the row template, via `lb-slot` on `<tbody>` |
|
|
123
|
-
|
|
124
|
-
- `data-group` on the row template sections rows under a derived heading row,
|
|
125
|
-
one per distinct value, whose `colSpan` matches the row's own column
|
|
126
|
-
count. `data-sort` orders rows within a section (or the whole body, with no
|
|
127
|
-
grouping) by comparing each row's cell text.
|
|
128
|
-
- The `foot` destination is not delivered through `lbPlaceRow` — it's an
|
|
129
|
-
ordinary scope carrying its own `lb-row`, resolved by name like any
|
|
130
|
-
other on the page. A grand total is a second query over the same
|
|
131
|
-
data, not a row the hub hands the table.
|
|
132
|
-
|
|
133
|
-
## `lb-picker`
|
|
134
|
-
|
|
135
|
-
`lb-options`, with the row template supplied by the definition instead of
|
|
136
|
-
the page — for when every row is one option and nothing else varies:
|
|
137
|
-
|
|
138
|
-
```html
|
|
139
|
-
<lb-picker
|
|
140
|
-
lb-list="statuses"
|
|
141
|
-
exp-label="Status"
|
|
142
|
-
exp-key="id"
|
|
143
|
-
exp-cell="label"
|
|
144
|
-
exp-group="category"
|
|
145
|
-
lb-action="setStatus"
|
|
146
|
-
></lb-picker>
|
|
147
|
-
```
|
|
135
|
+
| Parameter | Fills |
|
|
136
|
+
|---------------|----------------------|
|
|
137
|
+
| `exp-caption` | The `<caption>` text |
|
|
148
138
|
|
|
149
|
-
|
|
|
150
|
-
|
|
151
|
-
|
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
| `exp-group` | the row template's `data-group` |
|
|
139
|
+
| Template | Fills |
|
|
140
|
+
|-----------------------------------|-----------------------------------------|
|
|
141
|
+
| `<template lb-exp-template="head">` | The `<thead>` |
|
|
142
|
+
| `<template lb-exp-template="foot">` | The `<tfoot>` |
|
|
143
|
+
| Any other `<template>` | The row template, in the `<tbody>` |
|
|
155
144
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
145
|
+
- `data-group` on the row template sections rows under a heading row, one
|
|
146
|
+
per distinct value of that column, spanning every column of the row.
|
|
147
|
+
- `data-sort` orders rows within a section, or within the whole body with no
|
|
148
|
+
grouping, by the text of that column.
|
|
149
|
+
- The footer is not a row of `ledger`. Its `<tr>` names a `row` query of its
|
|
150
|
+
own, which lands on the `<tr>` itself. A grand total is a second query over
|
|
151
|
+
the same data.
|
|
160
152
|
|
|
161
153
|
## `lb-unknown-page`
|
|
162
154
|
|
|
@@ -170,9 +162,11 @@ The chrome's dialog for a URL that names no page, as one tag:
|
|
|
170
162
|
</lb-hub>
|
|
171
163
|
```
|
|
172
164
|
|
|
173
|
-
It expands to a `<dialog lb-unknown-
|
|
174
|
-
`lb-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
165
|
+
It expands to a `<dialog lb-url-unknown lb-query="lb-url">` showing
|
|
166
|
+
`lb-path`, and the hub opens it when no page's stub matches the path. See
|
|
167
|
+
[An unknown page](./chrome.md#an-unknown-page) for the same dialog written
|
|
168
|
+
by hand.
|
|
169
|
+
|
|
170
|
+
| Parameter | Fills |
|
|
171
|
+
|-------------------|------------------------------------------|
|
|
172
|
+
| `exp-button-text` | The text of the button that closes it, `OK` by default |
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
# Assessing the accidental complexity claim
|
|
2
|
-
|
|
3
|
-
[Theory](./theory.md) claims that Loadbare/app carries less accidental
|
|
4
|
-
complexity than React, Angular, or the hypermedia libraries. This document
|
|
5
|
-
tests that claim against [TECHREF-1.0](./TECHREF-1.0.md), which is the
|
|
6
|
-
authoritative statement of what 1.0 means.
|
|
7
|
-
|
|
8
|
-
Written 2026-09-07, against `@loadbare/app` 0.6.0.
|
|
9
|
-
|
|
10
|
-
Brooks separates the difficulty of the user's problem from the work the tool
|
|
11
|
-
demands. The second kind is accidental, and the test applied here is whether
|
|
12
|
-
Loadbare removes such work or relocates it somewhere the author still pays
|
|
13
|
-
for it.
|
|
14
|
-
|
|
15
|
-
## Where the claim holds
|
|
16
|
-
|
|
17
|
-
The whole owned surface fits in the technical reference's cross-reference
|
|
18
|
-
section.
|
|
19
|
-
|
|
20
|
-
| Owned | Count |
|
|
21
|
-
|------------------|-------|
|
|
22
|
-
| `lb-*` attributes | 15 |
|
|
23
|
-
| `lb*` methods | 3 |
|
|
24
|
-
| Builder flags | 4 |
|
|
25
|
-
| Reserved filenames| 8 |
|
|
26
|
-
| Reserved tags | 1 |
|
|
27
|
-
| Reserved events | 1 |
|
|
28
|
-
|
|
29
|
-
React reaches that size before an application adds a router, a data layer, or
|
|
30
|
-
a bundler configuration, and each of those carries a surface of its own.
|
|
31
|
-
Loadbare/app asks for no configuration file today.
|
|
32
|
-
|
|
33
|
-
The server half is the strongest part of the argument. A page is markup, a
|
|
34
|
-
queries file, and a requests file. The application designs no endpoints,
|
|
35
|
-
writes no route table, and picks no serialization contract. Four lines of
|
|
36
|
-
Express carry the data channel. Neither the fat frameworks nor the
|
|
37
|
-
hypermedia libraries remove that category of work; htmx in particular leaves
|
|
38
|
-
the developer designing every endpoint and every fragment it answers with.
|
|
39
|
-
|
|
40
|
-
Building the HTML once removes reconciliation, keys, memoization, and effect
|
|
41
|
-
dependencies together. That is the largest single deletion in the design,
|
|
42
|
-
and it is the one the hypermedia libraries do not make either, since they
|
|
43
|
-
ship markup at run time and carry swap semantics to place it.
|
|
44
|
-
|
|
45
|
-
## What the technical reference already knows
|
|
46
|
-
|
|
47
|
-
Most of what an assessment finds is already on the blocker list. Each
|
|
48
|
-
concern below adds weight to an open item rather than naming a new one.
|
|
49
|
-
|
|
50
|
-
| Concern | Blocker |
|
|
51
|
-
|--------------------------------------------|--------------------------------------------|
|
|
52
|
-
| Values carry no type, so every application formats its own dates and money | Data types |
|
|
53
|
-
| No page is reachable by row | Take a position on the URL space |
|
|
54
|
-
| A widget receives one cell at a time | Whether a widget may receive a whole row |
|
|
55
|
-
| A widget repeats hub code to fire a request| Give a widget a way to fire its own request|
|
|
56
|
-
| Master-detail is unspecified | Decide what a nested list means |
|
|
57
|
-
| Every page redeclares the chrome's queries | Give the chrome a way to state its own queries |
|
|
58
|
-
|
|
59
|
-
The technical reference's own sample query calls `String()` on a count by
|
|
60
|
-
hand, which is the data-type blocker showing up in the documentation.
|
|
61
|
-
|
|
62
|
-
Two [roadmap](./roadmap.md) items carry the same weight as these and appear
|
|
63
|
-
in the first real application. Concurrent writers on one list have no
|
|
64
|
-
version or conflict story. Per-keystroke validation has no home, and the
|
|
65
|
-
roadmap expects it to sit in the widget while the server stays authoritative
|
|
66
|
-
for the same field.
|
|
67
|
-
|
|
68
|
-
Five sections of the technical reference are still marked `UNEDITED`:
|
|
69
|
-
Binding, Requests, Links, Widgets, and Widget authoring. Those are the
|
|
70
|
-
mechanisms an author touches on every page.
|
|
71
|
-
|
|
72
|
-
## Positions that carry a cost
|
|
73
|
-
|
|
74
|
-
Two constraints appear in the body of the technical reference as current
|
|
75
|
-
behavior rather than on the blocker list, so the project has taken a position
|
|
76
|
-
on each. Naming the cost is still fair.
|
|
77
|
-
|
|
78
|
-
The hub reaches its endpoint by absolute path, so an application cannot be
|
|
79
|
-
hosted under a subpath such as `example.com/myapp/`. A deployment that wants
|
|
80
|
-
several applications behind one host gives each one an origin.
|
|
81
|
-
|
|
82
|
-
Every route answers 200 with the same document, so the browser detects an
|
|
83
|
-
unknown page after the fact and the chrome's `lb-unknown-page` dialog reports
|
|
84
|
-
it. A crawler or a monitor that reads status codes sees a healthy response
|
|
85
|
-
for a path the application does not have.
|
|
86
|
-
|
|
87
|
-
## The gap recorded nowhere
|
|
88
|
-
|
|
89
|
-
Loadbare/app offers no way to display or style an element according to the
|
|
90
|
-
value that landed in it.
|
|
91
|
-
|
|
92
|
-
A cell lands on a custom element as the `lb-value` attribute, which a
|
|
93
|
-
stylesheet can select. A cell lands on a native element as its
|
|
94
|
-
`textContent`, which no selector reaches. The hub stamps `lb-pending`,
|
|
95
|
-
`lb-error`, and `lb-row-count`, which cover a request in flight, a request
|
|
96
|
-
that failed, and an empty list. Nothing covers a row whose `status` column
|
|
97
|
-
reads `overdue`.
|
|
98
|
-
|
|
99
|
-
An application that wants this writes a widget, and a widget that fires a
|
|
100
|
-
request pays the cost the widget-protocol blocker already names. So the
|
|
101
|
-
missing piece pushes the author toward the mechanism that is itself
|
|
102
|
-
unfinished.
|
|
103
|
-
|
|
104
|
-
This appears in neither the blockers, the roadmap's open questions, nor the
|
|
105
|
-
decided-against section. It is the one finding here that the technical
|
|
106
|
-
reference does not already record.
|
|
107
|
-
|
|
108
|
-
## The general form of the query gap
|
|
109
|
-
|
|
110
|
-
The URL-space blocker names one consequence of a broader constraint, and
|
|
111
|
-
stating the constraint directly is more useful than stating the consequence.
|
|
112
|
-
|
|
113
|
-
A query takes no argument from the browser. Its signature is `(ctx) => Row`
|
|
114
|
-
or `(ctx) => Row[]`, and `ctx` is what the application built from the Express
|
|
115
|
-
request. The hub's own request carries `page=<name>`. The hub reads
|
|
116
|
-
`location.pathname`, which drops the query string, so a path segment and a
|
|
117
|
-
search parameter are both invisible to the server.
|
|
118
|
-
|
|
119
|
-
An application that shows one selected record therefore holds the selection
|
|
120
|
-
in server state. A click fires an action, the handler records the selection
|
|
121
|
-
where `ctx` reaches it, and the refreshed query reads it back. That works
|
|
122
|
-
today and needs no new mechanism. It puts selection in the same territory as
|
|
123
|
-
the roadmap's concurrent-writers question, and it means a reload or a shared
|
|
124
|
-
link does not carry the record.
|
|
125
|
-
|
|
126
|
-
## Comparison with the hypermedia libraries
|
|
127
|
-
|
|
128
|
-
Theory rejects the existing tools for combining interpolation with
|
|
129
|
-
conditional and list rendering. The distinction is narrower than that.
|
|
130
|
-
Loadbare/app interpolates at build time, with defaults and a substitution
|
|
131
|
-
grammar, and renders lists at run time through `<template lb-key>`. What the
|
|
132
|
-
design rules out is conditionals and interpolation after the build.
|
|
133
|
-
|
|
134
|
-
Loadbare/app also adds a builder, a filename grammar, and a slot and template
|
|
135
|
-
system, where htmx asks for no build step. Against React and Angular the
|
|
136
|
-
surface comparison is decisive. Against htmx it is close, and the server
|
|
137
|
-
half is where Loadbare/app wins instead.
|
|
138
|
-
|
|
139
|
-
## Verdict
|
|
140
|
-
|
|
141
|
-
The claim holds on the server, and it holds on the client for everything the
|
|
142
|
-
reconciliation layer used to cost.
|
|
143
|
-
|
|
144
|
-
On the rest of the client it currently holds partly by not doing several
|
|
145
|
-
things database applications need, and the technical reference lists most of
|
|
146
|
-
them itself. Whether the claim survives 1.0 depends on how the URL space,
|
|
147
|
-
the row hook, the chrome queries, and value-driven display are answered, and
|
|
148
|
-
an answer of "decided against" counts as an answer only where an application
|
|
149
|
-
can still reach the behavior some other way.
|
|
@@ -1,210 +0,0 @@
|
|
|
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 one cell from 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
|
-
### Done: `lb-cell-change` folded into `lb-row-update`
|
|
79
|
-
|
|
80
|
-
A cell change is an update whose `values` holds one entry. SQL has one
|
|
81
|
-
UPDATE whether it sets one column or many. On the server, `cellChange` and
|
|
82
|
-
`rowUpdate` merged into `rowUpdate(key, values)`, where a column absent from
|
|
83
|
-
`values` is left as it is.
|
|
84
|
-
|
|
85
|
-
The fold needed one rule beyond this proposal. Without it, a widget sending
|
|
86
|
-
`lb-row-update` gathers its whole record, and every other editable cell in
|
|
87
|
-
the row goes with it. The rule: an element carrying `lb-cell` is a record
|
|
88
|
-
of one cell, the way a control has a value and a form has values. Its
|
|
89
|
-
`values` holds that cell alone, from the `value` the widget sent or else its
|
|
90
|
-
control, so a widget never writes its own column name into the request.
|
|
91
|
-
|
|
92
|
-
### Reordering is not an operation
|
|
93
|
-
|
|
94
|
-
Position is not a relational concept; order is a column. A drag is an update
|
|
95
|
-
to an ordering column such as `display_order`, which the wire already
|
|
96
|
-
carries in `values`. No move operation is needed, and no field for "where
|
|
97
|
-
the row went".
|
|
98
|
-
|
|
99
|
-
## 4. Position: taken from the page, never written in the request
|
|
100
|
-
|
|
101
|
-
- An element's own attributes say what it displays. Its ancestors say where
|
|
102
|
-
it belongs.
|
|
103
|
-
- A nested scope begins a new scope.
|
|
104
|
-
- A request is an action plus a position. The hub supplies the position;
|
|
105
|
-
only an interaction supplies a value.
|
|
106
|
-
|
|
107
|
-
These are the rules [Theory](./theory.md) already states. They are listed
|
|
108
|
-
here because closure depends on them.
|
|
109
|
-
|
|
110
|
-
## 5. Landing: every combination defined
|
|
111
|
-
|
|
112
|
-
| Result → target | list scope | row scope | no scope yet |
|
|
113
|
-
| --------------- | ------------ | --------------------------------- | -------------------------- |
|
|
114
|
-
| whole list | reconcile | error: one name has one shape | stored |
|
|
115
|
-
| patch | upsert, drop | error | applied to the stored copy |
|
|
116
|
-
| row | error | fill | stored |
|
|
117
|
-
|
|
118
|
-
A result lands on every scope bound to its name, at any depth of nesting.
|
|
119
|
-
0.7.3 already does this: a patch to `groups` reaches a picker in every row of
|
|
120
|
-
an outer table, and the outer list's reconciliation does not mistake the
|
|
121
|
-
picker's rows for its own.
|
|
122
|
-
|
|
123
|
-
### The missing rule: timing
|
|
124
|
-
|
|
125
|
-
In 0.7.3 a scope that appears after its name has landed stays empty. A row
|
|
126
|
-
added to an outer list by a patch starts with every nested list empty, and a
|
|
127
|
-
widget's scaffolding built during landing, such as a ghost row holding a
|
|
128
|
-
picker, starts empty too. Pages work around it by ordering their queries so
|
|
129
|
-
the outer list lands first, which only covers the first load.
|
|
130
|
-
|
|
131
|
-
The closed rule:
|
|
132
|
-
|
|
133
|
-
> **The hub holds each name's current value. A scope shows that value
|
|
134
|
-
> whenever it appears.**
|
|
135
|
-
|
|
136
|
-
In relational terms a name is a relation variable, results are assignments to
|
|
137
|
-
it, and scopes are views of it. Whether a scope existed before or after its
|
|
138
|
-
data arrived stops mattering.
|
|
139
|
-
|
|
140
|
-
It also settles several things that were not worked on directly:
|
|
141
|
-
|
|
142
|
-
- The blocker in [TECHREF-1.0](./TECHREF-1.0.md), "Decide how a new row
|
|
143
|
-
fills a nested list".
|
|
144
|
-
- Pickers in newly created rows, and pickers in ghost rows built after the
|
|
145
|
-
first load.
|
|
146
|
-
- Query order in a page's queries file stops mattering.
|
|
147
|
-
- **What a partial patch row means.** A patch row upserts by key, and a
|
|
148
|
-
column it does not name is left unchanged, as an SQL UPDATE leaves it.
|
|
149
|
-
The reference is silent on this today.
|
|
150
|
-
- "No scope for a name" becomes an ordinary state rather than a console
|
|
151
|
-
warning, since a scope may simply not exist yet.
|
|
152
|
-
|
|
153
|
-
#### Mechanism
|
|
154
|
-
|
|
155
|
-
- The hub fills a cloned row, nested scopes included, before inserting it.
|
|
156
|
-
This extends the rule it already follows of filling before insertion.
|
|
157
|
-
- After each landing, it sweeps for scopes that have never received data.
|
|
158
|
-
For a list, an absent `lb-row-count` already marks one. A row scope would
|
|
159
|
-
need an equivalent mark.
|
|
160
|
-
- A widget that builds scopes outside a landing, at some later time, would
|
|
161
|
-
need a `MutationObserver`. No widget built so far does that.
|
|
162
|
-
|
|
163
|
-
#### What would prove it wrong
|
|
164
|
-
|
|
165
|
-
A case where the stored copy and the document legitimately disagree. The
|
|
166
|
-
only candidate is a control holding an edit not yet sent, and landing already
|
|
167
|
-
overwrites that in 0.7.3, so the rule introduces nothing new.
|
|
168
|
-
|
|
169
|
-
## 6. State attributes: four, stamped by the hub, read by CSS
|
|
170
|
-
|
|
171
|
-
`lb-value`, `lb-row-count`, `lb-pending`, `lb-error`.
|
|
172
|
-
|
|
173
|
-
## 7. Outside the algebra, deliberately bounded
|
|
174
|
-
|
|
175
|
-
- **Composition at build time:** `lb-slot`, `lb-template`.
|
|
176
|
-
- **The host channel:** `lb-page`, `lb-nav-link`, `lb-unknown-page`, and the
|
|
177
|
-
hub's own `lb-navigation` row. That row already shows the hub's own state
|
|
178
|
-
landing through the same three forms.
|
|
179
|
-
- **The widget protocol:** the `lb-request` event, which an ancestor may
|
|
180
|
-
stop, and the `lbPlaceRow` and `lbRowsLanded` hooks.
|
|
181
|
-
|
|
182
|
-
## What this changes in 0.7.3
|
|
183
|
-
|
|
184
|
-
1. The hub holds each name's current value and fills scopes that appear
|
|
185
|
-
late.
|
|
186
|
-
2. `lb-cell-change` folds into `lb-row-update`, with an element carrying
|
|
187
|
-
`lb-cell` sending itself alone, and the server's `cellChange` and
|
|
188
|
-
`rowUpdate` merge. Done.
|
|
189
|
-
3. The reference states the patch row rule: upsert by key, unnamed columns
|
|
190
|
-
unchanged.
|
|
191
|
-
|
|
192
|
-
Everything else is the current vocabulary, now justified as closed.
|
|
193
|
-
|
|
194
|
-
## What the evidence has not settled
|
|
195
|
-
|
|
196
|
-
Everything above is backed by pages already built. These are not:
|
|
197
|
-
|
|
198
|
-
1. **Parameters and view state:** a filter, a selection, a collapsed
|
|
199
|
-
section. The likely closed answer is that URL query parameters become
|
|
200
|
-
columns of the `lb-navigation` row and reach queries as arguments, as
|
|
201
|
-
`<form method="get">` puts its fields in the query string. No page needs
|
|
202
|
-
it yet, so this is a prediction rather than evidence.
|
|
203
|
-
2. **Checkboxes and data types:** probably HTML's presence-or-absence
|
|
204
|
-
convention, still undecided.
|
|
205
|
-
3. **Concurrent writers:** staleness is a property of the stored copy, so
|
|
206
|
-
the rule in section 5 makes this question easier to state without
|
|
207
|
-
answering it.
|
|
208
|
-
4. **Error content:** `lb-error` records that a round trip failed, not why.
|
|
209
|
-
A closed answer would deliver the message as a result like any other,
|
|
210
|
-
perhaps as a hub row. Untested.
|