@loadbare/app 0.18.2 → 0.19.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 (42) hide show
  1. package/README.md +17 -17
  2. package/dist/build/assemble.d.ts +7 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +81 -11
  5. package/dist/build/assemble.js.map +1 -1
  6. package/dist/build/cli.d.ts +3 -0
  7. package/dist/build/cli.d.ts.map +1 -1
  8. package/dist/build/cli.js +6 -1
  9. package/dist/build/cli.js.map +1 -1
  10. package/dist/build/declared.d.ts +25 -0
  11. package/dist/build/declared.d.ts.map +1 -0
  12. package/dist/build/declared.js +62 -0
  13. package/dist/build/declared.js.map +1 -0
  14. package/dist/build/pages.d.ts +6 -0
  15. package/dist/build/pages.d.ts.map +1 -1
  16. package/dist/build/pages.js +32 -12
  17. package/dist/build/pages.js.map +1 -1
  18. package/docs/TECHREF-1.0.md +27 -72
  19. package/docs/comparison.md +692 -480
  20. package/docs/departures.md +63 -0
  21. package/docs/miswarts.md +140 -0
  22. package/docs/opinions.md +155 -0
  23. package/docs/reference/builder.md +9 -2
  24. package/docs/reference/chrome.md +2 -1
  25. package/docs/reference/custom-elements.md +1 -1
  26. package/docs/reference/data-binding.md +36 -9
  27. package/docs/reference/page-files.md +36 -4
  28. package/docs/roadmap.md +55 -43
  29. package/docs/testing.md +62 -28
  30. package/docs/theory.md +348 -108
  31. package/package.json +1 -1
  32. package/skills/loadbare-app/SKILL.md +10 -6
  33. package/skills/loadbare-app/references/TECHREF-1.0.md +27 -72
  34. package/skills/loadbare-app/references/builder.md +9 -2
  35. package/skills/loadbare-app/references/chrome.md +2 -1
  36. package/skills/loadbare-app/references/custom-elements.md +1 -1
  37. package/skills/loadbare-app/references/data-binding.md +36 -9
  38. package/skills/loadbare-app/references/miswarts.md +140 -0
  39. package/skills/loadbare-app/references/page-files.md +36 -4
  40. package/docs/possible-ideas.md +0 -314
  41. package/docs/theory-old.md +0 -839
  42. package/docs/what-does-loadbare-extend.md +0 -124
package/docs/roadmap.md CHANGED
@@ -15,13 +15,6 @@ mechanism Release 1.0 already ships. Each is here because deciding it
15
15
  speculatively, before a real page forces the question, risks designing the
16
16
  wrong thing. Revisit when the described symptom actually shows up.
17
17
 
18
- ### Staleness and concurrent writers
19
-
20
- Two tabs, or two users, updating the same rows 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
-
25
18
  ### A query that pages or limits its rows
26
19
 
27
20
  The user's order and filters apply to the rows the browser holds. A query
@@ -49,6 +42,31 @@ end up living in the widget while the server remains authoritative for the
49
42
  same field. Not yet designed — revisit the first time an application needs
50
43
  inline validation.
51
44
 
45
+ ### Compound foreign keys
46
+
47
+ A picker is the UI for a foreign key. `<select lb-query="statuses"
48
+ lb-column="status">` says the row's `status` references a row of
49
+ `statuses`, and offers every one of them. For a single-column foreign key
50
+ that is exactly right, which is why a query in a row template answers the
51
+ same rows in every live row.
52
+
53
+ A compound foreign key cannot be expressed. When `(type, status)`
54
+ references `allowed_statuses(type, status)`, the choices for `status`
55
+ depend on the row's own `type`, and every row is offered every status.
56
+ The database knows the constraint, and the UI cannot say it, so a user is
57
+ offered choices the server will refuse.
58
+
59
+ A likely direction keeps one query and one answer: the lookup answers every
60
+ allowed pair as ordinary rows, the picker names the columns of its row that
61
+ restrict it, and the hub shows each picker only the rows that match, as it
62
+ already filters the rows it holds. Two questions come with it:
63
+
64
+ - Whether the restricting value is the one that landed or the control's
65
+ current value, so the choices follow an edit before it is saved.
66
+ - What becomes of a chosen value the restriction no longer allows.
67
+
68
+ Revisit when an application needs a choice restricted by its row.
69
+
52
70
  ## Release 1.1 candidates
53
71
 
54
72
  Net-new scope, not gaps in 1.0. Release 1.1 will optimize for operational
@@ -128,9 +146,10 @@ breaks nothing.
128
146
 
129
147
  ### Queries stated by the chrome
130
148
 
131
- A custom element in the chrome naming a query forces every page to declare
132
- that query, since the page load covers one page's set. `lb-url` shows the
133
- pattern from the framework side, and an application has no equivalent.
149
+ The chrome belongs to no page, so the builder refuses an application query
150
+ named there, and a custom element in the chrome cannot show application
151
+ data. `lb-url` and `lb-choices` show the pattern from the framework side,
152
+ and an application has no equivalent.
134
153
 
135
154
  A chrome-level query set on `createHub` would close this, and it is additive.
136
155
  It takes its place in the [options on `createHub`](#options-on-createhub).
@@ -159,39 +178,6 @@ A query parm write replaces the history entry, and `lb-url-push` pushes
159
178
  one. Which of the two a user wants may be the user's to say, rather
160
179
  than the page's.
161
180
 
162
- ### Build-time checking of `lb-request` against the declared requests
163
-
164
- Release 1.0 resolves every `lb-request` value at request time. A value naming
165
- no entry under `handlers`, or one of the requests Loadbare provides for a
166
- query with no entry under `crud`, is found on the first commit: a server
167
- console warning and an empty answer, described in
168
- [Page files](./reference/page-files.md). A typo builds cleanly.
169
-
170
- The builder already expands each page and walks the finished document in
171
- `walkMarkup`, tracking the enclosing `lb-query`, so collecting every request
172
- name together with the query it is issued for needs no new machinery. What
173
- it lacks is the other half of the comparison: it knows the path of a
174
- `.requests.ts` file and never reads its contents.
175
-
176
- Two routes to the declared names, and the choice is the decision:
177
-
178
- - **Load the built module.** Bundle the generated `pages.ts`, import it, and
179
- read the keys off the finished objects. Exact, and indifferent to how the
180
- object was written. The build then executes application import-time code,
181
- and a builder that only reads files stops being that.
182
- - **Parse the source.** Read the keys statically with the TypeScript compiler
183
- API. No application code runs, at the price of a real dependency and of
184
- guessing wrong on anything not written as a plain object literal.
185
-
186
- Two smaller calls come with either route. The chrome belongs to no page, so
187
- a request there can only be required of every page or forbidden. A page with
188
- no requests file makes any request on it an error. The check is sound
189
- because the developer writes `lb-*` attributes in markup and script never
190
- assigns them.
191
-
192
- The same machinery would then also let `lb-query` names be checked against
193
- the declared queries.
194
-
195
181
  ### A language server
196
182
 
197
183
  ...for Loadbare HTML.
@@ -203,6 +189,23 @@ expansion allows a strings file. We could either preserve the fully
203
189
  static build-time system and create multiple versions of `app.html`, or we
204
190
  could add label hydration to the page navigation stage.
205
191
 
192
+ ### Version checks on update and delete
193
+
194
+ The last write to a row wins, at the grain of the columns each request
195
+ carries, and a write whose outcome matters states its precondition in its
196
+ own SQL; see [crud](./reference/page-files.md#crud). A plain edit has no
197
+ such precondition: only the value the user saw, or a version standing for
198
+ it, tells that the row changed in between, and the server remembers
199
+ nothing.
200
+
201
+ A query would declare its version column. The hub keeps every query's
202
+ last answer, patches included, so it would send the version it holds with
203
+ each update and delete for that query, and the handler would put it in its
204
+ `WHERE`. No widget changes, and a control committing alone takes part.
205
+
206
+ Revisit when an application needs a write refused because the row changed
207
+ since the user saw it.
208
+
206
209
  ## Decided against
207
210
 
208
211
  Settled questions, kept here so they are not reopened. Each names what was
@@ -222,3 +225,12 @@ attribute value, with no escape for either.
222
225
 
223
226
  Text has a workaround: `<div>Hello, <span>{{world}}</span></div>` places the
224
227
  placeholder in a text node of its own. Attribute values have none.
228
+
229
+ ### Serving the static files
230
+
231
+ A router beside `hubRoutes` that serves `client.js`, `app.css` and
232
+ `app.html` would save an application three routes. It would also decide
233
+ for every application how those files are read, cached and placed among
234
+ its own routes, and whether a path naming no page answers `app.html`.
235
+ Loadbare/app ships no server, and serving the build's output is the
236
+ application's choice, as the rest of its server is.
package/docs/testing.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Testing
2
2
 
3
+ > LLM-authored and LLM-maintained. This is acceptable because the
4
+ > document is fact-based, describing tests that exist in this package, and
5
+ > is written to be consumed by an LLM working on the package.
6
+
3
7
  A contributor's document, and a companion to [theory.md](./theory.md). It
4
8
  describes how this package tests itself.
5
9
 
@@ -26,6 +30,23 @@ All four tiers run under `npm test` today and need no dependency that is not
26
30
  already installed. A fifth environment — a real browser — is discussed at the
27
31
  end and is not built.
28
32
 
33
+ ## What the tests are evidence for
34
+
35
+ [Theory](./theory.md) makes three claims that can be checked, and this suite
36
+ carries evidence for two of them.
37
+
38
+ - **Correctness.** Most of what Theory's correctness section names is
39
+ tested here: rows landed by key, an unchanged row left alone with what the
40
+ user typed, an answer in the wrong shape left out, a write sent once, a
41
+ refusal shown on the controls it names, and a server that refuses what a
42
+ page did not declare.
43
+ - **The vocabulary rule.** The markup checks in tier 1 enforce it at build
44
+ time: an `lb-` attribute outside the set the developer writes is a build
45
+ that fails.
46
+ - **Performance.** No tier measures the 300ms round trip. Node and jsdom
47
+ have no network and no paint, so they are not evidence for it, and this
48
+ suite does not try.
49
+
29
50
  ## The runner
30
51
 
31
52
  `node:test`, run through `tsx`:
@@ -78,17 +99,14 @@ the most of what Loadbare promises, so it is first.
78
99
  - more than one `lb-exp-slot`
79
100
 
80
101
  **Markup checks.** The assembled chrome and every page are checked before
81
- anything ships:
82
-
83
- - an `lb-` attribute that is not one the developer writes
84
- - `lb-request="lb-…"` naming none of the requests Loadbare provides
85
- - `lb-url-link` off an `<a>`, `lb-url-push` without `lb-request`, and
86
- `lb-url-unknown` off a `<dialog>` or outside `<lb-hub>`
87
- - `lb-show` on a `<template>`, on a row template's root, or with no row
88
- around it
89
- - a chrome without exactly one `<lb-hub>` and one empty `<main>` inside it,
90
- and a page carrying either
91
- - a widget script declaring an `lb` method Loadbare does not define
102
+ anything ships, against the list in
103
+ [The markup checks](./reference/data-binding.md#the-markup-checks). Each one
104
+ is a build that must not produce output. The checks of `lb-query` and
105
+ `lb-request` names take each page's declarations as `loadDeclared` reads
106
+ them, and `loadDeclared` is tested against real page modules: names read
107
+ off objects built with spreads and computed keys, nothing declared for a
108
+ page with no modules, nothing left in the output directory, and a module
109
+ that throws named in the error.
92
110
 
93
111
  A page file's `<title>` is lifted out and stamped as `lb-page-title`.
94
112
 
@@ -118,7 +136,7 @@ Review the golden files rather than blessing them. `npm run test:golden -w
118
136
  @loadbare/demo` rewrites them; the diff is the point, and a diff nobody read
119
137
  is a test nobody ran.
120
138
 
121
- The rest of the build — `assemble`, `elements`, `pages`, `styles`,
139
+ The rest of the build — `assemble`, `declared`, `elements`, `pages`, `styles`,
122
140
  `package-css` — is tested the same way and in the same tier, since none of it
123
141
  needs a browser either.
124
142
 
@@ -126,7 +144,7 @@ needs a browser either.
126
144
  a temporary project, which carries a stand-in `@loadbare/app` pointing at
127
145
  the hub's source so the test needs no prior build. It is tested for its
128
146
  default `src` and `dist`, every file it writes, `--minify`, a failed build's
129
- exit code, and `--watch` rebuilding on a change, skipping a file it does not
147
+ exit code, a name the page does not declare, and `--watch` rebuilding on a change, skipping a file it does not
130
148
  watch, and surviving a failed build.
131
149
 
132
150
  ## Tier 2 — The engine
@@ -149,11 +167,22 @@ watch, and surviving a failed build.
149
167
  operator away from silently reversing
150
168
  - a handler returning `url()` answers with the `lb-url` item alone, and runs
151
169
  no refresh set
170
+ - a `row` query that finds no row answers `null`
171
+ - at startup, a malformed order, an unknown column type, and a crud request
172
+ that refreshes its own `rows` query are refused
173
+ - an update or delete for several rows runs once per key, lays the patches
174
+ together, and refreshes once
175
+ - `onPageEnter` may return `url()` or `choices()`, and `onChoices` hears
176
+ every choice the user makes
177
+ - a refusal carries a message, columns or both, and passes through the
178
+ engine untouched
152
179
 
153
180
  `tests/lb-express.test.ts` runs against a listening app: the page comes from
154
181
  the path, the query parms reach `contextFor`, a `url()` answer is followed by
155
- the page loaded at the new URL, an undeclared request is refused, and a
156
- malformed body does not throw.
182
+ the page loaded at the new URL, an undeclared request is refused, a
183
+ malformed body does not throw, an oversized body is refused, a refusal
184
+ answers 422 with its words, and any other throw answers 500 with nothing in
185
+ it.
157
186
 
158
187
  ## Tier 3 — Landing
159
188
 
@@ -187,6 +216,10 @@ evidence here.
187
216
  groups and leaving them out of counts and aggregates (`lb-choices.test.ts`)
188
217
  - `lb-show` moves an element into a template and back, and landing reaches
189
218
  inside it
219
+ - a row whose values arrive as it already shows them is not filled again,
220
+ so what the user typed into it stays
221
+ - a `row` query answered with no row empties the columns it names and drops
222
+ its key
190
223
 
191
224
  Two properties, written as loops rather than with a library. Applying the
192
225
  same rows twice is applying them once. And rows reached through any sequence
@@ -252,6 +285,17 @@ What the hub is tested for here:
252
285
  loaded there
253
286
  - `hidden` comes off the body once a page has landed, including the page
254
287
  that failed to load, and a link whose page fails to load marks no element
288
+ - an answer that returns after the user has moved to another URL does not
289
+ land
290
+ - focus goes to the first element the user can operate once a page's
291
+ queries land
292
+ - a form pointed at a row by `commandfor` fills from the row the hub holds,
293
+ and a row not yet entered sends no update or delete
294
+ - ticked `lb-select` rows travel as `keys`, and the selection clears once
295
+ the request succeeds
296
+ - a refusal lands its message on the element that committed and on each
297
+ control it names
298
+ - `lb-request-done` reaches the element that committed and its ancestors
255
299
 
256
300
  Widgets are small and their logic is local, so jsdom carries them: a value
257
301
  reaches the control the widget owns, the widget is a form-associated control
@@ -282,18 +326,8 @@ No browser test runner is installed and none of the following exists. They
282
326
  are recorded here as the shape of the work, not as coverage:
283
327
 
284
328
  1. a cold load fills `<main>` with no empty flash
285
- 2. the wizard never displays a step the server has not confirmed
286
- 3. the view-source invariant
287
-
288
- The last is the one worth a browser. Loadbare's central claim is that no
289
- markup exists in the DOM that is not in view-source, and that the difference
290
- between the two is exactly the dynamic half. That is a property, it is stated
291
- precisely in [theory.md](./theory.md), and it should be enforced mechanically
292
- rather than believed: fetch the shell as text, compare the element set
293
- against the live document after interaction, and assert that the only
294
- additions are clones of row templates.
295
-
296
- See [TODO.md](../TODO.md) for this and the rest of the open work.
329
+ 2. the wizard page in `@loadbare/demo` never displays a step the server has
330
+ not confirmed
297
331
 
298
332
  ## Continuous integration
299
333
 
@@ -307,7 +341,7 @@ tiers here stay fast enough to run on every save.
307
341
  ## One change the tests want
308
342
 
309
343
  The client reports through `console.error` and `console.warn` directly, from
310
- roughly twenty places across `hub/` and `@loadbare/widgets`. Routing those through a
344
+ more than twenty places across `hub/` and `@loadbare/widgets`. Routing those through a
311
345
  single `report()` in `core/` would give the tests one seam to observe instead
312
346
  of a global to mock per file, and would put the framework's error channel in
313
347
  the same file as the rest of its vocabulary. It is a small change and it is