@loadbare/app 0.5.6 → 0.7.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 (143) hide show
  1. package/README.md +3 -4
  2. package/dist/build/assemble.d.ts +1 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +81 -7
  5. package/dist/build/assemble.js.map +1 -0
  6. package/dist/build/cli.d.ts +2 -2
  7. package/dist/build/cli.js +3 -2
  8. package/dist/build/cli.js.map +1 -0
  9. package/dist/build/elements.js +1 -0
  10. package/dist/build/elements.js.map +1 -0
  11. package/dist/build/expand.d.ts.map +1 -1
  12. package/dist/build/expand.js +20 -32
  13. package/dist/build/expand.js.map +1 -0
  14. package/dist/build/format.js +1 -0
  15. package/dist/build/format.js.map +1 -0
  16. package/dist/build/locations.d.ts +4 -4
  17. package/dist/build/locations.d.ts.map +1 -1
  18. package/dist/build/locations.js +16 -5
  19. package/dist/build/locations.js.map +1 -0
  20. package/dist/build/origins.d.ts +0 -13
  21. package/dist/build/origins.d.ts.map +1 -1
  22. package/dist/build/origins.js +33 -8
  23. package/dist/build/origins.js.map +1 -0
  24. package/dist/build/package-root.js +1 -0
  25. package/dist/build/package-root.js.map +1 -0
  26. package/dist/build/pages.d.ts +7 -3
  27. package/dist/build/pages.d.ts.map +1 -1
  28. package/dist/build/pages.js +14 -7
  29. package/dist/build/pages.js.map +1 -0
  30. package/dist/build/styles.js +1 -0
  31. package/dist/build/styles.js.map +1 -0
  32. package/dist/core/lb-constants.d.ts +15 -13
  33. package/dist/core/lb-constants.d.ts.map +1 -1
  34. package/dist/core/lb-constants.js +104 -53
  35. package/dist/core/lb-constants.js.map +1 -0
  36. package/dist/core/lb-types.d.ts +103 -62
  37. package/dist/core/lb-types.d.ts.map +1 -1
  38. package/dist/core/lb-types.js +12 -3
  39. package/dist/core/lb-types.js.map +1 -0
  40. package/dist/hub/lb-apply.d.ts +28 -4
  41. package/dist/hub/lb-apply.d.ts.map +1 -1
  42. package/dist/hub/lb-apply.js +227 -40
  43. package/dist/hub/lb-apply.js.map +1 -0
  44. package/dist/hub/lb-hub.browser.d.ts +1 -1
  45. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  46. package/dist/hub/lb-hub.browser.js +165 -110
  47. package/dist/hub/lb-hub.browser.js.map +1 -0
  48. package/dist/server/lb-express.d.ts +8 -5
  49. package/dist/server/lb-express.d.ts.map +1 -1
  50. package/dist/server/lb-express.js +46 -33
  51. package/dist/server/lb-express.js.map +1 -0
  52. package/dist/server/lb-server.d.ts +67 -45
  53. package/dist/server/lb-server.d.ts.map +1 -1
  54. package/dist/server/lb-server.js +56 -18
  55. package/dist/server/lb-server.js.map +1 -0
  56. package/docs/TECHREF-1.0.md +1107 -0
  57. package/docs/analysis-accidental-complexity.md +149 -0
  58. package/docs/reference/builder.md +4 -4
  59. package/docs/reference/chrome.md +10 -9
  60. package/docs/reference/custom-elements.md +54 -46
  61. package/docs/reference/data-binding.md +179 -97
  62. package/docs/reference/overview.md +1 -1
  63. package/docs/reference/page-files.md +64 -49
  64. package/docs/reference/server.md +25 -6
  65. package/docs/reference/widgets.md +22 -30
  66. package/docs/roadmap.md +68 -22
  67. package/docs/testing.md +47 -17
  68. package/docs/theory.md +116 -3
  69. package/docs/tutorials/010-pages-and-navigation.md +8 -8
  70. package/docs/tutorials/040-displaying-data.md +9 -9
  71. package/docs/tutorials/050-actions.md +5 -5
  72. package/docs/tutorials/060-custom-element-code.md +1 -1
  73. package/docs/tutorials/065-conditional-rendering.md +4 -4
  74. package/docs/tutorials/070-displaying-a-list.md +24 -47
  75. package/docs/tutorials/072-inserting-into-a-list.md +18 -15
  76. package/docs/tutorials/074-deleting-from-a-list.md +15 -17
  77. package/docs/tutorials/076-updating-a-list-item.md +20 -22
  78. package/docs/tutorials/080-widget-requests.md +22 -35
  79. package/docs/tutorials/090-using-widget-libraries.md +1 -1
  80. package/package.json +2 -3
  81. package/dist/hub/lb-rows.d.ts +0 -18
  82. package/dist/hub/lb-rows.d.ts.map +0 -1
  83. package/dist/hub/lb-rows.js +0 -106
  84. package/dist/tests/assemble.test.d.ts +0 -8
  85. package/dist/tests/assemble.test.d.ts.map +0 -1
  86. package/dist/tests/assemble.test.js +0 -58
  87. package/dist/tests/elements.test.d.ts +0 -8
  88. package/dist/tests/elements.test.d.ts.map +0 -1
  89. package/dist/tests/elements.test.js +0 -118
  90. package/dist/tests/expand.test.d.ts +0 -10
  91. package/dist/tests/expand.test.d.ts.map +0 -1
  92. package/dist/tests/expand.test.js +0 -250
  93. package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
  94. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
  95. package/dist/tests/fixtures/elements/collision/imports.js +0 -1
  96. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
  97. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
  98. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
  99. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
  100. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
  101. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
  102. package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
  103. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
  104. package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
  105. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
  106. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
  107. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
  108. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
  109. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
  110. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
  111. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
  112. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
  113. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
  114. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
  115. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
  116. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
  117. package/dist/tests/helpers/console.d.ts +0 -20
  118. package/dist/tests/helpers/console.d.ts.map +0 -1
  119. package/dist/tests/helpers/console.js +0 -28
  120. package/dist/tests/helpers/dom.d.ts +0 -18
  121. package/dist/tests/helpers/dom.d.ts.map +0 -1
  122. package/dist/tests/helpers/dom.js +0 -22
  123. package/dist/tests/lb-apply.test.d.ts +0 -8
  124. package/dist/tests/lb-apply.test.d.ts.map +0 -1
  125. package/dist/tests/lb-apply.test.js +0 -153
  126. package/dist/tests/lb-express.test.d.ts +0 -14
  127. package/dist/tests/lb-express.test.d.ts.map +0 -1
  128. package/dist/tests/lb-express.test.js +0 -238
  129. package/dist/tests/lb-rows.test.d.ts +0 -12
  130. package/dist/tests/lb-rows.test.d.ts.map +0 -1
  131. package/dist/tests/lb-rows.test.js +0 -336
  132. package/dist/tests/lb-server.test.d.ts +0 -9
  133. package/dist/tests/lb-server.test.d.ts.map +0 -1
  134. package/dist/tests/lb-server.test.js +0 -495
  135. package/dist/tests/origins.test.d.ts +0 -10
  136. package/dist/tests/origins.test.d.ts.map +0 -1
  137. package/dist/tests/origins.test.js +0 -369
  138. package/dist/tests/pages.test.d.ts +0 -6
  139. package/dist/tests/pages.test.d.ts.map +0 -1
  140. package/dist/tests/pages.test.js +0 -98
  141. package/dist/tests/styles.test.d.ts +0 -7
  142. package/dist/tests/styles.test.d.ts.map +0 -1
  143. package/dist/tests/styles.test.js +0 -76
@@ -0,0 +1,149 @@
1
+ # Assessing the accidental complexity claim
2
+
3
+ [Theory](./theory.md) claims that Loadbare/app carries less accidental
4
+ complexity than React, Angular, or the hypermedia libraries. This document
5
+ tests that claim against [TECHREF-1.0](./TECHREF-1.0.md), which is the
6
+ authoritative statement of what 1.0 means.
7
+
8
+ Written 2026-09-07, against `@loadbare/app` 0.6.0.
9
+
10
+ Brooks separates the difficulty of the user's problem from the work the tool
11
+ demands. The second kind is accidental, and the test applied here is whether
12
+ Loadbare removes such work or relocates it somewhere the author still pays
13
+ for it.
14
+
15
+ ## Where the claim holds
16
+
17
+ The whole owned surface fits in the technical reference's cross-reference
18
+ section.
19
+
20
+ | Owned | Count |
21
+ |------------------|-------|
22
+ | `lb-*` attributes | 15 |
23
+ | `lb*` methods | 3 |
24
+ | Builder flags | 4 |
25
+ | Reserved filenames| 8 |
26
+ | Reserved tags | 1 |
27
+ | Reserved events | 1 |
28
+
29
+ React reaches that size before an application adds a router, a data layer, or
30
+ a bundler configuration, and each of those carries a surface of its own.
31
+ Loadbare/app asks for no configuration file today.
32
+
33
+ The server half is the strongest part of the argument. A page is markup, a
34
+ queries file, and a requests file. The application designs no endpoints,
35
+ writes no route table, and picks no serialization contract. Four lines of
36
+ Express carry the data channel. Neither the fat frameworks nor the
37
+ hypermedia libraries remove that category of work; htmx in particular leaves
38
+ the developer designing every endpoint and every fragment it answers with.
39
+
40
+ Building the HTML once removes reconciliation, keys, memoization, and effect
41
+ dependencies together. That is the largest single deletion in the design,
42
+ and it is the one the hypermedia libraries do not make either, since they
43
+ ship markup at run time and carry swap semantics to place it.
44
+
45
+ ## What the technical reference already knows
46
+
47
+ Most of what an assessment finds is already on the blocker list. Each
48
+ concern below adds weight to an open item rather than naming a new one.
49
+
50
+ | Concern | Blocker |
51
+ |--------------------------------------------|--------------------------------------------|
52
+ | Values carry no type, so every application formats its own dates and money | Data types |
53
+ | No page is reachable by row | Take a position on the URL space |
54
+ | A widget receives one cell at a time | Whether a widget may receive a whole row |
55
+ | A widget repeats hub code to fire a request| Give a widget a way to fire its own request|
56
+ | Master-detail is unspecified | Decide what a nested list means |
57
+ | Every page redeclares the chrome's queries | Give the chrome a way to state its own queries |
58
+
59
+ The technical reference's own sample query calls `String()` on a count by
60
+ hand, which is the data-type blocker showing up in the documentation.
61
+
62
+ Two [roadmap](./roadmap.md) items carry the same weight as these and appear
63
+ in the first real application. Concurrent writers on one list have no
64
+ version or conflict story. Per-keystroke validation has no home, and the
65
+ roadmap expects it to sit in the widget while the server stays authoritative
66
+ for the same field.
67
+
68
+ Five sections of the technical reference are still marked `UNEDITED`:
69
+ Binding, Requests, Links, Widgets, and Widget authoring. Those are the
70
+ mechanisms an author touches on every page.
71
+
72
+ ## Positions that carry a cost
73
+
74
+ Two constraints appear in the body of the technical reference as current
75
+ behavior rather than on the blocker list, so the project has taken a position
76
+ on each. Naming the cost is still fair.
77
+
78
+ The hub reaches its endpoint by absolute path, so an application cannot be
79
+ hosted under a subpath such as `example.com/myapp/`. A deployment that wants
80
+ several applications behind one host gives each one an origin.
81
+
82
+ Every route answers 200 with the same document, so the browser detects an
83
+ unknown page after the fact and the chrome's `lb-unknown-page` dialog reports
84
+ it. A crawler or a monitor that reads status codes sees a healthy response
85
+ for a path the application does not have.
86
+
87
+ ## The gap recorded nowhere
88
+
89
+ Loadbare/app offers no way to display or style an element according to the
90
+ value that landed in it.
91
+
92
+ A cell lands on a custom element as the `lb-value` attribute, which a
93
+ stylesheet can select. A cell lands on a native element as its
94
+ `textContent`, which no selector reaches. The hub stamps `lb-pending`,
95
+ `lb-error`, and `lb-row-count`, which cover a request in flight, a request
96
+ that failed, and an empty list. Nothing covers a row whose `status` column
97
+ reads `overdue`.
98
+
99
+ An application that wants this writes a widget, and a widget that fires a
100
+ request pays the cost the widget-protocol blocker already names. So the
101
+ missing piece pushes the author toward the mechanism that is itself
102
+ unfinished.
103
+
104
+ This appears in neither the blockers, the roadmap's open questions, nor the
105
+ decided-against section. It is the one finding here that the technical
106
+ reference does not already record.
107
+
108
+ ## The general form of the query gap
109
+
110
+ The URL-space blocker names one consequence of a broader constraint, and
111
+ stating the constraint directly is more useful than stating the consequence.
112
+
113
+ A query takes no argument from the browser. Its signature is `(ctx) => Row`
114
+ or `(ctx) => Row[]`, and `ctx` is what the application built from the Express
115
+ request. The hub's own request carries `page=<name>`. The hub reads
116
+ `location.pathname`, which drops the query string, so a path segment and a
117
+ search parameter are both invisible to the server.
118
+
119
+ An application that shows one selected record therefore holds the selection
120
+ in server state. A click fires an action, the handler records the selection
121
+ where `ctx` reaches it, and the refreshed query reads it back. That works
122
+ today and needs no new mechanism. It puts selection in the same territory as
123
+ the roadmap's concurrent-writers question, and it means a reload or a shared
124
+ link does not carry the record.
125
+
126
+ ## Comparison with the hypermedia libraries
127
+
128
+ Theory rejects the existing tools for combining interpolation with
129
+ conditional and list rendering. The distinction is narrower than that.
130
+ Loadbare/app interpolates at build time, with defaults and a substitution
131
+ grammar, and renders lists at run time through `<template lb-key>`. What the
132
+ design rules out is conditionals and interpolation after the build.
133
+
134
+ Loadbare/app also adds a builder, a filename grammar, and a slot and template
135
+ system, where htmx asks for no build step. Against React and Angular the
136
+ surface comparison is decisive. Against htmx it is close, and the server
137
+ half is where Loadbare/app wins instead.
138
+
139
+ ## Verdict
140
+
141
+ The claim holds on the server, and it holds on the client for everything the
142
+ reconciliation layer used to cost.
143
+
144
+ On the rest of the client it currently holds partly by not doing several
145
+ things database applications need, and the technical reference lists most of
146
+ them itself. Whether the claim survives 1.0 depends on how the URL space,
147
+ the row hook, the chrome queries, and value-driven display are answered, and
148
+ an answer of "decided against" counts as an answer only where an application
149
+ can still reach the behavior some other way.
@@ -40,7 +40,7 @@ The builder classifies by name, not location.
40
40
  |----------------------------|---------------------------------------------------|
41
41
  | `chrome.html` | The chrome — see [`chrome.html`](./chrome.md) |
42
42
  | `*.page.html` | A page |
43
- | `*.hooks.ts` | A page's hooks, matched by base name |
43
+ | `*.requests.ts` | A page's requests, matched by base name |
44
44
  | `*.queries.ts` | A page's queries, matched by base name |
45
45
  | `imports.ts` | The packages this app takes widgets from |
46
46
  | `*.css` | A stylesheet — see [CSS](./css.md) |
@@ -48,7 +48,7 @@ The builder classifies by name, not location.
48
48
  | `<tag>.browser.ts` | A widget script, named for the tag it registers |
49
49
 
50
50
  Give the application exactly one `chrome.html` and at most one `imports.ts`.
51
- Give every `.hooks.ts` and `.queries.ts` a `.page.html` of the same base name.
51
+ Give every `.requests.ts` and `.queries.ts` a `.page.html` of the same base name.
52
52
 
53
53
  Name a widget script `<tag>.browser.ts`, not `<tag>.ts`. Only a file whose
54
54
  name carries `.browser` is bundled for the browser; every other module under
@@ -125,10 +125,10 @@ A tag with neither a script nor a definition in any origin is an error.
125
125
  The builder expands the chrome and every page against the available widget
126
126
  definitions — see [Custom Elements](./custom-elements.md#html) for the
127
127
  substitution rules — wraps each expanded page in
128
- `<template id="page-<name>">`, and splices them into the chrome's `<body>`. It formats the result with
128
+ `<template lb-page="<name>">`, and splices them into the chrome's `<body>`. It formats the result with
129
129
  Prettier when the application has it installed.
130
130
 
131
131
  The builder writes `app.css` only when it finds a stylesheet, and `pages.ts`
132
- only when some page has a `.hooks.ts` or a `.queries.ts` file. Import `hub`
132
+ only when some page has a `.requests.ts` or a `.queries.ts` file. Import `hub`
133
133
  from `pages.ts` — see [The Express Server](./server.md) for the rest of the
134
134
  wiring.
@@ -24,7 +24,7 @@ Here is a minimal but fully complaint chrome for a typical app:
24
24
  </head>
25
25
  <body hidden>
26
26
  <lb-hub>
27
- <header lb-query="lb-navigation">
27
+ <header lb-row="lb-navigation">
28
28
  <h1>Membership Roster</h1>
29
29
  <h2 lb-cell="page-label"></h2>
30
30
  </header>
@@ -34,7 +34,7 @@ Here is a minimal but fully complaint chrome for a typical app:
34
34
  <a href="https://example.org/">Our website</a>
35
35
  </nav>
36
36
  <main></main>
37
- <dialog lb-unknown-page lb-query="lb-navigation">
37
+ <dialog lb-unknown-page lb-row="lb-navigation">
38
38
  The URL <span lb-cell="page-uri"></span> is not in this app.
39
39
  </dialog>
40
40
  </lb-hub>
@@ -60,7 +60,7 @@ Everything else is optional:
60
60
  |-------------------------------------------|---------------------------------------------|
61
61
  | `<link rel="stylesheet" href="/app.css">` | The bundled stylesheet |
62
62
  | `<a lb-nav-link>` | Navigation between pages |
63
- | `lb-query="lb-navigation"` | Where the page is — see below |
63
+ | `lb-row="lb-navigation"` | Where the page is — see below |
64
64
  | `<dialog lb-unknown-page>` | A message when a URL matches no page |
65
65
  | Custom elements | The chrome, decomposed into widget files |
66
66
  | `<body hidden>` + a `<noscript>` fallback | Avoids a first-load blink — see below |
@@ -77,8 +77,9 @@ A navigation anchor's `href` is a path, and the path names a page:
77
77
  landing page is the one named `index.page.html`. An anchor without
78
78
  `lb-nav-link` is left alone and behaves like any other link.
79
79
 
80
- The `lb-unknown-page` attribute, if used, must appear on a `<dialog>`. The
81
- hub opens it when a path names no page. What it says is up to the chrome:
80
+ The `lb-unknown-page` attribute, if used, must appear on a `<dialog>` inside
81
+ `<lb-hub>`; the builder rejects it anywhere else. The hub opens it when a
82
+ path names no page. What it says is up to the chrome:
82
83
  the dialog is a subtree like any other, and it displays where the page is by
83
84
  naming the hub's own query, described next.
84
85
 
@@ -86,11 +87,11 @@ naming the hub's own query, described next.
86
87
 
87
88
  On every navigation the hub lands a query of its own, `lb-navigation`, on
88
89
  any subtree inside the hub that names it. It arrives the way a server's
89
- query arrives — `lb-query` on the subtree, `lb-cell` on each element that
90
+ row arrives — `lb-row` on the subtree, `lb-cell` on each element that
90
91
  shows a value — so a chrome displays the current page with no code at all:
91
92
 
92
93
  ```html
93
- <header lb-query="lb-navigation">
94
+ <header lb-row="lb-navigation">
94
95
  <h1>Membership Roster</h1>
95
96
  <h2 lb-cell="page-label"></h2>
96
97
  </header>
@@ -112,12 +113,12 @@ namespace: no server answers a query so named. It is landed only where a
112
113
  subtree names it, so a chrome that displays no navigation is not warned
113
114
  about a query with no scope.
114
115
 
115
- The same tuple is what an unknown-page dialog has to work with. It lands
116
+ The same row is what an unknown-page dialog has to work with. It lands
116
117
  before the page host is looked up, so a miss has it too. Name the query on
117
118
  the dialog and show whichever cell fits:
118
119
 
119
120
  ```html
120
- <dialog lb-unknown-page lb-query="lb-navigation">
121
+ <dialog lb-unknown-page lb-row="lb-navigation">
121
122
  The URL <span lb-cell="page-uri"></span> is not in this app.
122
123
  </dialog>
123
124
  ```
@@ -223,12 +223,15 @@ as a string literal.
223
223
  |------------------|------------|
224
224
  | `ATTR_VALUE` | `lb-value` |
225
225
  | `ATTR_CELL` | `lb-cell` |
226
- | `ATTR_QUERY` | `lb-query` |
226
+ | `ATTR_LIST` | `lb-list` |
227
+ | `ATTR_ROW` | `lb-row` |
227
228
  | `ATTR_KEY` | `lb-key` |
228
- | `ATTR_GROUP` | `lb-group` |
229
- | `ATTR_SORT` | `lb-sort` |
229
+ | `ATTR_KEY_VALUE` | `lb-key-value` |
230
230
  | `ATTR_ACTION` | `lb-action`|
231
- | `ATTR_ROW_COUNT` | `data-rows`|
231
+ | `ACTION_ROW_INSERT`, `ACTION_ROW_DELETE`, `ACTION_ROW_UPDATE`, `ACTION_CELL_CHANGE` | the reserved `lb-action` values |
232
+ | `LB_ACTIONS` | all four of them, in one array |
233
+ | `LB_RESERVED_PREFIX` | `lb-`, the prefix every reserved name begins with |
234
+ | `ATTR_ROW_COUNT` | `lb-row-count`|
232
235
  | `LB_EVENT_NAME` | `lb-request` |
233
236
 
234
237
  ### Receiving a value
@@ -261,81 +264,86 @@ no separate hydration path to write.
261
264
  ### Sending a request
262
265
 
263
266
  A widget that owns its own interaction — a `<select>`'s choice rather than a
264
- click — builds its own request and dispatches it as a bubbling
265
- `CustomEvent` named `LB_EVENT_NAME`, carrying one of the `HubRequest` shapes
266
- as its `detail`:
267
+ click — dispatches its own request as a bubbling `CustomEvent` named
268
+ `LB_EVENT_NAME`, carrying the action and, where it wraps a control, that
269
+ control's value as its `detail`:
267
270
 
268
271
  ```ts
269
- import { ATTR_CELL, ATTR_KEY, ATTR_QUERY, LB_EVENT_NAME } from "@loadbare/app/constants";
272
+ import { LB_EVENT_NAME } from "@loadbare/app/constants";
270
273
  import type { HubRequest } from "@loadbare/app/types";
271
274
 
272
- const detail: HubRequest = {
273
- op: "cell-change",
274
- query: this.closest(`[${ATTR_QUERY}]`)!.getAttribute(ATTR_QUERY)!,
275
- key: this.closest(`[${ATTR_KEY}]`)!.getAttribute(ATTR_KEY)!,
276
- cell: this.getAttribute(ATTR_CELL)!,
277
- value: input.value,
278
- };
275
+ const detail: HubRequest = { action: "lb-cell-change", value: input.value };
279
276
  this.dispatchEvent(new CustomEvent(LB_EVENT_NAME, { bubbles: true, detail }));
280
277
  ```
281
278
 
282
- Find `query` and `key` by walking up with `closest()`, the same way the hub
283
- finds the binding for a native element. See
284
- [Data Binding](./data-binding.md#requests) for the request vocabulary and
285
- what each operation carries.
279
+ The hub fills in the scope the widget sits in before any ancestor sees the
280
+ event, and does not send a request missing a field its operation requires.
281
+ See [TECHREF-1.0](../TECHREF-1.0.md#requests) for what each operation is
282
+ filled with.
286
283
 
287
284
  Let the event bubble, so an ancestor widget can intercept and stop it before
288
285
  the hub sees it. A hand-written widget and a native element carrying
289
286
  `lb-action` produce the same event.
290
287
 
291
- ### Accepting rows
288
+ ### Decorating a list
292
289
 
293
- A query answering with many rows is delivered to whichever element carries
294
- an `acceptRows` method. It is a protocol, not a tag name — the hub tests for
295
- the method and never for the element:
290
+ The hub reconciles every list scope itself. A plain element carrying
291
+ `lb-list` with a row template inside it is a whole list and needs no widget,
292
+ so a widget exists only when the rows need scaffolding or placement that
293
+ only it can decide.
294
+
295
+ Two optional methods say what it decides. Both are named in the `lb`
296
+ namespace, which Loadbare reserves for methods it calls on classes it does
297
+ not own, so a widget's own methods can never collide with a later one:
296
298
 
297
299
  ```ts
298
- import { applyRows } from "@loadbare/app/rows";
299
- import type { Projection, HubRowHost } from "@loadbare/app/types";
300
+ import type { ListHost, Row } from "@loadbare/app/types";
301
+
302
+ class SortedList extends HTMLElement implements ListHost {
303
+ lbPlaceRow(el: Element, row: Row, template: HTMLTemplateElement) {
304
+ // Where this row goes. Called with the element detached, on its first
305
+ // appearance and again whenever a whole set decides the order.
306
+ }
300
307
 
301
- class UpdateList extends HTMLElement implements HubRowHost {
302
- acceptRows(result: Projection) {
303
- applyRows(this, result);
308
+ lbRowsLanded() {
309
+ // Once, after the result has landed. For scaffolding derived from the
310
+ // rows: a section heading, an <optgroup>, anything that goes when its
311
+ // last row does.
304
312
  }
305
313
  }
306
314
  ```
307
315
 
308
- Call `applyRows(scope, result, place?)` rather than reimplementing rows.
309
- Every built-in list widget uses it, and it settles four things:
316
+ Everything else is the hub's, and a widget never reimplements it:
310
317
 
311
- | Concern | What `applyRows` does |
318
+ | Concern | What the hub does |
312
319
  |-------------|---------------------------------------------------------|
313
- | Cloning | Clones the `<template lb-key="...">` in the widget |
320
+ | Cloning | Clones the `<template lb-key="...">` in the scope |
314
321
  | Matching | Updates the row already showing that key, or clones one |
315
322
  | Reconciling | Removes the rows the response says are gone |
316
- | Counting | Stamps `data-rows` with the number of rows showing |
323
+ | Counting | Stamps `lb-row-count` with the number of rows showing |
317
324
 
318
- `rows` is the whole set, so it decides membership and order, and a key
319
- absent from it is removed. `patch` touches only the rows it names and leaves
325
+ An array is the whole set, so it decides membership and order, and a key
326
+ absent from it is removed. A patch touches only the rows it names and leaves
320
327
  every other row's contents and position alone.
321
328
 
322
- Supply a `place` function to decide where a row goes — `(row, tuple,
323
- template) => void`, called with a fresh or reordered row. The default
324
- inserts immediately before the template, so rows accumulate in arrival
325
- order. A widget that groups or sorts supplies its own `place` instead of
326
- reimplementing matching and cloning around it; `lb-options.browser.ts` and
327
- `lb-table.browser.ts` in [`@loadbare/widgets`](./widgets.md) are two different
328
- `place` functions over the same `applyRows`.
329
+ `lbPlaceRow` is called with the row already filled and not yet in the
330
+ document, so a widget that reads a cell to decide where the row goes can. A
331
+ scope without it lands rows immediately before the template, in arrival
332
+ order. `lb-options.browser.ts` and `lb-table.browser.ts` in
333
+ [`@loadbare/widgets`](./widgets.md) are two different placements over the
334
+ same machinery.
335
+
336
+ A list scope with no row template displays nothing, which is not an error.
329
337
 
330
- Style an empty list against `data-rows` rather than carrying an empty-state
338
+ Style an empty list against `lb-row-count` rather than carrying an empty-state
331
339
  conditional in the widget — see
332
340
  [Conditional rendering](./data-binding.md#conditional-rendering).
333
341
 
334
342
  ### Filling a scope by hand
335
343
 
336
- `@loadbare/app/rows` also exports `applyTuple(root, cells)`, the same
337
- tuple-landing operation a page host uses. Call it in a widget that builds
338
- its own rows or scopes rather than relying on `acceptRows`. It fills `root`
344
+ `@loadbare/app` also exports `applyRow(root, row)`, the same row-landing
345
+ operation a page host uses. Call it in a widget that builds a scope of its
346
+ own rather than one the hub reconciles. It fills `root`
339
347
  itself when `root` carries a matching `lb-cell`, and every matching
340
348
  descendant.
341
349
  </content>