tosijs-ui 1.12.4 → 1.12.6
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 +490 -0
- package/bin/tunnel.ts +26 -0
- package/dist/code-editor.d.ts +3 -0
- package/dist/code-editor.js +59 -0
- package/dist/data-table.d.ts +1 -0
- package/dist/data-table.js +41 -1
- package/dist/diff.d.ts +84 -0
- package/dist/diff.js +307 -12
- package/dist/doc-browser.js +50 -10
- package/dist/doc-system/release-notes.d.ts +0 -1
- package/dist/doc-system/release-notes.js +19 -1
- package/dist/doc-system/render.d.ts +1 -1
- package/dist/doc-system/render.js +28 -1
- package/dist/doc-system/site/build-lock.d.ts +16 -0
- package/dist/doc-system/site/build-lock.js +20 -0
- package/dist/doc-system/site/debug-sink.d.ts +39 -0
- package/dist/doc-system/site/debug-sink.js +72 -0
- package/dist/doc-system/site/dev-auth.d.ts +47 -2
- package/dist/doc-system/site/dev-auth.js +70 -3
- package/dist/doc-system/site/dev-server.d.ts +20 -0
- package/dist/doc-system/site/dev-server.js +219 -19
- package/dist/doc-system/site/docs.d.ts +22 -0
- package/dist/doc-system/site/docs.js +43 -2
- package/dist/doc-system/site/index.d.ts +2 -0
- package/dist/doc-system/site/index.js +13 -0
- package/dist/doc-system/site/invite-page.d.ts +20 -0
- package/dist/doc-system/site/invite-page.js +59 -0
- package/dist/doc-system/site/orchestrator.js +4 -0
- package/dist/doc-system/site/site-config.d.ts +28 -0
- package/dist/doc-system/site/sourcemap-check.d.ts +19 -0
- package/dist/doc-system/site/sourcemap-check.js +39 -0
- package/dist/iife.js +81 -80
- package/dist/iife.js.map +14 -14
- package/dist/live-example/code-transform.d.ts +29 -4
- package/dist/live-example/code-transform.js +62 -6
- package/dist/live-example/component.js +22 -2
- package/dist/live-example/execution.js +3 -3
- package/dist/live-example/test-harness.js +9 -2
- package/dist/notifications.js +13 -3
- package/dist/side-nav.d.ts +9 -0
- package/dist/side-nav.js +36 -4
- 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 +3 -2
- package/tls/create-dev-certs.sh +39 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,495 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.12.6
|
|
4
|
+
|
|
5
|
+
### `<tosi-diff resolvable>` — "do I want to accept this?"
|
|
6
|
+
|
|
7
|
+
The diff viewer can now be operated, not just read. Set `resolvable` and every change gets a
|
|
8
|
+
pair of buttons; `value` is the text those choices imply and `change` fires when one moves. It
|
|
9
|
+
is a review surface for a **proposed** edit — an AI suggestion, a collaborator's revision, a
|
|
10
|
+
file that moved under you while you were editing it.
|
|
11
|
+
|
|
12
|
+
The labels are yours: `originalLabel`/`modifiedLabel`. "Mine"/"Theirs" is git's framing,
|
|
13
|
+
"Current"/"Proposed" suits reviewing a suggestion, and this component's own vocabulary is
|
|
14
|
+
original/modified — none of which is right for everyone, so none of it is hardcoded. Also
|
|
15
|
+
`acceptAll()`, `rejectAll()`, `changeCount`, and a `resolutions` array you can read and write.
|
|
16
|
+
`diffBlocks()` and `resolveDiff()` are exported too, if you want the model without the DOM.
|
|
17
|
+
|
|
18
|
+
The unit of decision is a change **block**, not a line. The LCS walk can emit
|
|
19
|
+
remove/add/remove/add for a single multi-line edit, and offering four independent choices there
|
|
20
|
+
lets a reviewer accept half an edit — producing text neither side wrote.
|
|
21
|
+
|
|
22
|
+
The read-only path is deliberately unchanged: grouping reorders interleaved edits, which is
|
|
23
|
+
better to resolve and a **different rendering** for the two components already shipping this
|
|
24
|
+
one. Additive means additive.
|
|
25
|
+
|
|
26
|
+
Two notes on how this was tested, because **both** bugs that nearly shipped were invisible to
|
|
27
|
+
the unit lane, and for the same underlying reason.
|
|
28
|
+
|
|
29
|
+
The first version rendered **nothing**: `dataset: {…}` in an element creator assigns
|
|
30
|
+
`el.dataset`, a read-only accessor, so `render()` threw and left an empty shadow root.
|
|
31
|
+
Twenty-six green tests sat behind it, every one asserting `value` — which is computed from the
|
|
32
|
+
model and is correct whether or not a single element reaches the DOM. tosijs renders on an
|
|
33
|
+
animation frame and happy-dom runs none, so a unit test that merely _mounts_ an element asserts
|
|
34
|
+
against an empty shadow root; appending hydrates, and `render()` must be called explicitly.
|
|
35
|
+
|
|
36
|
+
The second was worse, because the render-level tests added for the first one _also_ passed. The
|
|
37
|
+
buttons did nothing when clicked, in every browser. `<tosi-diff>` is a shadow-DOM component and
|
|
38
|
+
tosijs delegates `on*` handlers from above the shadow boundary, so a real click is
|
|
39
|
+
**retargeted**: `event.target` is the host, not the button, and `event.target.closest(…)` — the
|
|
40
|
+
idiom this project documents, and which is correct for a light-DOM component — is `null` every
|
|
41
|
+
time. The fix is `composedPath()`, the only one of the two that survives the boundary.
|
|
42
|
+
|
|
43
|
+
The happy-dom test could not see it: it called `.click()` on a node it had already looked up and
|
|
44
|
+
then asserted the value moved. Measured against a deliberately broken build, **30 unit tests
|
|
45
|
+
pass and all 3 browser tests fail** — so `tests/diff-resolve.pw.ts` now clicks what a person
|
|
46
|
+
clicks and reads what a host reads. The rule this leaves behind: **shadow-DOM interaction needs
|
|
47
|
+
a browser test**, not a DOM-shaped unit test.
|
|
48
|
+
|
|
49
|
+
### Selective revert in live examples
|
|
50
|
+
|
|
51
|
+
The diff resolver is wired into `<tosi-example>`'s **View changes**, and into `<tosi-code>`
|
|
52
|
+
generally via a new `diffResolvable` (plus `diffOriginalLabel`/`diffModifiedLabel`).
|
|
53
|
+
|
|
54
|
+
Revert was all-or-nothing, which is the wrong shape for what actually happens: you tried four
|
|
55
|
+
things and three worked. Each change in the diff now carries keep/revert buttons — labelled
|
|
56
|
+
**Source** and **Yours** in an example — defaulting to _keep_, so the feature costs nothing to
|
|
57
|
+
ignore and doing nothing leaves your edit exactly as it was.
|
|
58
|
+
|
|
59
|
+
Choices are applied when the diff **closes**, not as you click, and a `change` event fires if
|
|
60
|
+
the text moved. Writing each choice straight back into `value` would feed the new text into the
|
|
61
|
+
overlay's `modified`, and `<tosi-diff>` resets its decisions when either input changes — so live
|
|
62
|
+
application would wipe the choices making it and renumber the hunks under the pointer.
|
|
63
|
+
|
|
64
|
+
### Fixed: `<tosi-diff>` and notifications ignored dark mode
|
|
65
|
+
|
|
66
|
+
Both hardcoded a light palette — `#fff`/`#222` for the diff, `#fafafa`/`#444` for
|
|
67
|
+
notifications — so on a dark page you got a white block, and in the notification's case the one
|
|
68
|
+
element in the library that appears unbidden over whatever you are looking at was also the one
|
|
69
|
+
that ignored your theme.
|
|
70
|
+
|
|
71
|
+
Both now fall back to the theme (`--background`/`--text-color`) with the old literals as the
|
|
72
|
+
final fallback, so an unthemed page is unchanged and every `--tosi-diff-*` /
|
|
73
|
+
`--notification-*` override still wins exactly as before.
|
|
74
|
+
|
|
75
|
+
The diff needed both halves at once: darkening the surface alone would have been **worse** than
|
|
76
|
+
leaving it, because light text on a solid `#e6ffed` row is unreadable. The add/remove tints are
|
|
77
|
+
now translucent (`color-mix` with `transparent`), so one value reads correctly as a wash over
|
|
78
|
+
either surface.
|
|
79
|
+
|
|
80
|
+
### Fixed: a consumer could not use the build lock ([#117](https://github.com/tonioloewald/tosijs-ui/issues/117), [#118](https://github.com/tonioloewald/tosijs-ui/issues/118))
|
|
81
|
+
|
|
82
|
+
1.12.5 added a build lock that records `pid`, `role`, `root` and `port` per project, so a
|
|
83
|
+
`stop` command can identify what is running instead of `pkill`-ing sibling checkouts. It worked
|
|
84
|
+
— and none of it was reachable from outside the package. `import('tosijs-ui/site')` gave
|
|
85
|
+
`currentHolder === undefined`, and the deep path is blocked by the exports map.
|
|
86
|
+
|
|
87
|
+
`currentHolder`, `describeHolder`, `lockPathFor`, `lockDecision`, `isProcessAlive` and the
|
|
88
|
+
`LockHolder`/`LockDecision` types are now re-exported from `tosijs-ui/site`.
|
|
89
|
+
|
|
90
|
+
The reporter deliberately did **not** re-derive the lock path locally, and was right not to:
|
|
91
|
+
that means copying the FNV-1a hash of the resolved root, and if the hash ever changed the
|
|
92
|
+
consumer would report "nothing running" _while a server runs_. Silently-wrong is the exact
|
|
93
|
+
failure this whole area exists to eliminate; duplicating the algorithm reintroduces it one
|
|
94
|
+
layer out.
|
|
95
|
+
|
|
96
|
+
### haltija floor raised to `^1.12.6`
|
|
97
|
+
|
|
98
|
+
The floor encodes fixes, not a date. 1.12.6 is where a `--private` instance finally gained a
|
|
99
|
+
**lifetime bound** (haltija#39, filed from here after one was found 12 days old at 5.7 GB and
|
|
100
|
+
~150% CPU on a machine at load average 212); where **LAN and Bonjour access started working at
|
|
101
|
+
all** (served scripts handed the browser `localhost:<port>`, and `localhost` in a served script
|
|
102
|
+
means the _browser's_ machine, so a phone was told to connect to itself); where the widget sends
|
|
103
|
+
`X-Haltija-Token`, so a `--token` server and a page that can talk back stopped being mutually
|
|
104
|
+
exclusive; where results carry `paintAgeMs`, making a rAF-starved tab detectable rather than
|
|
105
|
+
silently wrong; and where Electron went 40.6.1 → 43.4.1, clearing two context-isolation
|
|
106
|
+
bypasses in the thing we spawn.
|
|
107
|
+
|
|
108
|
+
(Internal, but worth recording: our own `CLAUDE.md` documented this pin as `^1.6.1` long after
|
|
109
|
+
the code had moved to `^1.11.2` — a stale number in the one place a reader would trust for it.
|
|
110
|
+
Corrected.)
|
|
111
|
+
|
|
112
|
+
### Docs: our haltija gate covers the bridge, not the port
|
|
113
|
+
|
|
114
|
+
The `haltijaDev: 'tunnel'` documentation read as a complete security story — session cookie,
|
|
115
|
+
404-on-unauthorized, relay rather than hole — and a reader could reasonably conclude the whole
|
|
116
|
+
surface was gated. It is not. Enabling `haltijaDev` at all means haltija's **own** port is
|
|
117
|
+
running, and a default instance binds beyond loopback with `Access-Control-Allow-Origin: *` and
|
|
118
|
+
no token, where `POST /terminal/command` reaches a shell. Set `HALTIJA_TOKEN` on a network you
|
|
119
|
+
do not control; it only became genuinely usable in haltija 1.12.6, which is why older advice
|
|
120
|
+
said to skip it.
|
|
121
|
+
|
|
122
|
+
## 1.12.5
|
|
123
|
+
|
|
124
|
+
### Fixed: 49 controls a screen reader announced as "button" and "edit text"
|
|
125
|
+
|
|
126
|
+
Lighthouse scored the doc site 91 on accessibility, and the cause was entirely mechanical.
|
|
127
|
+
`<tosi-table>` builds a column-options button that contains only an icon, and an editable cell
|
|
128
|
+
that is an empty `<input>` — neither has any text to derive an accessible name from. On the
|
|
129
|
+
data-table page alone that is 37 unnamed buttons and 12 unnamed fields. The doc-browser's search
|
|
130
|
+
box made it worse in a quieter way: it had a `placeholder` and nothing else, on **every page of
|
|
131
|
+
every site built with `tosijs-ui/site`**. A placeholder is not a label — it is an unreliable
|
|
132
|
+
accessible name and it vanishes the moment anything is typed.
|
|
133
|
+
|
|
134
|
+
The controls now carry a name: `title` on the column-options button, where the tooltip is a
|
|
135
|
+
genuine win for sighted mouse users too, and `aria-label` on the cells and the search field,
|
|
136
|
+
where a tooltip on every hover would just repeat the column header as noise.
|
|
137
|
+
|
|
138
|
+
To be precise about what `title` buys, because its two halves land on different people: the
|
|
139
|
+
**accessible name** is real and works — `title` feeds the accessible name computation, so
|
|
140
|
+
VoiceOver on iOS/iPadOS announces these controls, as do NVDA and JAWS. The **visible tooltip** is
|
|
141
|
+
mouse-only, and a screen-reader user was never relying on it.
|
|
142
|
+
|
|
143
|
+
No visible label is added, deliberately. The vertical-more glyph is the standard overflow
|
|
144
|
+
affordance and reads as one; spelling it out on every column header would be clutter that makes
|
|
145
|
+
the header worse for everyone in order to restate what the icon already says. The defect was that
|
|
146
|
+
the control had no name in the accessibility tree, and that is what is fixed.
|
|
147
|
+
|
|
148
|
+
Localized tables get this through the existing `data-tosi-localized` directive rather than a
|
|
149
|
+
`<tosi-localized>` child, because an `<input>` cannot contain a child element. Add
|
|
150
|
+
**`Column Options`** to your localized strings; the editable cells reuse the column names you
|
|
151
|
+
already localize, so an editable table wants those present even if you are happy with
|
|
152
|
+
untranslated headers.
|
|
153
|
+
|
|
154
|
+
The regression test is in `tests/a11y-names.pw.ts`, and its first version **passed with both
|
|
155
|
+
fixes reverted** — it waited for `buttons.length > 2`, which the nav chrome satisfies on its own,
|
|
156
|
+
and then asserted an empty list against a page that had not built the controls yet. `toEqual([])`
|
|
157
|
+
is trivially true when there is nothing to check. Every assertion there now has a positive
|
|
158
|
+
precondition that the things under test exist, and the mutation that removes the fixes fails it
|
|
159
|
+
by name.
|
|
160
|
+
|
|
161
|
+
Not fixed, and not ours: the two images without `alt` on the 3D page come from Babylon's own
|
|
162
|
+
default loading screen.
|
|
163
|
+
|
|
164
|
+
### Fixed: on a narrow screen, tapping a nav link left you looking at the nav
|
|
165
|
+
|
|
166
|
+
A regression from 1.12.3's full-screen work, reported from a phone-width window. The sidebar
|
|
167
|
+
fills the screen, you tap a link, the URL changes — and the nav stays up. The article you asked
|
|
168
|
+
for never appears.
|
|
169
|
+
|
|
170
|
+
Leaving full-screen had been written as the tidy mirror of entering it: `navVisible = true`. But
|
|
171
|
+
that setter's show-the-nav branch writes `contentVisible = false`, and it ran on **every**
|
|
172
|
+
navigation to a non-full-screen page — immediately after the nav click handler had set
|
|
173
|
+
`contentVisible = true` to do precisely the thing the reader asked for. The layout code was
|
|
174
|
+
overwriting a decision that belongs to the person reading.
|
|
175
|
+
|
|
176
|
+
The two directions are asymmetric now, and that is the point rather than an oversight: entering
|
|
177
|
+
full-screen states what it needs, and stepping out of it releases only what it claimed
|
|
178
|
+
(`alwaysCompact`). Which pane is showing at a narrow width is not the layout's business.
|
|
179
|
+
|
|
180
|
+
**Nothing in the test suite could have caught this.** Every layout test ran at 1400px, where
|
|
181
|
+
normal mode shows the nav and the content together and `contentVisible` has no visible effect —
|
|
182
|
+
the bug was invisible at every width the tests used. There are now narrow-viewport tests for
|
|
183
|
+
both directions, and the mutation that restores the old line fails them.
|
|
184
|
+
|
|
185
|
+
### `<tosi-sidenav>` no longer races a timer to hide its own load-time animation
|
|
186
|
+
|
|
187
|
+
Its defaults are the compact arrangement — 50/50 columns, margin -100% — so the first layout pass
|
|
188
|
+
moves it to wherever it actually belongs. With a transition live, that move _animates_: the
|
|
189
|
+
sidebar visibly slides into place on load.
|
|
190
|
+
|
|
191
|
+
The suppression was `setTimeout(…, 250)`, which is a race rather than a rule — it assumes the
|
|
192
|
+
first successful layout pass lands within 250ms of the element upgrading. Measured here it lands
|
|
193
|
+
~70ms after (chromium 236→297ms, webkit 213→291, firefox 229→304), leaving ~175ms spare, which
|
|
194
|
+
is exactly why this never reproduced locally. But `handleResize` returns early while
|
|
195
|
+
`offsetParent` is null, so on a bigger corpus or a slower device the first pass can be deferred
|
|
196
|
+
past the window, and then the animation is visible.
|
|
197
|
+
|
|
198
|
+
It now releases on the first pass that actually computes something, one frame later so the
|
|
199
|
+
corrected values paint before transitions come back. No duration is correct for every page; a
|
|
200
|
+
rule needs no number.
|
|
201
|
+
|
|
202
|
+
**This does not eliminate the hydration flash, and it is worth being honest about the limit.** It
|
|
203
|
+
removes one race. What remains is the interval between the element upgrading — at which point its
|
|
204
|
+
shadow defaults apply — and the first layout pass correcting them: ~70ms here, invisible, but a
|
|
205
|
+
real window in which the element is painted wrong rather than not painted. The principled next
|
|
206
|
+
step is for the defaults to _be_ the common case, and it is written up in `TODO.md` rather than
|
|
207
|
+
attempted blind, since the flash does not reproduce on this machine and changing paint defaults
|
|
208
|
+
you cannot observe is how you trade a flash you cannot see for one you can.
|
|
209
|
+
|
|
210
|
+
### Decided: a nav-toggle override does not survive Back
|
|
211
|
+
|
|
212
|
+
Left open in 1.12.3 and now settled — **metadata wins on arrival**. A page declaring
|
|
213
|
+
`layout: "full-screen"` is full-screen however you reach it: a link, Back, or a fresh load. The
|
|
214
|
+
override is a transient viewing choice rather than a property of the document, and since the nav
|
|
215
|
+
button is always offered on such a page, restoring it is one tap.
|
|
216
|
+
|
|
217
|
+
Worth recording that the alternative was real. Browsers restore scroll position on Back, so "put
|
|
218
|
+
the view back how I left it" is an established expectation, not an exotic one. It was rejected
|
|
219
|
+
because the state would have to live in `history.state`, which makes the same URL behave
|
|
220
|
+
differently arrived-at-by-Back than clicked — trading the current surprise for a new one — and
|
|
221
|
+
adds state to a path that has none.
|
|
222
|
+
|
|
223
|
+
No behaviour change; the tests that pinned it now say it is settled rather than pending.
|
|
224
|
+
|
|
225
|
+
### The invite screen has a code box, so an installed PWA can let itself back in ([#75](https://github.com/tonioloewald/tosijs-ui/issues/75))
|
|
226
|
+
|
|
227
|
+
The screen offered only a command to run _on the machine hosting the server_. The devices a
|
|
228
|
+
tunnel exists for have no such keyboard — and an installed PWA has no address bar either, because
|
|
229
|
+
iOS gives a Home Screen app its own cookie jar and its own launch URL. An unauthenticated launch
|
|
230
|
+
was a black rectangle with no way out of it.
|
|
231
|
+
|
|
232
|
+
It now offers the 7-character code box directly: the same affordance as the `dev.tosijs.net`
|
|
233
|
+
landing page, moved to where it is actually needed. A plain GET form produces `/?t=CODE`, which is
|
|
234
|
+
the path the link already takes, so this needed no new server code. The input disables
|
|
235
|
+
autocapitalize, autocorrect and spellcheck, because the code is case-insensitive and a phone
|
|
236
|
+
keyboard's help is noise; there is a viewport meta, because a code box you cannot hit is not much
|
|
237
|
+
better than none.
|
|
238
|
+
|
|
239
|
+
**What this deliberately does not attempt** is carrying a session across the install. The
|
|
240
|
+
separate cookie jar is a privacy feature rather than a bug, and the issue's suggested "don't
|
|
241
|
+
redirect" fix would bake a live credential into a home-screen icon — working once, then failing
|
|
242
|
+
forever with no address bar to correct it. Seven characters is the better trade.
|
|
243
|
+
|
|
244
|
+
### A leading metadata comment is no longer the page title ([#100](https://github.com/tonioloewald/tosijs-ui/issues/100))
|
|
245
|
+
|
|
246
|
+
The title came from line one, literally. A file opening with its metadata block — the documented
|
|
247
|
+
way to set `order` or `parent` — published under the title `<!--{ "order": 2 }-->`, while `order`
|
|
248
|
+
itself parsed correctly. So the metadata visibly worked and the title visibly did not, which is a
|
|
249
|
+
confusing pair to be handed. Hit while adding `CHANGELOG.md` and `Migration.md` to a doc site,
|
|
250
|
+
which is precisely where a leading metadata comment is most natural.
|
|
251
|
+
|
|
252
|
+
Leading HTML comments are skipped now, including ones that wrap across lines, and an
|
|
253
|
+
unterminated comment yields no title rather than lifting text out of it — publishing something
|
|
254
|
+
the author had deliberately hidden would be worse than an empty string. One rule, used by both
|
|
255
|
+
call sites that previously had their own copy of the line.
|
|
256
|
+
|
|
257
|
+
No URLs move: slugs derive from the filename, not the title, so an affected page gets a correct
|
|
258
|
+
title at the address it already had.
|
|
259
|
+
|
|
260
|
+
**The other half of that issue was already fixed**: `docPaths` entries have been in the dev
|
|
261
|
+
server's watch set since #49, so editing a root `Migration.md` does trigger a rebuild.
|
|
262
|
+
|
|
263
|
+
### `debugSink` — telemetry back from a page that is not on this machine ([#99](https://github.com/tonioloewald/tosijs-ui/issues/99))
|
|
264
|
+
|
|
265
|
+
`haltijaDev` is localhost-gated for good reason: an injected drive-my-browser channel reachable
|
|
266
|
+
from the network is RCE-adjacent. But that leaves a page served to a headset or a phone with no
|
|
267
|
+
way to say **anything** — and in a WebXR session there is no console and `requestAnimationFrame`
|
|
268
|
+
is suspended, so every usual readback path is gone at once. The remaining channel is a human
|
|
269
|
+
reading an in-scene panel aloud.
|
|
270
|
+
|
|
271
|
+
`debugSink: true` accepts `POST /__debug-sink` and appends one line per event to a JSONL file
|
|
272
|
+
whose path it prints at startup, so an agent can `tail -f` it:
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
navigator.sendBeacon('/__debug-sink', JSON.stringify({ t: Date.now(), tag, ...event }))
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`sendBeacon` is the point — it needs no headers, reads no response, and survives the page going
|
|
279
|
+
away.
|
|
280
|
+
|
|
281
|
+
**Why this is reachable off-loopback when `/__docstore/source` is not.** It appends opaque bytes
|
|
282
|
+
to a scratch file, outside the repo, that the build never reads and nothing serves back. No code
|
|
283
|
+
runs; nothing is reconfigured. The source endpoint is remote code execution by design and stays
|
|
284
|
+
gated. This is a log.
|
|
285
|
+
|
|
286
|
+
Still **off by default**: an unauthenticated write endpoint on a LAN-reachable server should be a
|
|
287
|
+
decision you made rather than one you inherited. Bounded so a looping page cannot fill a disk —
|
|
288
|
+
bodies over 64 KB refused (413), the file stops at 32 MB (507). Newlines in a payload are
|
|
289
|
+
escaped, because one event must be one line or `tail -f` shows a split event and the reader
|
|
290
|
+
cannot tell.
|
|
291
|
+
|
|
292
|
+
### The dev cert covers the machine's LAN IP ([#92](https://github.com/tonioloewald/tosijs-ui/issues/92))
|
|
293
|
+
|
|
294
|
+
Every name the cert carried was useful only _on_ this machine, except `<host>.local` — which is
|
|
295
|
+
precisely the one another device is most likely to fail to resolve. Reported from a Quest:
|
|
296
|
+
`tosi.local` resolved to **IPv6 only**, including a link-local `fe80::…` a headset gets and
|
|
297
|
+
cannot route, while the LAN IP worked. The host never sees it, because it resolves mDNS
|
|
298
|
+
natively, so it fails only on the devices the name was added for.
|
|
299
|
+
|
|
300
|
+
Private ranges only (10/8, 172.16/12, 192.168/16) — a public address on a dev cert would be a
|
|
301
|
+
name this machine has no business claiming. A DHCP lease change moves the address and the cert
|
|
302
|
+
does not follow; the script says so, and prints the URLs another device can use.
|
|
303
|
+
|
|
304
|
+
Note this fixes the **name mismatch**, not the untrusted CA: mkcert's root still is not on the
|
|
305
|
+
headset, so you either install it there or use the tunnel, which has a real certificate.
|
|
306
|
+
|
|
307
|
+
### A dropped session says the server restarted, instead of blaming your cookies ([#114](https://github.com/tonioloewald/tosijs-ui/issues/114))
|
|
308
|
+
|
|
309
|
+
Sessions live in memory and die with the process. That is the design, not an oversight: a
|
|
310
|
+
credential should not outlive the thing that granted it, and nothing about a session — or the
|
|
311
|
+
token exchanged for it — is ever written to disk. **That has not changed and will not.**
|
|
312
|
+
|
|
313
|
+
What was wrong is that a cookie from a previous run and a cookie the server had never seen
|
|
314
|
+
produced the _identical_ screen. So a reader whose dev server had restarted was told "invite
|
|
315
|
+
links expire", and reasonably concluded their cookie was expiring — the one explanation the
|
|
316
|
+
evidence ruled out. The reporter's own words: the misleading failure "is the part I would most
|
|
317
|
+
want fixed".
|
|
318
|
+
|
|
319
|
+
The cookie now carries a per-process boot id (`<bootId>.<token>`), so the next process can tell a
|
|
320
|
+
cookie it _issued_ from one it has never seen. Both are still refused, identically; only the
|
|
321
|
+
sentence differs:
|
|
322
|
+
|
|
323
|
+
> **The dev server restarted, so your session ended with it.** Sessions are held in memory on
|
|
324
|
+
> purpose — they never outlive the process that issued them, and they are never written to disk.
|
|
325
|
+
> Your browser still holds the cookie; there is simply nothing on this side to match it to.
|
|
326
|
+
|
|
327
|
+
The prefix is not a credential and admits nobody: a cookie with the right run id and a wrong
|
|
328
|
+
token is refused exactly as before, and there is a test asserting that. An absent or malformed
|
|
329
|
+
cookie is _not_ reported as a restart — claiming one we cannot evidence would be its own lie.
|
|
330
|
+
|
|
331
|
+
### A bundle that references an unserved sourcemap now says so at build time ([#103](https://github.com/tonioloewald/tosijs-ui/issues/103))
|
|
332
|
+
|
|
333
|
+
`--sourcemap=linked` appends a `sourceMappingURL` comment, so devtools fetches that file on every
|
|
334
|
+
load. When the map is not in the served root the fetch fails — in exactly the session where
|
|
335
|
+
someone is reading the console carefully for something else. It cost the reporter a wrong
|
|
336
|
+
hypothesis while chasing a slow load.
|
|
337
|
+
|
|
338
|
+
The uglier half was already fixed by #116 in this same release: the SPA fallback used to answer
|
|
339
|
+
`.map` requests with the **HTML shell**, so devtools received a web page where a JSON map should
|
|
340
|
+
be. Missing assets 404 now.
|
|
341
|
+
|
|
342
|
+
The remaining half is a build-time warning naming the file and where it was expected. It checks
|
|
343
|
+
what is **served**, not what was built — those differ whenever `bundleOutDir` is set, and the
|
|
344
|
+
copy across is best-effort by design, so trusting the step is what let this ship. It warns rather
|
|
345
|
+
than fails: a map is a debugging convenience and no site should fail to build without one.
|
|
346
|
+
|
|
347
|
+
The check is a pure, tested function rather than an inline block, because the branch it lives in
|
|
348
|
+
is one **this repo's own build never takes** — our bundle comes from `bin/dev.ts`, not from
|
|
349
|
+
`buildSite`. That is exactly why the bug reached an adopter instead of us.
|
|
350
|
+
|
|
351
|
+
### `resolveUnder` asserts path containment instead of inheriting it ([#96](https://github.com/tonioloewald/tosijs-ui/issues/96))
|
|
352
|
+
|
|
353
|
+
Defence in depth: there is no live traversal, and the reporter checked the classes carefully —
|
|
354
|
+
`/../../../etc/passwd`, percent-encoded and double-encoded forms all stay inside the served root
|
|
355
|
+
today. But they stay inside for two reasons the static handler does not own: the WHATWG URL
|
|
356
|
+
parser collapses `../` before `.pathname` is read, and `.pathname` is never the raw request line.
|
|
357
|
+
Both are properties of the **caller**, so a future call with a path from a config value, a
|
|
358
|
+
manifest entry or a header inherits nothing. The asserted version already lived ten lines away
|
|
359
|
+
in the same file.
|
|
360
|
+
|
|
361
|
+
Now an exported, tested predicate — a test that went through the server would prove nothing,
|
|
362
|
+
since the URL parser makes a violation unreachable from outside.
|
|
363
|
+
|
|
364
|
+
### `bun run stop` — stop THIS project's dev server, not every one on the machine ([#117](https://github.com/tonioloewald/tosijs-ui/issues/117))
|
|
365
|
+
|
|
366
|
+
There was no way to stop a dev server, so what everyone reached for was
|
|
367
|
+
`pkill -f 'bun bin/dev.ts'` — which matches **every** dev server on the machine, because every
|
|
368
|
+
project on this pipeline runs an identical command line. A sibling checkout, a worktree or
|
|
369
|
+
another agent's session died to a command that reads as "restart mine". Observed five times in
|
|
370
|
+
one working day, twice while a tunnel link was in use by a remote reviewer.
|
|
371
|
+
|
|
372
|
+
The record needed for this already existed: the build lock stores `pid`, `port` and `root`,
|
|
373
|
+
per-project, with staleness decided by liveness rather than age. It needed a reader and a
|
|
374
|
+
command. `bun run stop` (or `bun bin/dev.ts --stop`) signals that pid and nothing else, and says
|
|
375
|
+
plainly when nothing is running.
|
|
376
|
+
|
|
377
|
+
### The dev server can no longer die silently ([#91](https://github.com/tonioloewald/tosijs-ui/issues/91))
|
|
378
|
+
|
|
379
|
+
Reported after a ~9.4h run: `pgrep` said running, `curl` got nothing, the last log line was a
|
|
380
|
+
**successful** build, and there was no error, signal or exit anywhere. Every diagnostic a person
|
|
381
|
+
would reach for answered "fine"; it was found the next day because a human said the site was
|
|
382
|
+
down.
|
|
383
|
+
|
|
384
|
+
Worth correcting the report's own diagnosis, because it does not survive the evidence: the idle
|
|
385
|
+
path **announces itself** before exiting, and no such line was logged — so the idle timeout is
|
|
386
|
+
not what stopped that listener. What did is still unknown, which is exactly why the fix targets
|
|
387
|
+
the symptom rather than a cause.
|
|
388
|
+
|
|
389
|
+
The health tick now probes the server's own listener (`GET /__alive`, a bare 204 that
|
|
390
|
+
deliberately does **not** count as activity — a self-check that did would hold the idle timer
|
|
391
|
+
open forever). Two consecutive failures and it exits non-zero with an explanation. A process that
|
|
392
|
+
cannot serve has no value, and the one useful thing left to it is to stop claiming to be alive.
|
|
393
|
+
|
|
394
|
+
### `tosijs-tunnel --link` stops printing links that cannot work ([#94](https://github.com/tonioloewald/tosijs-ui/issues/94))
|
|
395
|
+
|
|
396
|
+
`--link` asks the dev server for a token, which succeeds whether or not a tunnel exists. The URL
|
|
397
|
+
is well-formed and the token is real — it just answers 503, because nothing is listening
|
|
398
|
+
upstream. Printed with exit 0 and no comment, that is indistinguishable from the far side being
|
|
399
|
+
down.
|
|
400
|
+
|
|
401
|
+
It still prints the link, since you may be about to start the tunnel, but it now says so and
|
|
402
|
+
exits non-zero, so a script treating 0 as "a usable link was produced" is telling the truth.
|
|
403
|
+
|
|
404
|
+
### Doc tests survive a context key with a slash in it ([#111](https://github.com/tonioloewald/tosijs-ui/issues/111), [#112](https://github.com/tonioloewald/tosijs-ui/issues/112), [#109](https://github.com/tonioloewald/tosijs-ui/issues/109))
|
|
405
|
+
|
|
406
|
+
Context keys become **function parameter names** — the runner builds each block as
|
|
407
|
+
`new Fn(...contextKeys, body)`. `test-harness.ts` carried its own copy of the rule that turns a
|
|
408
|
+
key into an identifier, `key.replace(/-/g, '')`, which strips hyphens and leaves slashes and
|
|
409
|
+
`@`. So an ordinary specifier like `'tosijs-3d/demo-utils'` became the parameter name
|
|
410
|
+
`tosijs3d/demoutils`, and **every test in that file died before one of them ran** with V8's
|
|
411
|
+
`Arg string terminates parameters early`. The examples on the same page rendered fine, because
|
|
412
|
+
they went through the shared sanitiser — so nothing looked wrong except a red badge.
|
|
413
|
+
|
|
414
|
+
One implementation now, in `code-transform.ts`, used by all five call sites. It also handles
|
|
415
|
+
what the shared version had never needed to: a leading digit (`'3d-tools'`), a reserved word
|
|
416
|
+
(`'class'`), and a key that reduces to nothing.
|
|
417
|
+
|
|
418
|
+
**Two keys that reduce to the same identifier now throw, by name.** Quietly suffixing the second
|
|
419
|
+
would leave one module bound to a name no rewritten import references — undefined at runtime with
|
|
420
|
+
nothing to read. The message says which two keys and what to do:
|
|
421
|
+
|
|
422
|
+
```
|
|
423
|
+
example context keys "tosijs-3d" and "tosijs/3d" both reduce to the identifier "tosijs3d",
|
|
424
|
+
so an import from one of them could not be told from the other. Rename one of the context keys.
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
There is also a structural test asserting **nothing outside `code-transform.ts` sanitises a
|
|
428
|
+
context key**. That is the one that would have caught this: every unit test of the shared helper
|
|
429
|
+
passes with the divergent copy restored, because they exercise the helper rather than the call
|
|
430
|
+
site — which is exactly how the bug survived.
|
|
431
|
+
|
|
432
|
+
### A missing asset 404s instead of impersonating a page ([#116](https://github.com/tonioloewald/tosijs-ui/issues/116))
|
|
433
|
+
|
|
434
|
+
The dev server fell back to the SPA shell for **any** unknown path, so a missing
|
|
435
|
+
`waterbump.png` answered `200 text/html`. Babylon's `Texture` fetched it, failed to decode a web
|
|
436
|
+
page as an image, and substituted its checkerboard — a missing asset presented as a _styling
|
|
437
|
+
choice_, and was complimented before it was diagnosed.
|
|
438
|
+
|
|
439
|
+
Unknown **routes** still get the shell, because client-side routing depends on it. The
|
|
440
|
+
distinguishing signal is a dot in the last path segment: an extension was asked for, so a file
|
|
441
|
+
was meant. `/v1.2/guide` is still a route; `/thing.png` is not. The 404 names the path, because
|
|
442
|
+
a bare "File not found" sends people to the wrong layer — the reporter went through their
|
|
443
|
+
texture pipeline before suspecting the server.
|
|
444
|
+
|
|
445
|
+
### `staticDirs` are watched, so a replaced asset is not served stale ([#110](https://github.com/tonioloewald/tosijs-ui/issues/110))
|
|
446
|
+
|
|
447
|
+
`buildSite` copies `staticDirs` into the output on every build, but nothing watched them — so
|
|
448
|
+
re-exporting a GLB over `static/model.glb` left the previous copy served indefinitely, with no
|
|
449
|
+
error and no hint. Both files exist and only their contents differ, which is the hardest version
|
|
450
|
+
of stale to see, and the workaround people find is touching a source file to provoke a rebuild.
|
|
451
|
+
|
|
452
|
+
Same shape as #49, where `docPaths` was the omission. Verified end to end: a replaced static file
|
|
453
|
+
is now picked up in about half a second.
|
|
454
|
+
|
|
455
|
+
### A lone custom element is no longer wrapped in a paragraph ([#115](https://github.com/tonioloewald/tosijs-ui/issues/115))
|
|
456
|
+
|
|
457
|
+
Reported by tosijs-3d: a doc page whose entire content is one custom element — the case
|
|
458
|
+
`layout: "full-screen"` exists for — got an element that would not fill. marked classifies raw
|
|
459
|
+
HTML by tag name and cannot know whether an unknown tag is block or inline, so
|
|
460
|
+
`<my-editor></my-editor>` alone on a line came out as `<p><my-editor></my-editor></p>`. That
|
|
461
|
+
paragraph is auto-height, so `height: 100%` resolved against a **33px** box inside an 842px
|
|
462
|
+
content area.
|
|
463
|
+
|
|
464
|
+
Measured before and after on the same page: 33px, then **842 of 842** on all three engines. The
|
|
465
|
+
comment promising "a definite height, so a child asking for `height: 100%` gets one" is finally
|
|
466
|
+
true rather than aspirational.
|
|
467
|
+
|
|
468
|
+
The unwrap is deliberately narrow, since a heuristic here earns its own bugs: it fires only when
|
|
469
|
+
a paragraph's entire content is a single element whose tag name contains a hyphen — a custom
|
|
470
|
+
element, the one case marked provably cannot classify. `<p><img></p>`, `<p><em>text</em></p>`
|
|
471
|
+
and `<p>see <my-thing></my-thing> here</p>` are all left exactly as they are, and mutation
|
|
472
|
+
testing covers both directions — unwrapping everything fails three tests, unwrapping nothing
|
|
473
|
+
fails one.
|
|
474
|
+
|
|
475
|
+
### The inline doc tier runs at two widths now
|
|
476
|
+
|
|
477
|
+
The same gap, one level down: the doc-test runner's iframe was hardcoded to 800x600, so **every
|
|
478
|
+
inline `test` block had only ever run wide**. Its size is now overridable, and the Playwright
|
|
479
|
+
doc-test spec runs the whole tier a second time at 390x844.
|
|
480
|
+
|
|
481
|
+
An iframe is a real viewport, so this is not a simulation — verified that
|
|
482
|
+
`matchMedia('(max-width: 600px)')` matches inside a 390px frame and not a 1200px one, and that
|
|
483
|
+
the override reaches the runner (390px frame, 386 inner; 800/796 without it).
|
|
484
|
+
|
|
485
|
+
**Widths only, deliberately.** Touch cannot be emulated from inside a page: an iframe inherits
|
|
486
|
+
the host's input characteristics, so `(pointer: coarse)` stays false and `maxTouchPoints` stays 0
|
|
487
|
+
however small the frame is. `navigator.maxTouchPoints` _can_ be redefined, and doing so would be
|
|
488
|
+
worse than skipping it — the property would lie while the media queries told the truth, so a test
|
|
489
|
+
could pass against a page behaving as desktop. Touch belongs to a Playwright context
|
|
490
|
+
(`hasTouch`, `devices[…]`); noted in `TODO.md` as worth its own spec, since everything
|
|
491
|
+
touch-shaped here is currently tested only with a mouse.
|
|
492
|
+
|
|
3
493
|
## 1.12.4
|
|
4
494
|
|
|
5
495
|
### `.tjs` source files are documented again ([#108](https://github.com/tonioloewald/tosijs-ui/issues/108))
|
package/bin/tunnel.ts
CHANGED
|
@@ -191,6 +191,32 @@ if (has('link')) {
|
|
|
191
191
|
`\n🔗 Edit link:\n ${url}\n` +
|
|
192
192
|
(code ? `\n code: ${code} (case-insensitive)\n` : '')
|
|
193
193
|
)
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
A link nobody can reach is worse than no link.
|
|
197
|
+
|
|
198
|
+
`--link` asks the dev server for a token, which succeeds whether or not a tunnel exists — the
|
|
199
|
+
URL is well-formed and the token is real, it just resolves to a 503 from the reverse proxy
|
|
200
|
+
because nothing is listening upstream. Printed with exit 0 and no comment, that is
|
|
201
|
+
indistinguishable from the far side being down, and the reporter lost a chunk of an afternoon
|
|
202
|
+
to the difference (#94).
|
|
203
|
+
|
|
204
|
+
Checked AFTER printing, deliberately: the link is still the thing you asked for, and you may
|
|
205
|
+
be about to start the tunnel. What changes is that the output says so and the exit code agrees
|
|
206
|
+
— a script that treats 0 as "a usable link was produced" is now telling the truth.
|
|
207
|
+
|
|
208
|
+
`running()` is the same predicate `--status` uses, matching this project's own forward by its
|
|
209
|
+
exact ports and host rather than by an argv substring.
|
|
210
|
+
*/
|
|
211
|
+
const forwards = await running()
|
|
212
|
+
if (forwards.length === 0) {
|
|
213
|
+
console.error(
|
|
214
|
+
`⚠️ No tunnel is running, so that link will answer 503.\n` +
|
|
215
|
+
` The token is real — nothing is listening on the far side yet.\n` +
|
|
216
|
+
` Start one with \`tosijs-tunnel\` (or \`bun run tunnel\`), then use the link.\n`
|
|
217
|
+
)
|
|
218
|
+
process.exit(1)
|
|
219
|
+
}
|
|
194
220
|
process.exit(0)
|
|
195
221
|
}
|
|
196
222
|
|
package/dist/code-editor.d.ts
CHANGED
|
@@ -30,6 +30,9 @@ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
|
|
|
30
30
|
private diffOverlay;
|
|
31
31
|
private _pendingDiff;
|
|
32
32
|
get showingDiff(): boolean;
|
|
33
|
+
diffResolvable: boolean;
|
|
34
|
+
diffOriginalLabel: string;
|
|
35
|
+
diffModifiedLabel: string;
|
|
33
36
|
showDiff(on: boolean): void;
|
|
34
37
|
static initAttributes: {
|
|
35
38
|
mode: string;
|
package/dist/code-editor.js
CHANGED
|
@@ -24,6 +24,8 @@ body {
|
|
|
24
24
|
| `mode` | `javascript`, `typescript`, `tjs`, `ajs`, `css`, `html`, `markdown` |
|
|
25
25
|
| `disabled` | makes the editor read-only |
|
|
26
26
|
| `original` + `showDiff(on)` | diff the current `value` against a baseline, as an overlay |
|
|
27
|
+
| `diffResolvable` | make that overlay **resolvable**: each change gets keep/revert buttons, and the choices are applied to `value` when the diff closes |
|
|
28
|
+
| `diffOriginalLabel` / `diffModifiedLabel` | what the two buttons say (default `Original`/`Modified`) |
|
|
27
29
|
| `editor` | the underlying CodeMirror [`EditorView`](https://codemirror.net/docs/ref/#view.EditorView) (`undefined` until loaded) |
|
|
28
30
|
| `undo()` / `redo()` / `canUndo()` / `canRedo()` | history control |
|
|
29
31
|
| `tjsAutocomplete` | runtime-value autocomplete hooks (tjs mode) — see below |
|
|
@@ -38,6 +40,29 @@ runtime values** — including proxy members no static analysis can see:
|
|
|
38
40
|
codeEl.tjsAutocomplete = { getLiveBindings: () => ({ app, elements }) }
|
|
39
41
|
```
|
|
40
42
|
|
|
43
|
+
## Selective revert
|
|
44
|
+
|
|
45
|
+
`showDiff(on)` shows a read-only diff. Set `diffResolvable` first and the overlay becomes a
|
|
46
|
+
review surface instead: every change carries keep/revert buttons, defaulting to **keep**, and
|
|
47
|
+
whatever you leave alone survives. This is what `<tosi-example>`'s **View changes** uses — you
|
|
48
|
+
tried four things and three worked, so revert the one you regret rather than all four.
|
|
49
|
+
|
|
50
|
+
The choices are applied when the diff **closes**, not as you click, and a `change` event fires
|
|
51
|
+
if the text moved. That is deliberate: writing each choice straight back into `value` would
|
|
52
|
+
feed the new text into the overlay's `modified`, and `<tosi-diff>` resets its decisions when
|
|
53
|
+
either input changes — so live application would wipe the choices making it and renumber the
|
|
54
|
+
hunks under the pointer.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
codeEl.original = savedSource
|
|
58
|
+
codeEl.diffResolvable = true
|
|
59
|
+
codeEl.diffOriginalLabel = 'Source'
|
|
60
|
+
codeEl.diffModifiedLabel = 'Yours'
|
|
61
|
+
codeEl.showDiff(true)
|
|
62
|
+
// …user reverts a hunk or two…
|
|
63
|
+
codeEl.showDiff(false) // codeEl.value now reflects their choices
|
|
64
|
+
```
|
|
65
|
+
|
|
41
66
|
## Bundling
|
|
42
67
|
|
|
43
68
|
CodeMirror is a **lazy chunk**: with a bundler (ESM), a page that never uses
|
|
@@ -154,6 +179,17 @@ export class CodeEditor extends WebComponent {
|
|
|
154
179
|
return this._pendingDiff ?? false;
|
|
155
180
|
return !this.parts.diffHost.hidden;
|
|
156
181
|
}
|
|
182
|
+
/*
|
|
183
|
+
Make the diff overlay RESOLVABLE, turning it from a review into selective revert.
|
|
184
|
+
|
|
185
|
+
Off by default, so `showDiff()` stays exactly what it was. With it on, every change gets
|
|
186
|
+
keep/revert buttons and the ones you leave alone are kept — which is the useful default when
|
|
187
|
+
the "modified" side is your own edit: you are reverting the two lines you regret, not
|
|
188
|
+
re-accepting the twenty you meant.
|
|
189
|
+
*/
|
|
190
|
+
diffResolvable = false;
|
|
191
|
+
diffOriginalLabel = 'Original';
|
|
192
|
+
diffModifiedLabel = 'Modified';
|
|
157
193
|
showDiff(on) {
|
|
158
194
|
if (!this.hydrated) {
|
|
159
195
|
this._pendingDiff = on;
|
|
@@ -165,9 +201,32 @@ export class CodeEditor extends WebComponent {
|
|
|
165
201
|
this.diffOverlay = tosiDiff();
|
|
166
202
|
diffHost.append(this.diffOverlay);
|
|
167
203
|
}
|
|
204
|
+
this.diffOverlay.resolvable = this.diffResolvable;
|
|
205
|
+
this.diffOverlay.originalLabel = this.diffOriginalLabel;
|
|
206
|
+
this.diffOverlay.modifiedLabel = this.diffModifiedLabel;
|
|
168
207
|
this.diffOverlay.original = this.original;
|
|
169
208
|
this.diffOverlay.modified = this.value;
|
|
170
209
|
}
|
|
210
|
+
else if (this.diffOverlay !== undefined && this.diffResolvable) {
|
|
211
|
+
/*
|
|
212
|
+
Resolutions land on the way OUT, not as you click.
|
|
213
|
+
|
|
214
|
+
Writing each choice straight back into `value` would feed the editor's new text into the
|
|
215
|
+
overlay's `modified`, and `<tosi-diff>` resets its decisions when either input changes —
|
|
216
|
+
by design, because choice #2 of the old diff is not choice #2 of the new one. Live
|
|
217
|
+
application would therefore wipe the very choices making it, and the hunks would
|
|
218
|
+
renumber under the pointer. Applying once, when the diff closes, sidesteps that
|
|
219
|
+
completely and matches how a reviewer thinks: decide, then commit.
|
|
220
|
+
|
|
221
|
+
The `change` event matters as much as the value: it is how a host recomputes "does this
|
|
222
|
+
still differ from the source" after a partial revert.
|
|
223
|
+
*/
|
|
224
|
+
const resolved = this.diffOverlay.value;
|
|
225
|
+
if (resolved !== this.value) {
|
|
226
|
+
this.value = resolved;
|
|
227
|
+
this.dispatchEvent(new CustomEvent('change', { detail: { value: resolved } }));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
171
230
|
diffHost.hidden = !on;
|
|
172
231
|
}
|
|
173
232
|
static initAttributes = {
|
package/dist/data-table.d.ts
CHANGED
|
@@ -341,6 +341,7 @@ export declare class TosiTable extends WebComponent {
|
|
|
341
341
|
sortByColumn: (columnOptions: ColumnOptions, direction?: "ascending" | "descending" | "auto") => void;
|
|
342
342
|
popColumnMenu: (target: HTMLElement, options: ColumnOptions) => void;
|
|
343
343
|
get captionSpan(): ElementCreator;
|
|
344
|
+
labelAttrs(key: string, attr?: string): Record<string, string>;
|
|
344
345
|
get visibleRows(): any[];
|
|
345
346
|
get visibleSelectedRows(): any[];
|
|
346
347
|
get selectedRows(): any[];
|