tosijs-ui 1.14.2 → 1.15.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 +737 -0
- package/NOTICE +26 -0
- package/README.md +7 -1
- package/bin/tunnel.ts +17 -4
- package/dist/crud.js +14 -0
- package/dist/data-table.d.ts +2 -0
- package/dist/data-table.js +127 -23
- package/dist/doc-browser.js +46 -1
- package/dist/doc-system/book-html.d.ts +1 -1
- package/dist/doc-system/book-html.js +29 -0
- package/dist/doc-system/doc-system-styles.d.ts +11 -0
- package/dist/doc-system/doc-system-styles.js +48 -3
- package/dist/doc-system/doc-system.js +28 -1
- package/dist/doc-system/example-policy.d.ts +42 -0
- package/dist/doc-system/example-policy.js +71 -0
- package/dist/doc-system/highlight-styles.d.ts +4 -0
- package/dist/doc-system/highlight-styles.js +125 -0
- package/dist/doc-system/highlight.d.ts +73 -0
- package/dist/doc-system/highlight.js +559 -0
- package/dist/doc-system/print-window.d.ts +30 -0
- package/dist/doc-system/print-window.js +50 -0
- package/dist/doc-system/render.js +7 -4
- package/dist/doc-system/site/asset-stamp.d.ts +29 -0
- package/dist/doc-system/site/asset-stamp.js +67 -0
- package/dist/doc-system/site/audit-guard.js +3 -2
- package/dist/doc-system/site/check-examples-cli.js +12 -1
- package/dist/doc-system/site/check-examples.d.ts +9 -0
- package/dist/doc-system/site/check-examples.js +83 -3
- package/dist/doc-system/site/dev-auth.d.ts +65 -2
- package/dist/doc-system/site/dev-auth.js +81 -8
- package/dist/doc-system/site/dev-server.js +40 -5
- package/dist/doc-system/site/docs.d.ts +11 -0
- package/dist/doc-system/site/docs.js +102 -7
- package/dist/doc-system/site/epub.js +70 -13
- package/dist/doc-system/site/generate-site.d.ts +6 -0
- package/dist/doc-system/site/generate-site.js +7 -3
- package/dist/doc-system/site/host-preset.d.ts +45 -4
- package/dist/doc-system/site/host-preset.js +61 -6
- package/dist/doc-system/site/index.d.ts +1 -0
- package/dist/doc-system/site/index.js +12 -0
- package/dist/doc-system/site/orchestrator.js +169 -23
- package/dist/doc-system/site/site-config.d.ts +54 -1
- package/dist/highlight-block.d.ts +26 -0
- package/dist/highlight-block.js +154 -0
- package/dist/icons.js +11 -0
- package/dist/iife.js +163 -131
- package/dist/iife.js.map +52 -19
- package/dist/index-iife.js +9 -0
- package/dist/index.js +11 -1
- package/dist/live-example/code-transform.d.ts +15 -1
- package/dist/live-example/code-transform.js +23 -1
- package/dist/live-example/component.d.ts +20 -0
- package/dist/live-example/component.js +49 -4
- package/dist/live-example/insert-examples.d.ts +13 -0
- package/dist/live-example/insert-examples.js +41 -4
- package/dist/live-example/save-to-source.d.ts +5 -2
- package/dist/live-example/save-to-source.js +31 -11
- package/dist/live-example/styles.d.ts +11 -0
- package/dist/live-example/styles.js +21 -0
- package/dist/schema-form.js +36 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +5 -1
- package/package.json +11 -3
- package/dist/.metadata_never_index +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,742 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.15.0
|
|
4
|
+
|
|
5
|
+
### `save-to-source` wrote edits into the wrong fence, silently
|
|
6
|
+
|
|
7
|
+
Found by this release's own pre-release review, and it is caused by a feature this release
|
|
8
|
+
introduces — so it could not have shipped ahead of `:static`, and `:static` must not ship
|
|
9
|
+
ahead of it.
|
|
10
|
+
|
|
11
|
+
`example-policy.ts` was added this cycle as **the one rule** for "does this fence become a
|
|
12
|
+
live example", and `insert-examples` was converted to it. Its structural twin was not:
|
|
13
|
+
`save-to-source` kept a flat six-language set *and* its own fence-info regex that captured the
|
|
14
|
+
language as `[\w-]*` — which stops at the colon, so `js:static` read as plain `js` and the
|
|
15
|
+
mode was invisible there while being load-bearing everywhere else.
|
|
16
|
+
|
|
17
|
+
Example ordinals are a **shared coordinate system** between those two modules. They agree only
|
|
18
|
+
if both ask the same question, and after `:static` they did not: every ordinal following a
|
|
19
|
+
display-only fence pointed one group too far, so editing an example saved over a *different*
|
|
20
|
+
block. Silently — the "couldn't locate this example" guard only fires on an out-of-range
|
|
21
|
+
ordinal, and here a group existed at that index, just the wrong one. Under `liveExamples:
|
|
22
|
+
'opt-in'`, which is the prose and book setting, every ordinal on the page shifts.
|
|
23
|
+
|
|
24
|
+
Fixed at the class rather than the instance: `parseFenceInfo` now joins `isLiveFence` in
|
|
25
|
+
`example-policy.ts`, `render.ts` uses it instead of its own inline copy, and `save-to-source`
|
|
26
|
+
groups with `isLiveFence` and takes the policy from the caller. There is one parser and one
|
|
27
|
+
predicate; a third divergence has nowhere to live.
|
|
28
|
+
|
|
29
|
+
Mutation-verified — restoring either half of the old behaviour turns the regression tests red.
|
|
30
|
+
|
|
31
|
+
### `<tosi-table>` was silently capped at 10,000 rows in Firefox
|
|
32
|
+
|
|
33
|
+
Found by the release gate, and pre-existing — it fails identically at v1.14.3, verified in a
|
|
34
|
+
worktree rather than assumed.
|
|
35
|
+
|
|
36
|
+
`probeMaxElementHeight` asks for a 1e9-pixel element and measures what the engine gives back,
|
|
37
|
+
on the stated reasoning that *"every engine tried clamps it"*. Firefox does not: above its
|
|
38
|
+
ceiling it returns **0**, which is indistinguishable here from "no layout to probe". So
|
|
39
|
+
`derivedMaxVisibleRows` fell back to the flat 10,000 that #82 exists to replace — on every
|
|
40
|
+
Firefox page, silently, while the feature reported working everywhere else. Measured:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
1e9 → 0 1e8 → 0 1e7 → 10000000 1e6 → 1000000
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The probe now halves until the element lays out, which converges on the real ceiling within a
|
|
47
|
+
factor of two on any engine, clamping or not, in at most a dozen forced layouts — once per
|
|
48
|
+
page and cached. Result:
|
|
49
|
+
|
|
50
|
+
| engine | before | after |
|
|
51
|
+
| --- | ---: | ---: |
|
|
52
|
+
| Firefox | 10,000 rows | **520,833** |
|
|
53
|
+
| Chromium | 1,118,480 | 1,118,480 |
|
|
54
|
+
| WebKit | 1,118,480 | 1,118,480 |
|
|
55
|
+
|
|
56
|
+
The test had reimplemented the probe inline — the same single ask, carrying the same wrong
|
|
57
|
+
assumption — so it was testing a copy rather than the component. It now calls the shipped
|
|
58
|
+
function, which is why it caught this at all once the real one was fixed.
|
|
59
|
+
|
|
60
|
+
**Why it hid for so long:** CI runs Chromium only, and the fallback is graceful. Nothing was
|
|
61
|
+
broken, just quietly 52× smaller.
|
|
62
|
+
|
|
63
|
+
### Guards that could not fail, and a scope that was never stated
|
|
64
|
+
|
|
65
|
+
- **`theme.codeBg` was unguarded.** The WCAG contrast test hardcoded `#fdfdfd`, so it could
|
|
66
|
+
not see the public knob at all — with `codeBg: '#1e1e1e'` the worst light-mode token measures
|
|
67
|
+
**1.71:1** against AA's 4.5, all eleven light rules fail and all eleven dark ones too, and
|
|
68
|
+
the test stayed green throughout. It now reads the background out of the spec, so it asserts
|
|
69
|
+
against what ships. The limit is also stated rather than implied: the palette is eleven fixed,
|
|
70
|
+
contrast-checked literals and **does not track `codeBg`** — override it and you own the
|
|
71
|
+
contrast (retheme the tokens via the `--token-*` variables). There is now a test asserting
|
|
72
|
+
that limit, so a change in scope has to be a deliberate edit rather than a silent one.
|
|
73
|
+
- **The `checkExamples` process boundary had no test.** The example policy crosses it through
|
|
74
|
+
`TOSI_LIVE_EXAMPLES`, and every build we run sends `'auto'` — so `'opt-in'` had never crossed
|
|
75
|
+
a process boundary anywhere, which is precisely where the earlier M2 defect lived. Now covered
|
|
76
|
+
by an integration test that spawns the CLI: the env key, the clean-JSON stdout contract, and
|
|
77
|
+
the default. Mutation-verified against a child that ignores the env value.
|
|
78
|
+
- **The Print path changed twice in one cycle with no test at any tier.** Extracted as
|
|
79
|
+
`printWhenReady` and covered: an already-`complete` window prints without waiting for a `load`
|
|
80
|
+
that will never fire again, a loading one waits, a failing highlight still prints (plain code
|
|
81
|
+
beats a dialog that never opens), and highlighting happens *before* print rather than racing
|
|
82
|
+
it.
|
|
83
|
+
|
|
84
|
+
### The cache-busting stamp no longer skips an asset in silence
|
|
85
|
+
|
|
86
|
+
`?v=` is computed by hashing the assets it guards, and the loop `continue`d past a missing
|
|
87
|
+
input without a word. That is how `doc-system.css` came to be **named in the inputs and never
|
|
88
|
+
hashed** for the life of the feature: the stylesheet was generated ~80 lines later in a
|
|
89
|
+
~1270-line function. Measured at the time — the `?v=` on every page equalled
|
|
90
|
+
sha256(hydrate.js + iife.js) exactly. A `theme`-only change, which reaches the CSS generator as
|
|
91
|
+
argv and never enters a bundle, therefore deployed new CSS under an **unchanged URL** and
|
|
92
|
+
returning visitors kept the old stylesheet.
|
|
93
|
+
|
|
94
|
+
The previous fix was to move one statement earlier, with nothing asserting the ordering — so
|
|
95
|
+
the same reorder could undo it and every lane would stay green. `computeAssetStamp` is now its
|
|
96
|
+
own module: a named-but-missing input is reported with the file and the consequence, and the
|
|
97
|
+
properties are unit-tested (changing any input moves the stamp; an unchanged rebuild does not;
|
|
98
|
+
the fallback is used only when nothing exists). Verified end to end — the `?v=` in the built
|
|
99
|
+
site equals sha256 of all three assets.
|
|
100
|
+
|
|
101
|
+
### One `language-*` pattern, not five
|
|
102
|
+
|
|
103
|
+
The class pattern was written `[A-Za-z0-9_+#-]+` four times in `highlight.ts` and `[\w-]+` once
|
|
104
|
+
in `epub.ts`, so a `c++` or `c#` fence was a language to one pass over a document and plain `c`
|
|
105
|
+
to another. Hoisted into `langOfClass`, with `+` and `#` in the character class deliberately —
|
|
106
|
+
they are real language names. Mutation-verified against the narrower spelling.
|
|
107
|
+
|
|
108
|
+
### Corrections to the `registerGrammar` surface, before anyone builds on it
|
|
109
|
+
|
|
110
|
+
tjs-lang is writing a TJS Prism grammar against this API (#155), so the companion pieces
|
|
111
|
+
mattered more than their severity suggested.
|
|
112
|
+
|
|
113
|
+
- **`grammarFor` contradicted `highlight()` on registered languages.** It consulted the alias
|
|
114
|
+
table only, so after `registerGrammar('tjs', …)` — the one thing a language author does — it
|
|
115
|
+
still answered `javascript` while `highlight()` correctly used the supplied grammar. It is
|
|
116
|
+
now registration-aware, and documented as returning a grammar *name*.
|
|
117
|
+
- **A false guarantee about print.** The docs said a registered grammar reaches "the
|
|
118
|
+
pre-rendered page, the ePub **and print**". The first two are true; print is not. The
|
|
119
|
+
registry is module state in the *build* process and only `__TOSI_EXAMPLE_POLICY` is stamped
|
|
120
|
+
into the page — so a hard-loaded page is correct (the tokens are already in the HTML) while
|
|
121
|
+
client-side navigation and the in-app Print path re-highlight with an empty registry and fall
|
|
122
|
+
back to `javascript`. Now stated plainly, with the workaround (an ESM consumer can register
|
|
123
|
+
in their `bundleEntry` too) and the remaining gap (a CDN `<script>` consumer cannot).
|
|
124
|
+
- **Two exported types named `ExamplePolicy`** with different value sets — `'auto' | 'opt-in'`
|
|
125
|
+
and `'auto' | 'opt-in' | 'none'` — both reachable through the `./*` wildcard, so which one
|
|
126
|
+
you got depended on the import path. One type now, with `SiteExamplePolicy` expressing the
|
|
127
|
+
site-valid subset as `Exclude<ExamplePolicy, 'none'>` rather than as a second declaration
|
|
128
|
+
that can drift.
|
|
129
|
+
|
|
130
|
+
### The doc-system registration guard reported green on an inert bundle (#159)
|
|
131
|
+
|
|
132
|
+
`bundleRegistrations` tested `bundleSource.includes('tosi-doc-system')` and its own
|
|
133
|
+
documentation called that *"the one case where grepping a bundle is sound"*. It is not. The tag
|
|
134
|
+
being present is **necessary and not sufficient**, and our own documented API supplies the
|
|
135
|
+
counter-example: the context-map loop `document.querySelectorAll('tosi-doc-system')` puts the
|
|
136
|
+
string into a bundle that registers nothing. The reporter's build reported healthy with exactly
|
|
137
|
+
one `customElements.define` in it, none of it the doc system — and the caller then fell through
|
|
138
|
+
to warn about a *different* component, pointing them away from the defect.
|
|
139
|
+
|
|
140
|
+
The obvious repair is wrong in the other direction, and is worth recording because it looks
|
|
141
|
+
right: matching `define(` beside the quoted tag **false-negatives on our own bundle**.
|
|
142
|
+
`elementCreator()` registers with a variable, so the minified output reads
|
|
143
|
+
`customElements.define(i,this,r)` while the literal sits in a `closest()` call and some CSS
|
|
144
|
+
selectors, nowhere near a `define`.
|
|
145
|
+
|
|
146
|
+
It now looks for evidence of a registration by either route — `preferredTagName="<tag>"` (the
|
|
147
|
+
tosijs path, verified to survive minification in both `dist/iife.js` and a built `hydrate.js`)
|
|
148
|
+
or `define("<tag>"` (a direct call). Checked in both directions against real bundles and
|
|
149
|
+
against the #159 reproduction. The false soundness claim is gone from the shipped `.d.ts`; what
|
|
150
|
+
replaces it says plainly that this is a heuristic which now fails closed on the reported shape,
|
|
151
|
+
rather than claiming to be sound.
|
|
152
|
+
|
|
153
|
+
### tosijs-ui no longer steals a `Prism` the page already owns
|
|
154
|
+
|
|
155
|
+
Prism is a free-global library — its grammar files register against `window.Prism` at load
|
|
156
|
+
time — so load order decides who owns it and neither side is told. We were importing our copy
|
|
157
|
+
over the top of a host's. Reproduced rather than reasoned about, with a host Prism carrying one
|
|
158
|
+
custom language and one plugin:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
ensureGrammar(rust) = true
|
|
162
|
+
globalThis.Prism is host? = false
|
|
163
|
+
host custom lang reachable? = false
|
|
164
|
+
host plugins reachable? = false
|
|
165
|
+
host object itself intact? = true ← their captured reference points at an orphan
|
|
166
|
+
we leaked manual onto global? = true
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Nothing errored. Their languages and plugins were simply unreachable, and anything they had
|
|
170
|
+
captured addressed an orphan. The `manual` leak is the worse half: Prism reads that flag at
|
|
171
|
+
load, so a host Prism core loading *after* us computed `manual === true` and **silently never
|
|
172
|
+
ran `highlightAll()`** — us breaking their page, not merely losing a global.
|
|
173
|
+
|
|
174
|
+
Now: we **adopt** a host instance when one exists, never write `manual` onto an instance we did
|
|
175
|
+
not create, and restore the prior global if our own import fails. Every line of that
|
|
176
|
+
reproduction flips. Mutation-verified — deleting the adopt branch or leaking `manual` each
|
|
177
|
+
turns the tests red.
|
|
178
|
+
|
|
179
|
+
This is the #131 hazard class (`@codemirror/state` identity) in a worse container. That one got
|
|
180
|
+
a real remedy — `tosijs-ui/codemirror`, a re-export so only one copy exists — and this one still
|
|
181
|
+
has no equivalent: `registerGrammar` supplies grammars, not instance access. The harm is fixed;
|
|
182
|
+
the asymmetry is recorded in `UPSTREAM.md` and `TODO.md`.
|
|
183
|
+
|
|
184
|
+
The adopter reference states that a page which brought no Prism of its own ends up with ours,
|
|
185
|
+
and that loading your own afterwards wins and stops our highlighting — that is Prism's model,
|
|
186
|
+
not something we can fix from here. Load yours first, or use `registerGrammar` to add a
|
|
187
|
+
language to ours instead of bringing a second copy.
|
|
188
|
+
|
|
189
|
+
### Published-surface corrections
|
|
190
|
+
|
|
191
|
+
- **`bundleEntry` lost its documentation in the emitted `.d.ts`.** `liveExamples` was added
|
|
192
|
+
between that field's JSDoc and the field itself, and TypeScript attaches a doc comment to
|
|
193
|
+
whatever declaration follows it — so `bundleEntry?: string;` shipped with no comment at all,
|
|
194
|
+
taking the #145 warning with it (the one explaining why omitting the doc-system import gives
|
|
195
|
+
you a site that serves 200s and shows nothing). Reordered, and guarded by a test that reads
|
|
196
|
+
the **emitted** `.d.ts` rather than the source, because the source looked fine throughout.
|
|
197
|
+
- **`<tosi-table>`'s selection docs described pre-1.15 behaviour**, calling `selectRow` /
|
|
198
|
+
`selectRows` internals "documented because they may as well be". They are now a supported
|
|
199
|
+
surface that enforces cardinality and fires `selectionChanged` — and the published page said
|
|
200
|
+
neither. It now also carries the feedback-loop guard `<tosi-crud>` needed, since any consumer
|
|
201
|
+
that both drives and observes selection will hit the same thing.
|
|
202
|
+
- **The README understated the iife by 21%** — "~385KB gzip" against a measured 468KB — and
|
|
203
|
+
named CodeMirror as the only contributor. Prism and its 27 grammars (+31.8KB) are the second,
|
|
204
|
+
with the same cause and the same remedy.
|
|
205
|
+
|
|
206
|
+
### `<tosi-highlight>` renders in colour anywhere, not only inside a doc site
|
|
207
|
+
|
|
208
|
+
The component shipped without the palette it needs. This package contains **no CSS files at
|
|
209
|
+
all** — every style is a `StyleSheet()` call — and the `.token.*` rules lived inside the
|
|
210
|
+
doc-system stylesheet. So a `<tosi-highlight>` on an ordinary page produced perfectly correct
|
|
211
|
+
`<span class="token …">` markup and rendered it in flat black: 17 token spans, every one
|
|
212
|
+
computing to `rgb(0,0,0)`. That is the single thing the component exists to do.
|
|
213
|
+
|
|
214
|
+
The doc block compounded it by telling you to "import the doc-system CSS" — naming an import
|
|
215
|
+
that did not exist.
|
|
216
|
+
|
|
217
|
+
The palette now lives in `doc-system/highlight-styles.ts` as an exported `highlightStyleSpec`,
|
|
218
|
+
the doc-system spreads that one copy rather than holding its own, and the element **injects it
|
|
219
|
+
on first use** — so the standalone case needs no import and no configuration. Every colour is
|
|
220
|
+
still a `varDefault`, so `:root { --token-keyword: rebeccapurple }` retunes one token type
|
|
221
|
+
without touching the rest.
|
|
222
|
+
|
|
223
|
+
> Writing the test for this found a second defect before it shipped: `StyleSheet()` does not
|
|
224
|
+
> dedupe by id on its own, and `render()` runs many times per element, so the first version
|
|
225
|
+
> injected a `<style>` per render — nine in a single test file. It now carries the same
|
|
226
|
+
> module-level guard as `ensureTooltipStyles`.
|
|
227
|
+
|
|
228
|
+
### Every "Run this example live" link in an `opt-in` book was dead
|
|
229
|
+
|
|
230
|
+
The ePub generator carried a **third** verbatim copy of the six-language set and
|
|
231
|
+
re-implemented example grouping without consulting `isLiveFence` or `config.liveExamples`.
|
|
232
|
+
|
|
233
|
+
The consequence landed hardest on exactly the people who build ePubs. Under
|
|
234
|
+
`liveExamples: 'opt-in'` — the setting the config docs recommend for book and prose sites —
|
|
235
|
+
the page produces no live examples at all, while the book went on injecting "▶ Run this example
|
|
236
|
+
live ↗" links to every one of them. Every link in the shipped book pointed at an anchor that
|
|
237
|
+
does not exist. With a single `:static` fence the ordinals also slid, so `#example-3` addressed
|
|
238
|
+
a block the page never anchored.
|
|
239
|
+
|
|
240
|
+
A book is the medium where a broken link cannot be corrected after publication, which is what
|
|
241
|
+
moved this from a note to a fix.
|
|
242
|
+
|
|
243
|
+
The ePub now uses the same predicate and the site's own policy, and reads the `:mode` off the
|
|
244
|
+
`<pre>` exactly where `insertExamples` reads it. Its language-class pattern also matched
|
|
245
|
+
`[\w-]+` while the highlighter matched `[A-Za-z0-9_+#-]+`, so a `c++` or `c#` fence was read
|
|
246
|
+
differently by the two; both now use the wider one.
|
|
247
|
+
|
|
248
|
+
The existing test asserted only that the link class was present. It now builds a chapter with a
|
|
249
|
+
`:static` fence ahead of a live one and asserts there is exactly **one** link and no
|
|
250
|
+
`#example-2`. Mutation-verified.
|
|
251
|
+
|
|
252
|
+
### A gitignored file shipped to npm, and no repo-reading check could have seen it
|
|
253
|
+
|
|
254
|
+
1.14.2's tarball contained `dist/.metadata_never_index` — a zero-byte macOS Spotlight marker
|
|
255
|
+
that is gitignored and untracked. Harmless in itself. The mechanism is not.
|
|
256
|
+
|
|
257
|
+
`files` includes `/dist` wholesale, and **when `files` is present npm consults neither
|
|
258
|
+
`.gitignore` nor `.npmignore`** for the paths it selects. So anything sitting in `dist/` ships,
|
|
259
|
+
and being gitignored makes it *less* visible rather than more: every guard that reads the repo
|
|
260
|
+
is blind to it by construction. A stray debug dump or a scratch `.env` would ship exactly as
|
|
261
|
+
readily.
|
|
262
|
+
|
|
263
|
+
Verified the hard way — an `.npmignore` naming the file was added first and changed nothing,
|
|
264
|
+
so there is no declarative fix and the config line was removed again. `bin/smoke-consumer.ts`
|
|
265
|
+
now asserts on the **packed listing**, the only place the truth exists, and fails on
|
|
266
|
+
`.DS_Store`, Spotlight markers, `.env*` and debug logs.
|
|
267
|
+
|
|
268
|
+
Found by diffing the published tarball against the tag — the post-publish check that reads the
|
|
269
|
+
artifact instead of the repo. All 871 built files were byte-identical; this was the only delta.
|
|
270
|
+
|
|
271
|
+
Also removed: **`bin/dist/`**, 1.6MB of tracked build output last rebuilt in August, not in
|
|
272
|
+
`files`, and referenced by nothing. `reviews-1.11-pass2.md` flagged it — and the identical
|
|
273
|
+
Spotlight marker inside it — two releases ago and left the box unticked. Every clone was paying
|
|
274
|
+
for it, and anyone grepping for `iife.js` found a stale copy.
|
|
275
|
+
|
|
276
|
+
### tjs-lang 0.13.13 (#135) — **already shipped in 1.14.3**
|
|
277
|
+
|
|
278
|
+
Carried here so a 1.13.x upgrader gets it too. Full notes under 1.14.3 below: on tjs-lang
|
|
279
|
+
<=0.13.11 a `test '…' { … }` inside a quoted string executed at transpile time, in the host
|
|
280
|
+
page, outside the iframe sandbox. If you are on 1.14.3 you already have this.
|
|
281
|
+
|
|
282
|
+
### Share a link with a PERSON: `tosijs-tunnel --link --share`
|
|
283
|
+
|
|
284
|
+
The magic link was tuned for **device** handoff — you read a seven-character code off one
|
|
285
|
+
screen and type it into the headset in your hands, seconds later, and you want to edit. Five
|
|
286
|
+
minutes is right for that, and the short window is exactly what pays for a token short enough
|
|
287
|
+
to type.
|
|
288
|
+
|
|
289
|
+
**Human** handoff is a different shape and had nothing. You text a URL to a colleague, she
|
|
290
|
+
opens it after her next meeting, and she gets a dead link — which reads as *your feature is
|
|
291
|
+
broken*, not *your link expired*. The alternative, in one real case, was building a Tauri app
|
|
292
|
+
and shipping her an installer so she could look at something.
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
tosijs-tunnel --link --share # read-only, 24 hours
|
|
296
|
+
tosijs-tunnel --link --share --share-ttl=120 # …or two
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
A share link is **read-only** — it browses and cannot save source — and that capability is
|
|
300
|
+
what makes the long life safe. It is deliberately **one flag rather than `--ttl` plus
|
|
301
|
+
`--read-only`**: two orthogonal knobs would let you construct a week-long *write-capable*
|
|
302
|
+
token sitting in a chat log, and nothing would stop you. For the same reason, raising
|
|
303
|
+
`linkTtlMinutes` is not the answer; that produces a long link that can still write.
|
|
304
|
+
|
|
305
|
+
The session it mints is narrowed to match and expires after **7 days** instead of the usual 30
|
|
306
|
+
— it belongs to someone else, so its horizon should be the demo rather than your dev server's
|
|
307
|
+
uptime. `--share-ttl` is capped there, because past it the link would outlive the credential it
|
|
308
|
+
can hand over. Share links also ignore `linkPolicy: 'single-use'`: a texted link gets opened
|
|
309
|
+
when the recipient gets to it, sometimes twice, sometimes after being forwarded — and spending
|
|
310
|
+
it on first redemption is the friction that led an adopter to replace the mechanism with a
|
|
311
|
+
permanent token of their own.
|
|
312
|
+
|
|
313
|
+
**New in the security surface, since this is a dev-tooling path people audit.** Read-only
|
|
314
|
+
sessions are the mechanism: `AuthState` gains a `readOnly` set marking a token, `redeemLink`
|
|
315
|
+
carries the capability from link to session, and `sessionMayWrite` / `cookieMayWrite` answer
|
|
316
|
+
it. `mayWriteSource` gains an optional `sessionMayWrite` — **optional, defaulting to
|
|
317
|
+
permissive**, so every existing three-field caller behaves exactly as before; a narrowing that
|
|
318
|
+
defaulted the other way would have silently disabled editing for everyone. Also exported:
|
|
319
|
+
`resolveShareTtlMs`, `READONLY_SESSION_TTL_MS`, `SHARE_LINK_TTL_MS`. The loopback-listener rule
|
|
320
|
+
that decides what counts as remote is untouched.
|
|
321
|
+
|
|
322
|
+
Configurable as `tunnel.shareTtlMinutes` (default 1440). Nothing about the existing edit link
|
|
323
|
+
changes: still 5 minutes, still write-capable, and `mayWriteSource` treats an omitted
|
|
324
|
+
capability as permissive so no existing caller behaves differently.
|
|
325
|
+
|
|
326
|
+
It still fronts the running dev server, so it needs your machine on. For a demo that has to
|
|
327
|
+
survive your laptop sleeping, use the static preview.
|
|
328
|
+
|
|
329
|
+
### `import 'tosijs-ui/doc-browser'` defines `<tosi-doc-system>` (#158, #159) — **already shipped in 1.14.2**
|
|
330
|
+
|
|
331
|
+
Carried here so a 1.13.x upgrader gets it too. Full notes under 1.14.2 below. If you are on
|
|
332
|
+
1.14.2 or later you already have this.
|
|
333
|
+
|
|
334
|
+
### Client-side navigation lost syntax highlighting on an `opt-in` site
|
|
335
|
+
|
|
336
|
+
The doc-browser called `highlightBlocks` without a policy, so the new `isLiveFence` filter
|
|
337
|
+
defaulted to `'auto'` and skipped all six executable languages — leaving them for a live
|
|
338
|
+
example that an `opt-in` site never creates. Measured on client-side navigation: **531
|
|
339
|
+
highlighted tokens down to 23.**
|
|
340
|
+
|
|
341
|
+
The symptom lands on exactly the audience `liveExamples: 'opt-in'` exists for — prose and book
|
|
342
|
+
sites, where nearly every fence is illustration. It now passes `examplePolicy()`, the same
|
|
343
|
+
global `insertExamples` reads, so the two cannot disagree.
|
|
344
|
+
|
|
345
|
+
### Print waited on a fixed timer instead of `load`
|
|
346
|
+
|
|
347
|
+
`autoPrint: true` used to inject a `load` wait; switching it off so the page could be
|
|
348
|
+
highlighted first dropped the wait with it, and printing raced the render. Restored.
|
|
349
|
+
|
|
350
|
+
### `<tosi-schema-form>`: non-conforming data is documented, not silently absorbed (#162)
|
|
351
|
+
|
|
352
|
+
The form is an editor, not a gate — a value that violates the schema is rendered, validated on
|
|
353
|
+
load and marked, never coerced, stripped or refused. That is deliberate, and it is now written
|
|
354
|
+
down, along with why there is no built-in save gate: with no validator registered `validate()`
|
|
355
|
+
returns `true`, so a gate here would open silently for every consumer who never installed one,
|
|
356
|
+
which reads as enforcement while enforcing nothing.
|
|
357
|
+
|
|
358
|
+
Three known limitations are documented with it, because the value is preserved but **cannot be
|
|
359
|
+
seen**: a value the typed control cannot hold renders empty (`age: "abc"` in a `number` input),
|
|
360
|
+
a union value matching no branch renders no fields at all, and an error whose path has no
|
|
361
|
+
rendered field is invisible while `validate()` returns `false`. Tracked as #162; the fix is a
|
|
362
|
+
panel listing what could not be displayed.
|
|
363
|
+
|
|
364
|
+
### `bun format` was red on two lint errors no gate ever ran
|
|
365
|
+
|
|
366
|
+
CI runs `format-check`, which is **prettier only** — so the ESLint half of `bun format`, the
|
|
367
|
+
command the docs tell you to run before committing, had gone red without failing anything.
|
|
368
|
+
Two errors in `audit-guard.ts` (a dead initializer and a `require()`), both fixed. Worth
|
|
369
|
+
knowing the shape: a documented command and the gate that checks it were not the same command.
|
|
370
|
+
|
|
371
|
+
The annotation gate had the mirror-image problem, and it is worth recording for anyone using
|
|
372
|
+
`tosijs-release-notes`: the commit that wrote up the last batch left `release-check` missing
|
|
373
|
+
three more, because a write-up is itself an annotation. Two were real user-visible fixes,
|
|
374
|
+
written up above. The third was internal and should have carried `[note]` — which is exactly
|
|
375
|
+
what CLAUDE.md means by "the last commit before `git tag` must carry only `[note]` bullets, or
|
|
376
|
+
the loop does not terminate."
|
|
377
|
+
|
|
378
|
+
### ePub NCX `playOrder` was duplicated and non-monotonic
|
|
379
|
+
|
|
380
|
+
`playOrder` is the NCX's **linear reading position**, and the spec requires it unique and
|
|
381
|
+
increasing — readers use it for "next chapter" and progress. It was read inside a template
|
|
382
|
+
literal *after* the recursive child call had already advanced the shared counter, so every
|
|
383
|
+
parent inherited its deepest descendant's number.
|
|
384
|
+
|
|
385
|
+
Our own ePub shipped **4 duplicate values across 72 navPoints**, with a sequence that went
|
|
386
|
+
backwards. A four-level section collapsed all four onto one value.
|
|
387
|
+
|
|
388
|
+
`id` was always correct, because it captured `++counter.n` into a const. `playOrder` now does
|
|
389
|
+
the same, which is the whole fix.
|
|
390
|
+
|
|
391
|
+
Found by checking whether the nav system supports a parent with a parent — it does, at every
|
|
392
|
+
level (model, static HTML, hydrated nav, EPUB3 `nav.xhtml`, NCX, and the CSS indentation),
|
|
393
|
+
and refreshing a deeply nested page discloses every ancestor correctly. A flat corpus cannot
|
|
394
|
+
reproduce this defect, which is why it shipped.
|
|
395
|
+
|
|
396
|
+
### `<tosi-table>`'s selection API agrees with clicking, and notifies (#157)
|
|
397
|
+
|
|
398
|
+
`selectRow`/`selectRows` stamped the selection key with no reference to the table's mode,
|
|
399
|
+
while the click path enforces it three separate ways. So `selectRows([a, b, c], true)` on a
|
|
400
|
+
single-select table left **three rows selected** — a state no amount of clicking can produce —
|
|
401
|
+
and neither method fired `selectionChanged`, so a consumer keeping its own UI in step silently
|
|
402
|
+
missed every programmatic selection. Including "restore selection after a data refresh", which
|
|
403
|
+
is what these methods are documented for.
|
|
404
|
+
|
|
405
|
+
Both now enforce cardinality and notify exactly once. Selecting in single-select mode
|
|
406
|
+
**replaces**, exactly as a plain click does, and warns if handed more than one row rather than
|
|
407
|
+
silently dropping the rest.
|
|
408
|
+
|
|
409
|
+
**Narrower than the issue proposed, and deliberately.** `multiple` is a data-model invariant
|
|
410
|
+
and is enforced; `select` is not, because it governs what the *user* may do rather than what
|
|
411
|
+
the *program* may do. Gating on it broke legitimate headless use — including this component's
|
|
412
|
+
own pinned-row example, which selects a totals row on a table that never enables clicking.
|
|
413
|
+
Cardinality is the invariant the report is really about, and `multiple` covers it alone.
|
|
414
|
+
|
|
415
|
+
Deselection is never gated: it can only reduce a selection, and refusing it would strand rows
|
|
416
|
+
selected before the mode changed with no way to clear them.
|
|
417
|
+
|
|
418
|
+
The click path keeps notifying once per interaction by composing a private non-notifying
|
|
419
|
+
primitive — a plain click deselects everything and then selects one, so a notifying public API
|
|
420
|
+
called from inside would fire several times per click.
|
|
421
|
+
|
|
422
|
+
**`<tosi-crud>` needed a guard for the new notification**, and the shape is worth knowing if
|
|
423
|
+
you drive a table programmatically: it both drives and observes the selection, so its own
|
|
424
|
+
`deSelect()`/`selectRow()` sync now called back into its handler — the deselect half reporting
|
|
425
|
+
an empty selection, which cleared the current record mid-edit. A component that writes and
|
|
426
|
+
watches the same state has to be able to tell its own writes from the user's.
|
|
427
|
+
|
|
428
|
+
### `registerGrammar` — a language can supply its own Prism definition (#155)
|
|
429
|
+
|
|
430
|
+
Our alias table maps fence names onto Prism's built-in grammars, which is the wrong seam for a
|
|
431
|
+
language that ships its own. tjs-lang is generating a TJS definition from the same source that
|
|
432
|
+
already emits their TextMate grammars; hardcoding it here would put their grammar behind our
|
|
433
|
+
release cadence.
|
|
434
|
+
|
|
435
|
+
```js
|
|
436
|
+
import { registerGrammar } from 'tosijs-ui/site'
|
|
437
|
+
import { tjsGrammar } from 'tjs-lang/prism'
|
|
438
|
+
registerGrammar('tjs', tjsGrammar)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Registered grammars win over the alias table, install under the **fence** name (so the class
|
|
442
|
+
stays `language-tjs` for a theme to target), and are used by the **build-time** pass — so they
|
|
443
|
+
reach the pre-rendered page, the ePub and print, not just a hydrated browser tab.
|
|
444
|
+
|
|
445
|
+
That last part is the whole point, and it is their argument rather than mine: a wrong token
|
|
446
|
+
colour is cosmetic on the web and permanent in a printed book. TJS's colon examples
|
|
447
|
+
(`greet(name: 'Alice')`) are *values*; rendered with TypeScript's type colour, the page
|
|
448
|
+
visually asserts the exact misreading the document exists to correct.
|
|
449
|
+
|
|
450
|
+
### `popstate` no longer re-renders on a hash-only change (#152)
|
|
451
|
+
|
|
452
|
+
Setting `location.hash` is a fragment navigation, and per spec it fires `popstate` before
|
|
453
|
+
`hashchange` — so any component keeping state in the hash re-mounted the entire article on
|
|
454
|
+
every write, destroying and re-creating every live example on the page. An editor lost focus
|
|
455
|
+
and its buffer mid-keystroke. The handler now compares the resolved filename and only scrolls
|
|
456
|
+
when the document is unchanged.
|
|
457
|
+
|
|
458
|
+
### No rule redefines a global palette token (#150)
|
|
459
|
+
|
|
460
|
+
`button, select, .clickable` and `pre, code` each set
|
|
461
|
+
`--text-color: var(--brand-color)` — redefining the token the whole palette derives from
|
|
462
|
+
(`--tosi-text: var(--text-color)`) on an *element* selector. From any button downward it
|
|
463
|
+
stopped meaning "the theme's text colour".
|
|
464
|
+
|
|
465
|
+
That reaches into anything embedded: an editor deriving its chrome from the page resolved
|
|
466
|
+
correctly in the document and to the brand colour on its own slotted toolbar buttons — oklab
|
|
467
|
+
L≈0.27 against an L 0.16 bar in dark mode, unreadable, with correct body text beside it. The
|
|
468
|
+
`pre, code` instance also put every syntax-highlighting token span in a poisoned palette.
|
|
469
|
+
|
|
470
|
+
Both now set `color`. A test encodes the distinction rather than an allowlist: a rule may
|
|
471
|
+
redefine the token **only if it owns a background** — `header` is brand-coloured, so its
|
|
472
|
+
contents genuinely need the matching text colour and should inherit it. A rule that only wants
|
|
473
|
+
to colour itself sets `color`.
|
|
474
|
+
|
|
475
|
+
### Packaging and API corrections
|
|
476
|
+
|
|
477
|
+
**`tosijs-ui/highlight-block` resolves under every condition.** It shipped as the only named
|
|
478
|
+
subpath without a `default` export condition — its ten siblings all had one. An exact key beats
|
|
479
|
+
the `./*` wildcard and *errors* (`ERR_PACKAGE_PATH_NOT_EXPORTED`) rather than falling through,
|
|
480
|
+
so a resolver under `['require','default']` — Jest's default, a CJS-targeting webpack/rollup
|
|
481
|
+
build, Vite SSR externalisation — hard-failed on that one subpath while `tosijs-ui/diff` beside
|
|
482
|
+
it resolved. `bun run test-consumer` now asserts **structurally** that every named subpath
|
|
483
|
+
carries `types` + `import` + `default`.
|
|
484
|
+
|
|
485
|
+
**`prismjs` is a real dependency, not an optional peer.** It first shipped declared *only* as
|
|
486
|
+
a devDependency while `dist/doc-system/highlight.js` imported it, so an adopter's build
|
|
487
|
+
resolved it by hoisting luck — and without it their doc site, ePub and print output had no
|
|
488
|
+
syntax highlighting at all, silently. Making it an optional peer then broke the build outright:
|
|
489
|
+
the grammar loaders are static imports a bundler must resolve, so an adopter without the
|
|
490
|
+
package could not build. This
|
|
491
|
+
is the fork already recorded for `tosijs-schema`: a literal import fails a consumer's build
|
|
492
|
+
when the package is absent, and a variable specifier cannot resolve in a browser. It costs one
|
|
493
|
+
lockfile entry and one audit surface; it costs **no bytes** unless something highlights, since
|
|
494
|
+
`<tosi-highlight>` is excluded from the root barrel and the grammars are lazy chunks. The iife
|
|
495
|
+
is the exception and pays **+31.8kb gzip** — see below.
|
|
496
|
+
|
|
497
|
+
**`ExamplePolicy` gained `'none'`** — "nothing here is live" — for targets that cannot run an
|
|
498
|
+
example at all. The ePub and the Print path pass it.
|
|
499
|
+
|
|
500
|
+
**`transformAvailable(dialect)`** is exported: the question a diagnostic must ask before it
|
|
501
|
+
accuses a document. `checkExamples` uses it to **skip** dialects whose transpiler could not be
|
|
502
|
+
resolved and report them as *unchecked*, instead of parsing them as JavaScript and blaming the
|
|
503
|
+
author (#154). `ExampleCheck.skipped` now survives the child-process boundary, so that field is
|
|
504
|
+
true on the path the build actually uses rather than always reading "nothing was skipped".
|
|
505
|
+
|
|
506
|
+
**The "writes outside `outputDir`" warning fires under `bun start`.** It was gated on
|
|
507
|
+
`!opts.skipAudit`, which means *interactive* — so it never appeared in the dev server, the
|
|
508
|
+
exact workflow the report came from.
|
|
509
|
+
|
|
510
|
+
### The iife is +31.8kb gzip — and that is above our own gate
|
|
511
|
+
|
|
512
|
+
`dist/iife.js` went **435.7kb → 467.5kb gzip (+7.3%)** between 1.14.1 and 1.15.0. An iife
|
|
513
|
+
cannot code-split, so it inlines Prism core and all 27 grammar modules. This lands on every CDN
|
|
514
|
+
`<script>` user and every `tosijs-ui/site` adopter without `bundleEntry`, on every page load,
|
|
515
|
+
and it compounds #120 where CodeMirror is already most of that bundle.
|
|
516
|
+
|
|
517
|
+
`CLAUDE.md` sets the gate for a new runtime dependency as the printed gzip delta, and this
|
|
518
|
+
exceeds it. It ships anyway because the alternative was a highlighter that did not work in a
|
|
519
|
+
browser — but the number is stated here rather than left in a commit body, and the unexplored
|
|
520
|
+
remedy (capping the grammar map, or marking prism external for the iife specifically) is
|
|
521
|
+
tracked in `TODO.md` so tagging cannot evaporate it.
|
|
522
|
+
|
|
523
|
+
**The ESM path is unaffected**: an app importing a button ships no prism at all.
|
|
524
|
+
|
|
525
|
+
### Need more icons?
|
|
526
|
+
|
|
527
|
+
The icons page now points at [lucide](https://lucide.dev) — a maintained fork of Feather in the
|
|
528
|
+
same style with far more options, ISC licensed. It emits the same geometry Feather does
|
|
529
|
+
(`24×24`, `fill="none"`, `stroke="currentColor"`, width 2, round caps), so a downloaded SVG
|
|
530
|
+
drops into `defineIcons()` and inherits colour and sizing like a built-in.
|
|
531
|
+
|
|
532
|
+
### Smaller corrections from the pre-release review
|
|
533
|
+
|
|
534
|
+
- **`checkExamples` no longer hard-fails on a fence that will never run.** It reduced
|
|
535
|
+
` ```js:static ` to `js`, so a block marked illustrative — or any block under
|
|
536
|
+
`liveExamples: 'opt-in'` that never asked to run — failed the build over its syntax, advising
|
|
537
|
+
the author to retag it `typescript`: exactly the mislabelling this release abolishes. It now
|
|
538
|
+
keeps the fence mode and gates on the same predicate the highlighter and `insertExamples` use.
|
|
539
|
+
- **`highlightBlocks` asks that predicate too**, rather than relying on `insertExamples` having
|
|
540
|
+
already wrapped live blocks. That held only by ordering, which nothing enforced, and
|
|
541
|
+
tokenizing live-example source is what cost seven doc tests mid-cycle.
|
|
542
|
+
- **`ensureGrammar` distinguishes "no such grammar" from "failed to load".** The first is a
|
|
543
|
+
plain code block; the second is a real problem and now says so. Conflating them is why a
|
|
544
|
+
browser-side failure shipped silently.
|
|
545
|
+
- **The `docPaths` escape from the default `reviews/` exclusion actually works** — the basename
|
|
546
|
+
test ran on the roots too, so naming the directory produced nothing while three shipped
|
|
547
|
+
places said otherwise.
|
|
548
|
+
- **The `-test-only` class** lets a stylesheet present a test-only example's results as its
|
|
549
|
+
body rather than as an annotation under an empty preview.
|
|
550
|
+
- **The barrel guard checks two properties**: a plain component build ships zero prism, and
|
|
551
|
+
every grammar import lives inside a thunk.
|
|
552
|
+
|
|
553
|
+
### A test-only example shows its results instead of an empty box
|
|
554
|
+
|
|
555
|
+
A ` ```test ` fence with no `js`/`html`/`css` beside it has nothing to render. The preview came
|
|
556
|
+
up empty and the results panel stayed hidden unless a test **failed** — so a passing suite
|
|
557
|
+
looked like a broken example, and hid the one thing the block existed to show.
|
|
558
|
+
|
|
559
|
+
Now the results are the example's body: always visible, in flow, with the empty preview taking
|
|
560
|
+
no space. Ordinary examples are unchanged — their results stay a floating panel, shown on the
|
|
561
|
+
test tab or on failure.
|
|
562
|
+
|
|
563
|
+
Useful for documenting a pure function, an invariant, or any behaviour with no visual output.
|
|
564
|
+
There is a worked example in the `example` docs, which is also what the Playwright test asserts
|
|
565
|
+
against — the corpus had none when this was written.
|
|
566
|
+
|
|
567
|
+
### Four adoption defects from tjs-lang (#151, #153, #154, #156)
|
|
568
|
+
|
|
569
|
+
All four are silent — the build succeeds and something is quietly wrong, which is #61's thesis
|
|
570
|
+
in four more instances.
|
|
571
|
+
|
|
572
|
+
**`SiteConfig` gained `ignoreDocPaths`, and `reviews/` is excluded by default (#153).** A
|
|
573
|
+
`docPaths: ['docs']` takes the directory wholesale, and one adopter's first successful build
|
|
574
|
+
**published 13 internal pre-release review reports as public pages** — including BLOCK
|
|
575
|
+
verdicts naming an adopter. `extractDocs` already had `ignore`; it simply was not reachable
|
|
576
|
+
from the config. The practices doc that tells you to write review reports is the same one
|
|
577
|
+
warning that publishing them is the bad outcome, so the safe thing is now the default rather
|
|
578
|
+
than the informed choice. An explicit `docPaths` entry still wins — naming a directory skips the basename exclusion,
|
|
579
|
+
which applies only to directories found while walking. And when a default exclusion withholds
|
|
580
|
+
files it now prints the directory, the count and how to publish them, rather than dropping
|
|
581
|
+
pages with no receipt.
|
|
582
|
+
|
|
583
|
+
> **If you built a site on 1.14.1 or earlier, upgrading is not enough.** The default only
|
|
584
|
+
> protects the *next* build. Scraped review pages carry no `noindex` and are listed in
|
|
585
|
+
> `sitemap.xml` and `llms.txt`, so anything already deployed stays public and indexable until
|
|
586
|
+
> you replace it. Check your **deployed** output — not your working tree — for pages derived
|
|
587
|
+
> from a `reviews/` directory, then rebuild on 1.15.0 and redeploy. If they were indexed, the
|
|
588
|
+
> rebuild removes the pages but you may also want a removal request.
|
|
589
|
+
|
|
590
|
+
**The build says which files outside `outputDir` it will overwrite (#154).** `outputDir` reads
|
|
591
|
+
as a box the build stays inside and is not one: `docsJson` defaults to `demo/docs.json` and
|
|
592
|
+
`llms.txt` is written at the project root. An adopter who set `outputDir` to a scratch
|
|
593
|
+
directory *specifically to evaluate without touching their repo* lost both — including the
|
|
594
|
+
`docs.json` their playground reads at runtime. Warned only when `outputDir` has been moved off
|
|
595
|
+
its default, which is the "I am containing this build" gesture; a project on the default has an
|
|
596
|
+
ordinary setup where these writes are the point.
|
|
597
|
+
|
|
598
|
+
**`checkExamples` no longer blames the document for a transpiler it could not load (#154).**
|
|
599
|
+
`loadTransform` degrades to identity when tjs-lang is unresolvable — right at runtime, wrong
|
|
600
|
+
for a build-time check, because the block is then parsed as raw JavaScript. The reporter got
|
|
601
|
+
~30 syntax errors in **valid TJS**, each advising them to fix correct code, with the one line
|
|
602
|
+
explaining the degradation printed far away. Unparseable dialects are now **skipped and
|
|
603
|
+
reported as unchecked** — "we did not look" and "we looked and it is fine" must not produce the
|
|
604
|
+
same output, and neither may masquerade as "your document is broken".
|
|
605
|
+
|
|
606
|
+
**Dev rebuilds bust the browser cache (#151).** Assets were stamped with the package version,
|
|
607
|
+
which does not change while you work — so `hydrate.js?v=0.2.0` was byte-identical across every
|
|
608
|
+
rebuild and the browser kept executing the bundle it cached hours ago, through edits and
|
|
609
|
+
reloads. It looks exactly like "my change had no effect": the reporter spent a long session
|
|
610
|
+
editing CSS that was already correct and already being served. Dev builds now stamp with a
|
|
611
|
+
**content hash** of the bundle, the iife and the stylesheet, so it moves when the output moves
|
|
612
|
+
and *only* then. **Every** build does — there is no separate release mode.
|
|
613
|
+
|
|
614
|
+
Two defects in the first cut, both caught by the 1.15.0 review. `doc-system.css` was named in
|
|
615
|
+
the hash inputs but generated ~80 lines after the stamp was consumed, and the hash loop skips a
|
|
616
|
+
missing file silently — so a `theme`-only change deployed new CSS under an unchanged URL, a
|
|
617
|
+
regression against the version stamp it replaced. And the "release builds keep the version"
|
|
618
|
+
branch keyed on `opts.lock`, which no shipped caller passes and which means *acquire the build
|
|
619
|
+
lock*: it never ran, and an adopter using `lock` for its documented purpose would have silently
|
|
620
|
+
changed stamping mode. The stylesheet is now generated before the stamp, and the branch is
|
|
621
|
+
gone.
|
|
622
|
+
|
|
623
|
+
**A doc that documents the metadata format is no longer classified by its own example
|
|
624
|
+
(#156).** `<!--{…}-->` matched anywhere in the file, so a page teaching the convention was
|
|
625
|
+
published with the illustration's `title` and `order`. Fenced code is now stripped before
|
|
626
|
+
scanning and the block must start a line — the same rule `/*#` doc comments already follow.
|
|
627
|
+
|
|
628
|
+
### Static code is syntax-highlighted — on the page, in the ePub, and in print
|
|
629
|
+
|
|
630
|
+
Display-only code was highlighted **nowhere**. The pre-rendered page emitted a bare
|
|
631
|
+
`<pre><code class="language-ts">`, the client never touched it, and the ePub stripped the
|
|
632
|
+
language class entirely — so a doc system that publishes books had no highlighting in the
|
|
633
|
+
books.
|
|
634
|
+
|
|
635
|
+
Highlighting now happens at **build time**, which is the only thing that reaches an ePub
|
|
636
|
+
(readers may run no JavaScript), print, a no-JS reader, or a crawler. Token markup is in the
|
|
637
|
+
HTML; all it needs is CSS.
|
|
638
|
+
|
|
639
|
+
- **Site**: **two palettes**, light (the default) and dark under `.darkmode`, each
|
|
640
|
+
contrast-checked against the `--code-bg` it sits on — worst case 5.32:1 light, 6.01:1 dark,
|
|
641
|
+
against WCAG AA's 4.5:1. Every colour is a `varDefault`, so a single token type can be
|
|
642
|
+
overridden in one mode without replacing the set.
|
|
643
|
+
- **Book and print**: a separate LIGHT palette, contrast-checked against the `#f6f8fa` code
|
|
644
|
+
background (the weakest is ~4.6:1, above WCAG AA).
|
|
645
|
+
- Both the ePub and the **Print** path pass a `'none'` example policy, because neither can run
|
|
646
|
+
a live example — so an executable fence is highlighted there rather than skipped. Without it
|
|
647
|
+
the ePub left **237 of 284 code blocks plain** (every `js`, `html`, `css` and `test` block,
|
|
648
|
+
which is most of the corpus) while this entry claimed otherwise, and the print path never
|
|
649
|
+
highlighted at all, so the book stylesheet's token rules matched nothing.
|
|
650
|
+
- **Client**: the same pass runs after client-side navigation, and skips anything already
|
|
651
|
+
highlighted, which is what keeps the pre-rendered page and its hydrated self identical.
|
|
652
|
+
|
|
653
|
+
**Executable fences are never highlighted** — they are live-example *source*, and tokenizing
|
|
654
|
+
them hands the example markup where it expected code. The build and the client now ask that
|
|
655
|
+
question through one shared predicate (`doc-system/example-policy.ts`), because for one build
|
|
656
|
+
they disagreed and seven doc tests failed as "Expected 0 to be 4": an example that rendered
|
|
657
|
+
nothing, reported as a broken component rather than a broken pipeline.
|
|
658
|
+
|
|
659
|
+
### New: `<tosi-highlight>` — by subpath, not from the root barrel
|
|
660
|
+
|
|
661
|
+
**Import it as `tosijs-ui/highlight-block`.** It is deliberately *not* re-exported from the
|
|
662
|
+
root barrel, for the same reason the doc-system cluster is not (#133): its import chain
|
|
663
|
+
reaches the static Prism grammar map, and a bundler emitted **15 grammar chunks / 357kb** for
|
|
664
|
+
an app that imported a rating and a select. Measured before and after — 30 chunks / 373kb → 1
|
|
665
|
+
chunk / 287kb.
|
|
666
|
+
|
|
667
|
+
The iife pulls it explicitly, so CDN `<script>` and doc-site users are unaffected. Bundling
|
|
668
|
+
grammars is right there and wrong in the barrel: an iife cannot code-split, and its consumers
|
|
669
|
+
want highlighting.
|
|
670
|
+
|
|
671
|
+
A tree-shakeable component for a fetched snippet, a generated example, an API response — the
|
|
672
|
+
cases the build cannot know about. Renders readable plain text synchronously and upgrades when
|
|
673
|
+
the grammar arrives, so a reader never sees an empty box and a missing grammar degrades to
|
|
674
|
+
plain code rather than failing.
|
|
675
|
+
|
|
676
|
+
Prism is a **lazy chunk**: an app that imports a button ships none of it. `src/index.test.ts`
|
|
677
|
+
asserts that against a real split bundle, since the property is easy to lose to a stray static
|
|
678
|
+
import.
|
|
679
|
+
|
|
680
|
+
Prism rather than reusing the CodeMirror we already ship, because #120 is an open issue about
|
|
681
|
+
CodeMirror being in every IIFE at 94% of the bundle — and routing static highlighting through
|
|
682
|
+
it would deepen exactly the dependency that issue wants to escape. It also covers the languages
|
|
683
|
+
a prose corpus uses and the editor never bundled: shell, python, rust, yaml, diff.
|
|
684
|
+
|
|
685
|
+
### Live examples can be opt-in, and a single fence can opt out (#140, #146)
|
|
686
|
+
|
|
687
|
+
Six fence languages execute — `js`, `ts`, `tjs`, `html`, `css`, `test` — and until now a
|
|
688
|
+
**static sample in one of them was impossible.** The only workaround was to mislabel the fence
|
|
689
|
+
(`xml` for HTML, `scss` for CSS), which changes the syntax highlighting to a language it isn't.
|
|
690
|
+
|
|
691
|
+
Two escapes. **The default does not move**, so no existing corpus changes:
|
|
692
|
+
|
|
693
|
+
```
|
|
694
|
+
```js:static one fence, opted out — works under either policy
|
|
695
|
+
liveExamples: 'opt-in' nothing runs unless its fence asks (`:inline` / `:iframe` / `:ide`)
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
`'opt-in'` is the setting for a **prose or book site**, where code is overwhelmingly
|
|
699
|
+
illustration. `html` and `css` are the ones that bite, because neither looks like something
|
|
700
|
+
you are asking to run: a fence showing how to style the component *in your app* is injected as
|
|
701
|
+
a page-wide stylesheet — a white slab in dark mode — and a fence showing the markup something
|
|
702
|
+
compiles to renders as an unstyled demo. Neither fails a build, so a site can ship both and
|
|
703
|
+
nobody finds out.
|
|
704
|
+
|
|
705
|
+
The policy reaches the client the same way `__TJS_LOCAL_BASE` does, and is **only emitted when
|
|
706
|
+
it differs from the default**, so an `auto` site's HTML is byte-identical to before.
|
|
707
|
+
|
|
708
|
+
`:static` is honoured under both policies, so one corpus can be written to work either way.
|
|
709
|
+
## 1.14.3
|
|
710
|
+
|
|
711
|
+
A single-fix patch cut from `v1.14.2`. Carries no 1.15.0 work.
|
|
712
|
+
|
|
713
|
+
### tjs-lang 0.13.13 — a quoted test block was executed in the HOST page (#135)
|
|
714
|
+
|
|
715
|
+
On tjs-lang ≤ 0.13.11, a `test '…' { … }` written inside a template literal or a
|
|
716
|
+
double-quoted string was mistaken for a real test block: the body ran at transpile time and
|
|
717
|
+
the text was **deleted from the output**. Both silent.
|
|
718
|
+
|
|
719
|
+
That is the bad case for a doc site specifically, because documentation about a language
|
|
720
|
+
quotes the language. `transform()` is called from the component — the **host page** — and its
|
|
721
|
+
output is then injected into the iframe, so anything the transpiler executes at transpile time
|
|
722
|
+
runs outside the sandbox that exists for exactly this. Any tjs example *showing* a test block
|
|
723
|
+
had it deleted from the rendered example and executed in the host page. Upstream lost 52 of 99
|
|
724
|
+
failures in their own conversion gate to it before finding the cause.
|
|
725
|
+
|
|
726
|
+
Fixed in 0.13.12; this moves to **0.13.13** (latest), verified across all four lanes rather
|
|
727
|
+
than on the release note alone. 0.13.5–0.13.13 also carry the AJS/TJS parser split, a
|
|
728
|
+
prototype-chain lookup fix in the VM, a source-size cap on `Eval`/`SafeFunction`, and further
|
|
729
|
+
literal-blindness fixes.
|
|
730
|
+
|
|
731
|
+
The pin lives in **two** places by design — `package.json` and `TJS_VERSION` in
|
|
732
|
+
`code-transform.ts`, which governs the CDN bundle a doc site actually loads — and both moved
|
|
733
|
+
together. The peer floor is unchanged at `^0.13.1`: raising it would warn on install for
|
|
734
|
+
people whose own pin is older, and the CDN pin is what fixes the behaviour on the default
|
|
735
|
+
path. **If you install tjs-lang yourself, upgrade to ≥0.13.12** — your copy wins over ours.
|
|
736
|
+
|
|
737
|
+
Reported by tjs-lang, which was blocked on this while moving its playground onto the doc-site
|
|
738
|
+
system. Released as a patch so nobody waits for 1.15.0 for it.
|
|
739
|
+
|
|
3
740
|
## 1.14.2
|
|
4
741
|
|
|
5
742
|
A single-fix patch, cut from `v1.14.1` rather than from `main`, so it carries none of the
|