@loadbare/app 0.4.0 → 0.5.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 (165) hide show
  1. package/README.md +53 -82
  2. package/dist/build/assemble.d.ts +7 -5
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +29 -9
  5. package/dist/build/cli.d.ts +20 -12
  6. package/dist/build/cli.d.ts.map +1 -1
  7. package/dist/build/cli.js +34 -16
  8. package/dist/build/elements.d.ts +15 -29
  9. package/dist/build/elements.d.ts.map +1 -1
  10. package/dist/build/elements.js +25 -111
  11. package/dist/build/expand.d.ts +1 -1
  12. package/dist/build/expand.js +1 -1
  13. package/dist/build/format.d.ts +6 -3
  14. package/dist/build/format.d.ts.map +1 -1
  15. package/dist/build/format.js +6 -3
  16. package/dist/build/locations.d.ts +14 -37
  17. package/dist/build/locations.d.ts.map +1 -1
  18. package/dist/build/locations.js +31 -69
  19. package/dist/build/origins.d.ts +109 -0
  20. package/dist/build/origins.d.ts.map +1 -0
  21. package/dist/build/origins.js +270 -0
  22. package/dist/core/lb-constants.d.ts +1 -0
  23. package/dist/core/lb-constants.d.ts.map +1 -1
  24. package/dist/core/lb-constants.js +15 -8
  25. package/dist/core/lb-types.d.ts +2 -2
  26. package/dist/core/lb-types.d.ts.map +1 -1
  27. package/dist/hub/lb-apply.js +1 -1
  28. package/dist/hub/lb-hub.d.ts.map +1 -1
  29. package/dist/hub/lb-hub.js +44 -17
  30. package/dist/hub/lb-rows.d.ts.map +1 -1
  31. package/dist/hub/lb-rows.js +3 -3
  32. package/dist/server/lb-express.d.ts.map +1 -1
  33. package/dist/server/lb-server.d.ts +5 -4
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/tests/assemble.test.js +11 -4
  36. package/dist/tests/elements.test.js +47 -51
  37. package/dist/tests/expand.test.d.ts +1 -1
  38. package/dist/tests/expand.test.js +2 -2
  39. package/dist/tests/fixtures/elements/collision/imports.d.ts +3 -0
  40. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +1 -0
  41. package/dist/tests/fixtures/elements/collision/imports.js +1 -0
  42. package/dist/tests/fixtures/elements/manifest/imports.d.ts +3 -0
  43. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +1 -0
  44. package/dist/tests/fixtures/elements/manifest/imports.js +1 -0
  45. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +3 -0
  46. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +1 -0
  47. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +1 -0
  48. package/dist/tests/fixtures/elements/{collision/elements.d.ts → manifest-not-array/imports.d.ts} +1 -1
  49. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +1 -0
  50. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +1 -0
  51. package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts +2 -0
  52. package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts.map +1 -0
  53. package/dist/tests/fixtures/elements/pkg/acme-widget.js +1 -0
  54. package/dist/tests/lb-express.test.js +1 -1
  55. package/dist/tests/origins.test.d.ts +10 -0
  56. package/dist/tests/origins.test.d.ts.map +1 -0
  57. package/dist/tests/origins.test.js +326 -0
  58. package/dist/tests/pages.test.js +3 -3
  59. package/dist/tests/styles.test.js +7 -4
  60. package/docs/reference/builder.md +128 -0
  61. package/docs/reference/chrome.md +75 -0
  62. package/docs/reference/css.md +44 -0
  63. package/docs/reference/custom-elements.md +327 -0
  64. package/docs/reference/data-binding.md +240 -0
  65. package/docs/reference/overview.md +38 -0
  66. package/docs/reference/page-files.md +175 -0
  67. package/docs/reference/server.md +123 -0
  68. package/docs/reference/widgets.md +163 -0
  69. package/docs/roadmap.md +130 -0
  70. package/docs/testing.md +228 -0
  71. package/docs/theory.md +344 -223
  72. package/docs/tutorials/000-getting-started.md +86 -0
  73. package/docs/tutorials/010-pages-and-navigation.md +129 -0
  74. package/docs/tutorials/020-css.md +103 -0
  75. package/docs/tutorials/030-html-decomposition.md +79 -0
  76. package/docs/tutorials/040-displaying-data.md +169 -0
  77. package/docs/tutorials/050-actions.md +77 -0
  78. package/docs/tutorials/060-custom-element-code.md +73 -0
  79. package/docs/tutorials/065-conditional-rendering.md +161 -0
  80. package/docs/tutorials/070-displaying-a-list.md +137 -0
  81. package/docs/tutorials/072-inserting-into-a-list.md +88 -0
  82. package/docs/tutorials/074-deleting-from-a-list.md +77 -0
  83. package/docs/tutorials/076-updating-a-list-item.md +86 -0
  84. package/docs/tutorials/080-widget-requests.md +124 -0
  85. package/docs/tutorials/090-using-widget-libraries.md +75 -0
  86. package/package.json +10 -18
  87. package/dist/client.js +0 -522
  88. package/dist/demo-static/src/widgets/app-box.d.ts +0 -15
  89. package/dist/demo-static/src/widgets/app-box.d.ts.map +0 -1
  90. package/dist/demo-static/src/widgets/app-box.js +0 -19
  91. package/dist/tests/fixtures/elements/collision/elements.d.ts.map +0 -1
  92. package/dist/tests/fixtures/elements/collision/elements.js +0 -3
  93. package/dist/tests/fixtures/elements/manifest/elements.d.ts +0 -5
  94. package/dist/tests/fixtures/elements/manifest/elements.d.ts.map +0 -1
  95. package/dist/tests/fixtures/elements/manifest/elements.js +0 -3
  96. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts +0 -5
  97. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts.map +0 -1
  98. package/dist/tests/fixtures/elements/manifest-bad-tag/elements.js +0 -3
  99. package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts +0 -5
  100. package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts.map +0 -1
  101. package/dist/tests/fixtures/elements/manifest-bad-value/elements.js +0 -3
  102. package/dist/tests/golden.test.d.ts +0 -19
  103. package/dist/tests/golden.test.d.ts.map +0 -1
  104. package/dist/tests/golden.test.js +0 -60
  105. package/dist/tests/helpers/window.d.ts +0 -43
  106. package/dist/tests/helpers/window.d.ts.map +0 -1
  107. package/dist/tests/helpers/window.js +0 -78
  108. package/dist/tests/lb-input.test.d.ts +0 -9
  109. package/dist/tests/lb-input.test.d.ts.map +0 -1
  110. package/dist/tests/lb-input.test.js +0 -78
  111. package/dist/tests/lb-list.test.d.ts +0 -12
  112. package/dist/tests/lb-list.test.d.ts.map +0 -1
  113. package/dist/tests/lb-list.test.js +0 -44
  114. package/dist/tests/lb-options.test.d.ts +0 -10
  115. package/dist/tests/lb-options.test.d.ts.map +0 -1
  116. package/dist/tests/lb-options.test.js +0 -121
  117. package/dist/tests/lb-picker.test.d.ts +0 -14
  118. package/dist/tests/lb-picker.test.d.ts.map +0 -1
  119. package/dist/tests/lb-picker.test.js +0 -59
  120. package/dist/tests/lb-select.test.d.ts +0 -9
  121. package/dist/tests/lb-select.test.d.ts.map +0 -1
  122. package/dist/tests/lb-select.test.js +0 -71
  123. package/dist/tests/lb-table.test.d.ts +0 -15
  124. package/dist/tests/lb-table.test.d.ts.map +0 -1
  125. package/dist/tests/lb-table.test.js +0 -205
  126. package/dist/widgets/index.d.ts +0 -7
  127. package/dist/widgets/index.d.ts.map +0 -1
  128. package/dist/widgets/index.js +0 -6
  129. package/dist/widgets/lb-input.d.ts +0 -2
  130. package/dist/widgets/lb-input.d.ts.map +0 -1
  131. package/dist/widgets/lb-input.js +0 -48
  132. package/dist/widgets/lb-list.d.ts +0 -2
  133. package/dist/widgets/lb-list.d.ts.map +0 -1
  134. package/dist/widgets/lb-list.js +0 -17
  135. package/dist/widgets/lb-options.d.ts +0 -26
  136. package/dist/widgets/lb-options.d.ts.map +0 -1
  137. package/dist/widgets/lb-options.js +0 -72
  138. package/dist/widgets/lb-picker.d.ts +0 -2
  139. package/dist/widgets/lb-picker.d.ts.map +0 -1
  140. package/dist/widgets/lb-picker.js +0 -25
  141. package/dist/widgets/lb-select.d.ts +0 -2
  142. package/dist/widgets/lb-select.d.ts.map +0 -1
  143. package/dist/widgets/lb-select.js +0 -43
  144. package/dist/widgets/lb-table.d.ts +0 -2
  145. package/dist/widgets/lb-table.d.ts.map +0 -1
  146. package/dist/widgets/lb-table.js +0 -113
  147. package/docs/application-chrome.md +0 -36
  148. package/docs/building-html-pages.md +0 -130
  149. package/docs/getting-started.md +0 -120
  150. package/docs/guide.md +0 -1164
  151. package/docs/hosting.md +0 -218
  152. package/docs/latent-risks.md +0 -20
  153. package/widgets/index.ts +0 -6
  154. package/widgets/lb-input.html +0 -1
  155. package/widgets/lb-input.ts +0 -64
  156. package/widgets/lb-list.html +0 -1
  157. package/widgets/lb-list.ts +0 -21
  158. package/widgets/lb-options.html +0 -4
  159. package/widgets/lb-options.ts +0 -88
  160. package/widgets/lb-picker.html +0 -7
  161. package/widgets/lb-picker.ts +0 -27
  162. package/widgets/lb-select.html +0 -4
  163. package/widgets/lb-select.ts +0 -55
  164. package/widgets/lb-table.html +0 -8
  165. package/widgets/lb-table.ts +0 -126
package/docs/theory.md CHANGED
@@ -1,226 +1,347 @@
1
1
  # Theory of Loadbare App
2
2
 
3
- Loadbare App is a web framework that answers the author's pain points with modern
4
- web development: a paradoxical situation in which developer ergonomics appear
5
- to dominate framework architecture but we end up with developer tools that
6
- inflict increasing pain and expense at scale. User
7
- experience, especially speed, is left as "an exercise for the reader" or waved
8
- away as irrelevant due to "powerful modern hardware."
9
-
10
- The result is that it is incredibly expensive to make a web app, and the result
11
- is usually very slow.
12
-
13
- The iterative effort began with Axiom Zero: The best
14
- developer experience is creating an application that users appreciate.
15
- A framework for application development must put user needs first, and
16
- craft the resulting solution patterns to developer needs after the best
17
- user result is identified and achieved.
18
-
19
- So we begin with user experience, a fancy way of saying, "why we built this
20
- darn app in the first place, and what makes people likely to come back."
21
- Modern expectations for a web app are numerous and span multiple domains. The
22
- one domain that seems to have been forgotten as inconvenient to --toy-making--
23
- developer tool forging is throughput, or performance. Loadbare App therefore puts
24
- performance first, every decision must result in a performant application.
25
-
26
- This leads to Axiom 1: Performance drives all architecture. Primary
27
- decisions drive performance, secondary decisions do not subvert it.
28
-
29
- ## The Performance Budget
30
-
31
- Research over the past 50 years concludes that users appreciate a tight average
32
- of ~300ms response time from a request to a completed paint.
3
+ by Ken Downs, August 29, 2026.
33
4
 
34
- As the round trip is the critical path, we look at a round trip and break down
35
- the budget into wire
36
- time, browser time, and server time. Wire time is limited by the speed of light,
37
- so we pick 200ms as a strong median for a browser and server located in roughly
38
- the same region.
39
-
40
- With 200ms consumed on the wire, that leaves 100ms for the server and the browser.
41
- Within that 100ms, there is browser paint, which we control, and request handling,
42
- which the framework can structure, and then database response time. Since the
43
- database is the one thing outside of our control, we come to a simple conclusion:
44
-
45
- > Global Requirement 1: The framework must consume as little of the
46
- > 100ms as possible, leaving
47
- > as much of the 100ms budget as possible to the database.
48
-
49
-
50
- ## The Split
51
-
52
- Since the author knew the fat browser frameworks could never be twisted into
53
- shape to satisfy the ground requirements, he experimented mostly with htmx, and
54
- investigated dataStar, Turbo, and others.
55
-
56
- What became clear is that all modern offerings have two things in common:
57
-
58
- COMMEN ELEMENT 1: They all assume that any DOM node can be mutated, added, or
59
- removed at any time. Most of the run-time expense and framework complexity
60
- is a direct result of this assumption.
61
-
62
- COMMON ELEMENT 2: Their templating systems mix the shape of a response (the
63
- HTML/CSS and some JS for behavior), with the content of the response (the stuff
64
- we got from a data store, search engine, relational database or what have you).
65
-
66
- > *Note: Phoenix LiveView is the only exception I found, it was rejected
67
- > for reasons explained in [prior-art.md](../docs-llm-slop/prior-art.md).
68
-
69
- The final iteration, which led to Release 1.0, only got off the ground
70
- once the fundamental split was identified:
71
-
72
- > The UI is shipped as a static and permanent "host" for data that is supplied
73
- > on a separate channel. Page navigation swaps in the page "host" code, and
74
- > user interaction refreshes data that the framework must push into the DOM.
75
-
76
- This split does not preclude conditional rendering or list processing, but it
77
- significantly changes their shape. There are plentiful examples in the
78
- [guide.md](./guide.md).
79
-
80
- ## Some Practical Wisdom
81
-
82
- Given a collection of ideals, a tight budget, and one lonely architectural
83
- decision, a few more ideas were needed to maintain alignment with Axiom zero,
84
- that user experience and developer ergonomics must be aligned.
85
-
86
- 0. Use the fewest mechanisms that are most expressive
87
- 1. Leverage the browser, don't fight it or supersede it.
88
- 2. Stick with HTML, don't invent a templating system
89
- 3. Ship example CSS, but don't force our solution path
90
- 4. Find a sane default for shipping Javascript
91
- 5. Use community solutions where they exist (eg, Express),
92
- don't reinvent a building block one exists that is not going
93
- to chew too much of our 100ms budget
94
- 6. The build is the developer-equivalent "speed trumps everything", the
95
- fastest build has the fewest steps, and the easiest build has the
96
- least configurations
97
- 7. Prefer explicit over magic, except in the build, we do not want
98
- the build to force boilerplate into the code.
99
- 8. Authentication and Authorization should be demonstrable but are
100
- squarely outside of scope.
101
- 9. Keep architectural elements loosely coupled, but don't over-force
102
- that to the detriment of a coherent solution that can be used
103
- as-is.
104
- 10. For 1.0, create a minimal working solution whose elements can
105
- be extended without fundamental refactoring.
106
-
107
- ## Implementation Decisions
108
-
109
- Custom Elements with Light DOM. Loadbare App connects javascript to HTML in
110
- the simplest way possible. Custom elements and light DOM
111
- leverage the browser instead of fighting it, and they naturally fit
112
- the role of code in a tree, which is to manage itself and its descendants.
113
-
114
- A browser "hub" with two jobs. Job 1 is to respond to navigation requests
115
- by swapping in the HTML "host" for the page. Job 2 is to catch events,
116
- send requests to the server, and update host elements that display results.
117
-
118
- Provide a fixed set of data shapes, from cell (scalar), to a tuple (a
119
- single collection of cells), or a projection, a collection of tuples.
120
-
121
- Have the hub locate nodes to update with attributes like `lb-query`,
122
- `lb-key` and `lb-cell`. Supplement the vocabulary with special ops
123
- for row deletions or adds, and both CRUD and non-CRUD requests to the
124
- server.
125
-
126
- A page system that organizes the HTML host template and the server-side
127
- code with minimal or zero boilerplate.
128
-
129
-
130
-
131
- ## Settled Implementation Approaches
132
-
133
- ### Widgets
134
-
135
- A widget is an HTML custom element without Shadow DOM. Widgets compose,
136
- up to the level of a page.
137
-
138
- ### Defining a Page
139
-
140
- A "page" is the static host of the HTML main landmark. It can be written into
141
- a plain old HTML document. How it is packaged, delivered and cached is settled
142
- in [bundling.md](../docs-llm-slop/bundling.md).
143
-
144
- We assume a page-level server-side state system.
145
-
146
- A page also has a set of declared queries in a data structure, mapped to
147
- the server-side state variables they require. A page GET must execute
148
- all queries and deliver all results.
149
-
150
- A page is a set of files sharing a basename: the host, the queries it
151
- declares, and the hooks it declares. The name of the file says which page
152
- it belongs to, so nothing inside it has to.
153
-
154
- User interactions break down into three groups:
155
-
156
- - Fire-and-forget. The user changed sort order on an HTML TABLE that
157
- is smart enough to sort its own rows. It still fires a fire-and-forget
158
- update to server state, so that user choice can be fetched on the next
159
- page load
160
- - CRUD operations with hooks, declared per page: the table, the operation,
161
- whatever custom code it runs, and the queries it refreshes.
162
- - Declared actions, for work that is not CRUD. The page names what it can be
163
- asked to do and what each one refreshes. The browser sends a declared name
164
- and nothing else, so the server decides every value.
165
-
166
- A hook always states which queries it refreshes. There is no automatic
167
- mapping from an operation to the projections it affects. The exception is a
168
- hook that runs before a page GET, which needs no such declaration because
169
- the whole query set runs after it.
170
-
171
- ### Data Updates
172
-
173
- A data update can be a full hydration or rehydration of page, or a refresh of
174
- some elements based on a user action. In all cases, the following statements hold.
175
-
176
- We assume the host HTML is fully loaded and upgraded with JS.
177
-
178
- We define an "addressable" widget as one with custom attributes that allow the
179
- hub to identify which widgets are responsible for the data fragments it has
180
- received. A widget is addressed by three coordinates: the declared query, the
181
- tuple key, and the cell.
182
-
183
- A cell lands as text when it addresses a native element and as an attribute
184
- when it addresses a widget. A native element has no behavior of its own, so
185
- the value is simply its text; a widget owns whatever control it wraps, so it
186
- receives the value and renders it. The test is the browser's own rule for what
187
- a custom element is, so the hub holds no knowledge of any particular element.
188
-
189
- Projections land by method call, and tuples get no mechanism of their own.
190
-
191
- Because the browser runs `attributeChangedCallback` for attributes already
192
- present when a widget upgrades, hydration and refresh are the same operation,
193
- and hydration timing is invisible to widget code.
194
-
195
- The attribute vocabulary and the binding mechanism are specified in
196
- [data-binding.md](../docs-llm-slop/data-binding.md).
197
-
198
- ## Open Implementation Topics
199
-
200
- Binding, addressing and the update mechanism are settled in
201
- [data-binding.md](../docs-llm-slop/data-binding.md). The request vocabulary is settled in
202
- [requests.md](../docs-llm-slop/requests.md). Packaging of the static half is settled in
203
- [bundling.md](../docs-llm-slop/bundling.md). Expansion of authored markup is settled in
204
- [expansion.md](../docs-llm-slop/expansion.md). What remains:
205
-
206
- topics to expand:
207
-
208
- - values bound to executable attributes. Markup injection is closed:
209
- values are applied through the DOM and never parsed. An attribute that
210
- is executable in its own right — href, src, style, on\* — is not, and
211
- the first widget that binds a URL has to answer for it.
212
- - navigation replaces a static host rather than data, the one operation
213
- that does replace host DOM. What makes something a new host versus new
214
- data for an existing host is the daily modeling decision.
215
- - nesting. Whether a tuple may contain a projection (master-detail, an
216
- expanding row). If it may not, that limit is load bearing.
217
- - staleness and concurrent writers. Two tabs or two users against one
218
- projection. Do tuples carry a version, or is last-write-wins the
219
- stated position?
220
- - validation placement. Per-keystroke feedback cannot afford a round
221
- trip, so the budget forces some validation into the widget while the
222
- server remains the source of truth.
223
- - the declarative path for setting a page-state variable to a literal. A
224
- native element can send a declared action, because an action carries
225
- nothing; a control that writes a literal directly has no markup for it
226
- yet.
5
+ Loadbare is one man's answer to the extreme expense and slow performance
6
+ of modern web apps. When using modern frameworks, it seems to me that
7
+ they are composed entirely of accidental complexity, I seem to be always working
8
+ for the tools instead of the other way around.
9
+
10
+ I wanted a new way to develop web apps that reduced accidental complexity
11
+ as far as possible to zero, and gave me snappy performant apps.
12
+
13
+ This essay describes the analysis and conclusions that led to the
14
+ design and implementation of the Loadbare application layer.
15
+
16
+ ## The Principle of Developer Experience
17
+
18
+ First I had to find my way out of the cruft that clutters
19
+ the front end discussions of today, and establish a "Principle zero" that
20
+ could be a starting point. This is it:
21
+
22
+ > The Principle of Developer Experience: The best developer experience is
23
+ > creating an application that people use and appreciate.
24
+
25
+ This principle combines and aligns business concerns with engineering
26
+ concerns, grounding any solution in why we do what we do.
27
+
28
+ This principle also
29
+ pre-empts arguments for seemingly elegant abstractions and tooling that
30
+ cannot be obviously and easily shown to contribute directly to an
31
+ application that end users use and appreciate.
32
+
33
+ ## The Principle of User Experience
34
+
35
+ The Principle of Developer experience grounds us in the end user,
36
+ and from here I considered my own largest single complaint about
37
+ web apps: they are so damn slow.
38
+
39
+ It seems to me that the front end industry, as represented by the
40
+ fat browser frameworks, has abandoned performance completely.
41
+
42
+ I wanted to put performance front and center, and design around
43
+ it instead of optimizing for it later.
44
+
45
+ After some research into what people perceive as performant, I came
46
+ upon the number of 300ms. Faster than that seems abrupt, slower
47
+ seems sluggish. This led to the next foundation principle:
48
+
49
+ > The Principle of User Experience: a full server round trip should
50
+ > complete, from user action through durable persitence to final
51
+ > paint, in a median time of 300ms.
52
+
53
+ This scopes the goal to performance and performance only. All other
54
+ aspects of the UI are mature, we have a mature inventory of widgets
55
+ for user display and interaction. We do not need to invent new
56
+ solutions for accessibility, tabs, buttons and so forth. What we need
57
+ is a way to use the mature solution and respect the user's time.
58
+
59
+ But we do have a practical implication: if we have a 300ms budget,
60
+ and roughly assume 200ms median wire time, we must split the remaining
61
+ 100ms between database, non-db server, and browser. Since the database
62
+ in the limiting case must provide durable persistence, our ideal
63
+ processing time for browser and non-db server activity must
64
+ approach zero. In other words, our solution must be a model citizen,
65
+ chewing up as few cycles as possible while providing the full modern
66
+ experience of a web app, leaving
67
+ as much of the 100ms as possible to the database.
68
+
69
+ ## The Primacy of Essential Complexity
70
+
71
+ Now we move on to developer efficiency. Fred Brooks gave us a powerful
72
+ way to consider this, when he made the distinction between "essential difficulties"
73
+ and "accidental difficulties". These terms have since been glossed into
74
+ "essential complexity" and "accidental complexity", and I will
75
+ use the complexity versions to be intelligble to the casual reader.
76
+
77
+ Essential complexity is the inherent difficulty of solving the user's
78
+ problem. This is where we want to spend our time and effort.
79
+ Accidental complexity is the work demanded by the tools we use to solve
80
+ the problem. We want to minimize this to being vanishingly small.
81
+
82
+ Because I believe there is some objective truth to my subjective feeling
83
+ that the fat browser frameworks have become nothing but accidental complexity,
84
+ it seemed necessary to state the princple of the Primacy of
85
+ Essential Complexity, which borrows phrasing from both Brooks and
86
+ Pike to establish that:
87
+
88
+ > Essential Complexity Dominates. Show me your abstractions and tooling and
89
+ > I shall remain mystified. Show me your tables and your visual vocabulary
90
+ > and I shall be enlightened - the organization of the code will be self-evident.
91
+
92
+
93
+ ## The Search For a Solution
94
+
95
+ The three principles stated above, once articulated, allowed me to
96
+ clarify my rejection of the fat browser frameworks: nowhere do they
97
+ state the same motivations that I have, and their illustrations and
98
+ examples make clear their motivations are irrelevent to mine and
99
+ vice-versa. In fact, my
100
+ extensive experience with React, Angular and VueJS contributed to
101
+ my articulation of these principles, the principles state what I found
102
+ lacking in the fat browser frameworks.
103
+
104
+ This led me to look at the Hypermedia libraries. After several weeks
105
+ of iterations, I found myself frustrated. These were very clearly
106
+ closer to what I wanted, and the various authors' motivations seemed
107
+ very close to my own, but I was still seeing more tooling concerns
108
+ than I wanted. I do not blame the hypermedia libraries, I think I was
109
+ fighting them. But that simply told me we differed on an assumption,
110
+ and that assumption remained unidentified and unstated.
111
+
112
+ It turns out that all of the tools and frameworks I could find are
113
+ supporting something I don't need: a DOM that is fully mutatable at
114
+ any time, for any reason deemed necessary by the developer. They
115
+ all provide or expect an HTML templating system that combines
116
+ interpolation of data values with conditional and list rendering,
117
+ controlled by run-time attributes. All of them must chase down the
118
+ consequences of the decision to allow a fully mutatable DOM. Those
119
+ consequences spill into the usage and tooling around the frameworks
120
+ and libraries. The arbitrarily mutatable DOM was the source of my
121
+ intuitive sense that I was dealing with too much accidental complexity.
122
+
123
+ ## The Loadbare Split
124
+
125
+ Finally I had the foundational idea for a framework that splits code
126
+ between a static "host" application and an active data channel, with
127
+ some small framework library updating only data values in the DOM
128
+ by following data-binding attributes in the HTML.
129
+
130
+ (As a side note, I originally considered calling Loadbare "liveload"
131
+ to draw the association to a bridge. The static HTML is the bridge,
132
+ the data that refreshes the page is the "live load" (traffic) travelling
133
+ on it. Alas, it seemed too close to 'livereload' and I gave it up.)
134
+
135
+ The static host application is pure HTML, including custom elements
136
+ with Light DOM. User interactions send requests to the server which
137
+ returns data objects. The HTML contains attributes identifying how
138
+ elements are bound to data, so the framework can update them.
139
+
140
+ This should be self-evidently efficacious for scalar cells and
141
+ tuples. Something like `<span lb-query="siteStats" lb-cell="visitCount"></span>`
142
+ or the same type of addressing for an input makes it trivial to
143
+ both hyrdrate and refresh forms, and to gather a form's values
144
+ to send to the server.
145
+
146
+ List processing, such as for an HTML SELECT and complex tables took a bit
147
+ longer to figure out, but the principle could be extended. Any list
148
+ element can accept a `<template>` that describes how to build its
149
+ items. The data channel must expand to allow "patch" operations,
150
+ removing or adding one row instead of reshipping the entire
151
+ query, but that is also straightforward.
152
+
153
+ So a purist might say, "hey you are still manipulating the DOM".
154
+ Of course we are, it is an application, not a static site. But the
155
+ point is that the HTML can be written, read, and reasoned
156
+ about as a static artefact, while still being dynamic at runtime.
157
+
158
+ The model can also be extended for trees, but my current projects
159
+ do not require this, so it is not implemented as of this writing.
160
+
161
+ ## Choosing the Mechanisms
162
+
163
+ So now I had three principles and one architectural decision that
164
+ seemed to make it all possible. The next step was to pick the
165
+ fewest number of mechanisms that would work.
166
+
167
+ On the benefit side, the fewer mechanisms there are the easier
168
+ it would be to create the solution and to use the solution. Fewer
169
+ mechanisms makes for a system that is easier to learn, and easier
170
+ to teach to an LLM.
171
+
172
+ On the cost-avoidance side, each mechanism is
173
+ an invitation to accidental complexity around the use of the
174
+ mechanism, so they must be kept at a minimum.
175
+
176
+ ### Browser Application Code
177
+
178
+ Using plain HTML, extended with custom elements that do not use Shadow
179
+ DOM has so far, apparently, completely solved the browser for me.
180
+
181
+ In my humble opinion,
182
+ Javascript belongs in the browser to enhance default behavior, and
183
+ the enhancements ought to always be scoped to a custom element and
184
+ its children. A custom element neatly satisfies this.
185
+
186
+ Custom elements give an nice answer to the question, why can't we just
187
+ use HTML like we used to? The Loadbare answer is: you can, go ahead.
188
+ If you need something special, make a custom element, it is still
189
+ just HTML.
190
+
191
+ A simple builder also gives server-side HTML includes for free with
192
+ no extra syntax. The builder scans for one fixed file (the chrome),
193
+ and all pages. Anywhere a custom element is found, it looks for
194
+ an HTML file to match, and inserts it into the built HTML. This is recursive
195
+ of course.
196
+
197
+ The builder became "tree shaking" for free. During HTML assembly
198
+ and composition, any Typescript file whose name matches a custom
199
+ element that was actually used gets added to the monolithic
200
+ `client.js`. No import statement required. The Loadbare code is
201
+ loading no extra Javascript outside of the single `client.js` named
202
+ in the main HTML document.
203
+
204
+ The validation during a build also fell out for free: if a custom
205
+ element is used, it must have either a matching Typescript file
206
+ or a matching HTML file, or both. The only error is if neither
207
+ are present.
208
+
209
+ ### Data Binding
210
+
211
+ For the hub to know what data to apply where and how, we need
212
+ custom attributes on the HTML. It was possible to define a small
213
+ and coherent vocabulary because the actual number of operations
214
+ afforded by a database is small and coherent.
215
+
216
+ ### Browser Framework Code
217
+
218
+ The entire browser framework code is in the hub `<lb-hub>`, and its
219
+ only real job is to handle page navigation and the data channel.
220
+
221
+ It also contains addressable methods to update DOM elements with
222
+ data objects, so that custom elements do not have to reproduce that
223
+ code.
224
+
225
+ ### Application Server Code
226
+
227
+ For server page code, this is where we truly got accidental complexity
228
+ to zero. A page is just its HTML, its queries, and its request handlers.
229
+
230
+ For the Express server, I was rather surprised myself to find that the
231
+ simplest thing was to show example code for an Express server. Nowadays
232
+ most frameworks provide their own Express server that is so completely
233
+ invisible that a newcomer to the industry can be forgiven for not even
234
+ knowing that Express exists. I wondered if pushing this concern to the
235
+ dev team violated the Principle of Essential Complexity, but I really
236
+ don't think so.
237
+
238
+ The reasoning for not providing a shrink-wrapped
239
+ server is simple: the boilerplate code is one-time effort, and the dev
240
+ team will need to add in their own database handler, security and
241
+ other concerns. A one-time effort may be seen as accidental
242
+ complexity to a skeptic, but its one-time set up means it is not a
243
+ continual tax being paid every time you write a new page. It is that
244
+ sense of paying an ongoing tax that I'm really trying to avoid.
245
+
246
+ ### The Database
247
+
248
+ Database concerns are mostly out of scope for the application layer,
249
+ but we must make mention of them because Loadbare is supposed to
250
+ be optimized for database apps.
251
+
252
+ Loadbare satisfies the minimal principle of not being rude by not
253
+ requiring a specific database, neither prohibiting nor requiring
254
+ the ORM of your choice if you use one.
255
+
256
+ However, I should mention that I personally always use
257
+ the `@loadbare/db` package to build
258
+ my databases, which enriches them with calculated values that are
259
+ declarative in the Loadbare db schema. For this reason, I need no
260
+ ORM because the access patterns are very simple: most database reads
261
+ come from one table or a fairly simple view, and user actions that
262
+ update a single table automatically cascade calculations
263
+ to related tables. For this reason, I do not need an ORM and
264
+ my `@loadbare/app` applications connect directly to the database
265
+ with no "business logic" code at all.
266
+
267
+ I must also state that a team not using `@loadbare/db` will probably
268
+ use an ORM or craft a business logic layer, and `@loadbare/app` does
269
+ nothing special for this. It simply provides the empty slot where
270
+ you plug it in.
271
+
272
+ ### The Builder
273
+
274
+ The builder kind of fell out for free from the architecture and
275
+ mechanism choices.
276
+
277
+ In addition to what was noted above, it is a very straightforward
278
+ piece of code: it walks the `src/` tree, requiring only a 'chrome.html',
279
+ and picking up all '*.page.html' files. It builds one Javascript file,
280
+ one CSS file, and one HTML file. Having the files be sparse, containing
281
+ only what was used, is trivial.
282
+
283
+ It also builds the application's API from the page's processing
284
+ code, and 4 lines in the Express server end up handling the entire
285
+ API.
286
+
287
+ In short, the builder satisfied the Principle of Essential Complexity, that
288
+ if the code is organized around just application concerns, the builder
289
+ algorithm is self-evident, straightforward, and easy to decorate with
290
+ nice-to-haves.
291
+
292
+ ## Assessing the Result
293
+
294
+ I am a biased examiner, but I can say that `@loadbare/app` solves mutliple
295
+ problems for me and achieves compliance with the principles stated
296
+ at the top of this essay.
297
+
298
+ The Principle of Developer Experience says my goal is to make something
299
+ that users actually use and appreciate. Loadbare cannot do that for me,
300
+ but it does free me from so many accidental concerns that my time is
301
+ free to pursue that quality application. It reduces the cost of iteration
302
+ and increases my ability to focus on the shape of the solution. That is
303
+ as much as I can ask for from a productivity tool.
304
+
305
+ In terms of the Principle of User Experience, that we must complete
306
+ round trips from click to paint in a median time of 300ms, Loadbare gives
307
+ me a better chance of doing this than any other tool I have seen.
308
+ The Loadbare app is so light and does so little that if we allow a median
309
+ time of 200ms for wire time, Loadbare likely needs 10ms or less between
310
+ the browser and the server, leaving 90ms for the database and business
311
+ logic. Making the budget in production can be reduced to careful modeling,
312
+ tuning, and hardware spend. (Note: using `@loadbare/db` makes this almost
313
+ a guaranteed win, provided we don't skimp on hardware and know how to tune
314
+ Postgres for production).
315
+
316
+ Finally, in terms of the Primacy of Essential Complexity, I may be a biased
317
+ examiner. Because I wrote it, I see zero accidental complexity, inasmuch
318
+ as everything seems to be there to serve the major principles. But I cannot
319
+ trust my judgment there for obvious reasons. Instead I will propose that
320
+ there are some reasonably obvious wins for Essential Complexity that even
321
+ a skeptic might agree with.
322
+
323
+ One big win is the ability to just write plain HTML. Mixing HTMl and
324
+ Javascript seems cool if you need a fully mutatable DOM, but when you realize
325
+ you don't need that, it's back to plain old reliable HTML.
326
+
327
+ Another big win is the server code for pages. The clean organization
328
+ of hooks, actions, CRUD and queries without boilerplate is pure
329
+ Essential Complexity. The automatic building of the API and 4 lines to
330
+ handle it in Express is lower than I have seen in any other tool.
331
+
332
+ Loadbare satisfies many other principles that do not merit a full explanation,
333
+ such as using technology instead of fighting it, organizing the use of
334
+ browser mechanisms instead of reimplementing them, and encouraging code
335
+ that is likely to have a very long shelf life. Loadbare does not
336
+ interfere with normal CSS usage, so you can bring any solution you like.
337
+
338
+ In closing I would add one more claim. Loadbare is easy to learn because
339
+ it has a small cognitive surface area. The main activity, coding pages,
340
+ is highly repetive and driven by a handful of patterns. It can be learned
341
+ by a person and taught to an LLM with just a few skills.
342
+
343
+ I wrote Loadbare originally in 2003, as "Andromeda", before Node existed
344
+ and even 4 years before we had JQuery. Andromeda was not nearly as
345
+ optimized as Loadbare is now, but the principles were the same then as
346
+ they are now. It has always worked for me, and I hope that it will work
347
+ for you.