tosijs-ui 1.12.1 → 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 +168 -0
- package/README.md +1 -1
- package/bin/release-notes.ts +9 -2
- package/dist/data-table.js +33 -6
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,173 @@
|
|
|
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
|
+
|
|
3
171
|
## 1.12.1
|
|
4
172
|
|
|
5
173
|
### The Playwright lane no longer fights itself
|
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.js
CHANGED
|
@@ -2149,9 +2149,29 @@ export class TosiTable extends WebComponent {
|
|
|
2149
2149
|
this.style.cursor = column !== undefined ? 'col-resize' : '';
|
|
2150
2150
|
};
|
|
2151
2151
|
resizeColumn = (event) => {
|
|
2152
|
-
const
|
|
2153
|
-
if (
|
|
2154
|
-
|
|
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;
|
|
2155
2175
|
const isTouchEvent = event.touches !== undefined;
|
|
2156
2176
|
const touchIdentifier = isTouchEvent
|
|
2157
2177
|
? event.touches[0].identifier
|
|
@@ -2163,9 +2183,16 @@ export class TosiTable extends WebComponent {
|
|
|
2163
2183
|
if (touch === undefined) {
|
|
2164
2184
|
return true;
|
|
2165
2185
|
}
|
|
2166
|
-
const
|
|
2167
|
-
|
|
2168
|
-
|
|
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;
|
|
2169
2196
|
this.setColumnWidths();
|
|
2170
2197
|
if (event.type === 'mouseup') {
|
|
2171
2198
|
return true;
|
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. */
|