@loadbare/app 0.5.6 → 0.7.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 -4
- package/dist/build/assemble.d.ts +1 -1
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +81 -7
- package/dist/build/assemble.js.map +1 -0
- package/dist/build/cli.d.ts +2 -2
- package/dist/build/cli.js +3 -2
- package/dist/build/cli.js.map +1 -0
- package/dist/build/elements.js +1 -0
- package/dist/build/elements.js.map +1 -0
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +20 -32
- package/dist/build/expand.js.map +1 -0
- package/dist/build/format.js +1 -0
- package/dist/build/format.js.map +1 -0
- package/dist/build/locations.d.ts +4 -4
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +16 -5
- package/dist/build/locations.js.map +1 -0
- package/dist/build/origins.d.ts +0 -13
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +33 -8
- package/dist/build/origins.js.map +1 -0
- package/dist/build/package-root.js +1 -0
- package/dist/build/package-root.js.map +1 -0
- package/dist/build/pages.d.ts +7 -3
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +14 -7
- package/dist/build/pages.js.map +1 -0
- package/dist/build/styles.js +1 -0
- package/dist/build/styles.js.map +1 -0
- package/dist/core/lb-constants.d.ts +15 -13
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +104 -53
- package/dist/core/lb-constants.js.map +1 -0
- package/dist/core/lb-types.d.ts +103 -62
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +12 -3
- package/dist/core/lb-types.js.map +1 -0
- package/dist/hub/lb-apply.d.ts +28 -4
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +227 -40
- package/dist/hub/lb-apply.js.map +1 -0
- 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 +165 -110
- package/dist/hub/lb-hub.browser.js.map +1 -0
- package/dist/server/lb-express.d.ts +8 -5
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +46 -33
- package/dist/server/lb-express.js.map +1 -0
- package/dist/server/lb-server.d.ts +67 -45
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +56 -18
- package/dist/server/lb-server.js.map +1 -0
- package/docs/TECHREF-1.0.md +1107 -0
- package/docs/analysis-accidental-complexity.md +149 -0
- package/docs/reference/builder.md +4 -4
- package/docs/reference/chrome.md +10 -9
- package/docs/reference/custom-elements.md +54 -46
- package/docs/reference/data-binding.md +179 -97
- package/docs/reference/overview.md +1 -1
- package/docs/reference/page-files.md +64 -49
- package/docs/reference/server.md +25 -6
- package/docs/reference/widgets.md +22 -30
- package/docs/roadmap.md +68 -22
- package/docs/testing.md +47 -17
- package/docs/theory.md +116 -3
- package/docs/tutorials/010-pages-and-navigation.md +8 -8
- package/docs/tutorials/040-displaying-data.md +9 -9
- package/docs/tutorials/050-actions.md +5 -5
- package/docs/tutorials/060-custom-element-code.md +1 -1
- package/docs/tutorials/065-conditional-rendering.md +4 -4
- package/docs/tutorials/070-displaying-a-list.md +24 -47
- package/docs/tutorials/072-inserting-into-a-list.md +18 -15
- package/docs/tutorials/074-deleting-from-a-list.md +15 -17
- package/docs/tutorials/076-updating-a-list-item.md +20 -22
- package/docs/tutorials/080-widget-requests.md +22 -35
- package/docs/tutorials/090-using-widget-libraries.md +1 -1
- package/package.json +2 -3
- package/dist/hub/lb-rows.d.ts +0 -18
- package/dist/hub/lb-rows.d.ts.map +0 -1
- package/dist/hub/lb-rows.js +0 -106
- package/dist/tests/assemble.test.d.ts +0 -8
- package/dist/tests/assemble.test.d.ts.map +0 -1
- package/dist/tests/assemble.test.js +0 -58
- package/dist/tests/elements.test.d.ts +0 -8
- package/dist/tests/elements.test.d.ts.map +0 -1
- package/dist/tests/elements.test.js +0 -118
- package/dist/tests/expand.test.d.ts +0 -10
- package/dist/tests/expand.test.d.ts.map +0 -1
- package/dist/tests/expand.test.js +0 -250
- package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/imports.js +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
- package/dist/tests/helpers/console.d.ts +0 -20
- package/dist/tests/helpers/console.d.ts.map +0 -1
- package/dist/tests/helpers/console.js +0 -28
- package/dist/tests/helpers/dom.d.ts +0 -18
- package/dist/tests/helpers/dom.d.ts.map +0 -1
- package/dist/tests/helpers/dom.js +0 -22
- package/dist/tests/lb-apply.test.d.ts +0 -8
- package/dist/tests/lb-apply.test.d.ts.map +0 -1
- package/dist/tests/lb-apply.test.js +0 -153
- package/dist/tests/lb-express.test.d.ts +0 -14
- package/dist/tests/lb-express.test.d.ts.map +0 -1
- package/dist/tests/lb-express.test.js +0 -238
- package/dist/tests/lb-rows.test.d.ts +0 -12
- package/dist/tests/lb-rows.test.d.ts.map +0 -1
- package/dist/tests/lb-rows.test.js +0 -336
- package/dist/tests/lb-server.test.d.ts +0 -9
- package/dist/tests/lb-server.test.d.ts.map +0 -1
- package/dist/tests/lb-server.test.js +0 -495
- package/dist/tests/origins.test.d.ts +0 -10
- package/dist/tests/origins.test.d.ts.map +0 -1
- package/dist/tests/origins.test.js +0 -369
- package/dist/tests/pages.test.d.ts +0 -6
- package/dist/tests/pages.test.d.ts.map +0 -1
- package/dist/tests/pages.test.js +0 -98
- package/dist/tests/styles.test.d.ts +0 -7
- package/dist/tests/styles.test.d.ts.map +0 -1
- package/dist/tests/styles.test.js +0 -76
package/docs/reference/server.md
CHANGED
|
@@ -44,7 +44,7 @@ app.listen(8787);
|
|
|
44
44
|
|------------------------------|-------------------------------|
|
|
45
45
|
| `/client.js` | `dist/client.js` |
|
|
46
46
|
| `/app.css` | `dist/app.css` |
|
|
47
|
-
| `hubRoutes(hub, contextFor)` |
|
|
47
|
+
| `hubRoutes(hub, contextFor)` | The hub's own route |
|
|
48
48
|
| Every other GET | `dist/app.html` |
|
|
49
49
|
|
|
50
50
|
Everything else is optional:
|
|
@@ -66,9 +66,10 @@ Serve `dist/app.html` for every route the application does not claim,
|
|
|
66
66
|
including a path that names no page. See [`chrome.html`](./chrome.md) for the
|
|
67
67
|
`<dialog lb-unknown-page>` that announces that case to the user.
|
|
68
68
|
|
|
69
|
-
Give the server the origin root.
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
Give the server the origin root. The hub reaches its own route by absolute
|
|
70
|
+
path, so the application cannot be hosted under a subpath such as
|
|
71
|
+
`example.com/myapp/`, and anything proxying in front of the server passes the
|
|
72
|
+
whole path space through unchanged.
|
|
72
73
|
|
|
73
74
|
Leave `express.json()` to `hubRoutes`, which mounts it on its own routes.
|
|
74
75
|
|
|
@@ -85,7 +86,25 @@ Run the server under a TypeScript-capable runner. The builder writes
|
|
|
85
86
|
}
|
|
86
87
|
```
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
Node 22.18 and later also runs it with no runner at all, since Node strips
|
|
90
|
+
TypeScript types itself. `dist/pages.ts` imports each `.requests.ts` and
|
|
91
|
+
`.queries.ts` file by its full name, extension included, because Node looks
|
|
92
|
+
for exactly the path an import names.
|
|
93
|
+
|
|
94
|
+
Enable `allowImportingTsExtensions` in the application's `tsconfig.json`
|
|
95
|
+
when `tsc` type-checks the server. Without it, `tsc` rejects the `.ts`
|
|
96
|
+
extensions in `dist/pages.ts`:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"compilerOptions": {
|
|
101
|
+
"noEmit": true,
|
|
102
|
+
"allowImportingTsExtensions": true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Restart the server after adding or changing a `.requests.ts` or `.queries.ts`
|
|
89
108
|
file.
|
|
90
109
|
|
|
91
110
|
## Database layer
|
|
@@ -119,5 +138,5 @@ declare module "@loadbare/app/server" {
|
|
|
119
138
|
```
|
|
120
139
|
|
|
121
140
|
Add a field for anything else a request needs — the authenticated user, a
|
|
122
|
-
request id, a feature flag set. Queries and
|
|
141
|
+
request id, a feature flag set. Queries and requests read them from `ctx`; see
|
|
123
142
|
[page files](./page-files.md).
|
|
@@ -26,11 +26,12 @@ for where a listed package sits in the cascade.
|
|
|
26
26
|
## `lb-input`
|
|
27
27
|
|
|
28
28
|
Wraps an `<input>`. `lb-value` sets the input's `.value`. The widget sends
|
|
29
|
-
nothing on its own: `
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
nothing on its own: `lb-action` names what the input's `change` sends. The
|
|
30
|
+
reserved `lb-cell-change` sends `lb-cell-change`; 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
33
|
|
|
33
|
-
An input inside an `lb-insert` or `lb-update` form leaves the attribute off.
|
|
34
|
+
An input inside an `lb-row-insert` or `lb-row-update` form leaves the attribute off.
|
|
34
35
|
The form reads every `lb-cell` in it on submit and sends one request for all
|
|
35
36
|
of them, so an input that also sent its own would write the same edit twice.
|
|
36
37
|
|
|
@@ -41,7 +42,7 @@ of them, so an input that also sent its own would write the same edit twice.
|
|
|
41
42
|
|
|
42
43
|
| Attribute | Asks for |
|
|
43
44
|
| ---------- | ----- |
|
|
44
|
-
| `
|
|
45
|
+
| `lb-action` | what to send on `change`; `lb-cell-change` for the cell's own edit |
|
|
45
46
|
|
|
46
47
|
## `lb-select`
|
|
47
48
|
|
|
@@ -57,15 +58,6 @@ action carries a value.
|
|
|
57
58
|
|
|
58
59
|
Requires `lb-action` — a change with none logs and sends nothing.
|
|
59
60
|
|
|
60
|
-
## `lb-list`
|
|
61
|
-
|
|
62
|
-
The plain repeater: whatever the author writes inside a
|
|
63
|
-
`<template lb-key="...">` is cloned once per row, in arrival order. No
|
|
64
|
-
grouping, no sorting, no request of its own — it exists because a
|
|
65
|
-
`Projection` has to land on something with `acceptRows`, and a bare
|
|
66
|
-
`<table>` can't be one (a custom element written inside `<tbody>` is
|
|
67
|
-
discarded by the parser).
|
|
68
|
-
|
|
69
61
|
## `lb-options`
|
|
70
62
|
|
|
71
63
|
A `<select>` whose `<option>`s come from a query instead of being written by
|
|
@@ -73,8 +65,8 @@ hand. The author supplies the row template inside the widget (via
|
|
|
73
65
|
`lb-slot`), same as any list widget:
|
|
74
66
|
|
|
75
67
|
```html
|
|
76
|
-
<lb-options lb-
|
|
77
|
-
<template lb-key="id"
|
|
68
|
+
<lb-options lb-list="statuses" exp-label="Status" lb-action="setStatus">
|
|
69
|
+
<template lb-key="id" data-group="category">
|
|
78
70
|
<option lb-cell="label"></option>
|
|
79
71
|
</template>
|
|
80
72
|
</lb-options>
|
|
@@ -84,11 +76,11 @@ hand. The author supplies the row template inside the widget (via
|
|
|
84
76
|
| ---------- | ----- |
|
|
85
77
|
| `exp-label` | the visible `<label>` text |
|
|
86
78
|
|
|
87
|
-
- The row's key becomes the option's `value
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
- The row's key becomes the option's `value`: the `lb-key-value` the hub
|
|
80
|
+
stamps on each option is copied across, so the page names the key column
|
|
81
|
+
once, with `lb-key`.
|
|
82
|
+
- `data-group` on the row template sections the options into `<optgroup>`s,
|
|
90
83
|
one per distinct value, created and removed as rows arrive and leave.
|
|
91
|
-
- `lb-sort` is not read by this widget.
|
|
92
84
|
- A `change` sends the action named by `lb-action`, value from the
|
|
93
85
|
select's `.value`.
|
|
94
86
|
|
|
@@ -102,15 +94,15 @@ heading row, the row template, and optionally a footer, each as a
|
|
|
102
94
|
`<template>` matched to a destination:
|
|
103
95
|
|
|
104
96
|
```html
|
|
105
|
-
<lb-table lb-
|
|
97
|
+
<lb-table lb-list="ledger" exp-caption="Ledger">
|
|
106
98
|
<template lb-template="head">
|
|
107
99
|
<tr><th>Date</th><th>Amount</th></tr>
|
|
108
100
|
</template>
|
|
109
|
-
<template lb-key="id"
|
|
101
|
+
<template lb-key="id" data-sort="date" data-group="month">
|
|
110
102
|
<tr><td lb-cell="date"></td><td lb-cell="amount"></td></tr>
|
|
111
103
|
</template>
|
|
112
104
|
<template lb-template="foot">
|
|
113
|
-
<tr lb-
|
|
105
|
+
<tr lb-row="ledger-total"><td>Total</td><td lb-cell="total"></td></tr>
|
|
114
106
|
</template>
|
|
115
107
|
</lb-table>
|
|
116
108
|
```
|
|
@@ -125,13 +117,13 @@ heading row, the row template, and optionally a footer, each as a
|
|
|
125
117
|
| `foot` (`lb-template="foot"`) | the `<tfoot>` content |
|
|
126
118
|
| slot (no `lb-template`) | the row template, via `lb-slot` on `<tbody>` |
|
|
127
119
|
|
|
128
|
-
- `
|
|
120
|
+
- `data-group` on the row template sections rows under a derived heading row,
|
|
129
121
|
one per distinct value, whose `colSpan` matches the row's own column
|
|
130
|
-
count. `
|
|
122
|
+
count. `data-sort` orders rows within a section (or the whole body, with no
|
|
131
123
|
grouping) by comparing each row's cell text.
|
|
132
|
-
- The `foot` destination is not delivered through `
|
|
133
|
-
ordinary scope carrying its own `lb-
|
|
134
|
-
other on the page. A grand total is a second
|
|
124
|
+
- The `foot` destination is not delivered through `lbPlaceRow` — it's an
|
|
125
|
+
ordinary scope carrying its own `lb-row`, resolved by name like any
|
|
126
|
+
other on the page. A grand total is a second query over the same
|
|
135
127
|
data, not a row the hub hands the table.
|
|
136
128
|
|
|
137
129
|
## `lb-picker`
|
|
@@ -141,7 +133,7 @@ the page — for when every row is one option and nothing else varies:
|
|
|
141
133
|
|
|
142
134
|
```html
|
|
143
135
|
<lb-picker
|
|
144
|
-
lb-
|
|
136
|
+
lb-list="statuses"
|
|
145
137
|
exp-label="Status"
|
|
146
138
|
exp-key="id"
|
|
147
139
|
exp-cell="label"
|
|
@@ -155,7 +147,7 @@ the page — for when every row is one option and nothing else varies:
|
|
|
155
147
|
| `exp-label` | the visible `<label>` text |
|
|
156
148
|
| `exp-key` | the row template's `lb-key` |
|
|
157
149
|
| `exp-cell` | the option's `lb-cell` |
|
|
158
|
-
| `exp-group` | the row template's `
|
|
150
|
+
| `exp-group` | the row template's `data-group` |
|
|
159
151
|
|
|
160
152
|
Behavior — grouping, key-as-value, the action on change — is inherited
|
|
161
153
|
whole from `lb-options`; a page author who needs a second element in the
|
package/docs/roadmap.md
CHANGED
|
@@ -17,32 +17,17 @@ wrong thing. Revisit when the described symptom actually shows up.
|
|
|
17
17
|
|
|
18
18
|
### Staleness and concurrent writers
|
|
19
19
|
|
|
20
|
-
Two tabs, or two users, updating the same
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
Two tabs, or two users, updating the same list at once. A solo developer
|
|
21
|
+
testing in one browser will not produce this by accident, and retrofitting a
|
|
22
|
+
version or conflict check onto every row after the fact touches every widget
|
|
23
|
+
that writes.
|
|
24
24
|
|
|
25
25
|
### Nesting
|
|
26
26
|
|
|
27
|
-
Whether a
|
|
28
|
-
row). [Theory](./theory.md) already flags this as possibly load-bearing if
|
|
27
|
+
Whether a row may contain a list (master-detail, an expanding row). [Theory](./theory.md) already flags this as possibly load-bearing if
|
|
29
28
|
disallowed. Worth a decision-in-principle the first time a master-detail
|
|
30
29
|
page is built, even before the mechanism is needed elsewhere.
|
|
31
30
|
|
|
32
|
-
### Whether `lb-query` may be inherited
|
|
33
|
-
|
|
34
|
-
Currently every scope states its own `lb-query`; nothing resolves one from
|
|
35
|
-
an ancestor. Inheritance would be friendlier to the page author but adds a
|
|
36
|
-
resolution rule, and a resolution rule is a mechanism this framework has
|
|
37
|
-
otherwise avoided. Worth deciding before an application grows deep enough
|
|
38
|
-
nesting that restating the query on every level starts to hurt.
|
|
39
|
-
|
|
40
|
-
### Whether `lb-key` is always required
|
|
41
|
-
|
|
42
|
-
Undecided whether every row needs `lb-key`, or only a row something
|
|
43
|
-
targets (a delete button, an update form). Revisit if a list widget shows
|
|
44
|
-
up that never needs to address an individual row by key.
|
|
45
|
-
|
|
46
31
|
### Pending appearance
|
|
47
32
|
|
|
48
33
|
A value that hasn't arrived yet is probably derivable from an absent
|
|
@@ -112,12 +97,53 @@ enough to measure.
|
|
|
112
97
|
|
|
113
98
|
### Data binding utilities
|
|
114
99
|
|
|
115
|
-
If a dev team wishes to make their own widgets that identify `lb-
|
|
116
|
-
`lb-key`, `lb-cell`, they must repeat the code that is present in the hub.
|
|
100
|
+
If a dev team wishes to make their own widgets that identify `lb-list` or `lb-row`,
|
|
101
|
+
`lb-key-value`, `lb-cell`, they must repeat the code that is present in the hub.
|
|
117
102
|
|
|
118
103
|
Perhaps a utility that can be called, like `getDataScope(el)`, to help
|
|
119
104
|
clean up the code in these cases.
|
|
120
105
|
|
|
106
|
+
### Build-time checking of `lb-action` against the declared requests
|
|
107
|
+
|
|
108
|
+
Release 1.0 resolves every `lb-action` value at request time. A value naming
|
|
109
|
+
no entry under `actions`, or a reserved operation naming a list with no entry
|
|
110
|
+
under `crud`, is found on the first click: a server console warning and an
|
|
111
|
+
empty answer, described in
|
|
112
|
+
[Page files](./reference/page-files.md). A typo builds cleanly.
|
|
113
|
+
|
|
114
|
+
The builder already expands each page and walks the finished document in
|
|
115
|
+
`checkBinding`, tracking the enclosing `lb-list` scope, so collecting every
|
|
116
|
+
action name together with the list it sits in needs no new machinery. What
|
|
117
|
+
it lacks is the other half of the comparison: it knows the path of a
|
|
118
|
+
`.requests.ts` file and never reads its contents.
|
|
119
|
+
|
|
120
|
+
Two routes to the declared names, and the choice is the decision:
|
|
121
|
+
|
|
122
|
+
- **Load the built module.** Bundle the generated `pages.ts`, import it, and
|
|
123
|
+
read the keys off the finished objects. Exact, and indifferent to how the
|
|
124
|
+
object was written. The build then executes application import-time code,
|
|
125
|
+
and a builder that only reads files stops being that.
|
|
126
|
+
- **Parse the source.** Read the keys statically with the TypeScript compiler
|
|
127
|
+
API. No application code runs, at the price of a real dependency and of
|
|
128
|
+
guessing wrong on anything not written as a plain object literal.
|
|
129
|
+
|
|
130
|
+
Three smaller calls come with either route. The chrome belongs to no page, so
|
|
131
|
+
an action there can only be required of every page or forbidden. A page with
|
|
132
|
+
no requests file makes any action on it an error. And the check sees markup
|
|
133
|
+
only, so it is sound only if `lb-action` is authored and never assigned by
|
|
134
|
+
script — no widget assigns it today, and the rule has never been written
|
|
135
|
+
down.
|
|
136
|
+
|
|
137
|
+
This reverses the position stated above `walkBinding`, that markup and server
|
|
138
|
+
are separate artifacts and the comparison belongs to `createHub`. The same
|
|
139
|
+
machinery would then also let `lb-list` and `lb-row` names be checked against
|
|
140
|
+
the declared queries.
|
|
141
|
+
|
|
142
|
+
One piece is separable and needs none of the above: an `lb-action` beginning
|
|
143
|
+
with `lb-` that names none of the four operations is a typo the builder can
|
|
144
|
+
refuse from markup alone. `LB_ACTIONS` in `core/lb-constants.ts` exists for
|
|
145
|
+
this, and the builder does not yet import it.
|
|
146
|
+
|
|
121
147
|
### A language server
|
|
122
148
|
|
|
123
149
|
...for Loadbare HTML.
|
|
@@ -128,3 +154,23 @@ Internationalization would require a potential extension to build-time
|
|
|
128
154
|
expansion allows a strings file. We could either preserve the fully
|
|
129
155
|
static build-time system and create multiple versions of `app.html`, or we
|
|
130
156
|
could add label hydration to the page navigation stage.
|
|
157
|
+
|
|
158
|
+
## Decided against
|
|
159
|
+
|
|
160
|
+
Settled questions, kept here so they are not reopened. Each names what was
|
|
161
|
+
asked for and why the answer is no.
|
|
162
|
+
|
|
163
|
+
### Interpolation in a placeholder
|
|
164
|
+
|
|
165
|
+
`{{name}}` is the whole of an attribute value or the whole of a text node. A
|
|
166
|
+
placeholder inside a longer string, such as `title="Hello {{name}}"`, ships
|
|
167
|
+
literal braces.
|
|
168
|
+
|
|
169
|
+
A value that is absent with no default drops the attribute, which is how a
|
|
170
|
+
boolean attribute is turned off. Inside a longer string the same placeholder
|
|
171
|
+
could only become an empty string, so one syntax would mean two things.
|
|
172
|
+
Interpolation would also reserve `{{` and `|` in every text node and
|
|
173
|
+
attribute value, with no escape for either.
|
|
174
|
+
|
|
175
|
+
Text has a workaround: `<div>Hello, <span>{{world}}</span></div>` places the
|
|
176
|
+
placeholder in a text node of its own. Attribute values have none.
|
package/docs/testing.md
CHANGED
|
@@ -19,8 +19,8 @@ emulation has given up the only failure mode it was looking for.
|
|
|
19
19
|
|------|--------------------------------------------------|-------------|
|
|
20
20
|
| 1 | Expansion and the build — `build/` | node |
|
|
21
21
|
| 2 | The engine — `server/`, and the Express adapter | node |
|
|
22
|
-
| 3 | Landing — `hub/lb-apply.ts
|
|
23
|
-
| 4 | The hub — `hub/lb-hub.browser.ts`
|
|
22
|
+
| 3 | Landing — `hub/lb-apply.ts` | jsdom |
|
|
23
|
+
| 4 | The hub — `hub/lb-hub.browser.ts` | jsdom |
|
|
24
24
|
|
|
25
25
|
All four tiers run under `npm test` today and need no dependency that is not
|
|
26
26
|
already installed. A fifth environment — a real browser — is discussed at the
|
|
@@ -112,7 +112,7 @@ needs a browser either.
|
|
|
112
112
|
`createHub` takes a plain object and returns an object. Nothing in
|
|
113
113
|
`server/lb-server.ts` opens a socket, so the fixtures are counting stubs.
|
|
114
114
|
|
|
115
|
-
- `
|
|
115
|
+
- `onPageEnter` runs before any query, and the whole query set runs after it
|
|
116
116
|
- an unknown page answers `{}` and says so
|
|
117
117
|
- an unknown query name in a refresh set is skipped, and its siblings run
|
|
118
118
|
- an action the page did not declare is refused — the rule that keeps the
|
|
@@ -128,7 +128,7 @@ malformed body does not throw.
|
|
|
128
128
|
|
|
129
129
|
## Tier 3 — Landing
|
|
130
130
|
|
|
131
|
-
`applyData`, `
|
|
131
|
+
`applyData`, `applyRow` and `applyList` are the most intricate code in the
|
|
132
132
|
framework and the most likely to break in ways nobody notices. They are also
|
|
133
133
|
pure DOM: no fetch, no widget upgrade, no history. jsdom is real evidence
|
|
134
134
|
here.
|
|
@@ -137,11 +137,11 @@ here.
|
|
|
137
137
|
- the root of a scope counts as a cell if it carries one, which is what makes
|
|
138
138
|
an `<option>` row possible
|
|
139
139
|
- a query with no scope is reported and skipped; several scopes for one query
|
|
140
|
-
are all filled; a
|
|
140
|
+
are all filled; a set of rows landing on a scope bound with `lb-row`
|
|
141
141
|
is reported rather than thrown
|
|
142
142
|
- `rows` decides membership and order, so a key that did not arrive is gone
|
|
143
143
|
- `patch` disturbs only what it names, in contents and in position
|
|
144
|
-
- `
|
|
144
|
+
- `lb-row-count` is counted from the DOM after reconciliation, so a set and a
|
|
145
145
|
patch ending in the same state report the same number
|
|
146
146
|
- the `place` callback is called for a fresh row always, and for an existing
|
|
147
147
|
row only under `rows`. That is today's behavior, not a decision — a patch
|
|
@@ -149,8 +149,8 @@ here.
|
|
|
149
149
|
is true now, and is the one that flips if that changes.
|
|
150
150
|
|
|
151
151
|
**Two properties**, written as loops rather than with a library. Applying the
|
|
152
|
-
same
|
|
153
|
-
sequence of patches is
|
|
152
|
+
same set twice is applying it once. And a set reached through any
|
|
153
|
+
sequence of patches is that set reached from empty. Convergence is the
|
|
154
154
|
actual contract of a reconciler, and those two say it better than twenty
|
|
155
155
|
examples.
|
|
156
156
|
|
|
@@ -159,7 +159,40 @@ examples.
|
|
|
159
159
|
The widget half of this tier lives in `@loadbare/widgets` and is tested
|
|
160
160
|
there, against the same jsdom harness described below — see
|
|
161
161
|
[`packages/widgets/AGENTS.md`](../../widgets/AGENTS.md). What follows applies
|
|
162
|
-
to both, and the hub is what remains here.
|
|
162
|
+
to both, and the hub element is what remains here.
|
|
163
|
+
|
|
164
|
+
The hub is the one source file whose coverage is split across two tiers, so
|
|
165
|
+
the split is written down rather than left to judgment. jsdom is evidence
|
|
166
|
+
for everything the hub does to the document it is already holding: which
|
|
167
|
+
element an event came from, what request that produces, which attributes
|
|
168
|
+
land where, and which page host a path selects. It is not evidence for
|
|
169
|
+
anything about a real network or a real frame, and those cases are listed
|
|
170
|
+
under the browser tier below and are not written.
|
|
171
|
+
|
|
172
|
+
Two things the hub reaches for do not exist in jsdom 25, so the harness
|
|
173
|
+
supplies them. `fetch` is absent, and a test wants a scripted one anyway.
|
|
174
|
+
`HTMLDialogElement.showModal` is absent, so the harness records the call
|
|
175
|
+
instead. `history.pushState` and `popstate` are real, so navigation needs no
|
|
176
|
+
help.
|
|
177
|
+
|
|
178
|
+
What the hub is tested for here:
|
|
179
|
+
|
|
180
|
+
- `requestFor` builds each of the three operations a native element can
|
|
181
|
+
send, and refuses a reserved name it cannot turn into one
|
|
182
|
+
- a click finds the nearest `lb-action`, and skips a hyphenated tag and a
|
|
183
|
+
form, both of which own the interaction themselves
|
|
184
|
+
- a submit gathers the form's cells, and reports a cell with no control
|
|
185
|
+
- a request arriving with no action, or with a reserved name that is not one
|
|
186
|
+
of the four, is refused before it reaches the wire
|
|
187
|
+
- `lb-pending` lands on the element that dispatched, `lb-error` replaces it
|
|
188
|
+
on failure, and the next request clears it
|
|
189
|
+
- a path with no page host reports and opens the unknown-page dialog; two
|
|
190
|
+
hosts for one name report and take the first
|
|
191
|
+
- `lb-navigation` lands with the path and the label of the link that names it
|
|
192
|
+
- `lb-nav-link` pushes state and swaps the host, `popstate` reverses it, and
|
|
193
|
+
an ordinary anchor is left alone
|
|
194
|
+
- `hidden` comes off the body once a page has landed, including the page
|
|
195
|
+
that failed to load
|
|
163
196
|
|
|
164
197
|
Widgets are small and their logic is local, so jsdom carries them: a value
|
|
165
198
|
reaches the control the widget owns, a change dispatches the declared action,
|
|
@@ -183,19 +216,16 @@ painting, or navigation.
|
|
|
183
216
|
|
|
184
217
|
## Not built: the browser tier
|
|
185
218
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
block never paint an empty frame are all statements about a browser.
|
|
219
|
+
Tier 4 stops at the document. A real network and a real frame are statements
|
|
220
|
+
about a browser, and stubbing either of them proves nothing about it.
|
|
189
221
|
|
|
190
222
|
No browser test runner is installed and none of the following exists. They
|
|
191
223
|
are recorded here as the shape of the work, not as coverage:
|
|
192
224
|
|
|
193
225
|
1. a cold load fills `<main>` with no empty flash
|
|
194
|
-
2.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
4. the wizard never displays a step the server has not confirmed
|
|
198
|
-
5. the view-source invariant
|
|
226
|
+
2. the ten-second deadline aborts a request that never answers
|
|
227
|
+
3. the wizard never displays a step the server has not confirmed
|
|
228
|
+
4. the view-source invariant
|
|
199
229
|
|
|
200
230
|
The last is the one worth a browser. Loadbare's central claim is that no
|
|
201
231
|
markup exists in the DOM that is not in view-source, and that the difference
|
package/docs/theory.md
CHANGED
|
@@ -138,7 +138,7 @@ returns data objects. The HTML contains attributes identifying how
|
|
|
138
138
|
elements are bound to data, so the framework can update them.
|
|
139
139
|
|
|
140
140
|
This should be self-evidently efficacious for scalar cells and
|
|
141
|
-
|
|
141
|
+
rows. Something like `<span lb-row="siteStats" lb-cell="visitCount"></span>`
|
|
142
142
|
or the same type of addressing for an input makes it trivial to
|
|
143
143
|
both hyrdrate and refresh forms, and to gather a form's values
|
|
144
144
|
to send to the server.
|
|
@@ -325,7 +325,7 @@ Javascript seems cool if you need a fully mutatable DOM, but when you realize
|
|
|
325
325
|
you don't need that, it's back to plain old reliable HTML.
|
|
326
326
|
|
|
327
327
|
Another big win is the server code for pages. The clean organization
|
|
328
|
-
of
|
|
328
|
+
of requests, actions, CRUD and queries without boilerplate is pure
|
|
329
329
|
Essential Complexity. The automatic building of the API and 4 lines to
|
|
330
330
|
handle it in Express is lower than I have seen in any other tool.
|
|
331
331
|
|
|
@@ -344,4 +344,117 @@ I wrote Loadbare originally in 2003, as "Andromeda", before Node existed
|
|
|
344
344
|
and even 4 years before we had JQuery. Andromeda was not nearly as
|
|
345
345
|
optimized as Loadbare is now, but the principles were the same then as
|
|
346
346
|
they are now. It has always worked for me, and I hope that it will work
|
|
347
|
-
for you.
|
|
347
|
+
for you.
|
|
348
|
+
|
|
349
|
+
## A Consistency Model
|
|
350
|
+
|
|
351
|
+
> **LLM-authored, not yet revised by a person.** Drafted by Claude on
|
|
352
|
+
> 2026-09-13, from a design session against `@loadbare/app` 0.6.0. Treat
|
|
353
|
+
> it as a proposal for this essay, not as the author's statement.
|
|
354
|
+
|
|
355
|
+
### From a subjective claim to a checkable one
|
|
356
|
+
|
|
357
|
+
Whether Loadbare carries less accidental complexity than another tool will
|
|
358
|
+
always be disputable. A developer fluent in React has muscle memory for its
|
|
359
|
+
rules and will see Loadbare's rules as foreign, and foreign work feels
|
|
360
|
+
accidental. That objection is about familiarity, and familiarity is a cost
|
|
361
|
+
paid once, in the same way this essay treats the Express boilerplate.
|
|
362
|
+
|
|
363
|
+
A stronger claim can be checked, and a single counterexample in markup
|
|
364
|
+
would refute it. The model for applying data to HTML is:
|
|
365
|
+
|
|
366
|
+
1. **Internally consistent.** A few rules, each applied the same way
|
|
367
|
+
everywhere, with no exceptions.
|
|
368
|
+
2. **Consistent with HTML.** Each rule has a counterpart in how HTML
|
|
369
|
+
already behaves, and none contradicts it.
|
|
370
|
+
3. **Consistent with relational data.** What the server sends is the shape
|
|
371
|
+
a SQL query already returns, so nothing is translated on the way.
|
|
372
|
+
|
|
373
|
+
### Where the vocabulary comes from
|
|
374
|
+
|
|
375
|
+
This essay says the vocabulary is small because the operations a database
|
|
376
|
+
affords are small and coherent. A sharper statement is that the vocabulary
|
|
377
|
+
sits where two systems that are already coherent meet: the relational model
|
|
378
|
+
and HTML's containment model. Each attribute is a relational idea placed on
|
|
379
|
+
an element.
|
|
380
|
+
|
|
381
|
+
| Loadbare | Relational | HTML precedent |
|
|
382
|
+
| ---------------------------------------- | -------------------------------------------- | ------------------------------------------------------------ |
|
|
383
|
+
| `lb-list` | a relation, a set of rows | `<select>`, `<ul>`: a container whose contents are its items |
|
|
384
|
+
| `lb-row` | a single row | `<form>`: one record of fields |
|
|
385
|
+
| `lb-cell` | a column | `name` on a control: which field this is |
|
|
386
|
+
| `lb-key`, `lb-key-value` | the primary key | `value` on `<option>`: identity apart from the label |
|
|
387
|
+
| `lb-action` | a closed set of writes, and named procedures | `action` on `<form>`: where a submission goes |
|
|
388
|
+
| scope from ancestors | | form ownership, `lang`, `<fieldset disabled>` |
|
|
389
|
+
| a nested scope begins a new one | | a nested element owns its own contents |
|
|
390
|
+
| `lb-row-count`, `lb-pending`, `lb-error` | | state attributes such as `open` on `<details>` |
|
|
391
|
+
|
|
392
|
+
### The rules
|
|
393
|
+
|
|
394
|
+
1. **An element's own attributes describe what it displays. Its ancestors
|
|
395
|
+
describe where it belongs.** A picker carrying `lb-list` displays that
|
|
396
|
+
list, and its choice is addressed to the row it sits in, the way a
|
|
397
|
+
`<select>` displays its options and submits to the form around it.
|
|
398
|
+
2. **A name answers with one shape, a row or a set of rows, and a cell holds
|
|
399
|
+
one value.** Master-detail is a row and a list under two names. Many
|
|
400
|
+
masters with their details is one list of joined rows, grouped for
|
|
401
|
+
display.
|
|
402
|
+
3. **A value lands where the element shows its state, and a form gathers
|
|
403
|
+
from the same places.** A widget receives `lb-value`, a form control its
|
|
404
|
+
`value`, and any other element its text.
|
|
405
|
+
4. **A request is an action and a position.** The hub supplies the position
|
|
406
|
+
from the document. Only an interaction supplies a value.
|
|
407
|
+
5. **The hub owns position and reconciliation. A widget owns interaction and
|
|
408
|
+
placement.**
|
|
409
|
+
|
|
410
|
+
### Evidence
|
|
411
|
+
|
|
412
|
+
The following decisions each removed an exception or a conflict, and none
|
|
413
|
+
added an attribute. Over the same span the model gained pickers inside rows,
|
|
414
|
+
native selects that show their own state, and nested lists that do not
|
|
415
|
+
destroy each other.
|
|
416
|
+
|
|
417
|
+
| Decision | Removed |
|
|
418
|
+
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
419
|
+
| The hub scopes every request | Native requests were scoped and widget requests were not |
|
|
420
|
+
| Scope comes from ancestors, never the element's own `lb-list` | `lb-list` meant "display" in one place and "address" in another |
|
|
421
|
+
| A form control receives its value as `value` | A `<select>` was treated as text, against HTML, and landing did not mirror gathering |
|
|
422
|
+
| Row and template lookups stop at a nested scope | Cells respected nesting while rows and templates did not |
|
|
423
|
+
| Master-detail is a row and a list | An open question born of document-shaped data |
|
|
424
|
+
| `lb-value` on every cell | Only a widget's landed value was visible to a stylesheet |
|
|
425
|
+
|
|
426
|
+
Two signs suggest these are rules rather than patches. Fixing where a
|
|
427
|
+
request's scope comes from also fixed an unrelated console error about insert
|
|
428
|
+
forms, which was never worked on directly. Making rows respect nested scopes
|
|
429
|
+
needed no change to the reference, which already described that behavior.
|
|
430
|
+
|
|
431
|
+
### Where the claim is not yet proven
|
|
432
|
+
|
|
433
|
+
- **Parameters and view state.** A query takes no argument from the
|
|
434
|
+
browser, so there is nowhere to put which record is selected, a filter, a
|
|
435
|
+
sort, or a collapsed section. The model is incomplete here rather than
|
|
436
|
+
inconsistent. If the answer is HTML's own, the URL, in the way
|
|
437
|
+
`<form method="get">` puts its fields in the query string, the claim grows
|
|
438
|
+
stronger. If it needs a state mechanism with no HTML precedent, that will
|
|
439
|
+
be the model's first real exception.
|
|
440
|
+
- **Checkboxes and radio buttons.** HTML's boolean convention is presence or
|
|
441
|
+
absence, as with `checked`, `hidden` and `disabled`. A rule following it
|
|
442
|
+
would avoid inventing truthiness, though it still touches data types.
|
|
443
|
+
- **Data types.** HTML attributes are strings, so comparing values as strings
|
|
444
|
+
in a stylesheet is consistent with HTML. What is not yet reconciled is
|
|
445
|
+
that a database's typed values arrive untyped.
|
|
446
|
+
- **A nested list shows the same rows in every outer row.** That follows
|
|
447
|
+
from the second rule: one name is one relation. Only a new outer row
|
|
448
|
+
starting with an empty nested list is a mechanical gap.
|
|
449
|
+
|
|
450
|
+
### A test for every change
|
|
451
|
+
|
|
452
|
+
Before a change is made, ask:
|
|
453
|
+
|
|
454
|
+
- Does it remove an exception, or add one?
|
|
455
|
+
- Does it have an HTML precedent?
|
|
456
|
+
- Does it have a relational counterpart?
|
|
457
|
+
|
|
458
|
+
Stamping every column of a row onto the row element as `data-*` fails the
|
|
459
|
+
first two: attributes nobody wrote, with no precedent in HTML. Writing
|
|
460
|
+
`lb-value` on every cell passes all three.
|
|
@@ -56,7 +56,7 @@ display something to the user if they type a URL that has no matching page.
|
|
|
56
56
|
<a href="/about" lb-nav-link>About</a>
|
|
57
57
|
</nav>
|
|
58
58
|
<main></main>
|
|
59
|
-
<dialog lb-unknown-page lb-
|
|
59
|
+
<dialog lb-unknown-page lb-row="lb-navigation">
|
|
60
60
|
The URL <span lb-cell="page-uri"></span> is not in this app.
|
|
61
61
|
</dialog>
|
|
62
62
|
</lb-hub>
|
|
@@ -75,7 +75,7 @@ link.
|
|
|
75
75
|
|
|
76
76
|
If a `<dialog lb-unknown-page>` element is present in the HTML, it will be
|
|
77
77
|
displayed to the user when a URL is entered that has no matching page in the app.
|
|
78
|
-
The `lb-
|
|
78
|
+
The `lb-row` and `lb-cell` attributes will be explained when we get to data
|
|
79
79
|
binding; for now, know that `lb-navigation` is a query the hub itself answers
|
|
80
80
|
on every navigation, and `page-uri` is the path that was asked for. The
|
|
81
81
|
widget library ships this dialog ready-made as `<lb-unknown-page>`, which
|
|
@@ -112,12 +112,12 @@ catch-all answers that request too.
|
|
|
112
112
|
|
|
113
113
|
## If you look at the console
|
|
114
114
|
|
|
115
|
-
On every navigation, the hub also asks the server for that page's data
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
On every navigation, the hub also asks the server for that page's data. It
|
|
116
|
+
posts that request, so it never collides with the routes above. Neither of
|
|
117
|
+
these pages is serving any data, and our Express server has no route handler
|
|
118
|
+
for the hub yet, so those posts currently go unanswered. This is expected in
|
|
119
|
+
this early stage of the tutorial, we will fix it when we get to data binding
|
|
120
|
+
and handling.
|
|
121
121
|
|
|
122
122
|
## Run it
|
|
123
123
|
|
|
@@ -20,7 +20,7 @@ the cell `count`.
|
|
|
20
20
|
<h1>About</h1>
|
|
21
21
|
<p class="about-note">This is the about page.</p>
|
|
22
22
|
|
|
23
|
-
<div lb-
|
|
23
|
+
<div lb-row="visits">
|
|
24
24
|
<p>This page has been visited <span lb-cell="count"></span> times.</p>
|
|
25
25
|
</div>
|
|
26
26
|
```
|
|
@@ -45,14 +45,14 @@ When the user navigates to a page, the loadbare hub `<lb-hub>` swaps
|
|
|
45
45
|
the contents of `<main>` and sends a request to the server for the
|
|
46
46
|
query results defined for that page.
|
|
47
47
|
|
|
48
|
-
For a visit counter, we need to write a `
|
|
48
|
+
For a visit counter, we need to write a `onPageEnter` hook to fire first
|
|
49
49
|
and increment the counter:
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
```ts
|
|
53
|
-
// src/pages/about.
|
|
54
|
-
export const
|
|
55
|
-
|
|
53
|
+
// src/pages/about.requests.ts
|
|
54
|
+
export const requests = {
|
|
55
|
+
onPageEnter: (ctx) => ctx.db.recordVisit(),
|
|
56
56
|
};
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -103,7 +103,7 @@ the authenticated caller instead of the same file for everyone.
|
|
|
103
103
|
|
|
104
104
|
## Wiring the server
|
|
105
105
|
|
|
106
|
-
The Loadbare builder compiles a list of all
|
|
106
|
+
The Loadbare builder compiles a list of all requests and queries.
|
|
107
107
|
Add the four
|
|
108
108
|
lines below, and our server will correctly route all data channel
|
|
109
109
|
requests to the correct page code.
|
|
@@ -121,8 +121,8 @@ const app = express();
|
|
|
121
121
|
app.use("/client.js", express.static("dist/client.js"));
|
|
122
122
|
app.use("/app.css", express.static("dist/app.css"));
|
|
123
123
|
|
|
124
|
-
// New: answers
|
|
125
|
-
// Must come before the catch-all, or the catch-all answers
|
|
124
|
+
// New: answers the hub's own route, which is the one the browser posts to.
|
|
125
|
+
// Must come before the catch-all, or the catch-all answers it too.
|
|
126
126
|
function contextFor(_req) {
|
|
127
127
|
return { db: openDb() };
|
|
128
128
|
}
|
|
@@ -160,7 +160,7 @@ to About.
|
|
|
160
160
|
View source on either page: the host HTML is exactly what's on disk. Only
|
|
161
161
|
the `<span>`'s text changes, never the markup around it.
|
|
162
162
|
|
|
163
|
-
This tutorial only used `lb-
|
|
163
|
+
This tutorial only used `lb-row` and `lb-cell` to display one value.
|
|
164
164
|
The full attribute vocabulary — actions, CRUD operations, lists, and
|
|
165
165
|
navigation — is in [Data Binding](../reference/data-binding.md).
|
|
166
166
|
|