@loadbare/app 0.18.2 → 0.19.1

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 (51) 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/comparison.md +697 -484
  19. package/docs/departures.md +63 -0
  20. package/docs/miswarts.md +140 -0
  21. package/docs/opinions.md +155 -0
  22. package/docs/reference/builder.md +20 -4
  23. package/docs/reference/chrome.md +45 -10
  24. package/docs/reference/css.md +0 -1
  25. package/docs/reference/custom-elements.md +3 -25
  26. package/docs/reference/data-binding.md +265 -48
  27. package/docs/reference/names.md +250 -0
  28. package/docs/reference/overview.md +6 -0
  29. package/docs/reference/page-files.md +77 -9
  30. package/docs/reference/server.md +54 -1
  31. package/docs/roadmap.md +55 -43
  32. package/docs/testing.md +62 -28
  33. package/docs/theory.md +348 -108
  34. package/docs/tutorials/040-displaying-data.md +1 -1
  35. package/package.json +1 -1
  36. package/skills/loadbare-app/SKILL.md +19 -13
  37. package/skills/loadbare-app/references/builder.md +20 -4
  38. package/skills/loadbare-app/references/chrome.md +45 -10
  39. package/skills/loadbare-app/references/css.md +0 -1
  40. package/skills/loadbare-app/references/custom-elements.md +3 -25
  41. package/skills/loadbare-app/references/data-binding.md +265 -48
  42. package/skills/loadbare-app/references/miswarts.md +140 -0
  43. package/skills/loadbare-app/references/names.md +250 -0
  44. package/skills/loadbare-app/references/overview.md +6 -0
  45. package/skills/loadbare-app/references/page-files.md +77 -9
  46. package/skills/loadbare-app/references/server.md +54 -1
  47. package/docs/TECHREF-1.0.md +0 -2312
  48. package/docs/possible-ideas.md +0 -314
  49. package/docs/theory-old.md +0 -839
  50. package/docs/what-does-loadbare-extend.md +0 -124
  51. package/skills/loadbare-app/references/TECHREF-1.0.md +0 -2312
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
package/docs/theory.md CHANGED
@@ -1,113 +1,353 @@
1
1
  # The Theory of Loadbare/app
2
2
 
3
- Loadbare has multiople opinions, but there are two co-primary drivers
4
- of all design:
5
-
6
- - The user should expect a median round trip of 300ms for server
7
- interactions.
8
- - Elegant abstractions and reimplementations of
9
- browser behavior must be avoided because they threaten
10
- the performance goal, and they introduce accidental
11
- complexity.
12
-
13
- Two early simplifying decisions were that Loadbare would use SQL
14
- terms, and would split server results into two shapes that are
15
- best suited to a UI: a single `row` and a set of `rows`, using
16
- `column` for the value of a single value in a row. Loadbare
17
- does not understand non-atomic column values.
18
-
19
- The framework's job then becomes to support displaying or "landing" values
20
- into the DOM for a given collection of the `row` and `rows` shapes. It
21
- also must provide the three writes, insert, update and delete, plus
22
- an escape hatch for bespoke operations on the server.
23
-
24
- Loadbare implements the following opinions on declarative systems.
25
- - Declarative support for the data shapes and their operations
26
- is easier to reason about for the developer, and greatly reduces
27
- bugs in application code.
28
- - Framework support for declarations about SQL shapes is finite
29
- and free of edge cases, allowing for creation of a robust
30
- framework processor in the browser.
31
-
32
- Other opinions include:
33
- - It would be nice to code straight static HTML, since HTML is
34
- up to the task, being mature and stable. When
35
- custom elements with light DOM are added, any bespoke behavior
36
- can be supported.
37
- - Frameworks should not interfere with CSS
38
- - Code written for loadbare should run reliably on a timeframe
39
- measured in decades. As long as we have browsers, a Loadbare
40
- app should run.
41
-
42
- ## The 300ms Budget
43
-
44
- The first non-negotiable primary goal is a framework where server
45
- round trips can run in a median 300ms, from click to paint.
46
-
47
- Of the 300ms, we allocate 200ms as typical wire time when application
48
- and user are in the same region, leaving 100ms for everything that
49
- happens in the browser, the application server, and the database.
50
-
51
- Since the database's response time is not under the control of the
52
- Loadbare/app framework, we realize Loadbare/app must do its job in as
53
- little time as possible. This is how we arrived at the second
54
- primary opinion of Loadbare/app: to avoid elegant abstractions or
55
- reimplementations of browser behavior that would threaten the
56
- performance goal. Loadbare/app must be thin and get its job done
57
- in low double-digit milliseconds, leaving as much time as possible
58
- to the database and such tasks as authentication or picking up
59
- a connection from the pool.
60
-
61
-
62
- ## Ken Random Thought, For later
63
-
64
- I often wonder if Loadbare/app does not in fact have "low accidental
65
- complexity" but instead has "different accidental complexity".
66
-
67
- Given the inherent complexity of the browser, any comprehensive
68
- solution is going to be non-trivial, so simple measures like vocabulary
69
- size are not good proxies.
70
-
71
- The test, when we put something in, is whether we are following
72
- SQL and following HTML.
73
-
74
- For SQL, we know that we have:
3
+ By Ken Downs
4
+ October 9, 2026
5
+
6
+ The primary goals of Loadbare/app are performance, correctness, and
7
+ developer ergonomics for server-bound applications. These goals motivate
8
+ all design and implementation decisions. The motivations cannot be
9
+ cleanly separated in implementation, they must all be true at once, and
10
+ often overlap and support each other.
11
+
12
+ Correctness: there is precise and perfect integrity between the database
13
+ and the screen as of the last round
14
+ trip. User writes hit the database once. Constraint violations are
15
+ intelligible and rendered in context.
16
+
17
+ Developer ergonomics means the simplest coding path results in an
18
+ application that is correct and performant. Getting the right
19
+ answer painted on the screen must be the lowest friction way to
20
+ write the code. This means many things, but it definitely rules
21
+ out elegant abstractions, excessive boilerplate and housekeeping code.
22
+ It must be difficult to express a solution that is slow or does not
23
+ maintain integrity between database and UI.
24
+
25
+ I admire elegance, but it is very dangerous when it becomes a goal
26
+ for its own sake. An elegant abstraction appeals from its own
27
+ design rather than from what it does for the user: it
28
+ hides the platform instead of mapping onto it, and it must be learned and
29
+ serviced forever.
30
+
31
+ From the user's perspective, performance is an aspect of correctness. A
32
+ slow round trip leaves the user wondering if what they see is correct. The
33
+ ideal user experience is certainty and trust in the application, which
34
+ requires a cumulative experience of an expectation:reward ratio of
35
+ exactly 1:1. All application behaviors that create doubt and
36
+ confusion erode the expectation:reward ratio, and must be removed
37
+ and made difficult to create by construction. A slow application cannot
38
+ provide an expectation:reward ratio of 1:1 because it is constantly
39
+ creating doubt.
40
+
41
+ ## Currency is not a motivator
42
+
43
+ Currency is not a motivator for 1.0.
44
+ By currency, we mean the screen may be updated, absent any action by
45
+ the current user, due to the actions of other users or automated processes.
46
+ The class of apps that require currency are different in kind from those
47
+ that require only correctness. Unprompted updates may even violate
48
+ the Loadbare/app definition of correctness.
49
+
50
+ Currency can be considered when use cases present, and once it is
51
+ reconciled to correctness.
52
+
53
+ ## From motivation to means
54
+
55
+ With the motivations firmly established, everything after is
56
+ technical decisions to establish the means.
57
+
58
+ This can be confusing, because sometimes a means and motivation
59
+ look the same. For example, I personally prefer to code static
60
+ HTML, and would like a data-driven framework that allows that.
61
+ It so happens Loadbare/app works that way. But it would be a
62
+ tedious and unproductive waste of words trying to determine if
63
+ that was a preference, motivation or means.
64
+
65
+ The three primary motivations force all design and implementation
66
+ decisions. All of my other preferences and motivations, such as
67
+ low boilerplate, declarative systems, static HTML and many others
68
+ were all worked in along the way, but always in a way that
69
+ supported the major three motivations.
70
+
71
+ I am happy to say I think it worked. In the sections below,
72
+ you will see how it was done. My own preferences and secondary
73
+ motivations will be plain to see in the decisions, but they
74
+ are always presented as what they are: a means to create a correct
75
+ and performant application with high developer ergonomics.
76
+
77
+ ## The organizing principle
78
+
79
+ Every framework needs an organizing principle that determines implementation
80
+ decisions and the developer experience. For Loadbare/app, a very early
81
+ decision established that principle: Loadbare uses SQL vocabulary and
82
+ understands two SQL data shapes: a `row` or a set of `rows`. We speak
83
+ of rows, columns, keys, insert, update, and delete. We use the word
84
+ "query" when talking about named SQL SELECT statements.
85
+
86
+ Loadbare/app does not understand non-atomic column values. If a
87
+ document is retrieved from a store that embeds child rows into a
88
+ parent row, it must be converted into two separate query results.
89
+
90
+ A relational
91
+ expert will quickly point out that `rows` subsumes `row` and only
92
+ one shape is necessary, but in a UI they are not subsumed. A form
93
+ with its singleton form controls per column is expressed with
94
+ fundamentally different mechanics than a `<ul>`, `<select>` or
95
+ `<table>`. This is a UI framework, not a relational store, so the
96
+ data shapes are optimized for making user interfaces.
97
+
98
+ This one decision serves all three motivations. Two shapes and three
99
+ writes make a small and closed vocabulary, so coding the framework
100
+ is a finite and terminating task. For correctness, a finite task has
101
+ no open-ended edge cases to get wrong, and because every row arrives
102
+ with its key, the screen always knows exactly which row in the
103
+ database it is showing. For performance, every task the framework
104
+ must perform is itself finite, so each one can be measured and
105
+ optimized. For ergonomics, a developer of a database application must
106
+ already know rows, keys, SELECT and DML. Loadbare/app asks them to
107
+ map that knowledge onto HTML, not to learn new concepts.
108
+
109
+ This organizing principle also guards us from creating elegant
110
+ abstractions. Anything new must be a SQL concept, an HTML one, or a
111
+ member of a small, named family of things every browser application
112
+ needs, and an elegant abstraction is none of these. An elegant abstraction works against
113
+ all three motivations at once: it is a place where what the developer
114
+ wrote and what actually happens can drift apart, it adds work to every
115
+ round trip, and it is one more thing to learn and service instead of
116
+ the application. That last cost is where yak-shaving begins.
117
+
118
+ With this decision, Loadbare/app becomes a natural fit for SQL applications,
119
+ and can also be used if back-end data can be formatted to the two
120
+ shapes of `row` and `rows`.
121
+
122
+ ## The performance goal
123
+
124
+ Because performance must be protected at all times, we need to
125
+ get specific about that first. All downstream decisions must know
126
+ the target they are protecting.
127
+
128
+ Research on response time gives two limits. Near 100ms, a response
129
+ feels instantaneous, as if caused directly by the user's action. Near
130
+ one second, the user's flow of thought begins to break (Miller 1968;
131
+ Card, Moran and Newell 1983; Nielsen 1993). Between the two, every
132
+ reduction pays, and the payoff grows sharply below a second (Doherty
133
+ and Thadhani 1982).
134
+
135
+ A server-bound application cannot reach 100ms, because the round trip
136
+ alone takes longer. So Loadbare/app sets its own target: a median of
137
+ 300ms from click to paint, as close to instantaneous as a round trip
138
+ allows, and well inside the limit where doubt begins.
139
+
140
+ With a 300ms budget, we must allocate 200ms as typical wire time
141
+ when an application and user are in the same region, leaving 100ms
142
+ for everything that happens in the browser, the application server,
143
+ and the database.
144
+
145
+ The database's response time is not under the control of
146
+ Loadbare/app, and neither is the application's own server work,
147
+ such as authentication or picking up a connection from the pool.
148
+ So Loadbare/app must be thin, and get its job done in low
149
+ double-digit milliseconds, leaving as much of the 100ms as possible
150
+ to the work that belongs to the application.
151
+
152
+ This is where the organizing principle pays off a second time.
153
+ Having already ruled out elegant abstractions, we extend the rule to
154
+ reimplementations of browser behavior. The browser already parses
155
+ HTML, finds elements by selector, clones templates and manages focus,
156
+ and it does those things in native code faster than any framework
157
+ can do them again in JavaScript. Every millisecond Loadbare/app
158
+ spends redoing the browser's work is a millisecond taken from the
159
+ database.
160
+
161
+ ## The correctness goal
162
+
163
+ Performance has a number, and we can hold a stopwatch to it.
164
+ Correctness has a definition instead, given at the top of this
165
+ document: integrity between database and screen as of the last round
166
+ trip, writes that hit the database once, and constraint violations
167
+ that are intelligible and rendered in context. We protect it by
168
+ making wrong states hard to reach, and by refusing or reporting the
169
+ ones we cannot rule out.
170
+
171
+ The foundation is an observation about data applications. Most
172
+ frameworks support a DOM that is fully mutable at any time for any
173
+ reason. But in a data application, the screen changes for one main
174
+ reason: the server answered with rows. The other changes, such as a
175
+ button waiting on its request, are a small and closed set. So
176
+ Loadbare/app changes the screen only from what the server sent, and
177
+ never ahead of it. There are no optimistic updates, and no guesses
178
+ about what the database probably did.
179
+
180
+ Because every row arrives with its key, the hub matches what arrived
181
+ to what is shown by key, never by position. A row that is gone
182
+ leaves, a row that changed is filled, and a row that did not change
183
+ is left alone, along with anything the user typed into it and has not
184
+ sent. An answer the server could not have meant for the current
185
+ screen is discarded: if the user moved to another page while a
186
+ request was in flight, the write still happened, but its answer does
187
+ not land on a page it says nothing about. When a query answers in a
188
+ shape it did not declare, the page goes without it. A page missing
189
+ one query is better than a page showing the wrong thing.
190
+
191
+ Writes hit the database once because a request comes from the
192
+ document, not from the developer's code. The developer names the
193
+ request, and the hub supplies the query and key from where the
194
+ element sits, so the developer cannot send the wrong key. An element
195
+ whose request is in flight ignores the next click, so a double click
196
+ cannot become a double write.
197
+
198
+ Constraints belong to the database, or to the application's own
199
+ server code. The framework's job is to carry the violation back to
200
+ the user in terms they can act on. A handler refuses a request with
201
+ words written for the user, either for the request as a whole or for
202
+ each column, and the browser shows them on the controls they belong
203
+ to. Anything else that goes wrong on the server reaches the browser
204
+ as a plain failure, so a stack trace or a database message never
205
+ lands in front of a user.
206
+
207
+ Finally, correctness by construction. Whatever can be checked before
208
+ the application runs is checked, and a mistake stops the build or the
209
+ server rather than shipping. The builder refuses a custom element it
210
+ cannot find and any `lb-` attribute the developer is not allowed to
211
+ write. The server refuses, at startup, patterns that are known to be
212
+ wrong, and refuses any request a page did not declare, so the wire
213
+ cannot reach anything the page has not published. What cannot be
214
+ checked ahead of time is reported on the console the moment it
215
+ happens, rather than silently papered over.
216
+
217
+ Each of these removes a source of doubt. Taken together, they are
218
+ how Loadbare/app keeps the expectation:reward ratio at 1:1 from the
219
+ correctness side, as the 300ms budget does from the performance side.
220
+
221
+ ## Guarding the vocabulary
222
+
223
+ A small, clean vocabulary is a means, and it serves ergonomics first.
224
+ Every term a developer must learn that is not about their own
225
+ application is accidental complexity, time spent servicing the tool
226
+ instead of building the app. A messy vocabulary also hurts
227
+ correctness, because overlapping terms create edge cases, and edge
228
+ cases are where the framework and the developer disagree about what
229
+ the code means.
230
+
231
+ With apologies to Brooks and Pike:
232
+
233
+ > Essential complexity should dominate. Show me your abstractions and
234
+ > tooling and I shall remain mystified. Show me your tables and your
235
+ > rendering vocabulary and I shall be enlightened - the organization of
236
+ > the code will be self-evident.
237
+
238
+ A fair critic might say Loadbare/app does not have low accidental
239
+ complexity, only different accidental complexity. I take the
240
+ critique seriously, and it cannot be answered by counting terms. The
241
+ browser is inherently complex, so any complete solution will be
242
+ non-trivial, and vocabulary size alone is not a good proxy. What
243
+ matters more is where the terms come from.
244
+
245
+ Most of the vocabulary comes from SQL, which a developer of a
246
+ database application already knows:
247
+
75
248
  - a `row`, with landing, insert, update, and delete
76
249
  - `rows`, with landing, insert, update, and delete
77
250
 
78
- All of the complexity comes when we map those to HTML, and here again
79
- HTML has a larger surface area but it is still finite.
80
-
81
- If we end up with 'best practices', and they look like accidental
82
- complexity, a bunch of stuff you have to learn, the test is whether
83
- a best practice already exists for HTML or SQL, and whether it makes
84
- sense in the combination.
85
-
86
-
87
- ## LLM-suggested nugget, to be integrated later
88
-
89
- On the critique that Loadbare has "different accidents" than React or
90
- HTMX. Scoped claim:
91
-
92
- > The data vocabulary is SQL data shapes, SELECT and DML applied to
93
- > HTML. It invents no new data concepts. Everything else is a small,
94
- > closed set covering what any browser app needs: navigation, request
95
- > status, and showing what changed between results.
96
-
97
- Supporting points:
98
-
99
- - The developer of a server-bound app must know rows, keys, SELECT and
100
- DML regardless of framework. That knowledge is essential complexity.
101
- Applying it to HTML adds a mapping, not new concepts.
102
- - The same SQL-shape decision that limits the vocabulary keeps the
103
- browser processor finite, which is what fits it in the 300ms budget.
104
- - The non-SQL families today: navigation (`lb-url*`, `lb-path`,
105
- `lb-page*`), request lifecycle (`lb-request-*`), changes between
106
- result sets (`lb-row-live/leaving/moved/changed`, `lb-group-live/leaving`),
107
- and structure (`lb-hub`, `lb-exp-template`, `lb-exp-slot`, `lb-release`).
108
- - The accidents most likely to accumulate are in the mapping rules SQL
109
- does not dictate: which element a row becomes, what a re-landing does,
110
- how a key identifies an element.
111
- - Design rule: a new term must be a SQL concept or belong to a named
112
- family. A new family, or growth in the non-SQL families, is the
113
- signal the critique is starting to land.
251
+ The rest of it comes from HTML, which they also already know.
252
+
253
+ Not everything is SQL or HTML. Any browser application also needs
254
+ navigation, request status, and a way to show what changed between
255
+ one result and the next. These are small, closed families of their
256
+ own. So the rule is that a new term must be a SQL concept, an HTML
257
+ one, or belong to a named family. Any mechanism that is not
258
+ directly serving SQL, HTML or a named family is a mistake and will, sooner
259
+ or later, obstruct progress on the main motivations.
260
+
261
+ The real complexity comes when we map SQL onto HTML. HTML has a
262
+ larger surface area than SQL, but it is still finite. The accidents
263
+ most likely to pile up are in the mapping rules SQL does not dictate:
264
+ which element a row becomes, what happens when a row lands again, and
265
+ how a key identifies an element. That is where to look first when
266
+ the framework starts to feel like something you have to learn.
267
+
268
+ The same test applies to "best practices." If we end up with a list
269
+ of them, and they look like a bunch of stuff you have to learn, we
270
+ ask whether the practice already exists in SQL or HTML, and whether
271
+ it makes sense in the combination. If it does not, it is accidental
272
+ complexity, and the framework should absorb it or drop it.
273
+
274
+ The payoff is that the whole vocabulary fits in one technical
275
+ reference. That means it fits in a developer's head, and in an
276
+ LLM's context window.
277
+
278
+ ## Conclusion
279
+
280
+ Loadbare/app is not for every application. It fits when the data is
281
+ rows, or can easily be shaped into rows, and when user actions
282
+ map to reading and writing those rows. It does not fit an
283
+ application whose data will not take the two shapes, and it does not
284
+ yet fit an application that needs currency. For a database
285
+ application, which describes a great many of the applications
286
+ people actually use at work, I believe it fits very well.
287
+
288
+ These are claims that can be checked. Performance has a
289
+ number: a median round trip of 300ms from click to paint.
290
+ Correctness has a definition: integrity between database and screen
291
+ as of the last round trip, writes that hit the database once, and
292
+ constraint violations rendered in context. The vocabulary has a
293
+ rule: every term is SQL, HTML, or a member of a named family. If
294
+ Loadbare/app misses the number, breaks the definition, or grows past
295
+ the rule, the theory has failed in a way anyone can see.
296
+
297
+ Code written for Loadbare/app should also last. Pages are HTML and
298
+ CSS, widgets are custom elements, the server is Express, and the
299
+ page code is queries against the application's own database. Each
300
+ of those is a standard or a mature tool in its own right, so code
301
+ written today is exposed to very little framework churn tomorrow.
302
+ As long as we have browsers, a Loadbare/app application should run.
303
+
304
+ The same properties matter more now that much code is written by
305
+ LLMs. SQL and HTML are about as well represented in an LLM's
306
+ training as anything can be, and the named families, navigation,
307
+ request status and showing what changed, are concepts every web
308
+ framework has. The model already knows nearly everything, so the
309
+ context we provide only has to spell out the mappings between them.
310
+ That is why the vocabulary fits in one reference, and one reference
311
+ fits in a context window. A framework that refuses mistakes at build time and at
312
+ startup catches the LLM's mistakes along with the developer's. The
313
+ correct application being the easiest one to write is just as true
314
+ when the writer is a model.
315
+
316
+ None of this is new to me. I first wrote this framework in 2003, as
317
+ Andromeda, before Node existed and before we had jQuery. The
318
+ foundation of Andromeda lives on in `@loadbare/db`, which makes the
319
+ data correct by declaring business logic in the schema. The
320
+ conviction behind Andromeda's web layer was that database
321
+ applications deserve a framework built for them. I am just as
322
+ convinced today, and `@loadbare/app` carries that correctness the
323
+ rest of the way, from the database to the paint.
324
+
325
+ So the whole theory comes down to this. Loadbare/app exists to make
326
+ a server-bound application correct, and to deliver that correctness
327
+ fast. The correct, fast application must also be the easiest one to
328
+ write. One early decision, that the framework speaks SQL and
329
+ understands only a `row` and a set of `rows`, serves all three at
330
+ once, and every other decision in this document is a means to the
331
+ same end.
332
+
333
+ ## Appendix: Opinions, Departures and Miswarts
334
+
335
+ In an imperfect world, well defined rules do not always converge to
336
+ perfection. Loadbare/app makes two significant
337
+ [departures](./departures.md) from what this document implies, in
338
+ navigation and on the wire, and a few [miswarts](./miswarts.md)
339
+ appeared during development.
340
+
341
+ The decisions in this document force some positions on the developer.
342
+ They are listed in one place as [opinions](./opinions.md).
343
+
344
+ ## References
345
+
346
+ - Card, S. K., Moran, T. P. and Newell, A. *The Psychology of
347
+ Human-Computer Interaction.* Lawrence Erlbaum, 1983.
348
+ - Doherty, W. J. and Thadhani, A. J. "The Economic Value of Rapid
349
+ Response Time." IBM, November 1982.
350
+ - Miller, R. B. "Response time in man-computer conversational
351
+ transactions." *Proceedings of the AFIPS Fall Joint Computer
352
+ Conference*, 1968.
353
+ - Nielsen, J. *Usability Engineering*, chapter 5. Academic Press, 1993.
@@ -130,7 +130,7 @@ requests to the correct page code.
130
130
  import express from "express";
131
131
  import { readFileSync } from "node:fs";
132
132
  import { hubRoutes } from "@loadbare/app/express";
133
- import { hub } from "./dist/pages";
133
+ import { hub } from "./dist/pages.ts";
134
134
  import { openDb } from "./src/database";
135
135
 
136
136
  const app = express();