@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.
Files changed (81) hide show
  1. package/README.md +3 -4
  2. package/dist/build/assemble.d.ts +1 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +80 -7
  5. package/dist/build/cli.d.ts +2 -2
  6. package/dist/build/cli.js +2 -2
  7. package/dist/build/expand.d.ts.map +1 -1
  8. package/dist/build/expand.js +30 -34
  9. package/dist/build/locations.d.ts +3 -3
  10. package/dist/build/locations.d.ts.map +1 -1
  11. package/dist/build/locations.js +15 -3
  12. package/dist/build/origins.d.ts +0 -13
  13. package/dist/build/origins.d.ts.map +1 -1
  14. package/dist/build/origins.js +32 -8
  15. package/dist/build/pages.d.ts +3 -3
  16. package/dist/build/pages.d.ts.map +1 -1
  17. package/dist/build/pages.js +9 -7
  18. package/dist/core/lb-constants.d.ts +17 -12
  19. package/dist/core/lb-constants.d.ts.map +1 -1
  20. package/dist/core/lb-constants.js +106 -43
  21. package/dist/core/lb-types.d.ts +103 -62
  22. package/dist/core/lb-types.d.ts.map +1 -1
  23. package/dist/core/lb-types.js +11 -3
  24. package/dist/hub/lb-apply.d.ts +18 -4
  25. package/dist/hub/lb-apply.d.ts.map +1 -1
  26. package/dist/hub/lb-apply.js +184 -34
  27. package/dist/hub/lb-hub.browser.d.ts +1 -1
  28. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  29. package/dist/hub/lb-hub.browser.js +165 -83
  30. package/dist/server/lb-express.d.ts +7 -4
  31. package/dist/server/lb-express.d.ts.map +1 -1
  32. package/dist/server/lb-express.js +45 -33
  33. package/dist/server/lb-server.d.ts +67 -45
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/server/lb-server.js +55 -18
  36. package/dist/tests/assemble.test.js +154 -2
  37. package/dist/tests/expand.test.js +30 -3
  38. package/dist/tests/helpers/hub.d.ts +73 -0
  39. package/dist/tests/helpers/hub.d.ts.map +1 -0
  40. package/dist/tests/helpers/hub.js +151 -0
  41. package/dist/tests/lb-apply.test.js +86 -62
  42. package/dist/tests/lb-express.test.d.ts +1 -1
  43. package/dist/tests/lb-express.test.js +43 -38
  44. package/dist/tests/lb-hub.test.d.ts +14 -0
  45. package/dist/tests/lb-hub.test.d.ts.map +1 -0
  46. package/dist/tests/lb-hub.test.js +319 -0
  47. package/dist/tests/{lb-rows.test.d.ts → lb-list.test.d.ts} +1 -1
  48. package/dist/tests/lb-list.test.d.ts.map +1 -0
  49. package/dist/tests/{lb-rows.test.js → lb-list.test.js} +109 -106
  50. package/dist/tests/lb-server.test.js +151 -100
  51. package/dist/tests/origins.test.js +19 -1
  52. package/dist/tests/pages.test.d.ts +1 -1
  53. package/dist/tests/pages.test.js +64 -14
  54. package/docs/TECHREF-1.0.md +1000 -0
  55. package/docs/reference/builder.md +4 -4
  56. package/docs/reference/chrome.md +56 -5
  57. package/docs/reference/custom-elements.md +59 -36
  58. package/docs/reference/data-binding.md +142 -84
  59. package/docs/reference/overview.md +1 -1
  60. package/docs/reference/page-files.md +64 -49
  61. package/docs/reference/server.md +7 -6
  62. package/docs/reference/widgets.md +43 -32
  63. package/docs/roadmap.md +68 -22
  64. package/docs/testing.md +47 -17
  65. package/docs/theory.md +2 -2
  66. package/docs/tutorials/010-pages-and-navigation.md +14 -8
  67. package/docs/tutorials/040-displaying-data.md +9 -9
  68. package/docs/tutorials/050-actions.md +5 -5
  69. package/docs/tutorials/060-custom-element-code.md +1 -1
  70. package/docs/tutorials/065-conditional-rendering.md +4 -4
  71. package/docs/tutorials/070-displaying-a-list.md +24 -47
  72. package/docs/tutorials/072-inserting-into-a-list.md +17 -17
  73. package/docs/tutorials/074-deleting-from-a-list.md +15 -17
  74. package/docs/tutorials/076-updating-a-list-item.md +20 -22
  75. package/docs/tutorials/080-widget-requests.md +27 -23
  76. package/docs/tutorials/090-using-widget-libraries.md +23 -1
  77. package/package.json +1 -2
  78. package/dist/hub/lb-rows.d.ts +0 -18
  79. package/dist/hub/lb-rows.d.ts.map +0 -1
  80. package/dist/hub/lb-rows.js +0 -106
  81. 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 hooks when the page shows data.
4
+ HTML, and adds queries and requests when the page shows data.
5
5
 
6
- | File | Holds |
7
- |---------------------|-------------------------------|
8
- | `<name>.page.html` | The page's HTML |
9
- | `<name>.queries.ts` | The data the page displays |
10
- | `<name>.hooks.ts` | What the page does when asked |
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-query="visits">
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-query`, `lb-cell`, and the rest of the
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-query`, and each value takes the request context and returns
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
- Return every cell as a string. Format numbers, dates, and money in the query,
54
- so the browser displays a value it never computes.
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
- Return many rows through `rows()`:
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 { rows, type Queries } from "@loadbare/app/server";
68
+ import { list, type Queries } from "@loadbare/app/server";
61
69
 
62
70
  export const queries: Queries = {
63
- directory: async (ctx) => rows(await ctx.db.directory()),
71
+ directory: list((ctx) => ctx.db.directory()),
64
72
  };
65
73
  ```
66
74
 
67
- Give every row a cell that identifies it, and name that cell with `lb-key` in
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 hook's job —
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
- ## Hooks, actions, CRUD
81
+ ## Requests, actions, CRUD
74
82
 
75
- Export `hooks` from `<name>.hooks.ts`. It holds three keys, each optional:
83
+ Export `requests` from `<name>.requests.ts`. It holds three keys, each optional:
76
84
 
77
- | Key | Runs |
78
- |-------------|------------------------------------------------------|
79
- | `beforeGet` | Before the page's queries, on a request for the page |
80
- | `actions` | What the page may be asked to do, by name |
81
- | `crud` | The four operations a query permits on its rows |
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
- ### beforeGet
91
+ ### onPageEnter
84
92
 
85
93
  ```ts
86
- // src/pages/about.hooks.ts
87
- import { type Hooks } from "@loadbare/app/server";
94
+ // src/pages/about.requests.ts
95
+ import { type Requests } from "@loadbare/app/server";
88
96
 
89
- export const hooks: Hooks = {
90
- beforeGet: (ctx) => ctx.db.recordVisit(),
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 hooks: Hooks = {
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 `query`, `key`, `cell`, and `value` when the element that dispatched
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 query they operate on. Each
122
- one takes the binding its trigger supplies:
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
- | Operation | The page writes | `run` receives |
125
- |---------------|--------------------|------------------------|
126
- | `cellChange` | `<lb-input data-fire-on-change>` | `key`, `cell`, `value` |
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.hooks.ts
133
- import { patch, type Hooks } from "@loadbare/app/server";
147
+ // src/pages/directory.requests.ts
148
+ import { patch, type Requests } from "@loadbare/app/server";
134
149
 
135
- export const hooks: Hooks = {
150
+ export const requests: Requests = {
136
151
  crud: {
137
152
  directory: {
138
- tupleInsert: {
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 query permits. An operation a query does not
151
- declare is refused, and a query with no `crud` entry permits none.
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
- | `rows([...])` | The entire set, and its order |
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
 
@@ -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)` | `GET /lb/data` and `POST /lb` |
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. A proxy in front of it passes `/lb/data` and
70
- `/lb` through unchanged, and the application cannot be hosted under a subpath
71
- such as `example.com/myapp/`.
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 `.hooks.ts` or `.queries.ts`
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 hooks read them from `ctx`; see
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
- Six widgets: `lb-input`, `lb-select`, `lb-list`, `lb-options`, `lb-table`,
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: `data-fire-on-change` asks it to send `cell-change` on the
30
- input's `change`, addressed by its own `lb-query`/`lb-key`/`lb-cell`
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
- | `data-fire-on-change` | an edit to be sent, on `change` |
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-query="statuses" exp-label="Status" lb-action="setStatus">
77
- <template lb-key="id" lb-group="category">
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` — `lb-key` supplies both, so
88
- nothing is declared twice.
89
- - `lb-group` on the row template sections the options into `<optgroup>`s,
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-query="ledger" exp-caption="Ledger">
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" lb-sort="date" lb-group="month">
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-query="ledger-total"><td>Total</td><td lb-cell="total"></td></tr>
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
- - `lb-group` on the row template sections rows under a derived heading row,
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. `lb-sort` orders rows within a section (or the whole body, with no
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 `acceptRows` — it's an
133
- ordinary scope carrying its own `lb-query`, resolved by name like any
134
- other on the page. A grand total is a second projection of the same
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-query="statuses"
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 `lb-group` |
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 projection at once. A solo
21
- developer testing in one browser will not produce this by accident, and
22
- retrofitting a version or conflict check onto every tuple after the fact
23
- touches every widget that writes.
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 tuple may contain a projection (master-detail, an expanding
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-query`,
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`, `hub/lb-rows.ts` | jsdom |
23
- | 4 | The hub — `hub/lb-hub.browser.ts` | jsdom |
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
- - `beforeGet` runs before any query, and the whole query set runs after it
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`, `applyTuple` and `applyRows` are the most intricate code in the
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 projection landing on something that is not a list widget
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
- - `data-rows` is counted from the DOM after reconciliation, so a set and a
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 `rows` twice is applying it once. And `rows(S)` reached through any
153
- sequence of patches is `rows(S)` reached from empty. Convergence is the
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
- The hub is where jsdom stops being evidence. Fetch, `history.pushState`,
187
- `popstate`, and the claim that insertion and hydration in one synchronous
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. `lb-nav-link` pushes state, swaps the host and lands data; `popstate`
195
- reverses it; an ordinary anchor is not hijacked
196
- 3. a native action button produces the same event a widget produces
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
- tuples. Something like `<span lb-query="siteStats" lb-cell="visitCount"></span>`
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 hooks, actions, CRUD and queries without boilerplate is pure
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