tosijs-ui 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +643 -0
- package/bin/caddy-install.ts +17 -32
- package/bin/deploy-preview.ts +14 -7
- package/bin/release-notes.ts +15 -4
- package/bin/resolve-site-config.ts +77 -0
- package/bin/tunnel.ts +15 -7
- package/dist/.metadata_never_index +0 -0
- package/dist/babylon-3d.d.ts +14 -0
- package/dist/babylon-3d.js +52 -0
- package/dist/code-editor.d.ts +1 -0
- package/dist/code-editor.js +50 -2
- package/dist/codemirror.d.ts +2 -0
- package/dist/codemirror.js +43 -0
- package/dist/data-table.d.ts +32 -1
- package/dist/data-table.js +177 -2
- package/dist/doc-browser.d.ts +1 -0
- package/dist/doc-browser.js +75 -10
- package/dist/doc-system/doc-system-styles.js +14 -6
- package/dist/doc-system/doc-system.js +15 -0
- package/dist/doc-system/site/audit-guard.d.ts +22 -0
- package/dist/doc-system/site/audit-guard.js +69 -0
- package/dist/doc-system/site/audit-reach.d.ts +16 -0
- package/dist/doc-system/site/audit-reach.js +56 -0
- package/dist/doc-system/site/build-lock.d.ts +11 -2
- package/dist/doc-system/site/build-lock.js +12 -1
- package/dist/doc-system/site/build-stamp.d.ts +18 -0
- package/dist/doc-system/site/build-stamp.js +77 -0
- package/dist/doc-system/site/dev-auth.d.ts +9 -3
- package/dist/doc-system/site/dev-auth.js +54 -24
- package/dist/doc-system/site/dev-server.js +93 -29
- package/dist/doc-system/site/editable-sources.d.ts +14 -0
- package/dist/doc-system/site/editable-sources.js +86 -0
- package/dist/doc-system/site/generate-site.d.ts +2 -0
- package/dist/doc-system/site/generate-site.js +10 -2
- package/dist/doc-system/site/host-preset.d.ts +13 -0
- package/dist/doc-system/site/host-preset.js +56 -0
- package/dist/doc-system/site/invite-page.d.ts +2 -2
- package/dist/doc-system/site/invite-page.js +10 -3
- package/dist/doc-system/site/orchestrator.d.ts +12 -0
- package/dist/doc-system/site/orchestrator.js +177 -7
- package/dist/doc-system/site/preflight.d.ts +26 -2
- package/dist/doc-system/site/preflight.js +97 -14
- package/dist/doc-system/site/site-config.d.ts +6 -2
- package/dist/filter-builder.js +35 -1
- package/dist/icon-types.d.ts +16 -1
- package/dist/iife.js +111 -92
- package/dist/iife.js.map +40 -40
- package/dist/index-iife.js +20 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.js +40 -4
- package/dist/live-example/code-transform.d.ts +3 -0
- package/dist/live-example/code-transform.js +58 -12
- package/dist/live-example/component.d.ts +15 -0
- package/dist/live-example/component.js +19 -0
- package/dist/live-example/execution.js +9 -2
- package/dist/live-example/index.d.ts +1 -1
- package/dist/live-example/index.js +1 -1
- package/dist/live-example/insert-examples.js +26 -0
- package/dist/menu.d.ts +13 -1
- package/dist/rich-text.d.ts +2 -1
- package/dist/segmented.d.ts +2 -1
- package/dist/segmented.js +1 -1
- package/dist/sizer.d.ts +1 -1
- 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 +9 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,648 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.14.0
|
|
4
|
+
|
|
5
|
+
### Quote style no longer decides whether a live example runs (#141)
|
|
6
|
+
|
|
7
|
+
The live-example import rewriter accepted `'single-quoted'` specifiers and rejected
|
|
8
|
+
`"double-quoted"` ones — the same import, the same `{ named }` form, differing only in quotes.
|
|
9
|
+
|
|
10
|
+
On its own that is a papercut. What made it serious is **Prettier**: it formats fenced code
|
|
11
|
+
inside markdown and normalises quotes to double, so on any project that formats its `.md`,
|
|
12
|
+
every live example was silently converted into a non-running one. The only symptom was a
|
|
13
|
+
build-time warning that reads as advisory.
|
|
14
|
+
|
|
15
|
+
Reported from tosijs-3d-ensemble, where the README's headline example — the one showing what a
|
|
16
|
+
game imports — had never run for the life of the repo. Because it never ran, nothing noticed it
|
|
17
|
+
also named a function the package has never exported. Two defects in the most-read code block
|
|
18
|
+
there, both invisible for the same reason. Found by the doc-test corpus, not by reading.
|
|
19
|
+
|
|
20
|
+
All three context forms (`{ named }`, `* as ns`, default) now accept either quote style, as
|
|
21
|
+
does the `.elements` accessor.
|
|
22
|
+
|
|
23
|
+
**And the error message named the wrong thing.** It listed the supported forms and the context
|
|
24
|
+
packages — so a failing `import { x } from "pkg"`, a listed package in a listed form, was
|
|
25
|
+
answered with a sentence that ruled out its own cause. The reporter spent ~40 minutes rewriting
|
|
26
|
+
a multiline import that was never the problem. The message now separates the two real causes: a
|
|
27
|
+
specifier the context does not carry, versus a clause shape the rewriter cannot handle.
|
|
28
|
+
|
|
29
|
+
### `<tosi-code>`'s own diff overlay is readable by default (#143)
|
|
30
|
+
|
|
31
|
+
`<tosi-diff>` resolves its surface as `--tosi-diff-bg || --background`. Inside `<tosi-code>`,
|
|
32
|
+
`--background` is still the page's white while `--text-color` is deliberately the light code
|
|
33
|
+
colour — so the diff `showDiff()` mounts rendered near-white text on white, and its unchanged
|
|
34
|
+
lines were invisible. The one place the component mounts a diff itself was the one place the
|
|
35
|
+
default did not work.
|
|
36
|
+
|
|
37
|
+
`--background` is now redefined for that subtree to follow `--code-bg`, so the nested diff
|
|
38
|
+
inherits the editor's palette. Deliberately **not** by setting `--tosi-diff-*` here: an inner
|
|
39
|
+
definition beats an inherited one, so that would have overridden the very custom properties
|
|
40
|
+
adopters are already using as an escape hatch — a silent break of the thing the issue was
|
|
41
|
+
about. A consumer's `--tosi-diff-bg` is still checked first and still wins.
|
|
42
|
+
|
|
43
|
+
### Live `ts` examples load a pinned TypeScript compiler
|
|
44
|
+
|
|
45
|
+
tjs-lang's `fromTS` lazy-loads the TypeScript compiler from `DEFAULT_TYPESCRIPT_URL` —
|
|
46
|
+
`https://esm.sh/typescript@5`, an **unpinned major range**. It was the one hop in the entire
|
|
47
|
+
live-example chain that was neither same-origin nor pinned: everything else routes through
|
|
48
|
+
`__TJS_LOCAL_BASE` or a `TJS_VERSION`-pinned CDN. Because it is fetched at runtime it appears
|
|
49
|
+
in no lockfile, so `bun audit` cannot see it and "which compiler ran in a reader's browser" had
|
|
50
|
+
no answer.
|
|
51
|
+
|
|
52
|
+
Now pinned, via the `typescriptUrl` option tjs-lang already exposes, to the **exact version
|
|
53
|
+
this repo type-checks with** — so a doc example is lowered by the same compiler that validates
|
|
54
|
+
the source it was extracted from. A drift test asserts the pin against the **installed**
|
|
55
|
+
compiler rather than the declared range, because `typescript` is a caret dependency here and a
|
|
56
|
+
range check would call `^5.9.3` satisfied while the pin said something else.
|
|
57
|
+
|
|
58
|
+
esm.sh stays as the host — tjs-lang measured it as the only CDN that reliably serves
|
|
59
|
+
`typescript` as ESM. The fix is the pin, not the host; a same-origin copy is the upstream ask.
|
|
60
|
+
|
|
61
|
+
Nobody was exposed: this repo's corpus contains no executable ` ```ts ` fences.
|
|
62
|
+
|
|
63
|
+
### `MenuAction.action` is optional, so a drop-only menu item is expressible
|
|
64
|
+
|
|
65
|
+
`acceptsDrop` + `dropAction` with no `action` — a target you can drag onto but not click — is
|
|
66
|
+
a shape the runtime supports and `popDropMenu`'s docs describe, but the type required `action`.
|
|
67
|
+
When the new typecheck lane compiled the tests for the first time, four literals were silenced
|
|
68
|
+
with `as unknown as MenuItem` rather than the type being corrected.
|
|
69
|
+
|
|
70
|
+
The casts are gone. Two tests now pin what the widened type admits: a drop-only item appears
|
|
71
|
+
**disabled** in a click menu and is removed only under `hideDisabled` — the documented
|
|
72
|
+
`hideDisabled` contract. (The first draft of that test asserted it was filtered out entirely,
|
|
73
|
+
and failed; the finding implied a behaviour the code does not have.)
|
|
74
|
+
|
|
75
|
+
### `buildSite` docs: `dist/` is reset only when the build owns it (#130)
|
|
76
|
+
|
|
77
|
+
Three shipped surfaces still promised an unconditional reset — the config table and the
|
|
78
|
+
gotchas list in `doc-site-system.md`, and the `prebuild` JSDoc, which reaches
|
|
79
|
+
`dist/doc-system/site/site-config.d.ts` and is what an adopter reads on editor hover.
|
|
80
|
+
|
|
81
|
+
`dist/` is cleared only when `emitLibrary` or `libraryTsconfig` means this build produces it.
|
|
82
|
+
A `libraryBuild` function owns the directory and may deliberately emit a subset, so it is never
|
|
83
|
+
cleared — **if you supply `libraryBuild`, cleaning `dist/` is yours.** The output dir is still
|
|
84
|
+
always reset, which is the part `prebuild` authors actually need to know.
|
|
85
|
+
|
|
86
|
+
### Security: the source editor can no longer write files the build executes (#128)
|
|
87
|
+
|
|
88
|
+
`editableSources` writes were contained only to the project root — which includes
|
|
89
|
+
`.git/hooks/*`, `bunfig.toml` (preload), `package.json` scripts and `bin/`, every one of which
|
|
90
|
+
runs on the developer's next ordinary command. With CSRF closed (#90/#121) there was no known
|
|
91
|
+
path to an unauthorised write; this removes the *consequence* of a future one.
|
|
92
|
+
|
|
93
|
+
`editableSourcePaths` / `mayEditSource` build an **allow-list derived from the doc corpus**,
|
|
94
|
+
not a deny-list of dangerous names: the endpoint exists to edit the source of a page you are
|
|
95
|
+
looking at, so the writable set is exactly what the extractor scraped. A deny-list needs
|
|
96
|
+
extending every time someone invents a new way to make a file execute and is wrong by default;
|
|
97
|
+
this is right by default. It **fails closed** — an unreadable corpus permits nothing.
|
|
98
|
+
|
|
99
|
+
**The first version of this was not enough, and the pre-release review caught it.** A doc
|
|
100
|
+
source can itself be an executed script: `bin/make-icon-data.js` carries a `/*#` block, so it
|
|
101
|
+
was a doc source, so it was writable — and `bin/dev.ts` spawns it on *every* build. The
|
|
102
|
+
original tests passed because the synthetic corpus omitted `bin/`, and the live check used
|
|
103
|
+
`.git/hooks/pre-commit`, which the corpus never contained. Both proved something true and
|
|
104
|
+
irrelevant. There is now an `isExecutedByBuild` deny layer (all of `bin/`, the spawned `-cli`
|
|
105
|
+
scripts, `generate-css`), and the tests run against the **shipped** corpus with an assertion
|
|
106
|
+
that the dangerous entry is really in it — otherwise the refusal test proves nothing.
|
|
107
|
+
|
|
108
|
+
The **read** half is gated by the same predicate. It was left open initially on the grounds
|
|
109
|
+
that the reported issue was a drive-by write; the review was right that the argument for
|
|
110
|
+
symmetry was stronger, since "view source" only ever reads a doc's own source.
|
|
111
|
+
|
|
112
|
+
### The dependency audit knows where a package sits (#56)
|
|
113
|
+
|
|
114
|
+
`runtimeReachable` / `classifyReach` walk the manifest graph and label an advisory's package
|
|
115
|
+
**runtime** or **build-only**. Reported from an app monorepo running `buildSite`: 18
|
|
116
|
+
high/critical advisories of which the runtime-reachable subset was a small fraction, so
|
|
117
|
+
blocking on all of them would have bricked local dev over risk they do not carry.
|
|
118
|
+
|
|
119
|
+
Conservative by construction — root `dependencies`, `optionalDependencies` **and
|
|
120
|
+
`peerDependencies`** are runtime seeds (a peer ends up in the consumer's app), and an
|
|
121
|
+
unresolvable package counts as reachable. A finding silently downgraded is the one outcome
|
|
122
|
+
worth engineering against.
|
|
123
|
+
|
|
124
|
+
`audit.blockOn: 'severity' | 'runtime'` defaults to `'severity'` — today's behaviour. Set
|
|
125
|
+
`'runtime'` and a build-only finding is reported but does not block. If the manifest walk fails
|
|
126
|
+
the filter is disabled entirely, because "we could not tell" must not read as "not reachable".
|
|
127
|
+
|
|
128
|
+
A build that passes **only because of an active gate** now says so:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
🟡 Build: PASSING ON A WAIVER — 1 finding(s) at or above high are suppressed
|
|
132
|
+
by an active gate. This is not a clean audit.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Only when the waiver is load-bearing: a gate suppressing something below the threshold would
|
|
136
|
+
have passed anyway, and flagging that would train people to ignore the marker.
|
|
137
|
+
|
|
138
|
+
### The doc-test runner stopped executing pages that only *mention* tests
|
|
139
|
+
|
|
140
|
+
`docsWithTests` matched ` ```test ` as a **substring anywhere in the document**, so a page
|
|
141
|
+
merely writing about the test tier qualified — and was rendered in a background iframe with its
|
|
142
|
+
examples executed. A failure in an example on a page with *no tests* therefore surfaced through
|
|
143
|
+
the doc-test tier, and only sometimes, depending on how quickly the TypeScript compiler loaded.
|
|
144
|
+
|
|
145
|
+
That produced a **false green**: a standalone doc-tests run reported "62 passed" against a build
|
|
146
|
+
the full suite failed on. Page selection now requires a fence at the start of a line, the same
|
|
147
|
+
rule the doc extractor applies to `/*#`.
|
|
148
|
+
|
|
149
|
+
The example it was hiding was ours: a ` ```ts ` fence in `doc-site-system.md` — added in the
|
|
150
|
+
#93 write-up — was **executable**, so the doc-test lane ran `Bun.spawn` and `process.on` in a
|
|
151
|
+
browser and threw `ReferenceError: process is not defined`. It is now ` ```typescript `, which
|
|
152
|
+
is display-only. One character, and it had been red on `main` from the moment it was pushed.
|
|
153
|
+
|
|
154
|
+
Related and still open (#138): an example that *compiles and then throws* is caught by nothing
|
|
155
|
+
on a page without a ` ```test ` block. The build-time check catches what will not compile, not
|
|
156
|
+
what compiles and fails.
|
|
157
|
+
|
|
158
|
+
### An existing `firebase.json` is checked against `outputDir` (#134)
|
|
159
|
+
|
|
160
|
+
`host: 'firebase'` scaffolds a config for a fresh project and keeps its hands off an existing
|
|
161
|
+
one — but checked nothing, so `buildSite` could write `docs/` while `firebase deploy` published
|
|
162
|
+
`.demo/`. Both commands succeed and the site serves whatever was there last. It now warns,
|
|
163
|
+
handling multi-site arrays, framework-aware hosting with no `public`, and `./docs` vs `docs`
|
|
164
|
+
spelling, and warns only when *every* declared target disagrees.
|
|
165
|
+
|
|
166
|
+
### tjs-lang 0.13.4 → 0.13.11, and the two pins can no longer drift (#135)
|
|
167
|
+
|
|
168
|
+
We were pinned to a version npm has **deprecated**, seven releases behind. The deprecation's
|
|
169
|
+
threat model does not reach live-example — it is about a server transpiling submitted source —
|
|
170
|
+
but sitting on a deprecated version with seven releases of fixes is its own problem.
|
|
171
|
+
|
|
172
|
+
`TJS_VERSION` (what the *published site* fetches from a CDN) and the exact devDependency (what
|
|
173
|
+
tests and local builds use) live in different files, so a test now asserts they agree. Drift
|
|
174
|
+
there is invisible exactly where it matters: every lane stays green while the deployed site
|
|
175
|
+
transpiles with a different version.
|
|
176
|
+
|
|
177
|
+
### `<tosi-example>` warns when executable fences collide (#139)
|
|
178
|
+
|
|
179
|
+
`js`/`tjs`/`ts` all write the same single-valued slot, so a second executable fence silently
|
|
180
|
+
overwrote the first — the earlier block vanished from the page, the example ran as the later
|
|
181
|
+
dialect, and nothing said so. Behaviour is unchanged (last one still wins); it now names the
|
|
182
|
+
file and which dialect survived. This is the seam a dialect selector needs, which is why it is
|
|
183
|
+
worth fixing before that feature rather than during it.
|
|
184
|
+
|
|
185
|
+
### `getSelectText` returned the empty string on every call
|
|
186
|
+
|
|
187
|
+
`<tosi-filter>` read `select.options[select.selectedIndex]`, and `selectedIndex` has never
|
|
188
|
+
existed on `TosiSelect` — it selects by value. So every filter chip read ` "needle"` with two
|
|
189
|
+
blanks where the field and condition belong, which looks like a styling bug. Found by adopting
|
|
190
|
+
tosijs 1.10.0, whose removal of `Component`'s `[key: string]: any` index signature made the
|
|
191
|
+
compiler able to see it for the first time.
|
|
192
|
+
|
|
193
|
+
### Markdown is no longer formatted, and `bun format` is green again
|
|
194
|
+
|
|
195
|
+
`*.md` is in `.prettierignore` by request from tosijs. `proseWrap` was already `preserve`, so
|
|
196
|
+
prettier's only remaining markdown behaviours were escaping literal characters
|
|
197
|
+
(`a * literal` → `a \* literal`), padding table cells, and rewriting `*` bullets — the first of
|
|
198
|
+
which edits a document's *content*, and markdown is the product here.
|
|
199
|
+
|
|
200
|
+
Separately, `bun format` had been exiting 1 on seven ESLint errors: CI gates `format-check`,
|
|
201
|
+
which runs prettier only, so the ESLint half of the documented pre-commit command was red
|
|
202
|
+
without failing anything. Among the fixes: two dead locals in the CSRF same-origin check
|
|
203
|
+
(checked rather than assumed — the logic was sound, only the initialisers were dead); a dead
|
|
204
|
+
`explanation` in `dev-server.ts` that was built and never used, left over from before
|
|
205
|
+
`invite-page.ts` took ownership of that copy in #114; and an
|
|
206
|
+
`AsyncFunction` construction failure that now rethrows with `{ cause }` so the engine's own
|
|
207
|
+
error — the one naming *which* parameter it choked on — is not discarded.
|
|
208
|
+
|
|
209
|
+
### `icons.*` was typed `SVGElement`, and composites are spans
|
|
210
|
+
|
|
211
|
+
**This is why the release is a minor.** `SVGIconMap` declared every icon as
|
|
212
|
+
`ElementCreator<SVGElement>`. A **composite** icon — any name containing `$`, the stacking
|
|
213
|
+
syntax `icon-composition.md` documents and the demos use throughout — returns an
|
|
214
|
+
`HTMLSpanElement`.
|
|
215
|
+
|
|
216
|
+
Measured rather than reasoned about:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
icons['tosiHat$tosi']() → SPAN, instanceof SVGElement === false
|
|
220
|
+
icons.lock() → svg, instanceof SVGElement === true
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`resolveIcon` has always returned the honest `Element`; only the cast on the exported proxy
|
|
224
|
+
disagreed. It is now `ElementCreator<IconElement>`, and **`IconElement` is exported from
|
|
225
|
+
`icon-types.ts` for consumers holding an icon** — defined as `SVGElement | HTMLSpanElement`.
|
|
226
|
+
Both members carry `.style` and `.dataset`,
|
|
227
|
+
which is what callers actually reach for — widening all the way to `Element` would break
|
|
228
|
+
working code to no purpose.
|
|
229
|
+
|
|
230
|
+
**Why a minor rather than a patch.** Nothing about the runtime changed; a composite has always
|
|
231
|
+
been a span. But code that annotated an icon as `SVGElement` compiled before and does not now,
|
|
232
|
+
and "a patch never breaks you" is the contract. This breaks you out of a false belief, which is
|
|
233
|
+
still breaking.
|
|
234
|
+
|
|
235
|
+
**The lie was load-bearing.** Correcting it surfaced **14 sites in this repo** — `data-table`,
|
|
236
|
+
`notifications`, `rich-text`, `segmented` — that stored or passed an icon as `SVGElement`. Any
|
|
237
|
+
consumer doing the same has the same latent bug, which is the argument for fixing the type
|
|
238
|
+
rather than the tests. If your build newly complains, that is the bug being shown to you: the
|
|
239
|
+
fix is `IconElement`, or `as SVGElement` at a site you know is not a composite.
|
|
240
|
+
|
|
241
|
+
### The test suite is type-checked now (#73)
|
|
242
|
+
|
|
243
|
+
`tsconfig.json` excludes `*.test.ts` — right for emit, and it meant nothing ever compiled them.
|
|
244
|
+
**48 errors had accumulated unseen** (the issue counted 40 when it was filed; it grows).
|
|
245
|
+
`tsconfig.tests.json` plus a CI step brings it to zero and holds it there.
|
|
246
|
+
|
|
247
|
+
The icons defect above is what that gate found on its first run. Two test files carried
|
|
248
|
+
duplicate imports — `sessionCookie` and `nodePath` — which is invalid ESM that the runtime
|
|
249
|
+
tolerated and nothing checked. The other 46 were genuine test-side noise, fixed honestly
|
|
250
|
+
rather than with blanket `any`.
|
|
251
|
+
|
|
252
|
+
A type-level test asserts nothing if nothing compiles it — which was already the argument for
|
|
253
|
+
`typecheck-guards`, applied to everything else.
|
|
254
|
+
|
|
255
|
+
### The test lane itself, honestly
|
|
256
|
+
|
|
257
|
+
Three things the pre-release review found in the tests rather than in the code, recorded
|
|
258
|
+
because a suite that is quietly wrong is worse than one that is loudly red.
|
|
259
|
+
|
|
260
|
+
**Six tests were deleted and the suite stayed green.** Adding the #139 cases to
|
|
261
|
+
`insert-examples.test.ts`, `cat >` was used instead of `cat >>`, wiping two `describe` blocks
|
|
262
|
+
that covered still-shipping behaviour — grouping across a baked `<script>`, and fenced
|
|
263
|
+
execution modes. Nothing complained, because fewer tests all passed. That is the exact shape of
|
|
264
|
+
a coverage regression no gate catches: green is not a measurement of how much you asked. The
|
|
265
|
+
blocks are restored, and the #139 cases rewritten against the same stub creator the originals
|
|
266
|
+
used.
|
|
267
|
+
|
|
268
|
+
**`ENOENT reading "https://esm.sh/typescript@5"` during `bun test`** came from the same
|
|
269
|
+
rewrite: the real `liveExample` transpiled a `ts` fence, and the rejection leaked across test
|
|
270
|
+
files, printing under an unrelated file's header. Zero occurrences now — and worth noting that
|
|
271
|
+
the message named a URL while claiming `ENOENT`, which is why it read as noise for as long as
|
|
272
|
+
it did.
|
|
273
|
+
|
|
274
|
+
**`caddy-install.test.ts` was flaky about one run in ten.** Each of its tests cold-starts a
|
|
275
|
+
real `bun` to exercise the generated remote script, so its cost is interpreter startup — ~1.2s
|
|
276
|
+
each, with the file swinging 3.5s to 10.1s under load. Under bun's 5s default that put single
|
|
277
|
+
tests over the line whenever the machine was busy. They now carry a 20s budget that describes
|
|
278
|
+
the work; a test exceeding *that* is a real defect rather than machine load.
|
|
279
|
+
|
|
280
|
+
### Every bin answers `--help`, and refuses what it does not understand (#85)
|
|
281
|
+
|
|
282
|
+
`caddy-install`, `deploy-preview`, `tunnel` and `release-notes` each carried their own
|
|
283
|
+
`flag()`/`has()` pair. One shared `parseArgv` replaces all four.
|
|
284
|
+
|
|
285
|
+
**Refusing unknown flags is the load-bearing part.** `tosijs-caddy-install --status` — a flag it
|
|
286
|
+
never had — used to read as "no flags at all", and the bin went on to ssh into the configured
|
|
287
|
+
host. The same shape was still live in `tosijs-deploy`. A tool that contacts a machine must not
|
|
288
|
+
read an instruction it does not understand as consent to proceed.
|
|
289
|
+
|
|
290
|
+
`--help` comes with it, and is parsed **before** the site config is read, so it works from any
|
|
291
|
+
directory. That matters because no lane runs a shipped bin's argument handling —
|
|
292
|
+
`smoke-consumer.ts` checks shebangs and deliberately does not execute the bins that reach the
|
|
293
|
+
network — so `tosijs-release-notes --help` shipped unrecognised and died on a raw `ShellError`
|
|
294
|
+
outside a git repo.
|
|
295
|
+
|
|
296
|
+
Also: `deploy:index` now asks for `PREVIEW_HOST` like the bins, instead of the `PREVIEW_SSH`
|
|
297
|
+
they moved off.
|
|
298
|
+
|
|
299
|
+
### Magic-link codes are eight letters, no digits (#132)
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
🔗 code: JCYGCCVX (case-insensitive)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The code is typed by hand into a floating keyboard on a headset. Crockford base32 was already
|
|
306
|
+
chosen for typo-resistance and that was right as far as it went — but it contained **digits**,
|
|
307
|
+
and on a headset the letter↔number switch is a trip to a different keyboard page. A reported XR
|
|
308
|
+
session minted `A70MDCD` and `X9Q3Z53`: three digits each in seven characters, so three round
|
|
309
|
+
trips for a code being gaze-and-pinched one glyph at a time.
|
|
310
|
+
|
|
311
|
+
**The extra character is free; the mode switch is not.** Eight letters of a 22-letter alphabet
|
|
312
|
+
is **35.7 bits against the old seven-of-32's 35.0** — slightly _stronger_, while never leaving
|
|
313
|
+
the alphabetic keyboard.
|
|
314
|
+
|
|
315
|
+
`I`, `L`, `O` and `U` stay excluded. `I`/`L` are indistinguishable across case, which matters
|
|
316
|
+
_more_ now that there are no digits, because the code is case-insensitive so the reader cannot
|
|
317
|
+
use case to tell them apart. Redemption also maps back the digits someone might type for a
|
|
318
|
+
letter they misread (`5`→`S`, `2`→`Z`, `8`→`B`, `6`→`G`) — pure upside, since a digit can never
|
|
319
|
+
be part of a real code — and ignores hyphens and spaces.
|
|
320
|
+
|
|
321
|
+
Rate limiting is unchanged and still does the heavy lifting: redemption is serialized at ~10
|
|
322
|
+
attempts/second, so 22⁸ takes ~174 years to exhaust and a five-minute window is about 1 in 18
|
|
323
|
+
million.
|
|
324
|
+
|
|
325
|
+
`dev-auth.ts` itself went on arguing from **32⁷** — ~111 years, 1 in 11 million — for the whole
|
|
326
|
+
of the change that made the token 22⁸. The conclusion never stopped holding, which is precisely
|
|
327
|
+
why nothing surfaced it: a safety argument that survives its own premises being replaced is not
|
|
328
|
+
being read. The figures are corrected in place, with the superseded ones kept beside them, since
|
|
329
|
+
this is the one file where the numbers *are* the argument rather than a description of it.
|
|
330
|
+
|
|
331
|
+
The invite page's code box (shipped for #75) covers most of the rest of the ask: bookmark the
|
|
332
|
+
host once, then each session is eight letters into a text field. The `dev.tosijs.net/<code>`
|
|
333
|
+
short URL needs host-side infrastructure and is tracked in `TODO.md`.
|
|
334
|
+
|
|
335
|
+
### BREAKING (narrow): the root barrel no longer re-exports the doc system (#133)
|
|
336
|
+
|
|
337
|
+
`import { … } from 'tosijs-ui'` no longer gives you `code-editor`, `doc-browser`,
|
|
338
|
+
`doc-system/doc-system` or `live-example`. **Import them by subpath instead** — they were
|
|
339
|
+
already reachable that way, and nothing else changes:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { tosiCode } from 'tosijs-ui/code-editor'
|
|
343
|
+
import { createDocBrowser } from 'tosijs-ui/doc-browser'
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Why.** `code-editor` pulls CodeMirror; `live-example` and `doc-system/doc-system` each pull
|
|
347
|
+
`tjs-lang` independently. This package has no `sideEffects` field — correctly, since
|
|
348
|
+
`elementCreator()` registers elements at import time and a blanket `sideEffects: false` shakes
|
|
349
|
+
a bare `import 'tosijs-ui'` down to zero registrations — so a bundler must treat every
|
|
350
|
+
`export *` target as side-effectful and cannot drop it. All four were therefore eager in any
|
|
351
|
+
app that imported _anything_ from the barrel.
|
|
352
|
+
|
|
353
|
+
Measured by snowfox-app moving a 15MB React bundle to the bun bundler, and reproduced here:
|
|
354
|
+
|
|
355
|
+
| | size |
|
|
356
|
+
| ----------------------- | ------------------------------------ |
|
|
357
|
+
| barrel as shipped | 1.68 MB |
|
|
358
|
+
| barrel without the four | 0.38 MB |
|
|
359
|
+
| real app bundle | 15.21 → 13.86 MB (**1.35 MB, 8.9%**) |
|
|
360
|
+
|
|
361
|
+
The doc cluster was **77% of the barrel**. For contrast the 3D, map and lottie components —
|
|
362
|
+
the ones you would expect to be the heavy tail — cost about 10kB, because they already
|
|
363
|
+
lazy-load properly.
|
|
364
|
+
|
|
365
|
+
The doc site and CDN `<script>` users are unaffected: `index-iife.ts` imports all four
|
|
366
|
+
explicitly, so the weight lands on the bundle built _for_ the doc site rather than on an app
|
|
367
|
+
that imports a button.
|
|
368
|
+
|
|
369
|
+
### New: `tosijs-ui/codemirror` — extend `<tosi-code>.editor` without a silent no-op (#131)
|
|
370
|
+
|
|
371
|
+
`<tosi-code>` exposes the raw `EditorView` and the 1.7 notes invite you to configure CodeMirror
|
|
372
|
+
through it. But CodeMirror 6 keys facets, `StateField`s and gutters by **object identity**, so
|
|
373
|
+
an extension built from a _second copy_ of `@codemirror/state` or `@codemirror/view` is not
|
|
374
|
+
mismatched — it is **silently ignored**. No error, no warning; the gutter never renders.
|
|
375
|
+
|
|
376
|
+
Reported with a real repro: a consumer's `@codemirror/view` resolved to `6.43.6` while ours
|
|
377
|
+
hoisted to a nested `6.43.10`, so their `gutter()` and `GutterMarker` came from a different
|
|
378
|
+
instance than the view. `@codemirror/state` happened to dedupe, which made it worse — enough
|
|
379
|
+
worked that the failure looked like a bug in their gutter code.
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { gutter, GutterMarker, StateField, StateEffect } from 'tosijs-ui/codemirror'
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Only `@codemirror/state` and `@codemirror/view` are re-exported — the two needed to extend a
|
|
386
|
+
view, exactly as the report identified.
|
|
387
|
+
|
|
388
|
+
**Why not peer dependencies**, which the issue proposed and is the obvious fix: a required peer
|
|
389
|
+
would make every consumer install two CodeMirror packages including the overwhelming majority
|
|
390
|
+
who never render an editor (`elementCreator()` registers eagerly, so importing tosijs-ui is not
|
|
391
|
+
evidence of using it), and an _optional_ peer is worse than either — the component silently
|
|
392
|
+
does nothing until you discover you needed 12 packages. Re-exporting removes the failure by
|
|
393
|
+
construction rather than policing it: there is no version to match and no `overrides` to write,
|
|
394
|
+
because there is only ever one copy.
|
|
395
|
+
|
|
396
|
+
### `buildSite` no longer wipes `dist/` — it is your package output, not a site artifact (#130)
|
|
397
|
+
|
|
398
|
+
The prebuild ran `rm -rf dist/` on **every** `buildSite`, including from `devServer` and
|
|
399
|
+
including for projects that configure no library build at all. For the repos this API exists to
|
|
400
|
+
serve, `dist/` is the **published package output** — an input to `npm publish` — so the wipe
|
|
401
|
+
destroyed build products the current run does not rebuild.
|
|
402
|
+
|
|
403
|
+
Reported from tosijs with the full chain: two bundles (`module.debug.js`, `module.safe.js`,
|
|
404
|
+
exposed as the `./debug` and `./safe` subpath exports) are produced only under `--build`,
|
|
405
|
+
because they need a 53-file transpile too slow for the dev loop. `bun start` — or Playwright's
|
|
406
|
+
`webServer`, which runs it — wiped `dist/` and rebuilt five of seven. A publish from that tree
|
|
407
|
+
ships two subpaths that throw `ERR_MODULE_NOT_FOUND`, and it reached a commit once already.
|
|
408
|
+
Nothing local could catch it: size and smoke gates iterate the bundles the current run _built_,
|
|
409
|
+
so a bundle that was **deleted rather than skipped** is invisible to all of them.
|
|
410
|
+
|
|
411
|
+
The rule is now "clean only what you wholly generate":
|
|
412
|
+
|
|
413
|
+
| configuration | `dist/` cleaned? |
|
|
414
|
+
| -------------------------------------------------------------- | ---------------- |
|
|
415
|
+
| `emitLibrary` / `libraryTsconfig` (tsc emits the complete set) | yes |
|
|
416
|
+
| `libraryBuild` — a consumer function that may emit a subset | **no** |
|
|
417
|
+
| none configured — nothing here owns it | **no** |
|
|
418
|
+
|
|
419
|
+
The doc `outputDir` wipe is unchanged and stays: that directory is wholly generated, and the
|
|
420
|
+
build announces it. `dist/` is different.
|
|
421
|
+
|
|
422
|
+
Incidental: `emitLibrary` runs `tsc --incremental`, whose `.tsbuildinfo` lives in `dist/`.
|
|
423
|
+
Wiping it every run meant incremental compilation had never once done anything.
|
|
424
|
+
|
|
425
|
+
### `bun start` serves in ~3.6s instead of ~23s, and prints 13 lines instead of 37
|
|
426
|
+
|
|
427
|
+
**20.1 of those 23 seconds were the dependency audit**, running synchronously before the port
|
|
428
|
+
was bound — where it hit its own 20s timeout, gave up, and **failed open**. So the stall
|
|
429
|
+
bought nothing on that run: it was the cost of a check that did not happen. Measured against
|
|
430
|
+
the live registry, `bun audit` takes **79.5s**.
|
|
431
|
+
|
|
432
|
+
The code justified blocking on the grounds that "the audit is sub-second… a gate you wait for
|
|
433
|
+
cannot be raced". The premise was false, so the conclusion was buying a twenty-second stall on
|
|
434
|
+
every start. It now runs after the server is up and reports when it lands, prefixed loudly
|
|
435
|
+
enough that a late advisory cannot be mistaken for part of the startup banner — non-blocking
|
|
436
|
+
must not mean easy to miss. **Release builds still block**: `buildSite` audits synchronously,
|
|
437
|
+
and `--test` gates there too, which is where waiting is the right trade.
|
|
438
|
+
|
|
439
|
+
**Build output is 13 lines, down from 37.** Eighteen of them were `bun build` listing every
|
|
440
|
+
chunk and every sourcemap — the same size information the build already summarises, only
|
|
441
|
+
longer. Captured now, and replayed **in full** on failure; verified by deliberately breaking
|
|
442
|
+
the bundle entry, which still prints the errors, exits 1, and restores the last good build.
|
|
443
|
+
|
|
444
|
+
Spam is not free: it trains people — and agents — to stop reading build output, which is
|
|
445
|
+
exactly what lets a real failure through.
|
|
446
|
+
|
|
447
|
+
The ePub was the assumed culprit and is innocent: 0.34s of the 23. Measuring first is the only
|
|
448
|
+
reason the actual cause was found.
|
|
449
|
+
|
|
450
|
+
### `docs.json` is cache-busted, so a stale corpus stops surviving a deploy
|
|
451
|
+
|
|
452
|
+
Every generated URL went through the asset stamp — the stylesheet, the hydration bundle, the
|
|
453
|
+
IIFE — **except the one that matters most**. `docs.json` is the entire corpus: nav, routes and
|
|
454
|
+
every page's content, and it was emitted bare.
|
|
455
|
+
|
|
456
|
+
A static host sends no `Cache-Control` for it, so browsers apply **heuristic** caching — free
|
|
457
|
+
to invent a freshness lifetime when you decline to state one. The site then renders a previous
|
|
458
|
+
deploy's corpus against the current bundle, and nothing in the console says so. It presents as
|
|
459
|
+
pages that will not update, and the fix is a hard reload nobody thinks to try. It cost a
|
|
460
|
+
maintainer a long debugging session before it was traced to a Chrome cache entry.
|
|
461
|
+
|
|
462
|
+
The corpus gets **its own stamp**, hashed from `docs.json`'s own bytes rather than the project
|
|
463
|
+
version. The version is right for the bundles — it moves once per release, which is when they
|
|
464
|
+
change. The corpus does not work that way: it changes whenever anyone edits a doc, many times
|
|
465
|
+
within one version, and a preview host redeployed mid-version is the normal way this project
|
|
466
|
+
reads its own site. A version-keyed corpus URL would have been stale for exactly that workflow.
|
|
467
|
+
|
|
468
|
+
The dev server was already correct here — it sends `no-store, must-revalidate` on everything,
|
|
469
|
+
verified. This was only ever the built static site.
|
|
470
|
+
|
|
471
|
+
### `<tosi-table>` stops silently capping at 10,000 rows (#82)
|
|
472
|
+
|
|
473
|
+
`maxVisibleRows` defaulted to a flat `10000` and `slice`d everything past it **without a
|
|
474
|
+
word**. A 25,000-row table showed 10,000, and every count, filter and sort then ran on the
|
|
475
|
+
truncated set — self-consistent, and disagreeing with the data. The property was documented
|
|
476
|
+
nowhere, so the only way to find it was to lose rows and go looking.
|
|
477
|
+
|
|
478
|
+
The number was wrong because it was sized as a _rendering-cost_ limit. It is a **layout**
|
|
479
|
+
limit. A virtual `listBinding` renders only the visible window, so the UI cost is O(1) in row
|
|
480
|
+
count — 300,000 rows scroll like 300 — and the array size is irrelevant. What binds is the
|
|
481
|
+
spacer element's height against the maximum height a browser will lay out.
|
|
482
|
+
|
|
483
|
+
So the cap is now derived at runtime: `maxElementHeight / rowHeight`, which at the default
|
|
484
|
+
30px rows is on the order of half a million to a million rows. **Probed, not hardcoded**,
|
|
485
|
+
because the ceiling is engine- _and version_-specific — two Chromium measurements in this
|
|
486
|
+
project sit a factor of two apart (Chrome 151 clamps at 16777214px, the Playwright Chromium
|
|
487
|
+
at 33554428px). Any constant would be wrong for somebody, in the direction that loses rows.
|
|
488
|
+
|
|
489
|
+
And when a cap does bite, it now **says so in the console** with both numbers and how to
|
|
490
|
+
raise it. `rowHeight: 0` stays conservative on purpose: with no fixed row height there is no
|
|
491
|
+
virtualisation, every row is a real DOM node, and the cost genuinely is O(n).
|
|
492
|
+
|
|
493
|
+
Set `table.maxVisibleRows = n` to override, exactly as before.
|
|
494
|
+
|
|
495
|
+
### A large `rowHeight: 0` table now complains, loudly (#84)
|
|
496
|
+
|
|
497
|
+
`rowHeight: 0` turns virtualisation off — every row becomes a real DOM node, and sorting,
|
|
498
|
+
filtering and group toggles walk all of them on every render. It is documented for "smaller
|
|
499
|
+
tables, or tables with variable row-heights", and that is the only thing it is for.
|
|
500
|
+
|
|
501
|
+
Past **1,000 rows** the table now warns **on every render** until a `rowHeight` is set. That
|
|
502
|
+
is deliberate, and deliberately unlike every other notice in this codebase, which fire once:
|
|
503
|
+
those describe states a developer may not be able to change, whereas this is a one-line fix
|
|
504
|
+
that persists until made, and a single notice scrolls out of the console and is gone.
|
|
505
|
+
|
|
506
|
+
The originally suggested fix was to binary-search the scroll-anchor walk. That was declined:
|
|
507
|
+
it optimises a configuration nobody should be in, and makes a misconfiguration marginally
|
|
508
|
+
less bad instead of surfacing it. A table big enough for the cost to matter is big enough to
|
|
509
|
+
virtualise, and virtual tables are O(1) in row count.
|
|
510
|
+
|
|
511
|
+
### `<tosi-3d>` is deprecated — use `tosijs-3d`
|
|
512
|
+
|
|
513
|
+
Nothing breaks: it still works, still ships, and stays until a future major. But new work
|
|
514
|
+
should use **[`tosijs-3d`](https://www.npmjs.com/package/tosijs-3d)**.
|
|
515
|
+
|
|
516
|
+
It is a much better library, and maintaining both is duplicated effort spent against the
|
|
517
|
+
weaker one. `<tosi-3d>` is a thin Babylon wrapper that grew inside a general-purpose UI
|
|
518
|
+
library; `tosijs-3d` is a project actually about 3D and XR, with the declarative component
|
|
519
|
+
set and XR support that implies. Every hour spent here is an hour not spent there, and the
|
|
520
|
+
result would still be the lesser of the two.
|
|
521
|
+
|
|
522
|
+
`tosijs-3d` grew out of this component, so migration is mostly a rename.
|
|
523
|
+
|
|
524
|
+
The deprecation appears in three places, deliberately: the doc page (what people read), the
|
|
525
|
+
`@deprecated` tags on `B3d`/`b3d` (what editors surface), and a console notice printed **once
|
|
526
|
+
per page on first use** — not at import, since `elementCreator()` registers eagerly and a bare
|
|
527
|
+
`import 'tosijs-ui'` is not evidence anyone is using this; and not per instance, because a page
|
|
528
|
+
with several scenes would spam its own console, which is how a deprecation notice teaches
|
|
529
|
+
people to filter warnings rather than act on them.
|
|
530
|
+
|
|
531
|
+
### The machine-health guard now reports 13GB of orphans instead of skipping them (#93)
|
|
532
|
+
|
|
533
|
+
`isDevProcess` decided what was even a _candidate_ for the runaway check, and excluded
|
|
534
|
+
`build` — because we spawn the bundler ourselves and a build peaking mid-run must not look
|
|
535
|
+
like a runaway to its own parent. But the exemption did not say "our bundler". It said
|
|
536
|
+
**every `bun build` on the machine, forever, regardless of owner or age**, so an orphan whose
|
|
537
|
+
parent died a week ago was exactly as exempt as one that started 200ms ago.
|
|
538
|
+
|
|
539
|
+
Reported with measurements: **69 orphaned `bun build --watch` processes holding 13.09GB**, ~55
|
|
540
|
+
of them spawned inside a single 61-minute window, on a 128GB box running these very dev servers
|
|
541
|
+
and sitting at 116MB free. The guard looked, and skipped every one — while resting on the
|
|
542
|
+
premise that "nothing noticed the runaways because nothing ever looked".
|
|
543
|
+
|
|
544
|
+
`--watch` is the distinguishing signal, and it was right there in the argv: a watcher is a
|
|
545
|
+
server whoever started it. The mid-rebuild false positive the exemption existed for is already
|
|
546
|
+
handled, and handled _precisely_, by parentage — the assessor filters `process.pid` plus
|
|
547
|
+
`descendantsOf(process.pid)` before assessing anything. That is the exact test where a
|
|
548
|
+
subcommand name was a blunt proxy for it, so this costs no false positives. `bun run` with
|
|
549
|
+
`--watch` is caught too; the reported machine had four such servers up to 18 days old.
|
|
550
|
+
|
|
551
|
+
**Visible is not the same as caught**, and the first pass at this shipped the difference. Every
|
|
552
|
+
trigger in the assessor asks "is _one_ process too big?", and 194MB is nowhere near any of them —
|
|
553
|
+
so dropping the exemption walked the guard past all 69 orphans and returned `ok` for 13GB, while
|
|
554
|
+
the note above claimed they had been dealt with. The assessor now carries a rule that never
|
|
555
|
+
consults RSS: **a watcher whose parent is gone (`ppid` 1) is waste at any size**, reported with
|
|
556
|
+
the `kill` command. One 12MB orphan warns. It warns rather than refuses, because a build that
|
|
557
|
+
will not start because of someone's stray process is a guard people switch off.
|
|
558
|
+
|
|
559
|
+
"A bunch of similar names" was the other candidate signal and is deliberately _not_ used: three
|
|
560
|
+
identical `bun --watch bin/dev.ts` lines are usually three sibling projects each running their
|
|
561
|
+
own server. Identical argv is not duplicate work — the distinguishing fact is the cwd, which
|
|
562
|
+
`ps` does not report.
|
|
563
|
+
|
|
564
|
+
**And the other half, in `doc-site-system.md`:** "shell out so the OS reclaims the memory on
|
|
565
|
+
exit" is only true if the child _exits_. A `dev.ts` that spawns `bun build --watch`, itself run
|
|
566
|
+
under `bun --watch dev.ts`, leaks one bundler per keystroke — the previous child is never
|
|
567
|
+
signalled. The doc now carries the reaping idiom (kill the previous child; reap on
|
|
568
|
+
`SIGINT`/`SIGTERM`/`beforeExit`) beside the advice that produced the leak. This project's own
|
|
569
|
+
builds are one-shot and were never affected.
|
|
570
|
+
|
|
571
|
+
### `layout: "full-screen"` finally gets its scroll container (#119)
|
|
572
|
+
|
|
573
|
+
`.doc-content`'s `overflow` is an **inline** style set by the doc-browser, so the full-screen
|
|
574
|
+
layout's stylesheet `overflow: auto` was competing with an inline style and always lost. The
|
|
575
|
+
`height: 100%` landed and the scroller did not — a full-screen page taller than its box was
|
|
576
|
+
clipped and unscrollable. It sat one line below a `padding` that correctly went through a
|
|
577
|
+
variable, with a comment explaining exactly why it had to.
|
|
578
|
+
|
|
579
|
+
Both now go through `--doc-content-overflow` (default `hidden`), on the inline style **and**
|
|
580
|
+
the pre-hydration rule — they have to match, or an override would take effect only after
|
|
581
|
+
hydration and the page would visibly change shape.
|
|
582
|
+
|
|
583
|
+
The second consequence is the one worth knowing about: `overflow: hidden` on an ancestor
|
|
584
|
+
kills `position: sticky` in its subtree, so a page wanting a sticky viewport-sized window
|
|
585
|
+
inside the article previously needed `.doc-content { overflow: visible !important }`. An
|
|
586
|
+
`!important` in a consumer's stylesheet is this component failing to offer a seam.
|
|
587
|
+
|
|
588
|
+
### A page with tests says so, even when tests are off (#113)
|
|
589
|
+
|
|
590
|
+
Tests default to on for localhost and off everywhere else, and when they are off the widget is
|
|
591
|
+
hidden — so **a page with failing tests looked identical to a page with no tests**. There was
|
|
592
|
+
an override, `localStorage['tosijs-ui-tests-enabled']`, but you had to know the key, which
|
|
593
|
+
means you had to already suspect there was something to see.
|
|
594
|
+
|
|
595
|
+
That bit hardest down the path we recommend: `tosijs-tunnel` necessarily serves from a
|
|
596
|
+
non-localhost hostname, so the sanctioned way to view a dev site remotely is exactly where test
|
|
597
|
+
state went invisible. A maintainer read a clean tunnelled page as "the failure must be
|
|
598
|
+
localhost-specific" and lost the thread.
|
|
599
|
+
|
|
600
|
+
The app menu now offers a **Tests** toggle — but only on a page that has tests, where it reads
|
|
601
|
+
`Tests (N not run)` while they are off. `pageTestCount()` is exported from live-example for
|
|
602
|
+
this: how many examples carry tests, regardless of whether tests are enabled — the number that
|
|
603
|
+
lets a UI say a runner exists here without turning it on. A published page with no tests offers nothing, so the
|
|
604
|
+
default stays as quiet as it should be. The default itself was never the problem; the silence
|
|
605
|
+
was.
|
|
606
|
+
|
|
607
|
+
### `/version.json` stops restamping a site that did not change (#122)
|
|
608
|
+
|
|
609
|
+
`docs/` is committed here and in the sibling projects, and the stamp named
|
|
610
|
+
`git rev-parse HEAD` — so **every commit's `version.json` named its own parent, forever**, and
|
|
611
|
+
no build→commit cycle converged:
|
|
612
|
+
|
|
613
|
+
```
|
|
614
|
+
build at A → version.json says A
|
|
615
|
+
commit → B contains a version.json saying A
|
|
616
|
+
rebuild → version.json says B → dirty
|
|
617
|
+
commit → C contains a version.json saying B → …
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
`build-stamp.ts` already argued against a wall-clock stamp for exactly this reason and missed
|
|
621
|
+
that the argument applies one level up: `HEAD` is not a clock, but in a repo that commits its
|
|
622
|
+
own output it moves on the same cadence. Small, and it cost something real — a false positive
|
|
623
|
+
on `git status` at the moment you are trying to confirm a release tree is clean, so it gets
|
|
624
|
+
checked, re-checked, and eventually ignored.
|
|
625
|
+
|
|
626
|
+
The fix is the reporter's: if nothing else in the output changed, leave the stamp alone. A new
|
|
627
|
+
`contentHash` field records what the rest of the output hashed to, so the next build can tell
|
|
628
|
+
"changed" from "unchanged" without keeping a copy of the old tree — though as it happens the
|
|
629
|
+
previous build is still on disk (the build moves it aside as `<outputDir>.last-good` rather
|
|
630
|
+
than deleting it, and drops it only on success), so the comparison costs no extra bookkeeping.
|
|
631
|
+
|
|
632
|
+
Verified end-to-end, which is the only way to check a convergence claim: build → commit →
|
|
633
|
+
rebuild now leaves a **clean tree**.
|
|
634
|
+
|
|
635
|
+
The stamp is also more honest for it. It now names the last build that actually changed the
|
|
636
|
+
site, rather than the last one that happened to run. **Deleting `version.json` still forces a
|
|
637
|
+
fresh stamp** — the escape hatch for re-identifying an unchanged build, at no extra flag.
|
|
638
|
+
|
|
639
|
+
### `describeHolder(null)` says nothing is building (#123)
|
|
640
|
+
|
|
641
|
+
`describeHolder(currentHolder(root))` is the natural spelling, and `currentHolder` returns
|
|
642
|
+
`null` whenever nothing holds the lock — the ordinary, healthy case. It rendered the alarm
|
|
643
|
+
template with the fields unfilled (`pid undefined … building in undefined`), reporting a scary
|
|
644
|
+
problem that did not exist, in a reader whose whole purpose is answering "who holds this?".
|
|
645
|
+
|
|
3
646
|
## 1.13.0
|
|
4
647
|
|
|
5
648
|
### tosijs `^1.9.1` — and a console with nothing in it
|