@orkestrel/scaffold 0.0.103 → 0.0.104

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 (88) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +84 -1
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +21 -6
  3. package/dist/agents/skills/orkestrel-harden/scripts/titles.js +338 -0
  4. package/dist/agents/skills/orkestrel-publish/scripts/swap.js +3 -39
  5. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +433 -47
  6. package/dist/agents/skills/orkestrel-publish/scripts/window.js +2 -1
  7. package/dist/bin/main.js +16 -9
  8. package/dist/bin/main.js.map +1 -1
  9. package/dist/host/agents/orchestration.md +3 -3
  10. package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +2 -2
  11. package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
  12. package/dist/host/agents/skills/orkestrel-build/SKILL.md +2 -2
  13. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
  14. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +1 -1
  15. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
  16. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
  17. package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +1 -0
  18. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +100 -1
  19. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +27 -6
  20. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +4 -4
  21. package/dist/host/agents/skills/orkestrel-falsify/agents/openai.yaml +1 -1
  22. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +8 -7
  23. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +6 -6
  24. package/dist/host/agents/skills/orkestrel-harden/SKILL.md +6 -1
  25. package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +1700 -0
  26. package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -2
  27. package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +48 -0
  28. package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +4 -2
  29. package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +425 -0
  30. package/dist/host/agents/skills/orkestrel-journey/SKILL.md +2 -2
  31. package/dist/host/agents/skills/orkestrel-journey/references/captures.md +9 -8
  32. package/dist/host/agents/skills/orkestrel-polish/SKILL.md +3 -3
  33. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +2 -2
  34. package/dist/host/agents/skills/orkestrel-publish/references/release.md +1 -0
  35. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +56 -13
  36. package/dist/host/agents/skills/orkestrel-publish/references/window.md +6 -6
  37. package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +5 -49
  38. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +646 -49
  39. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +2 -1
  40. package/dist/host/agents/transports/codex.md +1 -1
  41. package/dist/host/claude/agents/orkestrel.md +54 -53
  42. package/dist/host/claude/agents/reviewer.md +4 -4
  43. package/dist/host/claude/rules/documentation.md +2 -1
  44. package/dist/host/claude/rules/patterns.md +1 -1
  45. package/dist/host/claude/rules/quality.md +2 -2
  46. package/dist/host/claude/rules/styles.md +1 -1
  47. package/dist/host/claude/rules/tests.md +34 -8
  48. package/dist/host/claude/rules/typescript.md +29 -0
  49. package/dist/host/claude/rules/workspace.md +32 -22
  50. package/dist/host/claude/rules/writing.md +34 -26
  51. package/dist/host/claude/settings.json +2 -0
  52. package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +1 -1
  53. package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +1 -1
  54. package/dist/host/codex/agents/analyst.toml +3 -3
  55. package/dist/host/codex/agents/reviewer.toml +4 -4
  56. package/dist/host/configs/policy.ts +13 -2
  57. package/dist/host/dotfiles/editorconfig +0 -1
  58. package/dist/host/dotfiles/gitattributes +0 -1
  59. package/dist/host/dotfiles/gitignore +0 -1
  60. package/dist/host/dotfiles/oxlintrc.json +24 -0
  61. package/dist/host/guides/agent.md +1638 -598
  62. package/dist/host/guides/budget.md +89 -29
  63. package/dist/host/guides/form.md +477 -91
  64. package/dist/host/guides/mcp.md +14 -2
  65. package/dist/host/guides/ollama.md +97 -73
  66. package/dist/host/guides/probe.md +1 -1
  67. package/dist/host/guides/scaffold.md +158 -101
  68. package/dist/host/guides/study.md +2635 -0
  69. package/dist/host/guides/table.md +225 -15
  70. package/dist/host/guides/terminal.md +22 -20
  71. package/dist/host/guides/test.md +689 -255
  72. package/dist/host/manifest.json +87 -63
  73. package/dist/host/tests/config.test.ts +136 -82
  74. package/dist/host/tests/policy.test.ts +39 -1
  75. package/dist/host/tests/setupPolicy.ts +86 -28
  76. package/dist/src/core/index.cjs +1387 -611
  77. package/dist/src/core/index.cjs.map +1 -1
  78. package/dist/src/core/index.d.cts +35 -20
  79. package/dist/src/core/index.d.ts +35 -20
  80. package/dist/src/core/index.js +1385 -612
  81. package/dist/src/core/index.js.map +1 -1
  82. package/dist/src/server/index.cjs +12 -12
  83. package/dist/src/server/index.cjs.map +1 -1
  84. package/dist/src/server/index.d.cts +12 -12
  85. package/dist/src/server/index.d.ts +12 -12
  86. package/dist/src/server/index.js +12 -12
  87. package/dist/src/server/index.js.map +1 -1
  88. package/package.json +4 -4
@@ -4,11 +4,10 @@
4
4
  > holding the rows given against it, and one lens of sort, filter, and page deciding which of them
5
5
  > the view shows.
6
6
 
7
- Nothing here renders, measures a pixel, reads a keyboard, or names a host type. **A grid, a report,
7
+ The core renders nothing and names no host type. **A grid, a report,
8
8
  a terminal listing, and a CSV export are the same abstraction.** They all hold a set of records,
9
9
  order them, narrow them, and show a stretch of them. What differs is who draws the result, and
10
- drawing is the one part this package leaves out. The table owns values; the host owns everything a
11
- person looks at.
10
+ drawing belongs to each host. The browser face binds native table elements to that same document.
12
11
 
13
12
  The row store is the source of truth, and it is the whole of it. Sort terms, filters, the picked
14
13
  keys, the opened keys, and the page are held; `view` and every tally are worked out on read, so no
@@ -21,7 +20,8 @@ copy. Table-owned refusals raise `TableError`, and each one names a caller mista
21
20
 
22
21
  ## Surface
23
22
 
24
- Everything in this guide is exported from `@orkestrel/table` ([`src/core`](../src/core)). The manager
23
+ The core surface is exported from `@orkestrel/table` ([`src/core`](../src/core)); the native binding
24
+ surface comes from `@orkestrel/table/browser` ([`src/browser`](../src/browser)). The manager
25
25
  classes and the key-set shell they compose are the module's internal declarations: they stay out
26
26
  of the barrel, so no consumer can construct one, and each constructor takes the table's emitter
27
27
  and closures over state `Table` keeps private, which is why they are internal rather than
@@ -63,6 +63,208 @@ table.pagination.count // 2 — two pages of two
63
63
  table.view.map((row) => row.name) // ['Grace', 'Alan'] — page one, oldest first
64
64
  ```
65
65
 
66
+ ### Native binding
67
+
68
+ These declarations belong to the browser entry.
69
+
70
+ | API | Kind | Shape | Summary |
71
+ | ----------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
72
+ | `TableBinding` | class | `TableBindingInterface` | Retains keyed native rows while projecting every core table manager. |
73
+ | `TableBindingInterface` | interface | `{ element, document, destroyed, emitter } plus refresh, destroy` | Binds native table elements to a borrowed core document. |
74
+ | `TableBindingOptions` | interface | `{ row?, detail?, scope?, describe?: { range?, sort? }, on?, error? }` | Configures row ownership, detail presentation, scoped controls, and event listeners. |
75
+ | `TableRange` | interface | `{ first, last, count, page, pages, limit }` | Describes the displayed page within the filtered rows. |
76
+ | `TableSortDescription` | interface | `{ column, direction, priority, count }` | Describes a column's direction and one-based priority among active sort orders. |
77
+ | `TableExpansion` | interface | `{ key, expanded }` | Proposes one row's next expansion state in a cancelable expand request. |
78
+ | `TableBindingEventMap` | type | `{ sort: readonly [event: CustomEvent<readonly TableOrder[]>]; expand: readonly [event: CustomEvent<readonly TableExpansion[]>]; render: readonly []; error: readonly [error: unknown] }` | Announces cancelable sorting and expanding, completed rendering, and failed DOM operations. |
79
+ | `TableRenderHandler` | type | `(element: HTMLTableRowElement, row: TableRow) => void` | Patches the contents of a retained row without moving or replacing that row. |
80
+ | `TableProjection` | interface | `{ schema, rows }` | Holds a validated native table projection for constructing a core document. |
81
+ | `TableBindingErrorCode` | type | `'STRUCTURE'` | Names a native binding failure at its structure, rendering, or lifecycle boundary. |
82
+ | `TableBindingError` | class | `new (code: TableBindingErrorCode, message: string) => TableBindingError` | Represents a native table binding failure with a machine-readable code. |
83
+ | `isTableBindingError` | function | `(value: unknown) => value is TableBindingError` | Checks whether a caught value is a native table binding error. |
84
+ | `createTableBinding` | function | `(element: HTMLTableElement, document: TableInterface, options?: TableBindingOptions) => TableBindingInterface` | Creates a native table binding with one retained row owner. |
85
+ | `readTable` | function | `(element: HTMLTableElement) => TableProjection` | Reads declared header metadata and explicit row keys into a validated core projection. |
86
+ | `readTableSchema` | function | `(element: HTMLTableElement) => TableSchema` | Reads and validates explicit native header metadata. |
87
+ | `readTableRows` | function | `(schema: TableSchema, elements: readonly HTMLTableRowElement[]) => readonly TableRow[]` | Reads explicit keys and declared direct cells against a core schema. |
88
+
89
+ ### Enhance authored rows
90
+
91
+ Declare one body, a table identity-column name, explicit header column keys and cell kinds, and a
92
+ stable key on every data row. Direct cells declare their columns; omitted cells stay absent. Text
93
+ is preserved verbatim. Numbers and flags use the core parser. A native data element's value or a
94
+ time element's datetime takes precedence over cell text. Choice headers carry their explicit
95
+ choice array as JSON in data-table-choices. Nested tables supply no headers or controls to their parent.
96
+
97
+ This browser example constructs the document before binding it.
98
+
99
+ ```ts
100
+ import { createTable } from '@orkestrel/table'
101
+ import { createTableBinding, readTable } from '@orkestrel/table/browser'
102
+
103
+ const element = document.createElement('table')
104
+ element.setAttribute('data-table-key', 'id')
105
+ element.innerHTML =
106
+ '<caption>People</caption><thead><tr><th data-table-column="id" data-table-cell="text">Reference</th><th data-table-column="name" data-table-cell="text"><button data-table-sort="name">Name</button></th></tr></thead><tbody><tr data-table-key="ash"><td data-table-column="name">Ash</td></tr><tr data-table-key="cedar"><td data-table-column="name">Cedar</td></tr></tbody>'
107
+ document.body.append(element)
108
+ const projection = readTable(element)
109
+ const table = createTable(projection.schema, { rows: projection.rows })
110
+ const binding = createTableBinding(element, table)
111
+ table.filter.set({ column: 'name', operator: 'contains', text: 'Ash' })
112
+ binding.refresh()
113
+ element.tBodies[0]?.rows.length // 1
114
+ binding.destroy()
115
+ element.tBodies[0]?.rows.length // 2: restored in core store order
116
+ table.destroyed // false
117
+ element.remove()
118
+ ```
119
+
120
+ The binding validates the entire authored read before changing core rows. External inserts,
121
+ removals, and cell edits reach the document; an invalid read emits error and writes nothing.
122
+ Detached rows remain observed while their keys live. Authored mode owns cell values in the DOM;
123
+ use the core managers for the view, and edit authored cells to change values. Model mode owns
124
+ values in the document and requires an empty body and a row callback. That callback receives
125
+ the retained row first and its value second. It only patches contents, and runs on creation or
126
+ value change. Sort, selection, filtering, expansion, and pagination do not rerun it.
127
+
128
+ A detail callback receives a generated row containing one spanning cell. Patch that cell's
129
+ contents; the binding owns its placement and identity. Collapsed and off-view details are retained
130
+ until their data key leaves the store. Render callbacks must return synchronously. A throw emits
131
+ error without render success; refresh retries. Arbitrary callback effects are not rolled back.
132
+
133
+ Native hooks use real controls: a header button with data-table-sort naming the column, a row
134
+ checkbox with data-table-select, a row button with data-table-expand, and a scope checkbox with
135
+ data-table-page. Scope defaults to the table. Use a scope dedicated to that table for an external
136
+ controls. It selects or clears only current view keys; off-page selection stays selected.
137
+ Sort clicks cycle ascending, descending, and absent, replacing the primary order. Programmatic
138
+ multi-sort remains intact and is described on each sorted header button. The sort event carries
139
+ a cancelable CustomEvent whose detail is the proposed order array; preventDefault vetoes before
140
+ the document changes. A row expand button or the page expand button emits expand first, with a
141
+ cancelable CustomEvent whose detail lists each key and its proposed expanded state; preventDefault
142
+ leaves every key and row unchanged, so a host can run its own close transition and then clear the
143
+ key through the document's expansion. The core managers remain the only owners of filters and paging.
144
+
145
+ An external pager belongs inside the binding's dedicated scope. Use a select with
146
+ `data-table-limit`, buttons with `data-table-move="previous"` and `data-table-move="next"`, and an
147
+ authored `output` with `data-table-range`. A limit option's value is a positive integer; an empty
148
+ value removes paging. Invalid values emit a binding error without changing pagination. Programmatic
149
+ limit changes update the select; include an option for each limit your page uses. Native disabled
150
+ states follow the bounds. A focused button that becomes disabled hands focus to the enabled
151
+ opposite button, or to the named table when neither direction is available.
152
+
153
+ The range output is cleared before filling on a later task. It fills initially and when the
154
+ displayed range, filtered count, page, page count, or limit changes. Sorting, selection, expansion,
155
+ and refresh alone don't repeat the sentence. Its implicit status role needs no added live role.
156
+ The default is `1 to 2 of 3`, or `0 to 0 of 0` for an empty view. The `describe.range` callback
157
+ receives one-based bounds, filtered count, page and page count, and the optional page limit.
158
+ The `describe.sort` callback receives the column, direction, one-based priority, and active order
159
+ count. Its default is `Sort priority 2 of 2, descending`. Formatter throws emit error without
160
+ render success; refresh retries. Destroy cancels pending fills and restores control values still
161
+ carrying the binding's last write, preserving caller edits.
162
+
163
+ A button with `data-table-expand="page"` expands the visible keys, or collapses them when every
164
+ visible key is expanded. Off-page expansion stays intact. The cancelable `expand` request lists
165
+ only the keys whose state changes. The label stays authored;
166
+ `aria-expanded` carries the state, and an empty view disables the button. Row controls continue
167
+ to use bare `data-table-expand`. Detail rows and their identifiers survive collapse and paging.
168
+ Nested tables supply none of these controls to the containing binding. The binding renders no
169
+ numbered page window; presentation classes belong to the host.
170
+
171
+ This scoped binding example customizes wording while retaining native control labels.
172
+
173
+ ```ts
174
+ import { createTable } from '@orkestrel/table'
175
+ import { createTableBinding, readTable } from '@orkestrel/table/browser'
176
+
177
+ const scope = document.createElement('section')
178
+ scope.innerHTML =
179
+ '<table data-table-key="id"><caption>Accounts</caption><thead><tr><th data-table-column="id" data-table-cell="text"><button data-table-sort="id">Account</button></th></tr></thead><tbody><tr data-table-key="ash"><td data-table-column="id">ash</td></tr><tr data-table-key="cedar"><td data-table-column="id">cedar</td></tr></tbody></table><nav aria-label="Account pages"><output data-table-range></output><select aria-label="Rows per page" data-table-limit><option value="1">1</option><option value="">All</option></select><button type="button" data-table-move="previous">Previous</button><button type="button" data-table-move="next">Next</button><button type="button" data-table-expand="page">Page details</button></nav>'
180
+ document.body.append(scope)
181
+ const element = scope.querySelector('table')
182
+ if (element === null) throw new Error('Missing account table')
183
+ const projection = readTable(element)
184
+ const table = createTable(projection.schema, { rows: projection.rows, limit: 1 })
185
+ const binding = createTableBinding(element, table, {
186
+ scope,
187
+ describe: {
188
+ range: ({ first, last, count }) => `${first}–${last} of ${count} accounts`,
189
+ sort: ({ priority, count, direction }) => `Order ${priority}/${count}: ${direction}`,
190
+ },
191
+ detail: (node, row) => {
192
+ const cell = node.cells[0]
193
+ if (cell !== undefined) cell.textContent = `Account ${row['id']}`
194
+ },
195
+ })
196
+ // Keep the binding while the page is mounted; release it before removing the scope.
197
+ binding.destroy()
198
+ table.destroy()
199
+ scope.remove()
200
+ ```
201
+
202
+ Rendering coalesces on a later task, including writes during callbacks. Rows and child controls
203
+ retain identity across the view. Reorders use native state-preserving movement when available;
204
+ focus is restored on fallback moves. When a focused row leaves the view, focus moves to the named
205
+ table. An already focused external control keeps focus. Destroy disconnects scheduling, observers,
206
+ and listeners, restores attributes still carrying the binding's values, removes details, and
207
+ returns retained authored rows in core order while retaining a connected focused control. A caller's
208
+ deliberate focus choice outside the row remains untouched. Model rows remain an inert final snapshot. The
209
+ borrowed document stays live.
210
+
211
+ The lower-level readers share the same validation boundary, and the row comparison ignores
212
+ snapshot identity.
213
+
214
+ ```ts
215
+ import {
216
+ TableBinding,
217
+ TableBindingError,
218
+ isTableBindingError,
219
+ readTableRows,
220
+ readTableSchema,
221
+ } from '@orkestrel/table/browser'
222
+ import { createTable, matchesTableRow } from '@orkestrel/table'
223
+
224
+ const element = document.createElement('table')
225
+ element.setAttribute('data-table-key', 'id')
226
+ element.innerHTML =
227
+ '<thead><tr><th data-table-column="id" data-table-cell="text">Reference</th></tr></thead><tbody></tbody>'
228
+ const schema = readTableSchema(element)
229
+ const rows = readTableRows(schema, []) // []
230
+ const table = createTable(schema, { rows })
231
+ const binding = new TableBinding(element, table)
232
+ binding.destroy()
233
+ matchesTableRow({ id: 'ash' }, { id: 'ash' }) // true
234
+ isTableBindingError(new TableBindingError('STRUCTURE', 'Missing identity')) // true
235
+ ```
236
+
237
+ ### Replace complete rows
238
+
239
+ Replacement removes omitted optional cells while preserving the identity and its manager state.
240
+
241
+ ```ts
242
+ import { createTable } from '@orkestrel/table'
243
+
244
+ const table = createTable(
245
+ {
246
+ key: 'id',
247
+ columns: [
248
+ { key: 'id', cell: 'text' },
249
+ { key: 'name', cell: 'text' },
250
+ ],
251
+ },
252
+ { rows: [{ id: 'ash', name: 'Ash' }] },
253
+ )
254
+ table.selection.select('ash')
255
+ table.rows.replace({ id: 'ash' }) // true
256
+ table.rows.row('ash') // { id: 'ash' }
257
+ table.selection.keys.has('ash') // true
258
+ ```
259
+
260
+ ### Row equality
261
+
262
+ This helper belongs to the core entry.
263
+
264
+ | API | Kind | Shape | Summary |
265
+ | ----------------- | -------- | ----------------------------------------------------------- | ------------------------------------------------------------------------ |
266
+ | `matchesTableRow` | function | `(left: TableRow \| undefined, right: TableRow) => boolean` | Compares complete row values independently of their snapshot identities. |
267
+
66
268
  ### Rows, cells, and columns
67
269
 
68
270
  The document itself — what a table declares and what one row of it holds. All data, no behavior.
@@ -116,7 +318,7 @@ A `Shape` cell holds an interface's data members as bare names in braces, `?` ma
116
318
  | `createTable` | function | `(schema: TableSchema, options?: TableOptions) => TableInterface` | Opens a table against a schema. The schema is copied, and the copy is what the table declares. |
117
319
  | `TableOptions` | interface | `{ on?, error?, rows?, comparators?, matchers?, limit? }` | Describes how to open a table — the listeners wired at construction and where a throw from one goes, the rows seeded into it, the per-column comparison and test replacements, and the page size. |
118
320
  | `TableEventMap` | type | `{ write, remove, sort, filter, select, expand, paginate, clear }` | Lists everything a table announces, mapping each event to the payload its listeners receive. |
119
- | `RowManagerInterface` | interface | `{} plus row, rows, add, update, move, remove` | Manages the rows a table holds, in the order it holds them. |
321
+ | `RowManagerInterface` | interface | `{} plus row, rows, add, update, replace, move, remove` | Manages the rows a table holds, in the order it holds them. |
120
322
  | `SortManagerInterface` | interface | `{} plus order, orders, set, remove` | Manages the order a table reads its rows in. |
121
323
  | `FilterManagerInterface` | interface | `{} plus filter, filters, set, remove` | Manages which rows a table keeps. |
122
324
  | `SelectionManagerInterface` | interface | `{ keys } plus select, clear, toggle` | Manages the rows somebody has picked. |
@@ -1257,14 +1459,22 @@ it either way.
1257
1459
 
1258
1460
  #### `RowManagerInterface`
1259
1461
 
1260
- | Method | Returns | Summary |
1261
- | -------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------- |
1262
- | `row` | `TableRow` or `undefined` | Finds one row by key; `undefined` when the table holds no such key. |
1263
- | `rows` | `readonly TableRow[]` | Reads every row the table holds, in its own order — unfiltered, unsorted, and unpaged. |
1264
- | `add` | `void` | Takes in one row or several, appending them in the order given. Every row is checked before any is admitted. |
1265
- | `update` | `boolean` | Writes over one row or several, each found by the key it carries. The cells given replace; the cells left out stay. |
1266
- | `move` | `boolean` | Moves one row to another place in the table's own order, counted from zero and clamped to the rows that exist. |
1267
- | `remove` | `void` or `boolean` | Takes out every row, one row, or several. Selection and expansion drop the keys of the rows that went. |
1462
+ | Method | Returns | Summary |
1463
+ | --------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------- |
1464
+ | `row` | `TableRow` or `undefined` | Finds one row by key; `undefined` when the table holds no such key. |
1465
+ | `rows` | `readonly TableRow[]` | Reads every row the table holds, in its own order — unfiltered, unsorted, and unpaged. |
1466
+ | `add` | `void` | Takes in one row or several, appending them in the order given. Every row is checked before any is admitted. |
1467
+ | `update` | `boolean` | Writes over one row or several, each found by the key it carries. The cells given replace; the cells left out stay. |
1468
+ | `replace` | `boolean` | Replaces complete rows under existing keys, removing omitted cells without moving identities. |
1469
+ | `move` | `boolean` | Moves one row to another place in the table's own order, counted from zero and clamped to the rows that exist. |
1470
+ | `remove` | `void` or `boolean` | Takes out every row, one row, or several. Selection and expansion drop the keys of the rows that went. |
1471
+
1472
+ #### `TableBindingInterface`
1473
+
1474
+ | Method | Returns | Summary |
1475
+ | --------- | ------- | --------------------------------------------------------------------------------- |
1476
+ | `refresh` | `void` | Reconciles and renders the document, retrying a failed renderer. |
1477
+ | `destroy` | `void` | Releases listeners and restores owned attributes without destroying the document. |
1268
1478
 
1269
1479
  #### `SortManagerInterface`
1270
1480
 
@@ -1372,7 +1582,7 @@ table.count // 1
1372
1582
  These invariants hold across [`src/core`](../src/core) and this guide.
1373
1583
 
1374
1584
  1. **Documented surface equals exported surface.** Every row in the `## Surface` tables is a real
1375
- barrel export of `src/core`, and every barrel export is a row — both directions, exhaustively.
1585
+ barrel export of `src/core` or `src/browser`, and every barrel export is a row — both directions, exhaustively.
1376
1586
  The manager classes and the key-set shell are internal, so the parity suite's internal list names
1377
1587
  them and nothing else.
1378
1588
  2. **Documented methods equal interface methods.** Each `## Methods` table lists exactly its
@@ -1503,7 +1713,7 @@ this package answers today through a mechanism it already exposes.
1503
1713
  | CSV and spreadsheet export | host | `view` or `rows()` plus `schema.columns` is the whole input an exporter needs, and the file format, encoding, and download belong to the host that has a filesystem or a browser. |
1504
1714
  | Cell-level selection | out | Selection holds row keys. A rectangular cell range is a second selection model with its own vocabulary, and no consumer has asked for one. |
1505
1715
  | Choice `meta` | out | `meta` is on `ColumnBase` alone, because a column carrier is what the first consumer asked for. The exact guard refuses it on `ColumnChoice` until one asks. |
1506
- | Browser binding | `src/browser` | Binding a table to real elements — a grid, its headings, its scroll container — belongs in a future `src/browser`, taking a table and an element. Nothing renders in this round. |
1716
+ | Browser binding | `src/browser` | The browser entry binds native table rows and controls to a borrowed core document; the core remains host-independent. |
1507
1717
 
1508
1718
  ## Tests
1509
1719
 
@@ -351,23 +351,23 @@ A `Shape` cell holds an interface's data members as bare names in braces, `?` ma
351
351
  The stream guards, the cursor math behind the in-place re-render, and the per-field line projections
352
352
  the walk renders with ([`src/server`](../src/server)). All pure, all exported, all unit-tested.
353
353
 
354
- | API | Kind | Summary |
355
- | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
356
- | `isInputStream` | function | Checks whether `value` is a usable `InputStreamInterface` — a record with callable `on` / `off` `'data'` subscription methods. A total type guard: it never throws and returns `false` for anything off-shape, so it narrows the one unavoidable input boundary (the real `process.stdin`, or a fake TTY a test injects) to the exact slice the driver reads, never an assertion. |
357
- | `isReadable` | function | Checks whether `value` is a Node `NodeJS.ReadableStream` — a total structural guard checking for the callable `read` / `pipe` / `on` that `node:readline`'s `createInterface` requires as its `input`. The non-TTY fallback narrows the resolved input stream through this before handing it to readline, never through an assertion, so a real piped `process.stdin` (or a `PassThrough` a test injects) crosses into the readline boundary honestly. Never throws; returns `false` for a minimal fake that isn't a full readable. |
358
- | `supportsRawMode` | function | Checks whether an input stream can be driven in raw mode — it reports `isTTY === true` and exposes a callable `setRawMode`. The `Terminal` probes this to choose its path: true selects the interactive raw-mode fields, with arrow-key navigation and a live re-render; false selects the `node:readline` line-input fallback, because a piped or non-terminal stream cannot enter raw mode. Total — never throws. |
359
- | `lineCount` | function | Counts the terminal lines a rendered prompt `view` occupies — one more than its newline count, so a view with no newline is a single line and a view with N newlines spans N+1 lines. The basis of the in-place re-render: the driver records the line count of the view it wrote so the next redraw knows how far up to move the cursor before overwriting. Total; an empty string is one empty line. |
360
- | `renderCursorUp` | function | Returns the cursor-up control sequence that moves the cursor up `count` lines (`ESC[{count}A`), or the empty string when `count` is zero or negative, because no movement is needed and `ESC[0A` is a wasted write. The pure step the in-place re-render uses to climb back over the previous view before clearing it. Total. |
361
- | `redrawPrefix` | function | Returns the full reposition-and-clear prefix to write before re-rendering a prompt view in place — given the line count of the previous view, it moves the cursor up over those lines, returns it to column 0, and erases everything from there to the end of the screen, so the next view is drawn on a clean region and a taller previous view leaves no orphaned rows. Pure; the driver writes this immediately followed by the new view. |
362
- | `fieldToText` | function | Projects any field the walk reads as a line of text into the `TextField` the text reducer takes — `text` itself, and the controls a terminal has no widget for: `number`, `date`, `time`, `datetime`, `color`, and one `file` entry. The label carries that control's format cue from `CONTROL_HINTS`, and a declared `default` becomes the line a bare return submits. The projection carries no rule, because the authoritative form still evaluates the answer this line binds; it exists only so one reducer covers every one of them. |
363
- | `valueToText` | function | Projects one held answer into the text a read-only line shows — a scalar as itself, a boolean as `yes` / `no` (the word the confirm reducer commits), and a list joined by commas. Absence renders as nothing, because a locked field nobody has answered has nothing to show. |
364
- | `filterEnabled` | function | Returns the choices a `select` or `checkbox` field actually offers — the form refuses a disabled choice's value at every door, including a fill, so the walk never puts one in front of the cursor. Pair with `filterDisabled` to tell the reader what was withheld. |
365
- | `filterDisabled` | function | Returns the choices a `select` or `checkbox` field shows but refuses — the complement of `filterEnabled`, rendered by `renderUnavailableLine` above the list so a reader sees why a declared choice is missing from it. |
366
- | `renderGroupHeader` | function | Renders the section header the walk writes when it enters a new field group, painted by the `message` role. |
367
- | `renderLockedLine` | function | Renders the read-only line a locked field shows — its label, the `LOCKED_MARK`, and the answer the form already holds. The walk writes this instead of a prompt, because the field is still validated and still submitted but must not be edited here. |
368
- | `renderSuggestionLine` | function | Renders the line listing an open select's offered values above its text prompt — a suggestion list, because an open select admits an answer the list does not offer. |
369
- | `renderUnavailableLine` | function | Renders the line naming the choices a field shows but refuses, written above the list the walk drives. |
370
- | `renderNumberedList` | function | Renders the numbered choice list the non-TTY fallback prints — a piped stream cannot navigate with arrow keys, so each offered choice is printed with the number the reader types back. One line per choice, with no trailing newline. |
354
+ | API | Kind | Summary |
355
+ | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
356
+ | `isInputStream` | function | Checks whether `value` is a usable `InputStreamInterface` — a record with callable `on` / `off` `'data'` subscription methods. A total type guard: it never throws and returns `false` for anything off-shape, so it narrows the one unavoidable input boundary (the real `process.stdin`, or a fake TTY a test injects) to the exact slice the driver reads, never an assertion. |
357
+ | `isReadable` | function | Checks whether `value` is a Node `NodeJS.ReadableStream` — a total structural guard checking for the callable `read` / `pipe` / `on` that `node:readline`'s `createInterface` requires as its `input`. The non-TTY fallback narrows the resolved input stream through this before handing it to readline, never through an assertion, so a real piped `process.stdin` (or a `PassThrough` a test injects) crosses into the readline boundary honestly. Never throws; returns `false` for a minimal fake that isn't a full readable. |
358
+ | `supportsRawMode` | function | Checks whether an input stream can be driven in raw mode — it reports `isTTY === true` and exposes a callable `setRawMode`. The `Terminal` probes this to choose its path: true selects the interactive raw-mode fields, with arrow-key navigation and a live re-render; false selects the `node:readline` line-input fallback, because a piped or non-terminal stream cannot enter raw mode. Total — never throws. |
359
+ | `lineCount` | function | Counts the terminal lines a rendered prompt `view` occupies — one more than its newline count, so a view with no newline is a single line and a view with N newlines spans N+1 lines. The basis of the in-place re-render: the driver records the line count of the view it wrote so the next redraw knows how far up to move the cursor before overwriting. Total; an empty string is one empty line. |
360
+ | `renderCursorUp` | function | Returns the cursor-up control sequence that moves the cursor up `count` lines (`ESC[{count}A`), or the empty string when `count` is zero or negative, because no movement is needed and `ESC[0A` is a wasted write. The pure step the in-place re-render uses to climb back over the previous view before clearing it. Total. |
361
+ | `redrawPrefix` | function | Returns the full reposition-and-clear prefix to write before re-rendering a prompt view in place — given the line count of the previous view, it moves the cursor up over those lines, returns it to column 0, and erases everything from there to the end of the screen, so the next view is drawn on a clean region and a taller previous view leaves no orphaned rows. Pure; the driver writes this immediately followed by the new view. |
362
+ | `fieldToText` | function | Projects any field the walk reads as a line of text into the `TextField` the text reducer takes — `text` itself, and the controls a terminal has no widget for: `number`, `date`, `time`, `datetime`, `color`, one `list` entry, and one `file` entry. The label carries that control's format cue from `CONTROL_HINTS`, and a declared `default` becomes the line a bare return submits. The projection carries no rule, because the authoritative form still evaluates the answer this line binds; it exists only so one reducer covers every one of them. |
363
+ | `valueToText` | function | Projects one held answer into the text a read-only line shows — a scalar as itself, a boolean as `yes` / `no` (the word the confirm reducer commits), and a list joined by commas. Absence renders as nothing, because a locked field nobody has answered has nothing to show. |
364
+ | `filterEnabled` | function | Returns the choices a `select` or `checkbox` field actually offers — the form refuses a disabled choice's value at every door, including a fill, so the walk never puts one in front of the cursor. Pair with `filterDisabled` to tell the reader what was withheld. |
365
+ | `filterDisabled` | function | Returns the choices a `select` or `checkbox` field shows but refuses — the complement of `filterEnabled`, rendered by `renderUnavailableLine` above the list so a reader sees why a declared choice is missing from it. |
366
+ | `renderGroupHeader` | function | Renders the section header the walk writes when it enters a new field group, painted by the `message` role. |
367
+ | `renderLockedLine` | function | Renders the read-only line a locked field shows — its label, the `LOCKED_MARK`, and the answer the form already holds. The walk writes this instead of a prompt, because the field is still validated and still submitted but must not be edited here. |
368
+ | `renderSuggestionLine` | function | Renders the line listing an open select's offered values above its text prompt — a suggestion list, because an open select admits an answer the list does not offer. |
369
+ | `renderUnavailableLine` | function | Renders the line naming the choices a field shows but refuses, written above the list the walk drives. |
370
+ | `renderNumberedList` | function | Renders the numbered choice list the non-TTY fallback prints — a piped stream cannot navigate with arrow keys, so each offered choice is printed with the number the reader types back. One line per choice, with no trailing newline. |
371
371
 
372
372
  ### The server constants
373
373
 
@@ -385,6 +385,7 @@ A `Shape` cell holds the constant's declared type.
385
385
  | `CLEAR_DOWN` | const | `string` | Erases from the cursor down to the end of the screen (`ESC[J`) — wipes the whole previous view, which a `select` or `checkbox` can spread over several lines, in one write before the new view is rendered, so a redraw never leaves orphaned rows behind. |
386
386
  | `CONTROL_HINTS` | const | `Readonly<Partial<Record<FieldControl, string>>>` | Holds the format cue appended to a field's label for each control the walk reads as a line of text — the terminal has no date picker, no color well, and no file chooser, so the accepted shape is stated instead. A control with no entry needs none: `text` and `editor` accept any line, `password` masks one, and `confirm`, `select`, and `checkbox` are answered by key rather than by format. The form's own rules still decide whether the typed value is acceptable. |
387
387
  | `FILE_HINT` | const | `string` | Holds the instruction a `file` field with `multiple` shows before its entries — one path per line, and a blank line ends the list. |
388
+ | `LIST_HINT` | const | `string` | Holds the instruction a `list` field shows before its entries — one entry per line, and a blank line ends the list. |
388
389
  | `SUGGESTION_LEAD` | const | `string` | Holds the lead on the line listing an open `select`'s offered values, which a typed answer can ignore. |
389
390
  | `UNAVAILABLE_LEAD` | const | `string` | Holds the lead on the line listing the choices a `select` or `checkbox` shows but refuses, so a reader sees why one is missing from the list it heads. |
390
391
  | `LOCKED_MARK` | const | `string` | Holds the mark on a locked field's line — the walk renders its value and moves on, because the form refuses an edit there. |
@@ -561,9 +562,10 @@ These invariants hold across `src/core`, `src/server`, and this guide.
561
562
  carries no `name` with the raw sequence preserved, so the driver cannot crash on a stray byte.
562
563
  `KeyEvent.name` is therefore optional and absence is `undefined`: an undecoded key has no name
563
564
  rather than an empty one, and every reducer reads it as a key it does not consume.
564
- 10. **Every control reaches a reducer.** `text`, `number`, `date`, `time`, `datetime`, `color`, and
565
- each `file` entry are read as one line of text through `fieldToText`, which appends that
566
- control's format cue to the label; `password`, `confirm`, `editor`, `select`, and `checkbox`
565
+ 10. **Every control reaches a reducer.** `text`, `number`, `date`, `time`, `datetime`, `color`, each
566
+ `list` entry, and each `file` entry are read as one line of text through `fieldToText`, which
567
+ appends that control's format cue to the label; a `list` reads one entry per line until a blank
568
+ one and keeps its declared `default` when none is typed; `password`, `confirm`, `editor`, `select`, and `checkbox`
567
569
  each drive their own reducer. An open select is a suggestion list plus a typed line, because
568
570
  `open` means the answer need not come from the list. A disabled choice is named on an
569
571
  unavailable line and never offered, since the form refuses its value at every door. A `hidden`