@loadbare/app 0.5.5 → 0.6.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 +80 -7
- package/dist/build/cli.d.ts +2 -2
- package/dist/build/cli.js +2 -2
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +30 -34
- package/dist/build/locations.d.ts +3 -3
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +15 -3
- package/dist/build/origins.d.ts +0 -13
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +32 -8
- package/dist/build/pages.d.ts +3 -3
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +9 -7
- package/dist/core/lb-constants.d.ts +17 -12
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +106 -43
- 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 +11 -3
- package/dist/hub/lb-apply.d.ts +18 -4
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +184 -34
- 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 -83
- package/dist/server/lb-express.d.ts +7 -4
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +45 -33
- 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 +55 -18
- package/dist/tests/assemble.test.js +154 -2
- package/dist/tests/expand.test.js +30 -3
- package/dist/tests/helpers/hub.d.ts +73 -0
- package/dist/tests/helpers/hub.d.ts.map +1 -0
- package/dist/tests/helpers/hub.js +151 -0
- package/dist/tests/lb-apply.test.js +86 -62
- package/dist/tests/lb-express.test.d.ts +1 -1
- package/dist/tests/lb-express.test.js +43 -38
- package/dist/tests/lb-hub.test.d.ts +14 -0
- package/dist/tests/lb-hub.test.d.ts.map +1 -0
- package/dist/tests/lb-hub.test.js +319 -0
- package/dist/tests/{lb-rows.test.d.ts → lb-list.test.d.ts} +1 -1
- package/dist/tests/lb-list.test.d.ts.map +1 -0
- package/dist/tests/{lb-rows.test.js → lb-list.test.js} +109 -106
- package/dist/tests/lb-server.test.js +151 -100
- package/dist/tests/origins.test.js +19 -1
- package/dist/tests/pages.test.d.ts +1 -1
- package/dist/tests/pages.test.js +64 -14
- package/docs/TECHREF-1.0.md +1000 -0
- package/docs/reference/builder.md +4 -4
- package/docs/reference/chrome.md +56 -5
- package/docs/reference/custom-elements.md +59 -36
- package/docs/reference/data-binding.md +142 -84
- package/docs/reference/overview.md +1 -1
- package/docs/reference/page-files.md +64 -49
- package/docs/reference/server.md +7 -6
- package/docs/reference/widgets.md +43 -32
- package/docs/roadmap.md +68 -22
- package/docs/testing.md +47 -17
- package/docs/theory.md +2 -2
- package/docs/tutorials/010-pages-and-navigation.md +14 -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 +17 -17
- 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 +27 -23
- package/docs/tutorials/090-using-widget-libraries.md +23 -1
- package/package.json +1 -2
- 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/lb-rows.test.d.ts.map +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Page Files
|
|
2
2
|
|
|
3
3
|
A page is a set of files sharing one base name. The application writes the
|
|
4
|
-
HTML, and adds queries and
|
|
4
|
+
HTML, and adds queries and requests when the page shows data.
|
|
5
5
|
|
|
6
|
-
| File
|
|
7
|
-
|
|
8
|
-
| `<name>.page.html`
|
|
9
|
-
| `<name>.queries.ts`
|
|
10
|
-
| `<name>.
|
|
6
|
+
| File | Holds |
|
|
7
|
+
|----------------------|-------------------------------|
|
|
8
|
+
| `<name>.page.html` | The page's HTML |
|
|
9
|
+
| `<name>.queries.ts` | The data the page displays |
|
|
10
|
+
| `<name>.requests.ts` | What the page does when asked |
|
|
11
11
|
|
|
12
12
|
Name the landing page `index.page.html`. A bare `/` resolves to `index`, and
|
|
13
13
|
every other path names the page of the same name.
|
|
@@ -25,12 +25,12 @@ is supplied by [chrome.html](./chrome.md).
|
|
|
25
25
|
<h1>About</h1>
|
|
26
26
|
<p>This is the about page.</p>
|
|
27
27
|
|
|
28
|
-
<div lb-
|
|
28
|
+
<div lb-row="visits">
|
|
29
29
|
<p>This page has been visited <span lb-cell="count"></span> times.</p>
|
|
30
30
|
</div>
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Bind elements to data with `lb-
|
|
33
|
+
Bind elements to data with `lb-list` or `lb-row`, `lb-cell`, and the rest of the
|
|
34
34
|
attribute vocabulary in [Data Binding](./data-binding.md).
|
|
35
35
|
|
|
36
36
|
A page that displays no data needs no other file.
|
|
@@ -38,56 +38,64 @@ A page that displays no data needs no other file.
|
|
|
38
38
|
## Queries
|
|
39
39
|
|
|
40
40
|
Export `queries` from `<name>.queries.ts`. Each key is a name the HTML binds
|
|
41
|
-
to with `lb-
|
|
41
|
+
to with `lb-list` or `lb-row`, and each value takes the request context and returns
|
|
42
42
|
that query's result:
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
45
|
// src/pages/about.queries.ts
|
|
46
|
-
import { type Queries } from "@loadbare/app/server";
|
|
46
|
+
import { row, type Queries } from "@loadbare/app/server";
|
|
47
47
|
|
|
48
48
|
export const queries: Queries = {
|
|
49
|
-
visits: async (ctx) => ({ count: String(await ctx.db.visitCount()) }),
|
|
49
|
+
visits: row(async (ctx) => ({ count: String(await ctx.db.visitCount()) })),
|
|
50
50
|
};
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
Declare each query with `row()` or `list()`. Cardinality is a property of the
|
|
54
|
+
name rather than of any one answer, so one name answers with one shape,
|
|
55
|
+
always, and `createHub` refuses an answer that disagrees. A page that needs
|
|
56
|
+
the same data as one row and as a set declares two queries.
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
The hub hands each cell to the browser untouched and takes no position on
|
|
59
|
+
its type, so what a number, a date or a null looks like is decided here, in
|
|
60
|
+
the query. Formatting it here means the browser displays a value it never
|
|
61
|
+
computes.
|
|
62
|
+
|
|
63
|
+
Declare a query that answers with many rows using `list()`, and return the
|
|
64
|
+
array itself:
|
|
57
65
|
|
|
58
66
|
```ts
|
|
59
67
|
// src/pages/directory.queries.ts
|
|
60
|
-
import {
|
|
68
|
+
import { list, type Queries } from "@loadbare/app/server";
|
|
61
69
|
|
|
62
70
|
export const queries: Queries = {
|
|
63
|
-
directory:
|
|
71
|
+
directory: list((ctx) => ctx.db.directory()),
|
|
64
72
|
};
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
Give every row a
|
|
68
|
-
the HTML. Return the rows in the order the page shows them.
|
|
75
|
+
Give every row a column that identifies it, and name that column with
|
|
76
|
+
`lb-key` in the HTML. Return the rows in the order the page shows them.
|
|
69
77
|
|
|
70
|
-
Return the full result every time. Sending only what changed is a
|
|
78
|
+
Return the full result every time. Sending only what changed is a request's job —
|
|
71
79
|
see [refresh and patch](#refresh-and-patch).
|
|
72
80
|
|
|
73
|
-
##
|
|
81
|
+
## Requests, actions, CRUD
|
|
74
82
|
|
|
75
|
-
Export `
|
|
83
|
+
Export `requests` from `<name>.requests.ts`. It holds three keys, each optional:
|
|
76
84
|
|
|
77
|
-
| Key
|
|
78
|
-
|
|
79
|
-
| `
|
|
80
|
-
| `actions`
|
|
81
|
-
| `crud`
|
|
85
|
+
| Key | Runs |
|
|
86
|
+
|---------------|------------------------------------------------------|
|
|
87
|
+
| `onPageEnter` | Before the page's queries, on entering the page |
|
|
88
|
+
| `actions` | What the page may be asked to do, by name |
|
|
89
|
+
| `crud` | The four operations a list permits on its rows |
|
|
82
90
|
|
|
83
|
-
###
|
|
91
|
+
### onPageEnter
|
|
84
92
|
|
|
85
93
|
```ts
|
|
86
|
-
// src/pages/about.
|
|
87
|
-
import { type
|
|
94
|
+
// src/pages/about.requests.ts
|
|
95
|
+
import { type Requests } from "@loadbare/app/server";
|
|
88
96
|
|
|
89
|
-
export const
|
|
90
|
-
|
|
97
|
+
export const requests: Requests = {
|
|
98
|
+
onPageEnter: (ctx) => ctx.db.recordVisit(),
|
|
91
99
|
};
|
|
92
100
|
```
|
|
93
101
|
|
|
@@ -99,7 +107,7 @@ Declare an action under the name the HTML gives `lb-action`. Pair what it
|
|
|
99
107
|
does with the queries to re-run once it has:
|
|
100
108
|
|
|
101
109
|
```ts
|
|
102
|
-
export const
|
|
110
|
+
export const requests: Requests = {
|
|
103
111
|
actions: {
|
|
104
112
|
resetVisits: {
|
|
105
113
|
run: (ctx) => ctx.db.resetVisits(),
|
|
@@ -113,29 +121,36 @@ Declare every action the page allows. A name the page does not declare is
|
|
|
113
121
|
refused.
|
|
114
122
|
|
|
115
123
|
Read where the interaction happened from `run`'s second argument, which
|
|
116
|
-
carries `
|
|
117
|
-
the request had them.
|
|
124
|
+
carries `list` or `row`, whichever attribute scoped the element, plus `key`,
|
|
125
|
+
`cell` and `value` when the element that dispatched the request had them.
|
|
118
126
|
|
|
119
127
|
### crud
|
|
120
128
|
|
|
121
|
-
Declare CRUD operations under `crud`, keyed by the
|
|
122
|
-
|
|
129
|
+
Declare CRUD operations under `crud`, keyed by the list they operate on. All
|
|
130
|
+
four are list operations: each needs a key, and a key exists only on a live
|
|
131
|
+
row inside a list, so a single-row scope is read-only and a declared action is
|
|
132
|
+
the only thing it can send. Each operation takes the binding its trigger
|
|
133
|
+
supplies:
|
|
134
|
+
|
|
135
|
+
| Operation | The page writes | `run` receives |
|
|
136
|
+
|---------------|------------------------------------------|------------------------|
|
|
137
|
+
| `cellChange` | `<lb-input lb-action="lb-cell-change">` | `key`, `cell`, `value` |
|
|
138
|
+
| `rowDelete` | `lb-action="lb-row-delete"` | `key` |
|
|
139
|
+
| `rowInsert` | `<form lb-action="lb-row-insert">` | `values` |
|
|
140
|
+
| `rowUpdate` | `<form lb-action="lb-row-update">` | `key`, `values` |
|
|
123
141
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
| `tupleDelete` | `lb-delete` | `key` |
|
|
128
|
-
| `tupleInsert` | `<form lb-insert>` | `values` |
|
|
129
|
-
| `tupleUpdate` | `<form lb-update>` | `key`, `values` |
|
|
142
|
+
The operation names are reserved: a name beginning with `lb-` cannot be
|
|
143
|
+
declared under `actions` or as a query, and `createHub` refuses a page that
|
|
144
|
+
tries.
|
|
130
145
|
|
|
131
146
|
```ts
|
|
132
|
-
// src/pages/directory.
|
|
133
|
-
import { patch, type
|
|
147
|
+
// src/pages/directory.requests.ts
|
|
148
|
+
import { patch, type Requests } from "@loadbare/app/server";
|
|
134
149
|
|
|
135
|
-
export const
|
|
150
|
+
export const requests: Requests = {
|
|
136
151
|
crud: {
|
|
137
152
|
directory: {
|
|
138
|
-
|
|
153
|
+
rowInsert: {
|
|
139
154
|
run: async (ctx, { values }) => {
|
|
140
155
|
const entry = await ctx.db.addDirectoryEntry(values);
|
|
141
156
|
return { directory: patch({ rows: [entry] }) };
|
|
@@ -147,8 +162,8 @@ export const hooks: Hooks = {
|
|
|
147
162
|
};
|
|
148
163
|
```
|
|
149
164
|
|
|
150
|
-
Declare every operation the
|
|
151
|
-
declare is refused, and a
|
|
165
|
+
Declare every operation the list permits. An operation a list does not
|
|
166
|
+
declare is refused, and a name with no `crud` entry permits none.
|
|
152
167
|
|
|
153
168
|
### refresh and patch
|
|
154
169
|
|
|
@@ -159,7 +174,7 @@ would. What `run` returns is laid over the refreshed queries:
|
|
|
159
174
|
|
|
160
175
|
| Result | States |
|
|
161
176
|
|--------------------------|---------------------------------------------|
|
|
162
|
-
| `
|
|
177
|
+
| `[...]` | The entire set, and its order |
|
|
163
178
|
| `patch({ rows: [...] })` | These rows arrive or change; the rest stand |
|
|
164
179
|
| `patch({ drop: [...] })` | These keys are gone; the rest stand |
|
|
165
180
|
|
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,7 @@ Run the server under a TypeScript-capable runner. The builder writes
|
|
|
85
86
|
}
|
|
86
87
|
```
|
|
87
88
|
|
|
88
|
-
Restart the server after adding or changing a `.
|
|
89
|
+
Restart the server after adding or changing a `.requests.ts` or `.queries.ts`
|
|
89
90
|
file.
|
|
90
91
|
|
|
91
92
|
## Database layer
|
|
@@ -119,5 +120,5 @@ declare module "@loadbare/app/server" {
|
|
|
119
120
|
```
|
|
120
121
|
|
|
121
122
|
Add a field for anything else a request needs — the authenticated user, a
|
|
122
|
-
request id, a feature flag set. Queries and
|
|
123
|
+
request id, a feature flag set. Queries and requests read them from `ctx`; see
|
|
123
124
|
[page files](./page-files.md).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# The Basic Widget Library
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
`lb-picker`. Every one of them is written against the same two contracts
|
|
3
|
+
Seven widgets: `lb-input`, `lb-select`, `lb-list`, `lb-options`, `lb-table`,
|
|
4
|
+
`lb-picker`, `lb-unknown-page`. Every one of them is written against the same two contracts
|
|
5
5
|
documented elsewhere — [Custom Elements](./custom-elements.md#html) for its
|
|
6
6
|
definition, [Custom Elements](./custom-elements.md#code) for its class —
|
|
7
7
|
nothing here is special-cased machinery.
|
|
@@ -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
|
-
coordinates
|
|
29
|
+
nothing on its own: `lb-action` names what the input's `change` sends. The
|
|
30
|
+
reserved `lb-cell-change` sends `lb-cell-change`, addressed by the widget's own
|
|
31
|
+
`lb-list`/`lb-key-value`/`lb-cell` coordinates; any other name sends that
|
|
32
|
+
action with the input's value.
|
|
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,9 +147,28 @@ 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
|
|
162
154
|
row, or an option built from two columns, writes `lb-options` and its own
|
|
163
155
|
`<template>` instead.
|
|
156
|
+
|
|
157
|
+
## `lb-unknown-page`
|
|
158
|
+
|
|
159
|
+
The chrome's dialog for a URL that names no page, as one tag:
|
|
160
|
+
|
|
161
|
+
```html
|
|
162
|
+
<lb-hub>
|
|
163
|
+
<nav>...</nav>
|
|
164
|
+
<main></main>
|
|
165
|
+
<lb-unknown-page></lb-unknown-page>
|
|
166
|
+
</lb-hub>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
It expands to a `<dialog lb-unknown-page>` scoped to the hub's own
|
|
170
|
+
`lb-navigation` query, so `page-label` and `page-uri` land in it the way any
|
|
171
|
+
cell lands anywhere, and the hub opens it on a miss. It takes no parameters
|
|
172
|
+
and, so far, shows both cells rather than choosing between them. See
|
|
173
|
+
[`chrome.html`](./chrome.md#where-the-page-is) for the query, and for the same
|
|
174
|
+
dialog written by hand.
|
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
|
|