tosijs-ui 1.7.0-beta.1 → 1.7.0-beta.5

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +150 -23
  2. package/README.md +10 -4
  3. package/bin/make-icon-data.js +7 -3
  4. package/dist/babylon-3d.js +3 -1
  5. package/dist/code-editor-cm.d.ts +8 -7
  6. package/dist/code-editor-cm.js +28 -3
  7. package/dist/code-editor.d.ts +19 -3
  8. package/dist/code-editor.js +66 -19
  9. package/dist/doc-browser.d.ts +4 -0
  10. package/dist/doc-browser.js +54 -15
  11. package/dist/doc-system/book-html.js +7 -2
  12. package/dist/doc-system/book-manifest.js +1 -3
  13. package/dist/doc-system/doc-system-styles.d.ts +2 -0
  14. package/dist/doc-system/doc-system-styles.js +112 -0
  15. package/dist/doc-system/doc-system.js +20 -13
  16. package/dist/doc-system/doc-title.d.ts +19 -0
  17. package/dist/doc-system/doc-title.js +51 -0
  18. package/dist/doc-system/nav-tree.js +0 -0
  19. package/dist/doc-system/render.d.ts +8 -1
  20. package/dist/doc-system/render.js +36 -17
  21. package/dist/doc-system/routing.js +5 -4
  22. package/dist/doc-system/site/bundle-guard.d.ts +12 -3
  23. package/dist/doc-system/site/bundle-guard.js +15 -4
  24. package/dist/doc-system/site/check-examples-cli.d.ts +1 -0
  25. package/dist/doc-system/site/check-examples-cli.js +46 -0
  26. package/dist/doc-system/site/check-examples.d.ts +18 -2
  27. package/dist/doc-system/site/check-examples.js +27 -10
  28. package/dist/doc-system/site/dev-server.d.ts +26 -0
  29. package/dist/doc-system/site/dev-server.js +442 -115
  30. package/dist/doc-system/site/docs.d.ts +4 -0
  31. package/dist/doc-system/site/docs.js +1 -1
  32. package/dist/doc-system/site/epub.d.ts +9 -0
  33. package/dist/doc-system/site/epub.js +39 -2
  34. package/dist/doc-system/site/generate-css.js +7 -5
  35. package/dist/doc-system/site/generate-site.d.ts +18 -2
  36. package/dist/doc-system/site/generate-site.js +80 -27
  37. package/dist/doc-system/site/make-llms-txt.js +3 -1
  38. package/dist/doc-system/site/orchestrator.js +169 -6
  39. package/dist/doc-system/site/preflight.d.ts +152 -0
  40. package/dist/doc-system/site/preflight.js +401 -0
  41. package/dist/doc-system/site/site-config.d.ts +24 -0
  42. package/dist/hydrate/ajs-language-h1x0ydap.js +11 -0
  43. package/dist/hydrate/ajs-language-h1x0ydap.js.map +10 -0
  44. package/dist/hydrate/code-editor-cm-2hgj9002.js +10 -0
  45. package/dist/hydrate/code-editor-cm-2hgj9002.js.map +20 -0
  46. package/dist/hydrate/hydrate.js +214 -0
  47. package/dist/hydrate/hydrate.js.map +74 -0
  48. package/dist/hydrate/index-iife-5wzyvyzm.js +27 -0
  49. package/dist/hydrate/index-iife-5wzyvyzm.js.map +22 -0
  50. package/dist/hydrate/index-iife-64z9yngn.js +5 -0
  51. package/dist/hydrate/index-iife-64z9yngn.js.map +9 -0
  52. package/dist/hydrate/index-iife-x2h4f6fc.js +14 -0
  53. package/dist/hydrate/index-iife-x2h4f6fc.js.map +12 -0
  54. package/dist/hydrate/index-nsmyq9mv.js +6 -0
  55. package/dist/hydrate/index-nsmyq9mv.js.map +10 -0
  56. package/dist/iife.js +127 -132
  57. package/dist/iife.js.map +39 -37
  58. package/dist/live-example/code-transform.d.ts +0 -39
  59. package/dist/live-example/code-transform.js +37 -301
  60. package/dist/live-example/component.d.ts +5 -1
  61. package/dist/live-example/component.js +177 -41
  62. package/dist/live-example/example-store.d.ts +1 -0
  63. package/dist/live-example/execution.d.ts +15 -3
  64. package/dist/live-example/execution.js +30 -11
  65. package/dist/live-example/insert-examples.js +41 -1
  66. package/dist/localize.js +2 -1
  67. package/dist/mapbox.d.ts +1 -0
  68. package/dist/mapbox.js +24 -5
  69. package/dist/tooltip.js +3 -1
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/dist/version.d.ts +1 -1
  72. package/dist/version.js +1 -1
  73. package/llms.txt +1 -1
  74. package/package.json +6 -5
package/CHANGELOG.md CHANGED
@@ -1,43 +1,154 @@
1
1
  # Changelog
2
2
 
3
- ## 1.7.0-beta.1
3
+ ## 1.7.0-beta.3
4
+
5
+ **Self-contained examples, and CodeMirror + the tjs transpiler are now edit-time only.** A
6
+ reader loading a doc page — directly or via client-side navigation — runs every example with
7
+ **neither the tjs transpiler nor CodeMirror on first paint**. Both load only when you open a
8
+ code panel to edit.
9
+
10
+ Still a beta under the `beta` tag (`latest` stays 1.6.x): `npm i tosijs-ui@beta`.
11
+
12
+ ### Examples run without the transpiler
13
+
14
+ Each `tjs` example is transpiled at build time and its JavaScript is baked into the page as a
15
+ hidden, non-executing `<script type="application/tosi-transpiled">`; the example runs from that
16
+ bake. Plain `js` examples need no transpiler at all (`loadTransform('js')` is now identity). The
17
+ bakes also ship per-doc in `docs.json`, so client-side navigation gets them too — at **zero
18
+ added bytes for prose/book sites** (only docs with code examples carry bakes). Editing an
19
+ example drops the stale bake and transpiles on demand; a saved local edit keeps its own
20
+ transpiled code so it reloads without the transpiler.
21
+
22
+ ### CodeMirror panels build lazily
23
+
24
+ `<tosi-example>` no longer constructs its `<tosi-code>` panels up front (this delivers the
25
+ beta.2 "Remaining" note above): the panel — and the CodeMirror chunk — is built on first
26
+ `showCode`. A page with examples now ships zero CodeMirror until a reader opens a panel.
27
+
28
+ ### tjs-lang 0.9.1 → 0.10.1
29
+
30
+ Bumped the transpiler, **skipping 0.10.0** (it triggered a memory storm rooted in a bun bug;
31
+ 0.10.1 carries the fix). 0.10.x closed four upstream issues, letting this release **delete ~272
32
+ lines** of hand-rolled scope-scanning and a hand-declared autocomplete-config type in favor of
33
+ tjs-lang's own exports. The inline-WASM guard was updated for 0.10.x's renamed compiled export.
34
+
35
+ - `tjs-lang` peer: `^0.9.1` → `^0.10.1` (and the `TJS_VERSION` CDN-fallback pin, in lockstep).
36
+
37
+ ## 1.7.0-beta.2
38
+
39
+ The code editor moved from **ACE to [CodeMirror 6](https://codemirror.net/)**, `tjs`
40
+ became a first-class editing mode with runtime-value autocomplete, the doc-site builder
41
+ gained the hooks that unblock the tosijs 2.0 TJS port — and generated doc pages are now
42
+ **readable before any JavaScript runs**.
4
43
 
5
44
  **A beta, published under npm's `beta` tag** — `latest` stays on 1.6.x. A prerelease is not
6
45
  matched by `^1.6.x` (or by `^1.7.0`), so nobody is auto-upgraded into the editor swap; you get
7
46
  this only by asking for it:
8
47
 
9
48
  ```
10
- npm i tosijs-ui@beta # or tosijs-ui@1.7.0-beta.1
49
+ npm i tosijs-ui@beta # or tosijs-ui@1.7.0-beta.2
11
50
  ```
12
51
 
13
- The code editor moved from **ACE to [CodeMirror 6](https://codemirror.net/)**, `tjs`
14
- became a first-class editing mode with runtime-value autocomplete, and the doc-site
15
- builder gained the hooks that unblock the tosijs 2.0 TJS port.
16
-
17
- **Known cost, deliberate for now.** `dist/iife.js` carries CodeMirror (bun's IIFE format cannot
18
- code-split), and generated doc pages hide the body until hydration — so on a cheap phone the
19
- blank-screen window is ~4.5s vs ~3.7s without the editor. The fix is to pre-render the page
20
- chrome so hydration is additive and the gate can go (then the bundle gates *editing*, not
21
- *reading*); that's the next doc-system release, not this one. Measurements in `TODO.md`.
52
+ ### Doc pages no longer wait for the bundle
53
+
54
+ Generated pages used to hide the body (`opacity: 0`, with a 4s safety-net timeout) until
55
+ hydration, because hydration injected the whole page chrome and the reflow would have been
56
+ ugly. **That gate is gone.** The chrome is pre-rendered, so the page is styled, readable and
57
+ navigable — real `<a>` links, real headings — before a byte of JS executes, and hydration is
58
+ purely additive: nothing moves.
59
+
60
+ Measured on the built site, gzipped, CPU-throttled — blank-screen duration:
61
+
62
+ | device | before | after |
63
+ | -------------------------- | -------------------------------- | ---------- |
64
+ | cheap phone / Pi4, slow 4G | 4532ms | **1635ms** |
65
+ | cheap phone / Pi4, 3G | 4837ms — _hit the 4s safety net_ | **1635ms** |
66
+
67
+ The bundle now gates **editing**, not **reading** — which was the point.
68
+
69
+ Two regressions against that promise were caught in review and fixed here: the nav was styled
70
+ by two hand-copied rule sets that had drifted (so it reflowed ~4px per row on hydration), and
71
+ the page `<title>` was derived twice (so the home page flipped from its real title to
72
+ `tosijs-ui — tosijs-ui`, [#6](https://github.com/tonioloewald/tosijs-ui/issues/6)). Both are
73
+ now single, shared rules, and `tests/hydration.pw.ts` asserts that a no-JS page and a hydrated
74
+ page agree — geometry, styling and title.
75
+
76
+ ### Doc pages no longer ship CodeMirror to readers who don't open an editor
77
+
78
+ `dist/iife.js` can't code-split (bun's IIFE format), so `<tosi-code>`'s lazy `import()` is
79
+ flattened in — CodeMirror + lezer + acorn on every page whether or not it has an editor
80
+ (121KB → 388KB gzip). The generated **doc pages now load an ESM `--splitting` hydration bundle**
81
+ (`<script type="module">`) instead: the always-loaded entry is **~123KB gzip** and CodeMirror
82
+ rides a lazy chunk pulled only when an editor mounts. The tjs CM extension stays bundled, and
83
+ splitting preserves the shared single `@codemirror/state`. **`dist/iife.js` is unchanged and
84
+ still shipped for the CDN `<script>` path.** So a **pure-docs / book site with no code
85
+ examples now ships zero CodeMirror** — the case that was hurt worst.
86
+
87
+ Remaining (tracked in `TODO.md`): a page that DOES have live examples still eager-loads the
88
+ editor chunk, because `<tosi-example>` builds its code panels up front even while hidden. The
89
+ next step defers that construction until the reader opens a panel; the example still runs (the
90
+ preview and inline tests don't need the editors) — only the code view waits. **(Delivered in
91
+ 1.7.0-beta.3 — see below.)**
92
+
93
+ ### Dev-server safety (also in 1.6.23)
94
+
95
+ A leaking dev server took a machine down twice. The guards, all in `tosijs-ui/site`:
96
+
97
+ - **Machine-health preflight** before every build and dev-server launch: refuses to add load to
98
+ a machine that is already dying, and names the offending PIDs, sizes, ages and project dirs.
99
+ `preflight: 'fail' | 'warn' | false` in the site config; `DEV_SKIP_PREFLIGHT=1` to skip. Warns
100
+ rather than refuses in CI.
101
+ - **`idleTimeoutHours` (default 8)** — ⚠️ **a behavior change**: your dev server now exits after
102
+ 8 idle hours (no request, no rebuild). A forgotten dev server is not inert — it is a days-old
103
+ process still running the code it loaded at launch. Set `idleTimeoutHours: 0` to disable.
104
+ - **RSS ceiling** (`memoryLimitMb`, default 4096) sampled every 60s, not just after a rebuild.
105
+ - **Rebuild-loop detector** — a build that writes a file it also watches now stops, and names
106
+ the file, instead of spawning a bundler forever.
107
+ - The example check, the ePub build, and the bundle gzip all moved into **child processes**;
108
+ `Bun.build()` strands ~30MB of native arena per call ([oven-sh/bun#34053](https://github.com/oven-sh/bun/issues/34053), still open).
109
+ - **`killStrayServer` no longer `kill -9`s every process _connected to_ the dev port** — it used
110
+ `lsof -ti:PORT`, which matches clients as well as the listener, so it could kill your browser.
111
+ - haltija is spawned as a **pinned range** (`haltija@^1.4.0`, override with `HALTIJA_VERSION`),
112
+ not a floating `@latest`, and its teardown kills only its own process tree. The 1.4.0 floor
113
+ is deliberate: it is the first haltija that routes `hj` by working directory, never overwrites
114
+ a newer machine-wide `hj`, and exits non-zero on a failed command — so a project's dev server
115
+ drives its own browser and can't silently downgrade another project's CLI.
22
116
 
23
117
  ### Breaking
24
118
 
25
119
  `<tosi-code>` (ACE → CodeMirror 6). Each removed member now **warns once and no-ops**
26
120
  rather than failing silently, but they are gone:
27
121
 
28
- | removed | replacement |
29
- | --- | --- |
30
- | `theme` attribute | style the editor with `--code-bg` / `--text-color` |
31
- | `options` property (ACE-shaped) | configure via `editor` (a CodeMirror `EditorView`) |
32
- | `ace` getter | there is no ACE global; use `editor` |
33
- | `editor.session.getUndoManager()` | `undo()` / `redo()` / `canUndo()` / `canRedo()` |
122
+ | removed | replacement |
123
+ | --------------------------------- | -------------------------------------------------- |
124
+ | `theme` attribute | style the editor with `--code-bg` / `--text-color` |
125
+ | `options` property (ACE-shaped) | configure via `editor` (a CodeMirror `EditorView`) |
126
+ | `ace` getter | there is no ACE global; use `editor` |
127
+ | `editor.session.getUndoManager()` | `undo()` / `redo()` / `canUndo()` / `canRedo()` |
34
128
 
35
129
  **`editor` changed type in place** — it was an ACE `Editor`, it is now a CodeMirror
36
- [`EditorView`](https://codemirror.net/docs/ref/#view.EditorView). A grep for the
37
- removed names will not catch this; any code reaching into `editor` needs revisiting.
130
+ [`EditorView`](https://codemirror.net/docs/ref/#view.EditorView). A grep for the removed
131
+ names will not catch this, and the warn-once shims above **cannot** catch it either: the
132
+ property still exists and still returns an object; it is simply a different object. So
133
+ **`editor` now warns once on first access**, naming what moved — one line in the console
134
+ beats a `TypeError` from inside a library you have never opened. TypeScript users get a
135
+ compile error instead (1.6 typed it `any`; 1.7 types it `EditorView | undefined`).
136
+
137
+ **`change` now means the _user_ changed it.** The event is new in 1.7, and it fires only
138
+ on user edits — a programmatic `el.value = doc` (loading a document) does not fire it, and
139
+ neither does writing into a `disabled` editor. Without that, every app that populates an
140
+ editor would record a spurious save/dirty-flag on open. Programmatic sets are also not
141
+ undoable: loading a document is not an edit to Ctrl+Z back out of.
38
142
 
39
143
  Unchanged: `value`, `original` / `showDiff()`, `mode`, `disabled`.
40
144
 
145
+ **tosijs floor is now `^1.6.9`.** 1.6.9 fixes the `parts` proxy so a pre-hydration access no
146
+ longer poisons it, and adds public `hydrated` / `whenHydrated` seams
147
+ ([tosijs#13](https://github.com/tonioloewald/tosijs/issues/13)). Two internal hand-rolled
148
+ hydration guards (`<tosi-code>`, `<tosi-example>`) were deleted in favor of the official
149
+ `this.hydrated` — so any component that reaches into `parts` from a getter is safe against the
150
+ old "read it once and it's bricked forever" trap.
151
+
41
152
  **Semver stance (deliberate, not an oversight).** This library breaks in minors before 2.0 —
42
153
  **`2.0` is reserved for the tjs-native rewrite**, not for this. So `^1.6.x` resolves `1.7.0` and
43
154
  existing consumers pick this up on their next install.
@@ -46,6 +157,21 @@ existing consumers pick this up on their next install.
46
157
  is untouched, so a consumer of (say) `<tosi-rating>` can take 1.7 without changes — but note it
47
158
  now installs 12 `@codemirror/*` runtime dependencies where the library previously had none.
48
159
 
160
+ ## 1.6.23
161
+
162
+ **Dev-server safety.** Everything under "Dev-server safety" above, backported — this is the
163
+ release every `tosijs-ui/site` consumer wants, and it needs no code changes.
164
+
165
+ The one to know about: **`killStrayServer` was `kill -9`ing every process _connected to_ the
166
+ dev port, not just the listener.** `lsof -i:PORT` matches sockets whose local _or remote_ port
167
+ is `PORT`, so `bun start` could SIGKILL the browser reading your page, Playwright's browsers,
168
+ or an editor's language server. Now it signals only the listening process, only if it is a JS
169
+ runtime, SIGTERM first.
170
+
171
+ ⚠️ **Behavior change:** the dev server now exits after **8 idle hours** (`idleTimeoutHours: 0`
172
+ to disable), and refuses to start on a machine that is already out of memory
173
+ (`DEV_SKIP_PREFLIGHT=1`, or `preflight: 'warn' | false`).
174
+
49
175
  The trap to know about: **`editor` changed type in place** and no shim can catch it. The removed
50
176
  `theme` / `options` / `ace` members warn once and no-op, but `el.editor` still exists — it is
51
177
  simply a CodeMirror `EditorView` now, so `el.editor.session.*` is a runtime `TypeError`. A grep
@@ -56,7 +182,7 @@ for the removed names will not find it.
56
182
  - `<tosi-code>` gained a `change` event (`event.detail.value`), `undo()` / `redo()` /
57
183
  `canUndo()` / `canRedo()`, and `tjs` / `ajs` modes.
58
184
  - **Runtime-value autocomplete** in `tjs` mode: set `tjsAutocomplete` and completion
59
- suggests the *real members of live values* — including tosijs proxy members that no
185
+ suggests the _real members of live values_ — including tosijs proxy members that no
60
186
  static analysis can see. Live examples wire their own executed scope into it, so
61
187
  `const { app } = tosi(…)` gives real `app.` / `app.items.` completions.
62
188
  - Inline **WebAssembly** live examples (tjs `wasm {}` blocks).
@@ -120,7 +246,7 @@ for the removed names will not find it.
120
246
 
121
247
  ### Fixed
122
248
 
123
- - Doc-system nav-toggle in `routing: 'memory'` mode drove the *outer* doc-browser
249
+ - Doc-system nav-toggle in `routing: 'memory'` mode drove the _outer_ doc-browser
124
250
  instance instead of its own (now scoped per-instance).
125
251
 
126
252
  ### Added
@@ -159,7 +285,7 @@ Doc-system tooling; no component API changes.
159
285
  - **Build-time example transpile check.** Every executable live-example block
160
286
  (`js` / `tjs` / `ts` / `test`) across the whole corpus is run through the front
161
287
  half of the runtime pipeline — `rewriteImports` → transform → `new
162
- AsyncFunction` — at build time, **without executing it**, so a block that can't
288
+ AsyncFunction` — at build time, **without executing it**, so a block that can't
163
289
  build (a syntax/import error, or illustrative code mistakenly tagged with an
164
290
  executable language instead of the display-only `typescript`) **fails the build**
165
291
  with the offending doc/block named, on every page — including fences hidden in
@@ -173,7 +299,7 @@ Doc-system tooling; no component API changes.
173
299
  ### Fixed
174
300
 
175
301
  - **A live example's build/exec failure is now a test failure whether or not it
176
- defines `test` blocks.** So on any page the browser test runner loads, *all* of
302
+ defines `test` blocks.** So on any page the browser test runner loads, _all_ of
177
303
  its examples are checked for explosions — a no-test example that throws is
178
304
  reported as a failed test — not just blocks with explicit assertions.
179
305
 
@@ -374,6 +500,7 @@ below.
374
500
 
375
501
  - **Subpath exports**, so an app can import only what it needs without the barrel
376
502
  dragging in dev tools:
503
+
377
504
  - curated: `tosijs-ui/icons`, `tosijs-ui/code-editor`, `tosijs-ui/live-example`,
378
505
  `tosijs-ui/doc-browser`, `tosijs-ui/diff`, `tosijs-ui/theme`
379
506
  - per-component wildcard: `import { tosiRating } from 'tosijs-ui/rating'`
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tosijs-ui
2
2
 
3
- <!--{ "pin": "top", "headTitle": "tosijs-ui — robust, dependency-free web components", "description": "A small, fast library of standards-based web components (data tables, dialogs, forms, rich text, carousels, and more) built on tosijs — smaller and faster than a React app, with no virtual DOM.", "keywords": "web components, custom elements, tosijs, data table, dialog, form, rich text, no virtual dom" }-->
3
+ <!--{ "pin": "top", "headTitle": "tosijs-ui — robust, standards-based web components", "description": "A small, fast library of standards-based web components (data tables, dialogs, forms, rich text, carousels, and more) built on tosijs — smaller and faster than a React app, with no virtual DOM.", "keywords": "web components, custom elements, tosijs, data table, dialog, form, rich text, no virtual dom" }-->
4
4
 
5
5
  [ui.tosijs.net live demo](https://ui.tosijs.net) | [tosijs](https://tosijs.net) | [discord](https://discord.gg/ramJ9rgky5) | [github](https://github.com/tonioloewald/tosijs-ui#readme) | [npm](https://www.npmjs.com/package/tosijs-ui)
6
6
 
@@ -74,9 +74,15 @@ document.body.append(tosiTable())
74
74
 
75
75
  ### Using the iife via cdn
76
76
 
77
- The `tosijs-ui` iife build bundles `tosijs`, `tosijs-ui`, and `marked` into
78
- a single minified javascript source file. You can access `xinjs` and `xinjsui`
79
- as globals which contain all the things exported by `tosijs` and `tosijs-ui`.
77
+ The `tosijs-ui` iife build bundles `tosijs`, `tosijs-ui`, `marked` and — as of 1.7 —
78
+ **CodeMirror** into a single minified javascript source file. You can access `xinjs` and
79
+ `xinjsui` as globals which contain all the things exported by `tosijs` and `tosijs-ui`.
80
+
81
+ > **Size (1.7):** ~385KB gzip, up from ~120KB in 1.6. `bun build --format iife` cannot
82
+ > code-split, so `<tosi-code>`'s lazy CodeMirror import is flattened into this bundle
83
+ > whether or not the page uses an editor. **If you don't need `<tosi-code>`, use the ESM
84
+ > build** (`import 'tosijs-ui'` via a bundler), where CodeMirror stays a separate lazy
85
+ > chunk and a page with no editor never downloads it.
80
86
 
81
87
  ```
82
88
  <script src="https://ui.tosijs.net/iife.js"></script>
@@ -212,9 +212,13 @@ function findIcons(dirs, ignore = []) {
212
212
  } else if (path.extname(file) === '.svg') {
213
213
  const content = fs.readFileSync(filePath, 'utf8')
214
214
  const rawName = file.split('.')[0]
215
- const names = rawName.split(',').map((n) =>
216
- n.trim().replace(/-([a-z0-9])/g, (_, char) => char.toLocaleUpperCase())
217
- )
215
+ const names = rawName
216
+ .split(',')
217
+ .map((n) =>
218
+ n
219
+ .trim()
220
+ .replace(/-([a-z0-9])/g, (_, char) => char.toLocaleUpperCase())
221
+ )
218
222
  const name = names[0]
219
223
  let svgSource = content
220
224
  .replace(/(<\?xml.*?>|<!DOCTYPE.*?>)\s?/g, '')
@@ -345,7 +345,9 @@ export class B3d extends WebComponent {
345
345
  this.BABYLON = BABYLON;
346
346
  // A `transparent` (alpha < 1) clear-color needs an alpha WebGL context so
347
347
  // the canvas composites over the page.
348
- const clearColor = this.clearColor ? toColor4(BABYLON, this.clearColor) : null;
348
+ const clearColor = this.clearColor
349
+ ? toColor4(BABYLON, this.clearColor)
350
+ : null;
349
351
  this.engine = new BABYLON.Engine(canvas, true, clearColor && clearColor.a < 1
350
352
  ? { alpha: true, premultipliedAlpha: false }
351
353
  : undefined);
@@ -2,13 +2,14 @@ import { EditorView } from '@codemirror/view';
2
2
  import { Extension } from '@codemirror/state';
3
3
  /** Map a `<tosi-code mode>` value to a CodeMirror language extension. */
4
4
  export declare function languageForMode(mode: string): Extension;
5
- /** Runtime-introspection hooks for tjs autocomplete (all optional). */
6
- export interface TjsAutocompleteConfig {
7
- getMetadata?: () => Record<string, unknown> | undefined;
8
- getImports?: () => Record<string, Record<string, unknown>> | undefined;
9
- getLiveBindings?: () => Record<string, unknown> | undefined;
10
- getMembers?: (path: string) => Promise<unknown[] | undefined>;
11
- }
5
+ /**
6
+ * Runtime-introspection hooks for tjs autocomplete (all optional). tjs-lang 0.10.0+
7
+ * ships this type (tjs-lang#12 — we used to hand-declare it); aliased so the public
8
+ * `TjsAutocompleteConfig` name stays stable. `import type` is erased at build, so this
9
+ * adds nothing to the bundle.
10
+ */
11
+ import type { AutocompleteConfig } from 'tjs-lang/editors/codemirror';
12
+ export type TjsAutocompleteConfig = AutocompleteConfig;
12
13
  /**
13
14
  * Lazy-load tjs-lang's CodeMirror language + completion bundle. Returns a single
14
15
  * CodeMirror `Extension` (tjs language, forbidden-keyword highlighting, theme, and
@@ -11,7 +11,7 @@ tjs (highlighting + runtime-value autocomplete via tjs-lang's editors/codemirror
11
11
  layered in separately and only when tjs-lang is installed.
12
12
  */
13
13
  import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, } from '@codemirror/view';
14
- import { EditorState, Compartment } from '@codemirror/state';
14
+ import { EditorState, Compartment, Annotation, Transaction, } from '@codemirror/state';
15
15
  import { indentWithTab, history, defaultKeymap, historyKeymap, undo, redo, undoDepth, redoDepth, } from '@codemirror/commands';
16
16
  import { indentUnit, foldGutter, foldKeymap, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, } from '@codemirror/language';
17
17
  import { closeBrackets, closeBracketsKeymap, autocompletion, completionKeymap, } from '@codemirror/autocomplete';
@@ -29,6 +29,21 @@ import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
29
29
  const isDarkMode = () => typeof document !== 'undefined' &&
30
30
  !!document.body &&
31
31
  document.body.classList.contains('darkmode');
32
+ /**
33
+ * Marks a transaction as OURS — a programmatic `value` set, not a user edit.
34
+ *
35
+ * `change` is documented as "fires when the text changes", and consumers write
36
+ * `code.addEventListener('change', e => save(e.detail.value))`. Without this, `el.value
37
+ * = doc` — merely LOADING a document — dispatches a CM transaction, `docChanged` is
38
+ * true, and the app records a spurious save/dirty-flag every time it populates the
39
+ * editor. A `disabled` (read-only) editor fired it too: `EditorState.readOnly` gates
40
+ * user input, not `view.dispatch`. The library itself does this in five places,
41
+ * including writing results into a read-only *output* editor after every example run.
42
+ *
43
+ * The event is new in 1.7 (the ACE build dispatched nothing), so there is no prior
44
+ * semantic to preserve — and tightening it after 1.7 is on `latest` would be breaking.
45
+ */
46
+ const programmatic = Annotation.define();
32
47
  const highlightFor = (dark) => dark
33
48
  ? syntaxHighlighting(oneDarkHighlightStyle)
34
49
  : syntaxHighlighting(defaultHighlightStyle, { fallback: true });
@@ -189,8 +204,12 @@ export function createCmEditor(parent, opts = {}) {
189
204
  language.of(langExt),
190
205
  readonly.of(EditorState.readOnly.of(!!opts.readOnly)),
191
206
  EditorView.updateListener.of((u) => {
192
- if (u.docChanged && opts.onChange)
193
- opts.onChange(u.state.doc.toString());
207
+ if (!u.docChanged || !opts.onChange)
208
+ return;
209
+ // A programmatic `value` set is not a change the USER made. See `programmatic`.
210
+ if (u.transactions.some((t) => t.annotation(programmatic)))
211
+ return;
212
+ opts.onChange(u.state.doc.toString());
194
213
  }),
195
214
  ],
196
215
  }),
@@ -213,6 +232,12 @@ export function createCmEditor(parent, opts = {}) {
213
232
  return;
214
233
  view.dispatch({
215
234
  changes: { from: 0, to: view.state.doc.length, insert: text },
235
+ annotations: [
236
+ programmatic.of(true),
237
+ // Loading a document is not an edit to undo back out of. (The ACE wrapper
238
+ // called `getUndoManager().reset()` at this point, for the same reason.)
239
+ Transaction.addToHistory.of(false),
240
+ ],
216
241
  });
217
242
  },
218
243
  setMode(mode) {
@@ -28,9 +28,7 @@ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
28
28
  get original(): string;
29
29
  set original(text: string);
30
30
  private diffOverlay;
31
- private _partsHydrated;
32
31
  private _pendingDiff;
33
- private get partsReady();
34
32
  get showingDiff(): boolean;
35
33
  showDiff(on: boolean): void;
36
34
  static initAttributes: {
@@ -38,7 +36,22 @@ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
38
36
  disabled: boolean;
39
37
  };
40
38
  role: string;
41
- /** The underlying CodeMirror EditorView (undefined until loaded). */
39
+ /**
40
+ * The underlying CodeMirror `EditorView` (undefined until loaded).
41
+ *
42
+ * **Changed in 1.7 (ACE → CodeMirror 6).** In 1.6 this was an ACE editor, and
43
+ * `editor.session.getUndoManager()` was documented public API. `^1.6.x` resolves
44
+ * 1.7.0, so an app that never changed a line auto-upgrades into a `TypeError` on the
45
+ * next install — and this is the ONE break the warn-once shims below cannot catch,
46
+ * because the property still exists and still returns an object; it is simply a
47
+ * different object. TS consumers get a compile error (1.6 typed this `any`); vanilla
48
+ * JS and CDN consumers — the audience this component's own docs court — would get the
49
+ * bare TypeError with no explanation at all.
50
+ *
51
+ * So: say it once, on first access. One line in the console beats a stack trace in a
52
+ * library the reader has never opened. Use `undo()`/`redo()`/`canUndo()`/`canRedo()`
53
+ * for history — they are the supported surface and they survived the migration.
54
+ */
42
55
  get editor(): CmHandle['view'] | undefined;
43
56
  /** @deprecated Removed in 1.7 — CodeMirror themes via `--code-bg`/`--text-color`. */
44
57
  get theme(): string;
@@ -88,6 +101,9 @@ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
88
101
  * installed, or if the mode/handle changed before the async load resolved.
89
102
  */
90
103
  private applyTjsExtension;
104
+ /** True once tjs-lang's CM language+autocomplete is actually live (test seam). */
105
+ get tjsExtensionApplied(): boolean;
106
+ private _tjsExtensionApplied;
91
107
  render(): void;
92
108
  }
93
109
  export declare const codeEditor: ElementCreator<CodeEditor>;
@@ -76,6 +76,9 @@ const warnRemoved = (member, advice) => {
76
76
  warned.add(member);
77
77
  console.warn(`<tosi-code>.${member} was removed in tosijs-ui 1.7 (the editor is now CodeMirror 6, not ACE) — ${advice}. See CHANGELOG 1.7.0.`);
78
78
  };
79
+ // One warning per page, not per element — see the `editor` getter.
80
+ let warnedEditor = false;
81
+ let warnedTjs = false;
79
82
  export class CodeEditor extends WebComponent {
80
83
  static preferredTagName = 'tosi-code';
81
84
  source = '';
@@ -135,28 +138,24 @@ export class CodeEditor extends WebComponent {
135
138
  // it. (Re-adding a slot is NOT the fix — it would also project the element's
136
139
  // textContent, i.e. the initial code, and double-render it under the editor.)
137
140
  diffOverlay;
138
- // Set once hydration has actually happened (end of connectedCallback).
139
- //
140
- // This CANNOT be a `try { this.parts.x } catch` probe, which is what it used to be.
141
- // tosijs's `parts` proxy caches its query root on FIRST access — so touching it before
142
- // hydration permanently roots the proxy at the light-DOM element, and every later
143
- // `this.parts.*` throws, including the `this.parts.host` that mounts CodeMirror. The
144
- // editor then never mounts at all. Merely reading `el.showingDiff` on a constructed,
145
- // not-yet-inserted element was enough to brick it — silently, and unrecoverably.
146
- _partsHydrated = false;
147
- // A showDiff() call made before hydration, replayed once we're ready (rather than
148
- // silently dropped, which is what used to happen).
141
+ // Hydration state comes from the base class (`this.hydrated`), as of tosijs 1.6.9.
142
+ // This used to be a hand-rolled `_partsHydrated` flag — because before 1.6.9 the
143
+ // `parts` proxy poisoned itself on any pre-hydration read (touching it once rooted
144
+ // the proxy at the light-DOM element forever, so the editor never mounted), and
145
+ // there was no public way to ask "am I hydrated yet?" without triggering exactly
146
+ // that. 1.6.9 fixed both — it invalidates the cached proxy at hydrate AND exposes
147
+ // `hydrated`/`whenHydrated` — so the flag is gone. (tonioloewald/tosijs#13.)
148
+ // A showDiff() call made before hydration, replayed once we're ready. Still needed:
149
+ // `parts` genuinely doesn't exist before hydration (content is injected at connect),
150
+ // so work that touches it is legitimately deferred — that is not the poisoning bug.
149
151
  _pendingDiff;
150
- get partsReady() {
151
- return this._partsHydrated;
152
- }
153
152
  get showingDiff() {
154
- if (!this.partsReady)
153
+ if (!this.hydrated)
155
154
  return this._pendingDiff ?? false;
156
155
  return !this.parts.diffHost.hidden;
157
156
  }
158
157
  showDiff(on) {
159
- if (!this.partsReady) {
158
+ if (!this.hydrated) {
160
159
  this._pendingDiff = on;
161
160
  return;
162
161
  }
@@ -176,8 +175,30 @@ export class CodeEditor extends WebComponent {
176
175
  disabled: false,
177
176
  };
178
177
  role = 'code editor';
179
- /** The underlying CodeMirror EditorView (undefined until loaded). */
178
+ /**
179
+ * The underlying CodeMirror `EditorView` (undefined until loaded).
180
+ *
181
+ * **Changed in 1.7 (ACE → CodeMirror 6).** In 1.6 this was an ACE editor, and
182
+ * `editor.session.getUndoManager()` was documented public API. `^1.6.x` resolves
183
+ * 1.7.0, so an app that never changed a line auto-upgrades into a `TypeError` on the
184
+ * next install — and this is the ONE break the warn-once shims below cannot catch,
185
+ * because the property still exists and still returns an object; it is simply a
186
+ * different object. TS consumers get a compile error (1.6 typed this `any`); vanilla
187
+ * JS and CDN consumers — the audience this component's own docs court — would get the
188
+ * bare TypeError with no explanation at all.
189
+ *
190
+ * So: say it once, on first access. One line in the console beats a stack trace in a
191
+ * library the reader has never opened. Use `undo()`/`redo()`/`canUndo()`/`canRedo()`
192
+ * for history — they are the supported surface and they survived the migration.
193
+ */
180
194
  get editor() {
195
+ if (!warnedEditor) {
196
+ warnedEditor = true;
197
+ console.warn('<tosi-code>.editor is a CodeMirror EditorView as of 1.7 (it was an ACE editor ' +
198
+ 'in 1.6) — `editor.session`, `editor.getSession()`, `editor.setOption()` and ' +
199
+ '`editor.session.getUndoManager()` no longer exist. Use undo()/redo()/' +
200
+ 'canUndo()/canRedo() for history; see the CodeMirror 6 docs for the rest.');
201
+ }
181
202
  return this._handle?.view;
182
203
  }
183
204
  // ── Removed in 1.7 (ACE → CodeMirror 6) ────────────────────────────────────
@@ -251,8 +272,8 @@ export class CodeEditor extends WebComponent {
251
272
  }
252
273
  connectedCallback() {
253
274
  super.connectedCallback();
254
- // super.connectedCallback() hydrates, so `this.parts` is safe from here on.
255
- this._partsHydrated = true;
275
+ // super.connectedCallback() hydrated us, so `this.hydrated` is now true and
276
+ // `this.parts` is safe from here on.
256
277
  if (this.source === '') {
257
278
  this.value = this.textContent !== null ? this.textContent.trim() : '';
258
279
  }
@@ -330,9 +351,35 @@ export class CodeEditor extends WebComponent {
330
351
  .then((ext) => {
331
352
  if (ext && this._handle === handle && this.isTjsMode()) {
332
353
  handle.setLanguageExtension(ext);
354
+ this._tjsExtensionApplied = true;
355
+ return;
356
+ }
357
+ // Degrade LOUDLY. `loadTjsExtension` swallows everything and returns null by
358
+ // design, and the guard is `typeof mod.tjsEditorExtension === 'function'` — so
359
+ // an upstream export rename returns null without even throwing, and every tjs
360
+ // editor on the page silently falls back to plain TypeScript highlighting with
361
+ // no autocomplete. Nothing goes red; the feature just quietly isn't there.
362
+ if (!ext && this._handle === handle && this.isTjsMode() && !warnedTjs) {
363
+ warnedTjs = true;
364
+ console.warn(`<tosi-code mode="${this.mode}">: tjs-lang's CodeMirror extension did not load — ` +
365
+ `falling back to TypeScript highlighting (no tjs autocomplete). Install the ` +
366
+ `optional peer \`tjs-lang\`, or check that tjs-lang/editors/codemirror still ` +
367
+ `exports \`tjsEditorExtension\`.`);
368
+ }
369
+ })
370
+ .catch((e) => {
371
+ // The chain had no .catch(), so any throw here was an unhandled rejection.
372
+ if (!warnedTjs) {
373
+ warnedTjs = true;
374
+ console.warn(`<tosi-code>: failed to apply the tjs extension —`, e);
333
375
  }
334
376
  });
335
377
  }
378
+ /** True once tjs-lang's CM language+autocomplete is actually live (test seam). */
379
+ get tjsExtensionApplied() {
380
+ return this._tjsExtensionApplied;
381
+ }
382
+ _tjsExtensionApplied = false;
336
383
  render() {
337
384
  super.render();
338
385
  if (this._handle) {
@@ -23,6 +23,10 @@ export interface Doc {
23
23
  pin?: string;
24
24
  hidden?: boolean;
25
25
  testStatus?: 'passed' | 'failed' | 'pending';
26
+ bakes?: Array<[string, {
27
+ dialect: string;
28
+ js: string;
29
+ }]>;
26
30
  }
27
31
  export interface ProjectLinks {
28
32
  github?: string;