tosijs-ui 1.12.0 → 1.12.2
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.
- package/CHANGELOG.md +220 -0
- package/README.md +1 -1
- package/bin/release-notes.ts +9 -2
- package/dist/data-table.d.ts +1 -0
- package/dist/data-table.js +64 -7
- package/dist/doc-browser.d.ts +2 -0
- package/dist/doc-browser.js +33 -0
- package/dist/doc-system/doc-system-styles.js +10 -0
- package/dist/doc-system/release-notes.js +17 -2
- package/dist/doc-system/site/dev-auth.d.ts +17 -0
- package/dist/doc-system/site/dev-auth.js +15 -0
- package/dist/doc-system/site/dev-server.d.ts +22 -0
- package/dist/doc-system/site/dev-server.js +208 -3
- package/dist/doc-system/site/docs.d.ts +21 -0
- package/dist/doc-system/site/docs.js +14 -0
- package/dist/doc-system/site/generate-site.js +12 -1
- package/dist/doc-system/site/make-llms-txt.d.ts +5 -1
- package/dist/doc-system/site/site-config.d.ts +19 -1
- package/dist/editable-rect.js +11 -4
- package/dist/float.js +8 -1
- package/dist/iife.js +6 -6
- package/dist/iife.js.map +10 -10
- package/dist/sizer.js +8 -1
- package/dist/track-drag.js +51 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/dist/.metadata_never_index +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,225 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.12.2
|
|
4
|
+
|
|
5
|
+
### `haltijaDev: 'tunnel'` — let an agent drive a page running in a headset (#104)
|
|
6
|
+
|
|
7
|
+
`haltijaDev: true` is localhost-gated, and that gate is exactly what stopped the feature
|
|
8
|
+
working where it is worth the most. Debugging in a VR headset is close to blind: no readable
|
|
9
|
+
console, no devtools, no way to inspect the DOM without taking it off — and taking it off is
|
|
10
|
+
the thing you cannot do, because the bug is usually _about_ being in it.
|
|
11
|
+
|
|
12
|
+
Set `haltijaDev: 'tunnel'` and the channel is also served **same-origin over the tunnel**.
|
|
13
|
+
Redeem an edit link on the device, load the page, and it shows up in `hj windows` like any
|
|
14
|
+
other — `hj eval`, `hj navigate`, `hj tree` all work against it, and `hj` never learns the
|
|
15
|
+
page is remote.
|
|
16
|
+
|
|
17
|
+
**Why it works at all, and why it needed no change from haltija.** The page **dials out**:
|
|
18
|
+
`component.js` reads `window.__haltija_config__.serverUrl` and falls back to localhost only
|
|
19
|
+
when that config is absent. So nothing has to reach _into_ a headset — the dev server sets
|
|
20
|
+
the config itself, serves the component from the page's own origin, and relays the WebSocket
|
|
21
|
+
to the local channel. The upstream `dev.js`/`inject.js` chain is skipped entirely; both files
|
|
22
|
+
carry their own localhost gates and all three of their hardcoded `https://localhost:8701`
|
|
23
|
+
URLs would resolve on the **headset** rather than on your machine.
|
|
24
|
+
|
|
25
|
+
**A separate opt-in, deliberately.** `true` means "an agent may drive the page on this
|
|
26
|
+
machine". Upgrading must never silently turn that into "wherever it is reachable", so
|
|
27
|
+
`'tunnel'` is its own value — and `HALTIJA_DEV=1` does **not** enable it, because that
|
|
28
|
+
variable is a convenience toggle and this is not a convenience.
|
|
29
|
+
|
|
30
|
+
**What authorizes it:** a live dev session cookie — the same credential that gates source
|
|
31
|
+
writes, via the same predicate (`mayDriveWithAgent` delegates to `mayWriteSource`, and a test
|
|
32
|
+
asserts they agree on every input). Driving a page with an agent is at least as powerful as
|
|
33
|
+
writing source, so it gets no weaker a gate. What authorizes is the **listener**, never the
|
|
34
|
+
peer address: a reverse tunnel counterfeits "local" by construction. Unauthorized requests
|
|
35
|
+
get **404**, not 403 — whether a project enabled the bridge is nobody's business but the
|
|
36
|
+
session holder's.
|
|
37
|
+
|
|
38
|
+
Verified end to end rather than by inspection: a real browser on the tunnel origin stores the
|
|
39
|
+
session, receives the loader, resolves `serverUrl` to its own origin (not `localhost:8701`),
|
|
40
|
+
attaches the widget, opens the relayed socket, appears in `hj windows`, and answers `hj eval`
|
|
41
|
+
and `hj navigate`.
|
|
42
|
+
|
|
43
|
+
### Dragging works on Firefox now — every draggable, not just table columns ([#107](https://github.com/tonioloewald/tosijs-ui/issues/107))
|
|
44
|
+
|
|
45
|
+
**The first drag on a freshly-loaded page did not work in Firefox.** The thing you grabbed
|
|
46
|
+
stayed put and random text highlighted instead; it only started behaving once you had clicked
|
|
47
|
+
somewhere — anywhere — to settle Firefox's selection state. Land on a page and drag a column
|
|
48
|
+
edge, which is an entirely ordinary thing to do, and the first attempt failed.
|
|
49
|
+
|
|
50
|
+
This affected **every** `trackDrag` consumer — `<tosi-sizer>`, `<tosi-float>`,
|
|
51
|
+
`<tosi-editable-rect>` and `<tosi-table>` column resizing — and had done since the drag tracker
|
|
52
|
+
landed in **v0.5.1**. Measured with no prior click: `<tosi-sizer>` did not move at all, a table
|
|
53
|
+
column applied one 12px step and froze. All three engines now behave identically (150 → 210 →
|
|
54
|
+
270 on the same gesture, and nothing selected).
|
|
55
|
+
|
|
56
|
+
Two parts, because either alone does nothing. `trackDrag` calls `preventDefault()` on the
|
|
57
|
+
initiating mousedown, which is narrow rather than blunt — it only ever runs once a component has
|
|
58
|
+
already decided a drag is starting, so ordinary clicks, focus and selection are untouched. And
|
|
59
|
+
`<tosi-sizer>`, `<tosi-float>` and `<tosi-editable-rect>` had to stop registering their
|
|
60
|
+
**mousedown** listeners as `passive`, because a passive listener makes `preventDefault` a silent
|
|
61
|
+
no-op — which is exactly why the table (whose listener was never passive) improved first while
|
|
62
|
+
the others stayed dead. Nothing is lost: `passive`'s practical value is silencing the console
|
|
63
|
+
warning about handlers that delay scrolling, and that warning only ever applied to touch and
|
|
64
|
+
wheel. `touchstart` stays passive.
|
|
65
|
+
|
|
66
|
+
It is Firefox behaving badly, and that is a diagnosis rather than an excuse — the workaround is
|
|
67
|
+
cheap and users do not care whose bug it is.
|
|
68
|
+
|
|
69
|
+
### Page metadata: `layout: full-width`, so a route can leave the reading column ([#105](https://github.com/tonioloewald/tosijs-ui/issues/105))
|
|
70
|
+
|
|
71
|
+
Prose wants a measure — 44em is roughly what people read comfortably, and it stays the default.
|
|
72
|
+
But a demo, a dashboard, a wide table or a canvas is _worse_ squeezed into a column, and having
|
|
73
|
+
to pick one habit for the whole site is what makes people build a second site. Now a page can
|
|
74
|
+
opt out, the same way it sets `pin` or `order`:
|
|
75
|
+
|
|
76
|
+
```markdown
|
|
77
|
+
<!--{ "layout": "full-width" }-->
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
or `layout: full-width` in YAML frontmatter. **This project's own README uses it.**
|
|
81
|
+
|
|
82
|
+
Stamped into the served HTML rather than applied on load, so the first paint is already right —
|
|
83
|
+
a layout applied by script would show the reading column and snap wide, the flicker
|
|
84
|
+
pre-rendering exists to prevent. Client-side navigation keeps it in step **in both directions**;
|
|
85
|
+
that second direction is the one a naive implementation forgets, and it has its own test
|
|
86
|
+
(mutation-verified: dropping the clear-on-navigate fails it, dropping the static stamp fails
|
|
87
|
+
three more including first paint).
|
|
88
|
+
|
|
89
|
+
**`layout: "full-screen"` is not implemented and is rejected with a message rather than
|
|
90
|
+
half-honoured.** It needs a collapsed state on `<tosi-side-nav>`: after hydration the nav
|
|
91
|
+
carries an inline `display`, and side-nav sets its width as an inline property, so a stylesheet
|
|
92
|
+
cannot override either without `!important` — and hiding side-nav itself hides the content too,
|
|
93
|
+
since it wraps both. A value that dropped the measure while leaving the chrome exactly where it
|
|
94
|
+
was would be reported as a bug, correctly. Tracked in `TODO.md`.
|
|
95
|
+
|
|
96
|
+
### `<tosi-table>`: a column drag no longer dies when `columns` is reassigned under it
|
|
97
|
+
|
|
98
|
+
The other half of the reporting app's "resize funkiness", and a different defect from #102's
|
|
99
|
+
torn grid — this one is not a mismatch at all. `resizeColumn` captured the `ColumnOptions`
|
|
100
|
+
object and mutated it for the whole drag, and that object stops being the table's the instant a
|
|
101
|
+
caller assigns a new `columns` array. Which the app does on a page-size change, and a page-size
|
|
102
|
+
change is a very likely thing to happen while someone is dragging a column edge. From then on
|
|
103
|
+
the drag wrote widths into an orphan: pointer moving, column not moving, nothing reported.
|
|
104
|
+
|
|
105
|
+
The drag now re-resolves the column by `prop` each step — ending cleanly if it is gone, and
|
|
106
|
+
rebasing on the new width if the object was merely replaced. Confirmed pre-existing rather than
|
|
107
|
+
introduced by 1.12.1: reverting that fix leaves the drag equally dead.
|
|
108
|
+
|
|
109
|
+
### Known: column resize does not work on Firefox at all ([#107](https://github.com/tonioloewald/tosijs-ui/issues/107))
|
|
110
|
+
|
|
111
|
+
Found while investigating the above, and it is the bigger problem. On Firefox a **plain** column
|
|
112
|
+
drag — no reassignment anywhere near it — applies one step and freezes: +60px reaches 162 and
|
|
113
|
+
+120px still reads 162, against 210 then 270 on chromium and webkit. A document-level capture
|
|
114
|
+
listener shows Firefox stops delivering `mousemove` to the page entirely after the first event,
|
|
115
|
+
so no callback can run.
|
|
116
|
+
|
|
117
|
+
The cause looks to be `trackDrag` listening on a full-screen overlay while Firefox keeps
|
|
118
|
+
implicit mouse capture on the element that received the `mousedown`. The fix is Pointer Events
|
|
119
|
+
with explicit capture, and `trackDrag` is shared by every draggable affordance — so it wants
|
|
120
|
+
doing deliberately, with the others re-tested on Firefox at the same time, rather than as a
|
|
121
|
+
footnote to this release. Ruled out first: it is not the DOM being rebuilt under the drag
|
|
122
|
+
(deferring renders changes nothing) and not #102 (reverting that changes nothing).
|
|
123
|
+
|
|
124
|
+
### `tosijs-release-notes`: the bump gate stops blocking on dev tooling
|
|
125
|
+
|
|
126
|
+
Two fixes, both hit while cutting this release — which is the only way anyone finds them.
|
|
127
|
+
|
|
128
|
+
**A dev-tooling path with a security surface now WARNS instead of blocking a patch.** Every
|
|
129
|
+
path on that list is development tooling — a dev server, a tunnel, a deploy script — and none
|
|
130
|
+
of it runs in an end user's browser as part of an adopter's app. Halting a release over it
|
|
131
|
+
spends the maintainer's attention on the release with the least at stake, and a guard that
|
|
132
|
+
cries wolf gets overridden or deleted, taking the case it was _right_ about with it (#79, a
|
|
133
|
+
loosened dev-server default shipped as a patch). The warning keeps that signal and stops
|
|
134
|
+
charging for it. A gate that genuinely should block would have to key on code an adopter
|
|
135
|
+
**ships**, which none of these are.
|
|
136
|
+
|
|
137
|
+
**The blocking branch offered advice it could not honour.** It said "explain in the notes why
|
|
138
|
+
this does not reach anyone" — but `bumpConcerns` never sees the notes, so nothing you wrote
|
|
139
|
+
could satisfy it, and the only way forward was to work that out by reading the tool's own
|
|
140
|
+
source. Same defect this project already fixed in the tunnel's 401 page. It now says "cut a
|
|
141
|
+
minor", which is the only thing that ever worked.
|
|
142
|
+
|
|
143
|
+
### Security-relevant paths that changed, and why this is still a patch
|
|
144
|
+
|
|
145
|
+
`dev-auth.ts` — the module that decides who may do privileged things — changed in this
|
|
146
|
+
release, and a patch touching it deserves a heading rather than a footnote.
|
|
147
|
+
|
|
148
|
+
**The change is purely additive, and that is checkable rather than asserted.** The diff against
|
|
149
|
+
1.12.1 removes **zero lines**, and `mayWriteSource` is **byte-identical** to 1.12.1. What was
|
|
150
|
+
added is one new exported function, `mayDriveWithAgent`, whose entire body delegates to
|
|
151
|
+
`mayWriteSource` — with a test asserting the two agree on every combination of `viaTunnel`,
|
|
152
|
+
`hasValidSession` and peer address, so they cannot drift apart later without that test going
|
|
153
|
+
red.
|
|
154
|
+
|
|
155
|
+
**Nothing reaches an existing adopter.** The new predicate is consulted only by the
|
|
156
|
+
`/__haltija/` routes, which exist only when `haltijaDev: 'tunnel'` is set. Every existing
|
|
157
|
+
authorization decision — source writes, link minting, session validation — is executed by the
|
|
158
|
+
same bytes as in 1.12.1. If you have not opted in, the reachable behaviour of this release is
|
|
159
|
+
identical to 1.12.1.
|
|
160
|
+
|
|
161
|
+
**What it would mean if you do opt in:** a live dev session cookie becomes sufficient to let an
|
|
162
|
+
agent drive your page over the tunnel, in addition to editing source. That is a real widening
|
|
163
|
+
of what a session can do, which is why it is a distinct config value and not implied by
|
|
164
|
+
`haltijaDev: true`.
|
|
165
|
+
|
|
166
|
+
**A patch, not a minor**, despite reading like a feature: it is a new optional _value_ on an
|
|
167
|
+
existing config field, plus routes that exist only under it. `haltijaDev: true` behaves exactly
|
|
168
|
+
as before and nothing is removed or renamed, so the contract a patch carries — it does not
|
|
169
|
+
break you — holds.
|
|
170
|
+
|
|
171
|
+
## 1.12.1
|
|
172
|
+
|
|
173
|
+
### The Playwright lane no longer fights itself
|
|
174
|
+
|
|
175
|
+
Local workers are pinned to **6** rather than Playwright's default (~half the cores, 9 here).
|
|
176
|
+
Every engine had taken a turn producing a `page.goto` timeout or a `docs.json` load failure —
|
|
177
|
+
roughly one full run in five, always passing in isolation — because 9 browsers were hitting one
|
|
178
|
+
dev server. 1.11.0 fixed the client half (`fetchCorpus` retries); this reduces the contention
|
|
179
|
+
instead of surviving it.
|
|
180
|
+
|
|
181
|
+
Measured, not guessed: 9 workers ran the suite in 82s and 6 in 81s, so the lane is not
|
|
182
|
+
CPU-bound at 9 and a third of the load is free; 4 workers costs 106s and buys nothing further.
|
|
183
|
+
6/6 clean afterwards. Contributor-facing only — CI already runs at 1 worker.
|
|
184
|
+
|
|
185
|
+
### `<tosi-schema-form>`: all three perf guards, and they cover different things
|
|
186
|
+
|
|
187
|
+
The keystroke perf guard counts DOM lookups, and provably could not see `syncValues` — which
|
|
188
|
+
runs on add/remove-item, not on keystrokes. A root-scoped per-field scan reintroduced there
|
|
189
|
+
passed the existing test. There is now a sibling test on that path; mutation-testing takes it
|
|
190
|
+
from 1 lookup per add to 41 → 161 as field count grows, while the keystroke test stays green
|
|
191
|
+
under the same mutation.
|
|
192
|
+
|
|
193
|
+
Wall-clock is back alongside them, too. It was removed in 1.11.0 for flaking; with the lane
|
|
194
|
+
quiet the isolated and loaded distributions now overlap (3.20-4.57 vs 2.64-4.14 across all
|
|
195
|
+
three engines, against 4.0 for linear and 16 for quadratic), so a threshold of 8 separates
|
|
196
|
+
them. It earns its place by catching what counting cannot: an O(N) scan per field that uses no
|
|
197
|
+
selector at all fails it at 9.63x while both count guards pass.
|
|
198
|
+
|
|
199
|
+
### `<tosi-table>`: the header and body can no longer describe different columns (#102)
|
|
200
|
+
|
|
201
|
+
Reported from a host app that changes column visibility when the page size changes. `set
|
|
202
|
+
columns` updates the column list **synchronously** but defers the rebuild to the next frame,
|
|
203
|
+
so between the assignment and that frame anything recomputing widths — a resize drag, a scroll
|
|
204
|
+
handler, or the host's own page-size code — wrote a grid template for the NEW columns over the
|
|
205
|
+
cells the OLD ones had built.
|
|
206
|
+
|
|
207
|
+
Why that reads as _disagreement_ rather than a missing column, which is what made it confusing
|
|
208
|
+
to report: with fewer tracks than cells, CSS Grid auto-places the surplus into **implicit**
|
|
209
|
+
tracks, and implicit tracks size to content. Header text and body text differ, so the two rows
|
|
210
|
+
resolve them to different widths and the columns visibly step apart.
|
|
211
|
+
|
|
212
|
+
`setColumnWidths()` now describes the column set the DOM was actually built from, refreshed by
|
|
213
|
+
`render()` on the frame that installs those cells. A column change is therefore **deferred, not
|
|
214
|
+
dropped** — and because column objects are shared by reference, a resize drag still moves
|
|
215
|
+
widths immediately: it is the shape that is pinned to the DOM, not the widths. All three
|
|
216
|
+
properties have tests, and the fix is mutation-verified (reverting it fails with
|
|
217
|
+
`tracks: 3, headerCells: 4`).
|
|
218
|
+
|
|
219
|
+
The caller arguably should not reassign columns mid-render. It does not matter — a table that
|
|
220
|
+
can show a header and a body describing different columns is our defect whenever the
|
|
221
|
+
assignment lands.
|
|
222
|
+
|
|
3
223
|
## 1.12.0
|
|
4
224
|
|
|
5
225
|
### tosijs-ui is Apache-2.0 as of this release (MIT through 1.11.1)
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# tosijs-ui
|
|
2
2
|
|
|
3
|
-
<!--{ "pin": "top", "headTitle": "tosijs-ui — robust, standards-based web components", "description": "A small, fast library of standards-based web components (data tables, dialogs, forms, rich text, carousels, and more) built on tosijs — smaller and faster than a React app, with no virtual DOM.", "keywords": "web components, custom elements, tosijs, data table, dialog, form, rich text, no virtual dom" }-->
|
|
3
|
+
<!--{ "pin": "top", "layout": "full-width", "headTitle": "tosijs-ui — robust, standards-based web components", "description": "A small, fast library of standards-based web components (data tables, dialogs, forms, rich text, carousels, and more) built on tosijs — smaller and faster than a React app, with no virtual DOM.", "keywords": "web components, custom elements, tosijs, data table, dialog, form, rich text, no virtual dom" }-->
|
|
4
4
|
|
|
5
5
|
[ui.tosijs.net live demo](https://ui.tosijs.net) | [tosijs](https://tosijs.net) | [discord](https://discord.gg/ramJ9rgky5) | [github](https://github.com/tonioloewald/tosijs-ui#readme) | [npm](https://www.npmjs.com/package/tosijs-ui)
|
|
6
6
|
|
package/bin/release-notes.ts
CHANGED
|
@@ -159,9 +159,16 @@ if (has('check')) {
|
|
|
159
159
|
)
|
|
160
160
|
for (const e of c.evidence) console.error(` ${e}`)
|
|
161
161
|
console.error(
|
|
162
|
+
/*
|
|
163
|
+
Advice you can actually follow. The blocking branch used to offer "explain in the notes
|
|
164
|
+
why this does not reach anyone" — which `bumpConcerns` cannot honour, because it never
|
|
165
|
+
sees the notes. A gate whose stated remedy is unrunnable leaves you re-reading your own
|
|
166
|
+
tool to find out what it wants, which is the same defect this repo fixed in the tunnel's
|
|
167
|
+
401 page.
|
|
168
|
+
*/
|
|
162
169
|
blocking
|
|
163
|
-
? `\n Cut a minor
|
|
164
|
-
: `\n Check it is genuinely additive.\n`
|
|
170
|
+
? `\n Cut a minor — this is not something the notes can talk you out of.\n`
|
|
171
|
+
: `\n Check it is genuinely additive, and say what moved in the notes.\n`
|
|
165
172
|
)
|
|
166
173
|
}
|
|
167
174
|
|
package/dist/data-table.d.ts
CHANGED
package/dist/data-table.js
CHANGED
|
@@ -1311,6 +1311,11 @@ export class TosiTable extends WebComponent {
|
|
|
1311
1311
|
};
|
|
1312
1312
|
_array = [];
|
|
1313
1313
|
_columns = null;
|
|
1314
|
+
/*
|
|
1315
|
+
The columns the CURRENT DOM was built from — see `setColumnWidths`. Not `_columns`: that one
|
|
1316
|
+
changes the instant a caller assigns it, which is exactly the window this exists to cover.
|
|
1317
|
+
*/
|
|
1318
|
+
_renderedCols = [];
|
|
1314
1319
|
_filter = passThru;
|
|
1315
1320
|
_sort;
|
|
1316
1321
|
_rowGroupId = null;
|
|
@@ -2144,9 +2149,29 @@ export class TosiTable extends WebComponent {
|
|
|
2144
2149
|
this.style.cursor = column !== undefined ? 'col-resize' : '';
|
|
2145
2150
|
};
|
|
2146
2151
|
resizeColumn = (event) => {
|
|
2147
|
-
const
|
|
2148
|
-
if (
|
|
2149
|
-
|
|
2152
|
+
const grabbed = this.getColumn(event);
|
|
2153
|
+
if (grabbed !== undefined) {
|
|
2154
|
+
/*
|
|
2155
|
+
The drag follows the column BY PROP, not by holding the object it started on.
|
|
2156
|
+
|
|
2157
|
+
A drag used to capture the `ColumnOptions` object and mutate it for its whole life. That
|
|
2158
|
+
object stops being the table's the moment a caller assigns a new `columns` array — which
|
|
2159
|
+
the reporting app does on a page-size change, and a page-size change is a very likely
|
|
2160
|
+
thing to happen while someone is dragging a column edge. From then on the drag wrote
|
|
2161
|
+
widths into an orphan: pointer moving, column not moving, nothing reported anywhere.
|
|
2162
|
+
|
|
2163
|
+
Each step re-resolves from the current set instead. If the column is gone the drag ends
|
|
2164
|
+
(its premise went with it); if the object was replaced but the prop survives, the drag
|
|
2165
|
+
continues, rebased on the new width — `origWidth + dx` against a width the caller has
|
|
2166
|
+
since changed would make the column jump by the difference.
|
|
2167
|
+
|
|
2168
|
+
(Firefox needed a separate fix before any of this was observable there — it turned the
|
|
2169
|
+
first drag on a fresh page into a text selection. See track-drag.ts, and #107.)
|
|
2170
|
+
*/
|
|
2171
|
+
const prop = grabbed.prop;
|
|
2172
|
+
let anchor = grabbed;
|
|
2173
|
+
let anchorWidth = Number(grabbed.width);
|
|
2174
|
+
let anchorDx = 0;
|
|
2150
2175
|
const isTouchEvent = event.touches !== undefined;
|
|
2151
2176
|
const touchIdentifier = isTouchEvent
|
|
2152
2177
|
? event.touches[0].identifier
|
|
@@ -2158,9 +2183,16 @@ export class TosiTable extends WebComponent {
|
|
|
2158
2183
|
if (touch === undefined) {
|
|
2159
2184
|
return true;
|
|
2160
2185
|
}
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2163
|
-
|
|
2186
|
+
const live = this.columns.find((c) => c.prop === prop);
|
|
2187
|
+
if (!live)
|
|
2188
|
+
return true;
|
|
2189
|
+
if (live !== anchor) {
|
|
2190
|
+
anchor = live;
|
|
2191
|
+
anchorWidth = Number(live.width);
|
|
2192
|
+
anchorDx = dx;
|
|
2193
|
+
}
|
|
2194
|
+
const width = anchorWidth + (dx - anchorDx);
|
|
2195
|
+
live.width = width > this.minColumnWidth ? width : this.minColumnWidth;
|
|
2164
2196
|
this.setColumnWidths();
|
|
2165
2197
|
if (event.type === 'mouseup') {
|
|
2166
2198
|
return true;
|
|
@@ -2611,7 +2643,30 @@ export class TosiTable extends WebComponent {
|
|
|
2611
2643
|
this.addEventListener('keydown', this.handleKeyNav);
|
|
2612
2644
|
}
|
|
2613
2645
|
setColumnWidths() {
|
|
2614
|
-
|
|
2646
|
+
/*
|
|
2647
|
+
The widths describe the columns the DOM ACTUALLY HAS, not the ones it is about to get.
|
|
2648
|
+
|
|
2649
|
+
`set columns` updates `_columns` synchronously but defers the rebuild to `queueRender()`,
|
|
2650
|
+
so between the assignment and the frame, `visibleColumns` already answers for a column set
|
|
2651
|
+
no cell in the document belongs to. Anything that recomputes widths in that window — a
|
|
2652
|
+
resize drag, a scroll handler, a host reacting to a page-size change — used to write a
|
|
2653
|
+
template for the NEW columns over the OLD cells.
|
|
2654
|
+
|
|
2655
|
+
That reads as the header disagreeing with the body (#102), and the reason it looks like
|
|
2656
|
+
disagreement rather than simple breakage is worth knowing: with fewer tracks than cells,
|
|
2657
|
+
CSS Grid auto-places the surplus into IMPLICIT tracks, which size to content. Header text
|
|
2658
|
+
and body text differ, so the two rows resolve those tracks to different widths and the
|
|
2659
|
+
columns visibly step apart.
|
|
2660
|
+
|
|
2661
|
+
`_renderedCols` is the set the last render built the cells from; `render()` refreshes it
|
|
2662
|
+
immediately before calling here, so a legitimate column change still takes effect on the
|
|
2663
|
+
very frame that installs its cells. Column objects are shared by reference, so a resize
|
|
2664
|
+
drag that mutates `column.width` is still reflected — it is the SHAPE that is pinned to
|
|
2665
|
+
the DOM, not the widths.
|
|
2666
|
+
*/
|
|
2667
|
+
const cols = this._renderedCols.length
|
|
2668
|
+
? this._renderedCols
|
|
2669
|
+
: this.visibleColumns;
|
|
2615
2670
|
const total = cols.reduce((w, c) => w + c.width, 0);
|
|
2616
2671
|
/*
|
|
2617
2672
|
`fullWidthHeader` lets ONE column absorb whatever width is left over, so a table narrower
|
|
@@ -2886,6 +2941,8 @@ export class TosiTable extends WebComponent {
|
|
|
2886
2941
|
if (cols.length === 0)
|
|
2887
2942
|
return;
|
|
2888
2943
|
const stickyInfo = this.computeStickyInfo(cols);
|
|
2944
|
+
// Before setColumnWidths, so the widths describe the cells this render is about to build.
|
|
2945
|
+
this._renderedCols = cols;
|
|
2889
2946
|
this.style.setProperty('--tosi-table-row-height', this.rowHeight > 0 ? `${this.rowHeight}px` : 'auto');
|
|
2890
2947
|
this.setColumnWidths();
|
|
2891
2948
|
// Build the regions. Header + optional pinned tbodies are siblings
|
package/dist/doc-browser.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface Doc {
|
|
|
22
22
|
path: string;
|
|
23
23
|
pin?: string;
|
|
24
24
|
hidden?: boolean;
|
|
25
|
+
/** `'full-width'` drops the reading measure. See `Doc.layout` in docs.ts. */
|
|
26
|
+
layout?: 'full-width';
|
|
25
27
|
testStatus?: 'passed' | 'failed' | 'pending';
|
|
26
28
|
bakes?: Array<[string, {
|
|
27
29
|
dialect: string;
|
package/dist/doc-browser.js
CHANGED
|
@@ -711,6 +711,39 @@ export function createDocBrowser(options) {
|
|
|
711
711
|
bakes: doc.bakes ? new Map(doc.bakes) : undefined,
|
|
712
712
|
});
|
|
713
713
|
}
|
|
714
|
+
/*
|
|
715
|
+
Keep the page's layout in step with the doc on SPA navigation.
|
|
716
|
+
|
|
717
|
+
The static generator stamps `data-layout` so the first paint is already right without JS;
|
|
718
|
+
this is the other half — navigating from a full-width page to a prose one must put the
|
|
719
|
+
reading column back, and vice versa. Written on every showDoc rather than only on change,
|
|
720
|
+
because the adopted first page also arrives here and the attribute it was served with must
|
|
721
|
+
survive rather than be assumed.
|
|
722
|
+
*/
|
|
723
|
+
/*
|
|
724
|
+
On the SAME element the static generator stamps — `<tosi-doc-system>` — not on
|
|
725
|
+
`docContent.parentElement`.
|
|
726
|
+
|
|
727
|
+
Those are not the same node, which cost a debugging round: hydration wraps the adopted
|
|
728
|
+
`<article class="doc-content">` in a div, so the parent is that div while the generator's
|
|
729
|
+
attribute sits on the custom element above it. Writing to the div left the real host still
|
|
730
|
+
reading `full-width`, so navigating from the wide page to a prose one silently stayed wide —
|
|
731
|
+
and the test that only checked the wide direction would have passed.
|
|
732
|
+
|
|
733
|
+
A memory-routed browser is EXCLUDED deliberately. That mode exists so an embedded or nested
|
|
734
|
+
browser cannot reach out and change the page hosting it, and `closest()` from inside one
|
|
735
|
+
finds the OUTER doc-system — the host's, not its own. Same reasoning as its history/location
|
|
736
|
+
ban; a nested doc-browser rewriting its host's layout is the same bug wearing a hat.
|
|
737
|
+
*/
|
|
738
|
+
if (!memoryRouting) {
|
|
739
|
+
const layoutHost = docContent.closest('tosi-doc-system');
|
|
740
|
+
if (layoutHost) {
|
|
741
|
+
if (doc.layout)
|
|
742
|
+
layoutHost.setAttribute('data-layout', doc.layout);
|
|
743
|
+
else
|
|
744
|
+
layoutHost.removeAttribute('data-layout');
|
|
745
|
+
}
|
|
746
|
+
}
|
|
714
747
|
rewriteContentLinks();
|
|
715
748
|
// Stamp each example with its source file (for the source↔doc map). doc.path
|
|
716
749
|
// is the extracted file (.md, or a source file with doc comments).
|
|
@@ -98,6 +98,16 @@ export function docSystemStyleSpec(theme = {}) {
|
|
|
98
98
|
// below — if these two ever disagree, the page jumps on hydration.
|
|
99
99
|
_docContentMaxWidth: '44em',
|
|
100
100
|
},
|
|
101
|
+
/*
|
|
102
|
+
Per-page layout, driven by `layout:` in a doc's metadata (see `Doc.layout`).
|
|
103
|
+
|
|
104
|
+
Both are variable overrides rather than new layout rules, which is what keeps them cheap
|
|
105
|
+
and non-invasive: the measure is already `--doc-content-max-width`, so a page opts out by
|
|
106
|
+
changing one value. Nothing here runs for a corpus that never sets `layout`.
|
|
107
|
+
*/
|
|
108
|
+
'tosi-doc-system[data-layout="full-width"]': {
|
|
109
|
+
_docContentMaxWidth: 'none',
|
|
110
|
+
},
|
|
101
111
|
'.darkmode': {
|
|
102
112
|
...invertLuminance(colors),
|
|
103
113
|
/*
|
|
@@ -221,6 +221,21 @@ Deliberately a small, named list rather than a clever heuristic: it has to be ob
|
|
|
221
221
|
release was flagged, and obvious how to add to it. Matched as substrings of repo-relative
|
|
222
222
|
paths.
|
|
223
223
|
*/
|
|
224
|
+
/*
|
|
225
|
+
Paths where a change is worth a second look before it ships as a patch.
|
|
226
|
+
|
|
227
|
+
These are all DEVELOPMENT tooling — a dev server, a tunnel, a deploy script. None of it runs in
|
|
228
|
+
an end user's browser as part of an adopter's app, which is why touching them WARNS rather than
|
|
229
|
+
blocks. The distinction is the point: a security-relevant change to code an adopter ships to
|
|
230
|
+
their users deserves a gate that stops the release, and a change to a tool a developer chooses
|
|
231
|
+
to run on their own machine deserves a sentence in the notes.
|
|
232
|
+
|
|
233
|
+
It blocked, briefly, and that was wrong in a way worth recording. A gate that halts a release
|
|
234
|
+
over dev-only tooling spends the maintainer's attention on the release with the least at stake,
|
|
235
|
+
and the reliable outcome of a guard that cries wolf is that it gets overridden or deleted —
|
|
236
|
+
taking the case it was RIGHT about (#79, a loosened dev-server default shipped as a patch) with
|
|
237
|
+
it. Warning keeps that signal and stops charging for it.
|
|
238
|
+
*/
|
|
224
239
|
export const SENSITIVE_PATHS = [
|
|
225
240
|
'dev-auth',
|
|
226
241
|
'build-lock',
|
|
@@ -264,8 +279,8 @@ export function bumpConcerns(opts) {
|
|
|
264
279
|
const sensitive = changedPaths.filter((p) => SENSITIVE_PATHS.some((s) => p.includes(s)));
|
|
265
280
|
if (sensitive.length) {
|
|
266
281
|
out.push({
|
|
267
|
-
level: '
|
|
268
|
-
reason: 'a
|
|
282
|
+
level: 'warn',
|
|
283
|
+
reason: 'a dev-tooling path with a security surface changed in a PATCH — say what moved, under its own heading, in a release people will read',
|
|
269
284
|
evidence: sensitive.slice(0, 8),
|
|
270
285
|
});
|
|
271
286
|
}
|
|
@@ -184,6 +184,23 @@ export declare function mayWriteSource(opts: {
|
|
|
184
184
|
/** does the request carry a live session cookie? */
|
|
185
185
|
hasValidSession: boolean;
|
|
186
186
|
}): boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Who may attach the haltija dev channel — i.e. let an agent DRIVE this page.
|
|
189
|
+
*
|
|
190
|
+
* Deliberately the same rule as `mayWriteSource`, delegating rather than restating it, because
|
|
191
|
+
* driving a page with an agent is at least as powerful as writing source: an agent that can
|
|
192
|
+
* evaluate script in the page can read whatever the page can read and act as the logged-in
|
|
193
|
+
* user. A weaker gate here would quietly become the weakest link, and a *parallel* copy of the
|
|
194
|
+
* rule would be a second thing to keep in step — which is how the two drift.
|
|
195
|
+
*
|
|
196
|
+
* It is a separate NAME because the capability is separate: if the policies ever need to
|
|
197
|
+
* diverge, this is the one place to change, and every caller already says which it means.
|
|
198
|
+
*/
|
|
199
|
+
export declare function mayDriveWithAgent(opts: {
|
|
200
|
+
viaTunnel: boolean;
|
|
201
|
+
peer?: string | null;
|
|
202
|
+
hasValidSession: boolean;
|
|
203
|
+
}): boolean;
|
|
187
204
|
/**
|
|
188
205
|
* THE loopback test. dev-server re-exports this as `isLoopbackAddress`; it used to
|
|
189
206
|
* carry its own byte-identical copy.
|
|
@@ -417,6 +417,21 @@ export function mayWriteSource(opts) {
|
|
|
417
417
|
return opts.hasValidSession;
|
|
418
418
|
return isLoopbackAddressForAuth(opts.peer);
|
|
419
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* Who may attach the haltija dev channel — i.e. let an agent DRIVE this page.
|
|
422
|
+
*
|
|
423
|
+
* Deliberately the same rule as `mayWriteSource`, delegating rather than restating it, because
|
|
424
|
+
* driving a page with an agent is at least as powerful as writing source: an agent that can
|
|
425
|
+
* evaluate script in the page can read whatever the page can read and act as the logged-in
|
|
426
|
+
* user. A weaker gate here would quietly become the weakest link, and a *parallel* copy of the
|
|
427
|
+
* rule would be a second thing to keep in step — which is how the two drift.
|
|
428
|
+
*
|
|
429
|
+
* It is a separate NAME because the capability is separate: if the policies ever need to
|
|
430
|
+
* diverge, this is the one place to change, and every caller already says which it means.
|
|
431
|
+
*/
|
|
432
|
+
export function mayDriveWithAgent(opts) {
|
|
433
|
+
return mayWriteSource(opts);
|
|
434
|
+
}
|
|
420
435
|
/**
|
|
421
436
|
* THE loopback test. dev-server re-exports this as `isLoopbackAddress`; it used to
|
|
422
437
|
* carry its own byte-identical copy.
|
|
@@ -85,6 +85,28 @@ export declare function resolveLimitMb(configMb: number | undefined, envMb: stri
|
|
|
85
85
|
export { isLoopbackAddress };
|
|
86
86
|
export declare function haltijaIsDrivable(stdout: string): boolean;
|
|
87
87
|
export declare function haltijaLoaderSnippet(httpsPort: number): string;
|
|
88
|
+
/**
|
|
89
|
+
* The SAME-ORIGIN loader, for a page reached over the tunnel (`haltijaDev: 'tunnel'`).
|
|
90
|
+
*
|
|
91
|
+
* Over the tunnel `localhost` is the HEADSET, so every hardcoded `https://localhost:8701` in
|
|
92
|
+
* the upstream chain points at the wrong machine. This skips that chain entirely rather than
|
|
93
|
+
* asking haltija to change: `dev.js` and `inject.js` exist only to set `__haltija_config__`
|
|
94
|
+
* and load `component.js`, and both carry their own localhost gates. `component.js` has NO
|
|
95
|
+
* such gate — it reads `__haltija_config__.serverUrl` and falls back to localhost only when
|
|
96
|
+
* the config is absent — so setting the config ourselves and loading the component directly
|
|
97
|
+
* is both sufficient and the only part that can work remotely.
|
|
98
|
+
*
|
|
99
|
+
* `serverUrl` is derived from the page's own origin at runtime, not baked in, because the
|
|
100
|
+
* tunnel hostname is not known when this string is built and can differ per request.
|
|
101
|
+
*
|
|
102
|
+
* `self===top` is kept from the localhost loader: an iframe should not attach a second widget,
|
|
103
|
+
* and the doc-test runner executes pages in hidden iframes.
|
|
104
|
+
*/
|
|
105
|
+
export declare function haltijaTunnelLoaderSnippet(): string;
|
|
106
|
+
/** Same-origin paths the tunnel bridge serves. One place, so the loader and the routes agree. */
|
|
107
|
+
export declare const HALTIJA_BRIDGE_PREFIX = "/__haltija/";
|
|
108
|
+
export declare const HALTIJA_BRIDGE_WS = "/__haltija/ws";
|
|
109
|
+
export declare const HALTIJA_BRIDGE_COMPONENT = "/__haltija/component.js";
|
|
88
110
|
/** br if the client takes it, else gzip, else nothing. */
|
|
89
111
|
export declare function negotiateEncoding(accept: string | null): 'br' | 'gzip' | null;
|
|
90
112
|
/** Is this worth compressing? Already-compressed formats only get bigger. */
|