@svelterm/core 0.1.0 → 0.21.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.
Files changed (164) hide show
  1. package/CHANGELOG.md +425 -0
  2. package/README.md +42 -29
  3. package/dist/src/cli/build.d.ts +13 -0
  4. package/dist/src/cli/build.js +119 -0
  5. package/dist/src/cli/bundle.d.ts +25 -0
  6. package/dist/src/cli/bundle.js +61 -0
  7. package/dist/src/cli/dev.d.ts +10 -0
  8. package/dist/src/cli/dev.js +152 -0
  9. package/dist/src/cli/devtools.d.ts +9 -0
  10. package/dist/src/cli/devtools.js +47 -0
  11. package/dist/src/cli/init.d.ts +8 -0
  12. package/dist/src/cli/init.js +153 -0
  13. package/dist/src/cli/main.d.ts +9 -0
  14. package/dist/src/cli/main.js +52 -0
  15. package/dist/src/cli/svt-bin.d.ts +2 -0
  16. package/dist/src/cli/svt-bin.js +6 -0
  17. package/dist/src/cli/svt.d.ts +14 -0
  18. package/dist/src/cli/svt.js +76 -0
  19. package/dist/src/components/text-buffer.js +8 -5
  20. package/dist/src/css/animation-runner.d.ts +15 -6
  21. package/dist/src/css/animation-runner.js +80 -29
  22. package/dist/src/css/animation.d.ts +12 -0
  23. package/dist/src/css/animation.js +21 -0
  24. package/dist/src/css/calc.js +4 -3
  25. package/dist/src/css/color.d.ts +19 -0
  26. package/dist/src/css/color.js +371 -62
  27. package/dist/src/css/compute.d.ts +30 -3
  28. package/dist/src/css/compute.js +272 -33
  29. package/dist/src/css/defaults.d.ts +1 -1
  30. package/dist/src/css/defaults.js +9 -0
  31. package/dist/src/css/easing.d.ts +9 -0
  32. package/dist/src/css/easing.js +95 -0
  33. package/dist/src/css/incremental.d.ts +1 -1
  34. package/dist/src/css/incremental.js +2 -2
  35. package/dist/src/css/interpolate.d.ts +13 -0
  36. package/dist/src/css/interpolate.js +41 -0
  37. package/dist/src/css/parser.js +59 -3
  38. package/dist/src/css/pseudo-elements.d.ts +9 -0
  39. package/dist/src/css/pseudo-elements.js +97 -0
  40. package/dist/src/css/selector.d.ts +17 -2
  41. package/dist/src/css/selector.js +128 -13
  42. package/dist/src/css/specificity.js +17 -6
  43. package/dist/src/css/values.d.ts +6 -1
  44. package/dist/src/css/values.js +13 -6
  45. package/dist/src/debug/context.d.ts +13 -0
  46. package/dist/src/debug/context.js +11 -0
  47. package/dist/src/debug/css.d.ts +12 -0
  48. package/dist/src/debug/css.js +28 -0
  49. package/dist/src/debug/dom.d.ts +17 -0
  50. package/dist/src/debug/dom.js +92 -0
  51. package/dist/src/devtools/DevTools.compiled.js +327 -0
  52. package/dist/src/devtools/DevTools.css.js +1 -0
  53. package/dist/src/devtools/client.d.ts +36 -0
  54. package/dist/src/devtools/client.js +76 -0
  55. package/dist/src/framelog.d.ts +54 -0
  56. package/dist/src/framelog.js +99 -0
  57. package/dist/src/headless.js +12 -4
  58. package/dist/src/index.d.ts +65 -3
  59. package/dist/src/index.js +609 -81
  60. package/dist/src/input/checkable.d.ts +8 -0
  61. package/dist/src/input/checkable.js +66 -0
  62. package/dist/src/input/details.d.ts +6 -0
  63. package/dist/src/input/details.js +34 -0
  64. package/dist/src/input/focus.d.ts +6 -0
  65. package/dist/src/input/focus.js +27 -9
  66. package/dist/src/input/keyboard.d.ts +2 -2
  67. package/dist/src/input/keyboard.js +32 -5
  68. package/dist/src/input/label.d.ts +8 -0
  69. package/dist/src/input/label.js +53 -0
  70. package/dist/src/input/modal.d.ts +9 -0
  71. package/dist/src/input/modal.js +28 -0
  72. package/dist/src/input/mouse.d.ts +2 -2
  73. package/dist/src/input/mouse.js +15 -2
  74. package/dist/src/input/select.d.ts +12 -0
  75. package/dist/src/input/select.js +63 -0
  76. package/dist/src/input/selection.d.ts +48 -0
  77. package/dist/src/input/selection.js +150 -0
  78. package/dist/src/layout/engine.d.ts +2 -0
  79. package/dist/src/layout/engine.js +1084 -142
  80. package/dist/src/layout/flex.js +4 -4
  81. package/dist/src/layout/size.js +3 -2
  82. package/dist/src/layout/text.d.ts +3 -2
  83. package/dist/src/layout/text.js +96 -17
  84. package/dist/src/layout/unicode.d.ts +20 -0
  85. package/dist/src/layout/unicode.js +121 -0
  86. package/dist/src/render/animation-clock.d.ts +51 -0
  87. package/dist/src/render/animation-clock.js +213 -0
  88. package/dist/src/render/ansi-text.d.ts +26 -0
  89. package/dist/src/render/ansi-text.js +131 -0
  90. package/dist/src/render/ansi.d.ts +18 -0
  91. package/dist/src/render/ansi.js +64 -19
  92. package/dist/src/render/border.js +166 -17
  93. package/dist/src/render/buffer.d.ts +1 -0
  94. package/dist/src/render/buffer.js +5 -2
  95. package/dist/src/render/color-depth.d.ts +8 -0
  96. package/dist/src/render/color-depth.js +59 -0
  97. package/dist/src/render/context.d.ts +1 -0
  98. package/dist/src/render/context.js +17 -21
  99. package/dist/src/render/cursor-emit.d.ts +18 -0
  100. package/dist/src/render/cursor-emit.js +50 -0
  101. package/dist/src/render/diff.d.ts +12 -0
  102. package/dist/src/render/diff.js +120 -0
  103. package/dist/src/render/generation.d.ts +9 -0
  104. package/dist/src/render/generation.js +14 -0
  105. package/dist/src/render/graphics-layer.d.ts +27 -0
  106. package/dist/src/render/graphics-layer.js +86 -0
  107. package/dist/src/render/image.d.ts +27 -0
  108. package/dist/src/render/image.js +113 -0
  109. package/dist/src/render/incremental-paint.d.ts +7 -3
  110. package/dist/src/render/incremental-paint.js +52 -79
  111. package/dist/src/render/inline.d.ts +59 -0
  112. package/dist/src/render/inline.js +219 -0
  113. package/dist/src/render/kitty-graphics.d.ts +24 -0
  114. package/dist/src/render/kitty-graphics.js +58 -0
  115. package/dist/src/render/paint-text.js +68 -22
  116. package/dist/src/render/paint.d.ts +8 -1
  117. package/dist/src/render/paint.js +328 -30
  118. package/dist/src/render/png.d.ts +13 -0
  119. package/dist/src/render/png.js +145 -0
  120. package/dist/src/render/scrollbar.d.ts +8 -2
  121. package/dist/src/render/scrollbar.js +71 -14
  122. package/dist/src/render/snapshot.js +3 -1
  123. package/dist/src/renderer/default.d.ts +7 -0
  124. package/dist/src/renderer/default.js +11 -0
  125. package/dist/src/renderer/index.d.ts +8 -2
  126. package/dist/src/renderer/index.js +4 -2
  127. package/dist/src/renderer/node.d.ts +109 -0
  128. package/dist/src/renderer/node.js +165 -1
  129. package/dist/src/terminal/capabilities.d.ts +33 -0
  130. package/dist/src/terminal/capabilities.js +66 -0
  131. package/dist/src/terminal/clipboard.d.ts +9 -0
  132. package/dist/src/terminal/clipboard.js +39 -0
  133. package/dist/src/terminal/io.d.ts +82 -0
  134. package/dist/src/terminal/io.js +155 -0
  135. package/dist/src/terminal/screen.d.ts +3 -10
  136. package/dist/src/terminal/screen.js +5 -28
  137. package/dist/src/terminal/stdin-router.d.ts +8 -5
  138. package/dist/src/terminal/stdin-router.js +22 -11
  139. package/dist/src/utils/node-map.d.ts +24 -0
  140. package/dist/src/utils/node-map.js +75 -0
  141. package/dist/src/vite/config.d.ts +62 -0
  142. package/dist/src/vite/config.js +191 -0
  143. package/docs/compatibility.md +67 -0
  144. package/docs/debug/devtools.md +40 -0
  145. package/docs/debug/svt.md +50 -0
  146. package/docs/distribution.md +106 -0
  147. package/docs/elements.md +120 -0
  148. package/docs/getting-started.md +177 -0
  149. package/docs/guide/css.md +187 -0
  150. package/docs/guide/input.md +143 -0
  151. package/docs/guide/layout.md +171 -0
  152. package/docs/guide/theming.md +94 -0
  153. package/docs/how-it-works.md +115 -0
  154. package/docs/inline-mode.md +77 -0
  155. package/docs/layout.md +106 -0
  156. package/docs/motion.md +91 -0
  157. package/docs/reference/README.md +65 -0
  158. package/docs/reference/css/properties/border-corner.md +82 -0
  159. package/docs/reference/css/properties/border-style.md +168 -0
  160. package/docs/reference.md +226 -0
  161. package/docs/selectors.md +80 -0
  162. package/docs/terminal-css.md +149 -0
  163. package/docs/terminals.md +83 -0
  164. package/package.json +28 -7
package/CHANGELOG.md ADDED
@@ -0,0 +1,425 @@
1
+ # Changelog
2
+
3
+ ## 0.21.0 — 2026-07-05
4
+
5
+ DevTools polish.
6
+
7
+ ### Changed
8
+
9
+ - **Collapsible tree** — ← / → (or Enter) fold and unfold subtrees in
10
+ `svelterm devtools`; nodes with children show a ▸/▾ marker.
11
+ - **Fuller style panel** — the selected node's panel now lists every
12
+ non-default resolved value, instead of a fixed subset of keys.
13
+ - **Clearer connect error** — `svt` and `devtools` both name the exact
14
+ call to enable debugging (`run(App, { debug: true })`) when they can't
15
+ reach a server.
16
+
17
+ ## 0.20.0 — 2026-07-05
18
+
19
+ DevTools: a terminal inspector, itself a svelterm app.
20
+
21
+ ### Added
22
+
23
+ - **`svelterm devtools`** — connects to a `run(App, { debug: true })`
24
+ app's debug server and shows its live node tree (left) with each
25
+ selected node's computed style and layout box (right); ↑/↓ to select,
26
+ r to refresh. Built with svelterm and the DOM/CSS debug domains — it
27
+ dogfoods the renderer it inspects. See `docs/debug/devtools.md`.
28
+ - The DevTools component is precompiled into the package at build time,
29
+ so it runs without a build step in your project.
30
+
31
+ ## 0.19.0 — 2026-07-05
32
+
33
+ Kitty graphics: crisp `<img>` pixels where the terminal supports them.
34
+
35
+ ### Added
36
+
37
+ - **Kitty graphics protocol** — on kitty, Ghostty, and WezTerm (detected
38
+ via XTVERSION), `<img>` transmits real RGBA pixels scaled to its cell
39
+ box instead of half-blocks. Pixel data transmits once per (element,
40
+ src); each frame re-places over the box and deletes placements for
41
+ images that moved, scrolled away, or unmounted; teardown and suspend
42
+ clear them. Half-blocks stay the buffer fallback everywhere else.
43
+
44
+ ## 0.18.0 — 2026-07-04
45
+
46
+ Scroll-region diffing, and a border-clip fix found along the way.
47
+
48
+ ### Added
49
+
50
+ - **DECSTBM scroll diffing** — when a repaint is a clean vertical
51
+ translation of the previous frame (a full-viewport scroll), the diff
52
+ emits a scroll-region command + index/reverse-index and paints only
53
+ the newly revealed rows, instead of rewriting the screen. ~13× less
54
+ output on an 80×40 one-line scroll (`scripts/bench-scroll-bytes.mjs`).
55
+ Falls back to the normal cell diff for any non-translation change.
56
+
57
+ ### Fixed
58
+
59
+ - **Scrolled content painted over borders** — a bordered
60
+ `overflow: auto`/`scroll` box clipped its children to the box
61
+ *including* the border cells, so scrolled content overwrote the top
62
+ and bottom border rows. The clip now insets by the border. (Regression
63
+ from 0.13.0's virtual scrolling.)
64
+
65
+ ## 0.17.0 — 2026-07-04
66
+
67
+ Debug tooling: a minimal DevTools for terminal apps.
68
+
69
+ ### Added
70
+
71
+ - **DOM and CSS debug domains** — over the existing (opt-in) debug
72
+ WebSocket server: `DOM.getDocument` serialises the live node tree,
73
+ `DOM.querySelector` finds nodes by selector, `DOM.getBoxModel` reports
74
+ layout, `DOM.setAttribute`/`removeAttribute` mutate and repaint;
75
+ `CSS.getComputedStyle` returns the resolved style svelterm painted.
76
+ - **`svt` CLI** (`svelterm inspect`) — a client for the protocol:
77
+ `svt tree | query <sel> | style <id> | box <id> | console | raw`,
78
+ JSON on stdout for `jq`. Point it at a `run(App, { debug: true })`
79
+ app.
80
+
81
+ ## 0.16.0 — 2026-07-04
82
+
83
+ ### Fixed
84
+
85
+ - **`svelterm build` with symlinked component libraries** — a library
86
+ installed via `file:`/`link:` (e.g. a local `@svelterm/ui`) resolves
87
+ its imports from its real path, where the app's `node_modules` isn't
88
+ visible, so `@svelterm/core` and `svelte` failed to resolve. The
89
+ bundler now pins those packages to the project's own installation.
90
+
91
+ ## 0.15.0 — 2026-07-04
92
+
93
+ Images: `<img>` on the cell grid.
94
+
95
+ ### Added
96
+
97
+ - **`<img>`** — renders as half-blocks (two pixels per cell) from file
98
+ paths or `data:image/png;base64` URIs. PNG decoding (8-bit
99
+ RGB/RGBA/greyscale/palette) is built in with no dependencies, via
100
+ `node:zlib`. Intrinsic size is 1 column per pixel and 1 row per two;
101
+ CSS `width`/`height` scale nearest-neighbour. Loading is async — the
102
+ layout reflows when pixels arrive; transparent pixels show the
103
+ terminal background. (Kitty graphics passthrough is deliberately not
104
+ included yet — half-blocks work everywhere.)
105
+
106
+ ## 0.14.0 — 2026-07-04
107
+
108
+ Inline-mode maturity: the mouse works in the live area.
109
+
110
+ ### Added
111
+
112
+ - **Inline-mode mouse** — the zone's screen origin comes from a CPR
113
+ (cursor position report) query, snapshotted as the query bytes go out,
114
+ shifted as frames archive, and clamped when growth scrolls the zone.
115
+ Mouse coordinates map through it; clicks on shell history are ignored.
116
+ Origins re-query after resize and suspend/resume.
117
+ - **Playground example** — `inline mode` example with a `svelterm:inline`
118
+ marker that the site preview and the `run/*.mjs` bundles honour.
119
+
120
+ ### Fixed
121
+
122
+ - An explicit `mode` now wins over the `fullscreen` flag —
123
+ `mode: 'fullscreen', fullscreen: false` is full-viewport rendering
124
+ without the alternate screen again (embedded previews), instead of
125
+ being forced inline.
126
+
127
+ ## 0.13.0 — 2026-07-04
128
+
129
+ Virtual scrolling: long lists repaint at the speed of what's visible.
130
+
131
+ ### Changed
132
+
133
+ - **Paint culling** — subtrees fully outside the active clip are skipped
134
+ in the paint walk (cell writes were already clipped, so output is
135
+ identical). A 10,000-row `overflow: scroll` list drops from ~228 ms to
136
+ ~1.4 ms per scroll repaint on the benchmark machine
137
+ (`scripts/bench-scroll.mjs`); first paint from ~254 ms to ~5 ms.
138
+ - **Scrollbar extent caching** — the content-size walk behind scrollbar
139
+ overlays is memoized per layout, instead of re-walking every child on
140
+ every frame of the fade.
141
+
142
+ ### Fixed
143
+
144
+ - A focused input culled offscreen no longer reports a stale cursor
145
+ position — cursor positions carry the paint generation that wrote
146
+ them, and the terminal cursor hides when its owner leaves the
147
+ viewport.
148
+
149
+ ## 0.12.0 — 2026-07-04
150
+
151
+ Terminal matrix evidence: proof the emitted bytes work, per terminal
152
+ class.
153
+
154
+ ### Added
155
+
156
+ - **Round-trip test suite** — svelterm's emitted ANSI (full frames,
157
+ incremental diffs, the inline live zone) replays through a terminal
158
+ model in CI and must reproduce the exact cell grid, at every colour
159
+ depth (truecolor / 256 / 16 / mono) and with wide glyphs.
160
+ - **Support matrix** in `docs/terminals.md` — verified vs expected vs
161
+ unknown, per terminal, with the capability columns that matter
162
+ (truecolor, DEC 2026, kitty keys, OSC 52).
163
+
164
+ ## 0.11.0 — 2026-07-04
165
+
166
+ Unicode correctness: non-Latin text stops breaking layout.
167
+
168
+ ### Fixed
169
+
170
+ - **Cell widths** — text measurement previously assumed one JavaScript
171
+ character = one terminal cell, so CJK, fullwidth forms, and emoji
172
+ misaligned borders, wrapping, and diffs. Layout, paint, truncation,
173
+ and the inline renderer now work in grapheme clusters with East Asian
174
+ Width cell widths; wide glyphs own a continuation cell that diff
175
+ emission skips.
176
+ - **Input editing** — cursor movement, backspace, and delete operate on
177
+ grapheme boundaries (arrow keys no longer split surrogate pairs or ZWJ
178
+ emoji); the terminal cursor position accounts for wide glyphs.
179
+
180
+ ## 0.10.0 — 2026-07-04
181
+
182
+ Colour blending: real alpha compositing on the cell grid.
183
+
184
+ ### Added
185
+
186
+ - **Alpha colours** — `rgba()`, `hsl(... / a)`, `#rrggbbaa` keep their
187
+ alpha and composite over whatever the cell already holds at paint
188
+ time. Blending over ANSI names uses nominal xterm values; over the
189
+ terminal's default background it assumes black.
190
+ - **Numeric `opacity`** — folds into the element's colours as a blend
191
+ factor (previously any `opacity < 1` just set the dim attribute; the
192
+ non-standard `opacity: dim` still does).
193
+
194
+ ## 0.9.0 — 2026-07-04
195
+
196
+ Text & content: wrapping control, path-friendly truncation, and raw
197
+ ANSI passthrough.
198
+
199
+ ### Added
200
+
201
+ - **`word-break: break-all`** — wrap at any character (URLs, hashes,
202
+ paths) instead of only at spaces; inherits as in CSS and applies in
203
+ both layout and paint.
204
+ - **`text-overflow: ellipsis-middle`** — the parsed-but-unwired middle
205
+ truncation now paints: `/Users/tom/…/index.ts` style, keeping both
206
+ ends of long paths.
207
+ - **`<svt-ansi>`** — raw ANSI passthrough element: pre-styled output
208
+ (git diff, ls --color, build logs) renders with its own SGR colours
209
+ (16/256/truecolor + attributes); non-SGR sequences are stripped,
210
+ content is `pre`-formatted.
211
+
212
+ ## 0.8.0 — 2026-07-04
213
+
214
+ Input completeness: modern key reporting, honest job control, and
215
+ browser-style modals.
216
+
217
+ ### Added
218
+
219
+ - **Kitty keyboard protocol** — CSI u key reports parse with correct
220
+ modifiers (`Ctrl+Enter`, `Shift+Space`, …); the protocol is pushed at
221
+ startup and popped on exit and suspend. Terminals without it ignore
222
+ the push and keep the legacy encoding.
223
+ - **Suspend/resume** — `Ctrl+Z` restores the terminal for the shell
224
+ without unmounting; `fg` (SIGCONT) re-enters raw mode, alt screen,
225
+ mouse and keyboard modes, and repaints with component state intact.
226
+ Previously Ctrl+Z tore the app down.
227
+ - **`exitOn` option** — opt into `Ctrl+D` EOF-style exit
228
+ (`run(App, { exitOn: ['ctrl+c', 'ctrl+d'] })`).
229
+ - **Modal `<dialog open>`** — captures keys: Tab/Shift+Tab trap inside
230
+ the dialog, focus pulls in from outside, Escape removes `open` and
231
+ dispatches `close`.
232
+
233
+ ### Fixed
234
+
235
+ - Legacy CSI modifier parsing was off by one — `Shift+Arrow` reported
236
+ as Alt, `Ctrl+Arrow` as Shift+Alt.
237
+
238
+ ## 0.7.0 — 2026-07-04
239
+
240
+ Inline rendering: svelterm apps that live in the main buffer like a
241
+ CLI tool, not a fullscreen TUI.
242
+
243
+ ### Added
244
+
245
+ - **`mode: 'inline'`** — render at the shell cursor: the live area sizes
246
+ to content, updates via cell diffs with relative-only cursor movement
247
+ (LF to grow, erase-below to shrink), and leaves its output in place on
248
+ exit. `fullscreen: false` now routes here too. Mouse reporting is off
249
+ in inline mode (screen-absolute coordinates can't map to an unknown
250
+ origin); keyboard, focus, and the input cursor work as usual.
251
+ - **`FrameLog`** — append-only frame log for streaming sessions:
252
+ `append(Component, props)` / `update` / `archive` / `remove`.
253
+ Archived frames' rows scroll into the terminal's real history
254
+ untouched and their components unmount, so a long session's memory
255
+ tracks the live area, not the transcript. See `docs/inline-mode.md`.
256
+ - **`demo/inline`** — five streaming turns, each archived into
257
+ scrollback (`DEMO=inline npm run demo`).
258
+
259
+ ## 0.6.0 — 2026-07-04
260
+
261
+ Terminal integration: selection, clipboard, and a cursor that reads as
262
+ an insertion point.
263
+
264
+ ### Added
265
+
266
+ - **Text selection** — drag selects a row-major cell range (painted
267
+ inverted), double-click selects the word, triple-click the line.
268
+ Releasing copies the selection; the next click clears it. Works over
269
+ the diff pipeline without repainting the tree.
270
+ - **Clipboard** — selections copy via OSC 52 (in-band, ssh-safe) plus
271
+ the platform tool (`pbcopy`, `wl-copy`/`xclip`, `clip`) when present;
272
+ `copyToClipboard` is exported for apps.
273
+ - **Cursor shape** — a focused `<input>`/`<textarea>` shows a bar cursor
274
+ (DECSCUSR 6); the terminal's configured shape is restored otherwise
275
+ and on exit.
276
+
277
+ ## 0.5.0 — 2026-07-04
278
+
279
+ Terminal robustness: the same app now degrades gracefully from a
280
+ truecolor GPU terminal down to `TERM=xterm` — and respects `NO_COLOR`.
281
+
282
+ ### Added
283
+
284
+ - **Capability detection** — colour depth from
285
+ `NO_COLOR`/`COLORTERM`/`TERM`, plus an XTVERSION query identifying
286
+ known-truecolor terminals; DEC 2026 synchronized-output support probed
287
+ via DECRQM. Detection runs in the background with timeouts; the first
288
+ frame paints with modern defaults and re-paints on a downgrade.
289
+ - **Colour degradation** — hex/RGB colours quantize at emit time: xterm
290
+ 256 cube/grey-ramp on 256-colour terminals, nearest base colour on
291
+ 16-colour terminals, no colour under `NO_COLOR`. ANSI names always
292
+ pass through. Override with `run(App, { colorDepth })`.
293
+ - **Gated synchronized output** — frames wrap in DEC 2026 only when the
294
+ terminal reports the mode (previously sent unconditionally).
295
+ - **`docs/terminals.md`** — what svelterm emits and queries, per depth.
296
+
297
+ ## 0.4.0 — 2026-07-04
298
+
299
+ The developer-experience release: `npx @svelterm/core init` to a running,
300
+ hot-reloading, shippable app in one minute.
301
+
302
+ ### Added
303
+
304
+ - **`svelterm init <dir>`** — scaffold a working project: counter
305
+ component, vite config, dev/app/build scripts, fork-setup README.
306
+ - **`svelterm build [entry]`** — bundle the component graph, Svelte
307
+ runtime and svelterm into one self-contained `.mjs` (rolldown, node
308
+ platform) that runs with plain `node`. Global CSS via `--css` or
309
+ `src/main.css` convention; component CSS travels in the bundle through
310
+ the new `registerComponentCss` registry that `run()` falls back to.
311
+ - **Terminal-environment `.svelte` compilation** — `terminalServer()`
312
+ compiles components for the terminal environment itself, so
313
+ terminal-only projects need no `vite-plugin-svelte` (the registry
314
+ plugin is not environment-aware and emitted empty component stubs for
315
+ custom environments, rendering a blank screen).
316
+ - **Console forwarding in dev** — `console.log` from the app streams to
317
+ the vite terminal prefixed `[svelterm]`; previously any console call
318
+ crashed `svelterm dev`.
319
+
320
+ ### Changed
321
+
322
+ - The `svelterm` bin now dispatches `init` / `dev` / `build`
323
+ subcommands (previously `dev` only).
324
+ - The terminal environment marks `svelte` and `@svelterm/core` as
325
+ `noExternal` so a natively-imported second copy can't split module
326
+ state.
327
+
328
+ ## 0.3.0 — 2026-07-04
329
+
330
+ Motion completeness: easing everywhere, and keyframes that understand
331
+ your theme.
332
+
333
+ ### Added
334
+
335
+ - **Easing functions** — `animation-timing-function` and
336
+ `transition-timing-function` (longhands and inside the shorthands)
337
+ support `linear`, `ease`, `ease-in`, `ease-out`, `ease-in-out`,
338
+ `cubic-bezier()`, `steps(n[, start|end])`, `step-start` and `step-end`.
339
+ Easing applies per keyframe segment; non-interpolable values switch when
340
+ eased progress crosses the midpoint, as in CSS.
341
+ - **Keyframe `var()` / `light-dark()`** — keyframe declarations resolve
342
+ custom properties and colour-scheme pairs against the animated element
343
+ when the animation starts.
344
+
345
+ ### Changed
346
+
347
+ - Timing functions default to `ease` per spec (previously everything
348
+ interpolated linearly). Declare `linear` explicitly to keep the old
349
+ behaviour.
350
+
351
+ ## 0.2.0 — 2026-07-04
352
+
353
+ The browser-compatibility release: any HTML/CSS feature with a sensible
354
+ cell-grid meaning now works as a browser author expects. Full support matrix
355
+ in [`docs/reference.md`](docs/reference.md); manual in [`docs/`](docs/).
356
+
357
+ **Requires** a Svelte fork with the custom renderer API. Until
358
+ [sveltejs/svelte#18505](https://github.com/sveltejs/svelte/pull/18505) lands,
359
+ use [`tomyan/svelte#svelte-custom-renderer`](https://github.com/tomyan/svelte/tree/svelte-custom-renderer)
360
+ (upstream plus the `svelte/renderer` mount export svelterm needs on Node).
361
+
362
+ ### Added
363
+
364
+ - **CSS grid** — column and row templates with `fr`/`repeat()`/`minmax()`,
365
+ `grid-column`/`grid-row` placement and spans, `grid-template-areas` with
366
+ named `grid-area`
367
+ - **CSS tables** — `display: table*` including `inline-table`, sections and
368
+ captions, `colspan`/`rowspan`, `vertical-align`, `border-collapse` with
369
+ shared box-drawing grid lines, anonymous table boxes
370
+ - **Animations & transitions** — `@keyframes` wired into the render loop
371
+ with RGB colour interpolation between stops, cell-stepped length
372
+ animation, discrete stepping for other properties; `transition` on style
373
+ changes
374
+ - **Selectors** — attribute operators (`^=`, `$=`, `*=`, `~=`, `|=`),
375
+ `:is()`/`:where()`, the `:nth-child()` family, structural pseudo-classes
376
+ (`:empty`, `:first/last/only-of-type`, `:only-child`),
377
+ `:checked`/`:disabled`/`:enabled`, `::before`/`::after` with `content`
378
+ - **Form controls** — checkboxes and radios, cycling `<select>`,
379
+ `<progress>`/`<meter>` block-glyph bars, `<details>`/`<summary>`,
380
+ labels activate their controls on click
381
+ - **CSS values** — Color Level 4 syntax, `light-dark()`, inline `style`
382
+ attributes, `box-sizing`, `text-transform`, the `ch` unit as a `cell`
383
+ alias
384
+ - **Scrolling** — viewport scrolling with overlay scrollbars, horizontal
385
+ scroll, scroll clamping on resize
386
+ - **Borders** — block-character border styles with half-cell corner
387
+ treatment
388
+ - **IO abstraction** — `ProcessIO` (with `/dev/tty` fallback when stdin is
389
+ piped) and `InProcessIO` for embedding; browser-compatible input parsing
390
+ - **Dev mode** — `svelterm dev` CLI with Vite environments, HMR, and a
391
+ two-process WebSocket bridge
392
+ - **Docs** — chaptered manual under `docs/` and a full feature support
393
+ matrix with MDN links in `docs/reference.md`
394
+
395
+ ### Changed
396
+
397
+ - Tracks the upstream unified `mount({ renderer, target, props })` API from
398
+ the `svelte-custom-renderer` branch
399
+ - Exact hex/computed colours are no longer remapped to nearest ANSI names
400
+ - Flex `align-items: stretch` no longer overrides an explicit cross-axis
401
+ size
402
+
403
+ ### Fixed
404
+
405
+ - Incremental repaint artifacts (borders, list markers), flex `min-height:
406
+ auto` shrinking per spec, nested `@media` inside selector blocks, inline
407
+ whitespace and list bullets, scroll position clamping after relayout
408
+
409
+ ## 0.1.0
410
+
411
+ Initial release — name reservation and early preview.
412
+
413
+ **Requires** the unmerged [`svelte-custom-renderer`](https://github.com/paoloricciuti/svelte/tree/svelte-custom-renderer) branch of Svelte 5.
414
+
415
+ ### Features
416
+
417
+ - **CSS engine** — selectors, specificity, cascade, inheritance, scoped styles, `var()`, `calc()`, `@media`, `@keyframes`, `:focus`, `:hover`
418
+ - **Flexbox layout** — `flex-direction`, `justify-content`, `align-items`, `flex-grow`, `flex-shrink`, `flex-basis`, `gap`, `flex-wrap`, `order`
419
+ - **Terminal rendering** — ANSI colors (16, 256, truecolor), box-drawing borders (`single`, `double`, `rounded`, `heavy`), text styles, differential output
420
+ - **Incremental updates** — mutations classified as paint-only, style-resolve, layout-subtree, or layout-bubble to avoid full recomputation
421
+ - **Input handling** — keyboard events, mouse (click, scroll, motion), focus management with Tab/Shift+Tab, bracketed paste
422
+ - **Text input** — `<input>` and `<textarea>` with readline-like editing
423
+ - **Color scheme detection** — automatic `prefers-color-scheme` via OSC 11 terminal query
424
+ - **Debug protocol** — WebSocket-based CDP-inspired server with Console domain
425
+ - **Dual-target components** — same `.svelte` component renders in terminal and browser via `@media (display-mode: terminal/screen)`
package/README.md CHANGED
@@ -4,6 +4,14 @@ Svelte 5 components rendered to the terminal with real CSS.
4
4
 
5
5
  Write standard Svelte components with `<style>` blocks. They render in the terminal with ANSI escape sequences — flexbox layout, scoped styles, CSS variables, pseudo-classes, all on a cell grid.
6
6
 
7
+ **Try it:** live playground and docs at [svelterm.dev](https://svelterm.dev), or pipe a demo straight into a real terminal:
8
+
9
+ ```bash
10
+ curl -fsSL https://svelterm.dev/run/counter.mjs | node --input-type=module -
11
+ ```
12
+
13
+ svelterm is largely LLM-written — designed and directed by a human, with most of the code produced in pair-programming sessions with Claude, test-driven and reviewed as it landed. If that's not your thing, there are plenty of artisanal, hand-typed frameworks out there.
14
+
7
15
  > **Early release.** Svelterm requires an unmerged Svelte branch (`svelte-custom-renderer` by [@paoloricciuti](https://github.com/paoloricciuti)) that adds the custom renderer API. It is not usable with mainline Svelte yet.
8
16
 
9
17
  ## Example
@@ -88,21 +96,39 @@ Standard CSS works as expected. These are the terminal-specific additions:
88
96
 
89
97
  ## Features
90
98
 
91
- - **CSS engine** — selectors, specificity, cascade, inheritance, scoped styles, `var()`, `calc()`, `@media`, `@keyframes`
99
+ - **CSS engine** — selectors (attribute operators, structural/state pseudo-classes, `::before`/`::after`), specificity, cascade, inheritance, scoped styles, `var()`, `calc()`, `@media`, `@container`, `@supports`, `@keyframes`
92
100
  - **Flexbox layout** — `flex-direction`, `justify-content`, `align-items`, `flex-grow`, `flex-shrink`, `gap`, `flex-wrap`
101
+ - **CSS grid** — column and row templates with `fr`/`repeat()`/`minmax()`, `grid-column`/`grid-row` placement and spans, `grid-template-areas` with named `grid-area`
102
+ - **CSS tables** — `display: table*` including `inline-table`, sections and captions, `colspan`/`rowspan`, column sizing, `vertical-align`, `border-collapse` with shared box-drawing grid lines, anonymous boxes
103
+ - **Animations & transitions** — `@keyframes` with RGB colour interpolation and cell-stepped length animation, `transition` on style changes, easing functions
93
104
  - **Terminal rendering** — ANSI colors (16, 256, truecolor), borders, text styles, differential output
94
105
  - **Input** — keyboard events, mouse click and scroll, focus management with Tab/Shift+Tab, `:focus` and `:hover` pseudo-classes
95
- - **Text input** — `<input>` and `<textarea>` with readline-like editing
106
+ - **Form controls** — `<input>`/`<textarea>` editing, checkboxes and radios, cycling `<select>`, `<progress>`/`<meter>` bars, `<details>`/`<summary>`
96
107
  - **Incremental updates** — mutation tracking classifies changes as paint-only, style-resolve, or layout to avoid full recomputation
97
108
  - **Color scheme** — automatic `prefers-color-scheme` detection via terminal queries
98
109
 
110
+ ### Browser compatibility
111
+
112
+ Any HTML/CSS feature with a sensible cell-grid meaning works as a browser
113
+ author expects; pixel-derived features are dropped silently and targeted
114
+ per mode with `@media (display-mode: terminal | browser)`. The manual
115
+ lives in [`docs/`](docs/) — [getting started](docs/getting-started.md),
116
+ [terminal CSS](docs/terminal-css.md), [layout](docs/layout.md),
117
+ [selectors](docs/selectors.md), [elements & input](docs/elements.md),
118
+ [motion](docs/motion.md), [compatibility](docs/compatibility.md),
119
+ [terminal support](docs/terminals.md),
120
+ [inline mode](docs/inline-mode.md) — with
121
+ the one-page support matrix in [`docs/reference.md`](docs/reference.md)
122
+ and the design rationale in
123
+ [`DESIGN-browser-compat.md`](DESIGN-browser-compat.md).
124
+
99
125
  ## Prerequisites
100
126
 
101
- Svelterm requires the experimental custom renderer API, available on the [`svelte-custom-renderer`](https://github.com/paoloricciuti/svelte/tree/svelte-custom-renderer) branch:
127
+ Svelterm requires the experimental custom renderer API from the [`svelte-custom-renderer`](https://github.com/sveltejs/svelte/pull/18042) branch by [@paoloricciuti](https://github.com/paoloricciuti). Until [sveltejs/svelte#18505](https://github.com/sveltejs/svelte/pull/18505) lands (it exposes `mount`/`unmount` from `svelte/renderer`, which svelterm needs on Node), clone the branch from the svelterm fork, which tracks upstream plus that fix:
102
128
 
103
129
  ```bash
104
130
  # Clone the branch
105
- git clone -b svelte-custom-renderer https://github.com/paoloricciuti/svelte.git svelte-fork
131
+ git clone -b svelte-custom-renderer https://github.com/tomyan/svelte.git svelte-fork
106
132
  cd svelte-fork
107
133
  pnpm install
108
134
  pnpm -C packages/svelte build
@@ -120,34 +146,21 @@ Then reference it in your project's `package.json`:
120
146
 
121
147
  ## Setup
122
148
 
123
- Configure the Svelte compiler to use svelterm as the custom renderer:
149
+ Scaffold a project with the CLI (the Svelte fork above must be a sibling
150
+ directory, or adjust the `svelte` path in the generated `package.json`):
124
151
 
125
- ```typescript
126
- // vite.config.ts
127
- import { defineConfig } from 'vite'
128
- import { svelte } from '@sveltejs/vite-plugin-svelte'
129
-
130
- export default defineConfig({
131
- plugins: [
132
- svelte({
133
- compilerOptions: {
134
- experimental: {
135
- customRenderer: '@svelterm/core',
136
- },
137
- css: 'external',
138
- },
139
- }),
140
- ],
141
- build: {
142
- target: 'node22',
143
- rollupOptions: {
144
- external: ['svelte', 'svelte/renderer', 'svelte/internal',
145
- 'svelte/internal/client', 'ws', 'http', 'crypto'],
146
- },
147
- },
148
- })
152
+ ```bash
153
+ npx @svelterm/core init my-app
154
+ cd my-app && npm install
155
+ npm run dev # vite dev server (terminal 1)
156
+ npm run app # the app, hot-reloading, in this terminal (terminal 2)
157
+ npm run build # → dist/app.mjs — self-contained, runs with plain node
149
158
  ```
150
159
 
160
+ `console.log` output from the app streams to the vite terminal, like a
161
+ browser console. See [docs/getting-started.md](docs/getting-started.md)
162
+ for manual setup and dual-target configuration.
163
+
151
164
  ## API
152
165
 
153
166
  ### `run(component, options?)`
@@ -0,0 +1,13 @@
1
+ /**
2
+ * svelterm build — bundle a terminal app into one self-contained .mjs
3
+ * that any Node runtime can execute directly:
4
+ *
5
+ * svelterm build # src/App.svelte → dist/app.mjs
6
+ * svelterm build src/App.svelte -o dist/app.mjs
7
+ *
8
+ * Components compile with the project's Svelte (the custom-renderer
9
+ * fork) targeting @svelterm/core; each carries its extracted CSS via
10
+ * registerComponentCss. Requires rolldown (a dependency of vite 8 —
11
+ * `npm i -D rolldown` if resolution fails).
12
+ */
13
+ export declare function runBuild(argv: string[]): Promise<void>;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * svelterm build — bundle a terminal app into one self-contained .mjs
3
+ * that any Node runtime can execute directly:
4
+ *
5
+ * svelterm build # src/App.svelte → dist/app.mjs
6
+ * svelterm build src/App.svelte -o dist/app.mjs
7
+ *
8
+ * Components compile with the project's Svelte (the custom-renderer
9
+ * fork) targeting @svelterm/core; each carries its extracted CSS via
10
+ * registerComponentCss. Requires rolldown (a dependency of vite 8 —
11
+ * `npm i -D rolldown` if resolution fails).
12
+ */
13
+ import { readFileSync, mkdirSync } from 'node:fs';
14
+ import { createRequire } from 'node:module';
15
+ import path from 'node:path';
16
+ import { withCssRegistration, bootstrapModule, globalCssModule, findEntry, findGlobalCss, BOOTSTRAP_MODULE, GLOBAL_CSS_MODULE, WS_STUB_MODULE, WS_STUB_SOURCE, } from './bundle.js';
17
+ export async function runBuild(argv) {
18
+ const args = parseArgs(argv);
19
+ const projectDir = process.cwd();
20
+ const entry = args.entry ? path.resolve(args.entry) : findEntry(projectDir);
21
+ if (!entry) {
22
+ console.error('No entry component found (looked for src/App.svelte, App.svelte).');
23
+ console.error('Usage: svelterm build [entry.svelte] [-o out.mjs] [--css main.css]');
24
+ process.exit(1);
25
+ }
26
+ const cssPath = args.css ? path.resolve(args.css) : findGlobalCss(projectDir);
27
+ const globalCss = cssPath ? readFileSync(cssPath, 'utf8') : '';
28
+ const out = path.resolve(args.out ?? path.join('dist', 'app.mjs'));
29
+ const [{ rolldown }, { compile }] = await Promise.all([
30
+ importOrExplain('rolldown', 'rolldown bundles the app — npm i -D rolldown'),
31
+ importOrExplain('svelte/compiler', 'svelte must be installed (the custom-renderer fork)'),
32
+ ]);
33
+ const bundle = await rolldown({
34
+ input: BOOTSTRAP_MODULE,
35
+ platform: 'node',
36
+ plugins: [sveltermPlugin(compile, entry, globalCss)],
37
+ onwarn(warning, warn) {
38
+ if (warning.code === 'UNRESOLVED_IMPORT')
39
+ throw new Error(warning.message);
40
+ warn(warning);
41
+ },
42
+ });
43
+ mkdirSync(path.dirname(out), { recursive: true });
44
+ await bundle.write({ format: 'esm', file: out, codeSplitting: false });
45
+ await bundle.close();
46
+ console.log(`built ${path.relative(projectDir, out)}`);
47
+ console.log(`run it: node ${path.relative(projectDir, out)}`);
48
+ }
49
+ /** Compile .svelte modules and serve the bundle's virtual modules. */
50
+ function sveltermPlugin(compile, entry, globalCss) {
51
+ // Component libraries installed as symlinks (file:/link: deps) resolve
52
+ // imports from their real path, where the app's dependencies aren't
53
+ // visible — pin the renderer packages to this project's installation.
54
+ const requireFromProject = createRequire(path.join(process.cwd(), 'package.json'));
55
+ const PINNED = ['@svelterm/core', 'svelte'];
56
+ return {
57
+ name: 'svelterm',
58
+ resolveId(id) {
59
+ if (id === BOOTSTRAP_MODULE || id === 'svelterm:global-css' || id === 'ws') {
60
+ return id === 'ws' ? WS_STUB_MODULE
61
+ : id === BOOTSTRAP_MODULE ? BOOTSTRAP_MODULE : GLOBAL_CSS_MODULE;
62
+ }
63
+ if (PINNED.some(pkg => id === pkg || id.startsWith(`${pkg}/`))) {
64
+ try {
65
+ return requireFromProject.resolve(id);
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ return null;
72
+ },
73
+ load(id) {
74
+ if (id === BOOTSTRAP_MODULE)
75
+ return bootstrapModule(entry, globalCss);
76
+ if (id === GLOBAL_CSS_MODULE)
77
+ return globalCssModule(globalCss);
78
+ if (id === WS_STUB_MODULE)
79
+ return WS_STUB_SOURCE;
80
+ return null;
81
+ },
82
+ transform(code, id) {
83
+ if (!id.endsWith('.svelte'))
84
+ return null;
85
+ const compiled = compile(code, {
86
+ generate: 'client',
87
+ css: 'external',
88
+ filename: id,
89
+ experimental: { customRenderer: '@svelterm/core' },
90
+ });
91
+ return {
92
+ code: withCssRegistration(compiled.js.code, compiled.css?.code ?? null),
93
+ map: null,
94
+ };
95
+ },
96
+ };
97
+ }
98
+ async function importOrExplain(specifier, hint) {
99
+ try {
100
+ return await import(specifier);
101
+ }
102
+ catch {
103
+ console.error(`Cannot resolve '${specifier}'. ${hint}`);
104
+ process.exit(1);
105
+ }
106
+ }
107
+ function parseArgs(argv) {
108
+ const args = { entry: null, out: null, css: null };
109
+ for (let i = 0; i < argv.length; i++) {
110
+ const arg = argv[i];
111
+ if (arg === '-o' || arg === '--out')
112
+ args.out = argv[++i] ?? null;
113
+ else if (arg === '--css')
114
+ args.css = argv[++i] ?? null;
115
+ else if (!arg.startsWith('-'))
116
+ args.entry = arg;
117
+ }
118
+ return args;
119
+ }