@svelterm/core 0.1.0 → 0.23.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 (166) hide show
  1. package/CHANGELOG.md +465 -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 +31 -4
  28. package/dist/src/css/compute.js +273 -34
  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 +66 -3
  59. package/dist/src/index.js +610 -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 +1092 -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 +57 -0
  87. package/dist/src/render/animation-clock.js +221 -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/clock.d.ts +35 -0
  96. package/dist/src/render/clock.js +67 -0
  97. package/dist/src/render/color-depth.d.ts +8 -0
  98. package/dist/src/render/color-depth.js +59 -0
  99. package/dist/src/render/context.d.ts +1 -0
  100. package/dist/src/render/context.js +17 -21
  101. package/dist/src/render/cursor-emit.d.ts +18 -0
  102. package/dist/src/render/cursor-emit.js +50 -0
  103. package/dist/src/render/diff.d.ts +12 -0
  104. package/dist/src/render/diff.js +120 -0
  105. package/dist/src/render/generation.d.ts +9 -0
  106. package/dist/src/render/generation.js +14 -0
  107. package/dist/src/render/graphics-layer.d.ts +27 -0
  108. package/dist/src/render/graphics-layer.js +86 -0
  109. package/dist/src/render/image.d.ts +27 -0
  110. package/dist/src/render/image.js +113 -0
  111. package/dist/src/render/incremental-paint.d.ts +7 -3
  112. package/dist/src/render/incremental-paint.js +52 -79
  113. package/dist/src/render/inline.d.ts +59 -0
  114. package/dist/src/render/inline.js +219 -0
  115. package/dist/src/render/kitty-graphics.d.ts +24 -0
  116. package/dist/src/render/kitty-graphics.js +58 -0
  117. package/dist/src/render/paint-text.js +68 -22
  118. package/dist/src/render/paint.d.ts +8 -1
  119. package/dist/src/render/paint.js +358 -31
  120. package/dist/src/render/png.d.ts +13 -0
  121. package/dist/src/render/png.js +145 -0
  122. package/dist/src/render/scrollbar.d.ts +8 -2
  123. package/dist/src/render/scrollbar.js +71 -14
  124. package/dist/src/render/snapshot.js +3 -1
  125. package/dist/src/renderer/default.d.ts +7 -0
  126. package/dist/src/renderer/default.js +11 -0
  127. package/dist/src/renderer/index.d.ts +8 -2
  128. package/dist/src/renderer/index.js +4 -2
  129. package/dist/src/renderer/node.d.ts +109 -0
  130. package/dist/src/renderer/node.js +165 -1
  131. package/dist/src/terminal/capabilities.d.ts +33 -0
  132. package/dist/src/terminal/capabilities.js +66 -0
  133. package/dist/src/terminal/clipboard.d.ts +9 -0
  134. package/dist/src/terminal/clipboard.js +39 -0
  135. package/dist/src/terminal/io.d.ts +82 -0
  136. package/dist/src/terminal/io.js +155 -0
  137. package/dist/src/terminal/screen.d.ts +3 -10
  138. package/dist/src/terminal/screen.js +5 -28
  139. package/dist/src/terminal/stdin-router.d.ts +8 -5
  140. package/dist/src/terminal/stdin-router.js +22 -11
  141. package/dist/src/utils/node-map.d.ts +24 -0
  142. package/dist/src/utils/node-map.js +75 -0
  143. package/dist/src/vite/config.d.ts +62 -0
  144. package/dist/src/vite/config.js +191 -0
  145. package/docs/compatibility.md +67 -0
  146. package/docs/debug/devtools.md +40 -0
  147. package/docs/debug/svt.md +50 -0
  148. package/docs/distribution.md +106 -0
  149. package/docs/elements.md +120 -0
  150. package/docs/getting-started.md +177 -0
  151. package/docs/guide/css.md +187 -0
  152. package/docs/guide/input.md +143 -0
  153. package/docs/guide/layout.md +171 -0
  154. package/docs/guide/theming.md +94 -0
  155. package/docs/how-it-works.md +115 -0
  156. package/docs/inline-mode.md +77 -0
  157. package/docs/layout.md +112 -0
  158. package/docs/motion.md +91 -0
  159. package/docs/reference/README.md +65 -0
  160. package/docs/reference/css/properties/border-corner.md +82 -0
  161. package/docs/reference/css/properties/border-style.md +168 -0
  162. package/docs/reference.md +227 -0
  163. package/docs/selectors.md +80 -0
  164. package/docs/terminal-css.md +149 -0
  165. package/docs/terminals.md +83 -0
  166. package/package.json +28 -7
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Vite configuration helpers for svelterm.
3
+ *
4
+ * Usage in vite.config.ts:
5
+ *
6
+ * import { svelte } from '@sveltejs/vite-plugin-svelte'
7
+ * import { svelterm } from '@svelterm/core/vite'
8
+ *
9
+ * export default defineConfig({
10
+ * plugins: [
11
+ * svelte(svelterm.svelteOptions()),
12
+ * ...svelterm.terminalServer(),
13
+ * ],
14
+ * environments: svelterm.environments(),
15
+ * })
16
+ *
17
+ * Then in another terminal:
18
+ * npx svelterm dev http://localhost:5173/src/App.svelte
19
+ */
20
+ import { createRunnableDevEnvironment } from 'vite';
21
+ /**
22
+ * Returns svelte plugin options with dynamicCompileOptions for the
23
+ * terminal environment.
24
+ */
25
+ export function svelteOptions(config = {}) {
26
+ const renderer = config.renderer ?? '@svelterm/core';
27
+ return {
28
+ dynamicCompileOptions({ environment }) {
29
+ if (environment === 'terminal') {
30
+ return {
31
+ generate: 'client',
32
+ css: 'external',
33
+ experimental: {
34
+ customRenderer: renderer,
35
+ },
36
+ };
37
+ }
38
+ },
39
+ };
40
+ }
41
+ /**
42
+ * Returns Vite environment configuration for the terminal environment.
43
+ */
44
+ export function environments() {
45
+ return {
46
+ terminal: {
47
+ dev: {
48
+ createEnvironment(name, config) {
49
+ return createRunnableDevEnvironment(name, config);
50
+ },
51
+ },
52
+ resolve: {
53
+ conditions: ['svelte', 'node'],
54
+ // Everything svelte-adjacent must flow through the module
55
+ // runner — a natively-imported copy of svelte or the
56
+ // renderer would be a second instance with its own state,
57
+ // and mount would silently build an empty tree.
58
+ noExternal: ['svelte', '@svelterm/core'],
59
+ },
60
+ },
61
+ };
62
+ }
63
+ /**
64
+ * Returns Vite plugins that bridge the terminal environment over
65
+ * WebSocket and serve CSS for the svelterm CLI.
66
+ */
67
+ export function terminalServer(config = {}) {
68
+ const rendererModule = config.renderer ?? '@svelterm/core';
69
+ let wss;
70
+ let WS;
71
+ return [{
72
+ // Compile .svelte for the terminal environment ourselves —
73
+ // vite-plugin-svelte is not environment-aware (it emits an empty
74
+ // stub for non-client environments), and terminal-only projects
75
+ // shouldn't need the environment-aware plugin fork.
76
+ name: 'svelterm:terminal-svelte',
77
+ enforce: 'pre',
78
+ async transform(code, id) {
79
+ if (!id.endsWith('.svelte'))
80
+ return null;
81
+ const environment = this.environment;
82
+ if (environment?.name !== 'terminal')
83
+ return null;
84
+ const { compile } = await import('svelte/compiler');
85
+ const compiled = compile(code, {
86
+ generate: 'client',
87
+ css: 'external',
88
+ filename: id,
89
+ experimental: { customRenderer: rendererModule },
90
+ });
91
+ const css = compiled.css?.code;
92
+ const registration = css
93
+ ? `\nimport { registerComponentCss as __svelterm_registerCss } from '@svelterm/core/app'`
94
+ + `\n__svelterm_registerCss(${JSON.stringify(css)})\n`
95
+ : '';
96
+ return { code: compiled.js.code + registration, map: null };
97
+ },
98
+ }, {
99
+ name: 'svelterm:server',
100
+ apply: 'serve',
101
+ async configResolved() {
102
+ const ws = await import('ws');
103
+ WS = ws.WebSocket;
104
+ wss = new ws.WebSocketServer({ noServer: true });
105
+ },
106
+ configureServer(server) {
107
+ // Discovery endpoint
108
+ server.middlewares.use('/__svelterm/config', (_req, res) => {
109
+ res.setHeader('Content-Type', 'application/json');
110
+ res.end(JSON.stringify({
111
+ version: 1,
112
+ entry: config.entry ?? './App.svelte',
113
+ renderer: rendererModule,
114
+ }));
115
+ });
116
+ // CSS endpoint — re-compiles .svelte files to extract CSS
117
+ server.middlewares.use('/__svelterm/css', async (_req, res) => {
118
+ const env = server.environments?.terminal;
119
+ if (!env) {
120
+ res.end('');
121
+ return;
122
+ }
123
+ const parts = [];
124
+ const { compile } = await import('svelte/compiler');
125
+ const fs = await import('fs');
126
+ for (const mod of env.moduleGraph?.idToModuleMap?.values() ?? []) {
127
+ if (mod.id?.endsWith('.svelte') && !mod.id.includes('?')) {
128
+ try {
129
+ const source = fs.readFileSync(mod.id, 'utf-8');
130
+ const result = compile(source, {
131
+ css: 'external',
132
+ filename: mod.id,
133
+ experimental: { customRenderer: rendererModule },
134
+ });
135
+ if (result.css?.code) {
136
+ parts.push(result.css.code);
137
+ }
138
+ }
139
+ catch { }
140
+ }
141
+ }
142
+ res.setHeader('Content-Type', 'text/css');
143
+ res.end(parts.join('\n'));
144
+ });
145
+ // WebSocket bridge for ModuleRunner
146
+ server.httpServer?.on('upgrade', (req, socket, head) => {
147
+ if (req.url === '/__svelterm') {
148
+ wss.handleUpgrade(req, socket, head, (ws) => {
149
+ const env = server.environments?.terminal;
150
+ if (!env) {
151
+ ws.close();
152
+ return;
153
+ }
154
+ const client = {
155
+ send(payload) {
156
+ if (ws.readyState === WS.OPEN) {
157
+ ws.send(JSON.stringify(payload));
158
+ }
159
+ },
160
+ };
161
+ ws.on('message', (data) => {
162
+ try {
163
+ const msg = JSON.parse(data.toString());
164
+ if (msg.type === 'ping')
165
+ return;
166
+ if (msg.type === 'custom' && msg.event === 'svelterm:console') {
167
+ // The app's console output — the terminal
168
+ // shows the app, so logs surface here.
169
+ const { level, text } = msg.data ?? {};
170
+ server.config.logger.info(`[svelterm] console.${level}: ${text}`);
171
+ return;
172
+ }
173
+ if (msg.type === 'custom' && msg.event) {
174
+ env.hot.api?.innerEmitter?.emit(msg.event, msg.data, client);
175
+ }
176
+ }
177
+ catch { }
178
+ });
179
+ const forward = (payload) => client.send(payload);
180
+ env.hot.api?.outsideEmitter?.on('send', forward);
181
+ client.send({ type: 'connected' });
182
+ ws.on('close', () => {
183
+ env.hot.api?.outsideEmitter?.off('send', forward);
184
+ });
185
+ });
186
+ }
187
+ });
188
+ },
189
+ }];
190
+ }
191
+ export const svelterm = { svelteOptions, environments, terminalServer };
@@ -0,0 +1,67 @@
1
+ # Browser compatibility
2
+
3
+ The rule: **any HTML/CSS feature with a sensible meaning on a grid of
4
+ character cells works the way a browser author expects.** Features with
5
+ no grid meaning parse and are silently dropped — never a crash, never a
6
+ half-render. The test of "sensible" is whether the feature survives the
7
+ move from pixels to cells: structure, cascade, selectors, layout, colour,
8
+ and state all do; sub-cell geometry does not.
9
+
10
+ ## The three buckets
11
+
12
+ 1. **Implemented faithfully** — spec behaviour on the cell grid. This is
13
+ most of what pasted browser CSS touches; the other chapters document
14
+ it and the [reference](./reference.md) tabulates it.
15
+ 2. **Approximated, documented** — the concept maps but the grid forces a
16
+ compromise. Each approximation is deliberate and tested:
17
+ - 1 cell is the atom: every length rounds to whole cells.
18
+ - `opacity < 1` ≈ the terminal *dim* attribute (binary).
19
+ - `vertical-align: baseline` ≈ `top` in tables.
20
+ - `<select>` is a popup-less cycling control — a dropdown has no good
21
+ cell-grid answer.
22
+ - Animation is linear (no easing) and lengths step by whole cells.
23
+ 3. **Out of scope** — dropped silently, styled per mode instead.
24
+
25
+ ## The ignored list
26
+
27
+ Pixel-derived lengths (`px`, `em`, `rem`, `ex`, `vw`, `vh`) ·
28
+ typography (`font-size`, `font-family`, `line-height`,
29
+ `letter-spacing`, `word-spacing`) · sub-cell decoration
30
+ (`border-radius`, `box-shadow`, `outline`, `filter`,
31
+ `backdrop-filter`) · geometry (`transform`, `rotate`, `scale`,
32
+ `translate`, `perspective`) · `background-image` and gradients ·
33
+ `float` · `@font-face`, `@page` · easing keywords.
34
+
35
+ ## The pattern
36
+
37
+ Style each mode on its own terms with
38
+ [`display-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/display-mode)
39
+ blocks — svelterm matches `terminal`, real browsers apply the `browser`
40
+ block as ordinary CSS:
41
+
42
+ ```css
43
+ .card {
44
+ padding: 1ch; /* shared — 1ch is 1 cell */
45
+ @media (display-mode: browser) {
46
+ border: 1px solid #ddd;
47
+ border-radius: 8px;
48
+ box-shadow: 0 2px 8px #0002;
49
+ }
50
+ @media (display-mode: terminal) {
51
+ border: rounded;
52
+ }
53
+ }
54
+ ```
55
+
56
+ Because the ignored list drops rather than errors, a component pasted
57
+ from a website renders sensibly with **zero terminal-specific CSS** —
58
+ the shadows and radii simply don't appear until you add terminal styling.
59
+
60
+ ## Where this is pinned down
61
+
62
+ - [`reference.md`](./reference.md) — the one-page support matrix.
63
+ - `DESIGN-browser-compat.md` in the repo root — the design rationale and
64
+ slice history.
65
+ - The acceptance tests (`test/browser-compat-acceptance.test.ts`) keep
66
+ both promises honest: a website-flavoured card renders with no
67
+ terminal CSS, and every ignored declaration parses without effect.
@@ -0,0 +1,40 @@
1
+ # DevTools
2
+
3
+ `svelterm devtools` is a terminal DevTools — itself a svelterm app — that
4
+ connects to a running app's debug server and inspects its live tree,
5
+ computed styles, and layout.
6
+
7
+ ## Use
8
+
9
+ Run the app you want to inspect with `debug: true`:
10
+
11
+ ```ts
12
+ run(App, { css, debug: true }) // debug server on 127.0.0.1:9444
13
+ ```
14
+
15
+ Then, in another terminal (or tmux pane):
16
+
17
+ ```bash
18
+ svelterm devtools # connect on 9444
19
+ svelterm devtools --port 9500 # a non-default port
20
+ ```
21
+
22
+ - **Left pane** — the node tree, indented by depth, each row labelled
23
+ `<div#id.class>` / `"text"` / `<!--comment-->`.
24
+ - **Right pane** — the selected node's computed style (the values
25
+ svelterm actually painted) and its layout box.
26
+ - **↑ / ↓** move the selection, **← / →** collapse / expand a subtree
27
+ (Enter toggles), **r** refreshes the tree, **Ctrl+C** quits.
28
+
29
+ The style panel lists every non-default resolved value for the selected
30
+ element.
31
+
32
+ It talks the same JSON protocol as the [`svt`](./svt.md) one-shot client
33
+ (the `DOM` and `CSS` domains), so it works against any app that opens a
34
+ debug server — including one running over ssh, if you forward the port.
35
+
36
+ ## Note
37
+
38
+ Like the debug server itself, DevTools needs a real `ws` — it inspects
39
+ apps run from source or via `svelterm dev`, not `svelterm build` bundles
40
+ (which stub `ws` out).
@@ -0,0 +1,50 @@
1
+ # svt — debug CLI
2
+
3
+ `svt` connects to a running svelterm app's debug server (a WebSocket on
4
+ `127.0.0.1`) and inspects its live node tree, styles, and layout from the
5
+ command line. Also available as `svelterm inspect`. For an interactive
6
+ terminal inspector, see [DevTools](./devtools.md).
7
+
8
+ ## Enabling debug mode
9
+
10
+ Run the app with `debug: true`:
11
+
12
+ ```ts
13
+ run(App, {
14
+ css,
15
+ debug: true, // start the debug server
16
+ debugPort: 9444, // optional, default 9444
17
+ })
18
+ ```
19
+
20
+ The server binds to `127.0.0.1` only. (Note: `svelterm build` bundles
21
+ stub out `ws`, so the debug server is a no-op in a shipped bundle — run
22
+ from source, or via `svelterm dev`, to inspect.)
23
+
24
+ ## Commands
25
+
26
+ ```bash
27
+ svt tree # print the whole node tree (tags, attrs, text)
28
+ svt query '.card' # find a node id by CSS selector
29
+ svt style <nodeId> # the resolved computed style svelterm painted
30
+ svt box <nodeId> # the node's layout box (x, y, width, height)
31
+ svt console [count] # recent console entries
32
+ svt raw DOM.getDocument '{}' # any protocol method + JSON params
33
+ svt query '.card' --port 9500 # non-default port
34
+ ```
35
+
36
+ Output is JSON on stdout — pipe it through `jq`:
37
+
38
+ ```bash
39
+ svt query '.card' | jq .nodeId
40
+ svt style "$(svt query '.card' | jq .nodeId)" | jq '.style | {fg, width, borderStyle}'
41
+ ```
42
+
43
+ ## Protocol
44
+
45
+ The server speaks a small JSON-over-WebSocket protocol: a request is
46
+ `{ id, method, params }`, a reply `{ id, result }` or `{ id, error }`.
47
+ Domains: `DOM` (`getDocument`, `querySelector`, `getBoxModel`,
48
+ `setAttribute`, `removeAttribute`), `CSS` (`getComputedStyle`), and
49
+ `Console` (`getEntries`, `clear`). `svt raw <method> <json>` reaches any
50
+ of them, so tooling can build on the same channel.
@@ -0,0 +1,106 @@
1
+ # Shipping terminal apps
2
+
3
+ The playground proves components in a browser-hosted emulator; this page
4
+ is about the other half — running the same code in real terminals, and
5
+ packaging it so other people can.
6
+
7
+ ## Try it: the playground demos in your terminal
8
+
9
+ Every playground example is also published as a single self-contained
10
+ module:
11
+
12
+ ```bash
13
+ curl -fsSL https://svelterm.dev/run/counter.mjs | node --input-type=module -
14
+ ```
15
+
16
+ (`https://svelterm.dev/run.txt` lists them all.) The `.mjs` has the
17
+ component, its CSS, svelterm, and the Svelte runtime bundled flat — the
18
+ only requirement is Node 20+.
19
+
20
+ Piping a script into `node -` normally costs you interactivity, because
21
+ stdin *is* the script. svelterm's `ProcessIO` handles this: when stdin is
22
+ not a TTY it reopens the controlling terminal (`/dev/tty`) for input, so
23
+ Tab/Enter/mouse keep working. On Windows (no `/dev/tty`), download first:
24
+ `curl -o app.mjs … && node app.mjs`.
25
+
26
+ ## Bundling an app
27
+
28
+ The demo endpoint is also the reference recipe
29
+ (`svelterm-site/scripts/build-demos.mjs`): compile the component with the
30
+ custom renderer, wrap it in a `run()` bootstrap, and bundle for Node.
31
+ The shape, with any bundler (rolldown shown; esbuild works the same way):
32
+
33
+ ```js
34
+ // 1. compile — terminal variant
35
+ const { js, css } = compile(source, {
36
+ generate: 'client',
37
+ css: 'external',
38
+ experimental: { customRenderer: '@svelterm/core' },
39
+ })
40
+
41
+ // 2. bootstrap
42
+ // import App from './App.js'
43
+ // import { run } from '@svelterm/core/app'
44
+ // run(App, { css: <extracted css string> })
45
+
46
+ // 3. bundle: platform node, single-file ESM output
47
+ const bundle = await rolldown({ input: 'entry.js', platform: 'node' })
48
+ await bundle.write({ format: 'esm', file: 'app.mjs', codeSplitting: false })
49
+ ```
50
+
51
+ In a Vite project, `@svelterm/core/vite` configures the compiler side for
52
+ you; the bundle step is the same. Result: one `.mjs`, ~350 kB, no
53
+ `node_modules` at runtime.
54
+
55
+ ## Distribution options
56
+
57
+ **A URL** — host the `.mjs` anywhere static and document the `curl | node -`
58
+ line. Zero install, easiest to keep current; requires Node on the
59
+ machine. This is what svelterm.dev/run does.
60
+
61
+ **An npm package** — point `bin` at a small launcher that imports your
62
+ bundle, and users run `npx your-app` (or install it globally). Works
63
+ wherever npm does; versioning and updates come free.
64
+
65
+ ```json
66
+ { "name": "your-app", "bin": { "your-app": "./app.mjs" }, "engines": { "node": ">=20" } }
67
+ ```
68
+
69
+ (Add `#!/usr/bin/env node` at the top of the bundle for the bin path.)
70
+
71
+ **A single executable** — bundle the runtime in, so users need nothing:
72
+
73
+ - `bun build --compile app.mjs --outfile your-app` produces a per-platform
74
+ binary (~90 MB).
75
+ - Node's [single executable applications](https://nodejs.org/api/single-executable-applications.html)
76
+ inject the bundle into a copied `node` binary.
77
+ - `deno compile` similarly, via Deno's Node compatibility.
78
+
79
+ These are documented paths rather than tested ones — the `.mjs` bundles
80
+ are what svelterm exercises today (Node 20+, verified on macOS/Linux
81
+ terminals). Bun and Deno run Node-flavoured code well and the bundles
82
+ avoid native dependencies entirely, but treat them as "should work,
83
+ verify" until the test matrix says otherwise.
84
+
85
+ ## What svelterm needs from a terminal
86
+
87
+ Any reasonably modern emulator qualifies: raw mode, cursor addressing,
88
+ and 16-colour ANSI at minimum. Truecolor is used when hex colours appear
89
+ (near-universal now); mouse support needs SGR mouse mode; synchronized
90
+ updates are used when available and harmless when not. The alternate
91
+ screen buffer hosts fullscreen apps (`fullscreen: false` opts out).
92
+ Windows Terminal covers all of this on Windows.
93
+
94
+ ## A testing story for real terminals
95
+
96
+ Three layers, increasing in realism:
97
+
98
+ 1. **Headless** — `@svelterm/core/headless` renders components to cell
99
+ buffers in plain Node for unit tests; svelterm's own thousand-test
100
+ suite runs this way.
101
+ 2. **The playground** — visual verification in the browser emulator,
102
+ side by side with the DOM render.
103
+ 3. **The bundles** — pipe a demo (or your own app's bundle) into a real
104
+ terminal. This is scriptable too: run the bundle inside `tmux`, drive
105
+ it with `tmux send-keys`, assert on `tmux capture-pane` — a real
106
+ PTY, real escape-sequence parsing, no emulator in the loop.
@@ -0,0 +1,120 @@
1
+ # Elements, input and events
2
+
3
+ What HTML renders to on the grid, how form controls behave, and how
4
+ events reach your handlers. Anything unlisted renders as a plain box per
5
+ its display default.
6
+
7
+ ## Text and structure
8
+
9
+ Headings, paragraphs, lists (`ul`/`ol` with markers), `blockquote`,
10
+ `pre`, `hr` (a `─` rule), `figure`, `dl`, and the text-level elements
11
+ (`strong`/`b`, `em`/`i`, `u`, `s`/`del`, `mark`, `code`, `kbd`, `abbr`,
12
+ `samp`, `var`) carry a browser-like UA stylesheet in cells. `img`
13
+ renders (see [Images](#images) below); `video`, `canvas`, and `iframe`
14
+ are not rendered.
15
+
16
+ ## Images
17
+
18
+ `<img src="...">` renders as half-blocks — each cell shows two vertically
19
+ stacked pixels ('▀' with per-half colours), so a 40×20-pixel image is
20
+ 40 columns × 10 rows. Sources: file paths and `data:image/png;base64`
21
+ URIs (PNG: 8-bit RGB/RGBA/greyscale/palette, decoded with no native
22
+ dependencies). Sizing follows CSS `width`/`height` in cells, defaulting
23
+ to the intrinsic pixel size; loading is async and reflows on arrival.
24
+ Mostly-transparent pixels show the terminal background.
25
+
26
+ On terminals that speak the [kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/)
27
+ (kitty, Ghostty, WezTerm — detected automatically), the image renders as
28
+ real pixels scaled to its cell box, covering the half-blocks. Everywhere
29
+ else the half-blocks are what you see.
30
+
31
+ ## Links
32
+
33
+ `<a>` is underlined and focusable. `Enter` or a click fires `click`; if
34
+ unprevented, the `href` opens in the local browser.
35
+
36
+ ## Form controls
37
+
38
+ | Control | Rendering | Interaction |
39
+ |---|---|---|
40
+ | `input` (text) | one-row editor on the grid | readline-style editing with a real cursor; `input` events carry `{ value, cursor }` |
41
+ | `textarea` | multi-line editor | as above |
42
+ | `input type="checkbox"` | `[x]` / `[ ]` (3×1) | `Space` or click toggles; `change`/`input` carry `{ checked, value }` |
43
+ | `input type="radio"` | `(•)` / `( )` | selecting unchecks same-`name` radios across the tree; never untoggles itself |
44
+ | `select` + `option`/`optgroup` | selected label + `▾`, sized to the longest option | popup-less cycling: `ArrowUp`/`ArrowDown` move with wraparound, `Space`/`Enter`/click advance; `change` carries `{ value }` |
45
+ | `button` | centred text, stylable | `Enter`/click dispatch `click` |
46
+ | `progress` / `meter` | 20×1 block bar: `█` fill, eighth-block partials, `░` track | `value`/`max` (+`min` for meter); no value = indeterminate track |
47
+ | `details` / `summary` | ▶/▼ disclosure marker | `Enter`/click toggles `open`; fires `toggle` with `{ open }`; closed details hide non-summary children |
48
+ | `label` | plain inline text | clicking activates its control — wrapping or `for="id"` both work |
49
+
50
+ State attributes drive selectors: `:checked`, `:disabled`/`:enabled`,
51
+ `[open]`. `disabled` controls are skipped by focus traversal and swallow
52
+ clicks. DOM-compat properties exist where Svelte bindings expect them:
53
+ `el.checked`, `el.value`.
54
+
55
+ ## Focus
56
+
57
+ `Tab` / `Shift+Tab` cycle `button`, `input`, `textarea`, `a`, `select`,
58
+ `summary` in document order, skipping disabled controls. Clicking a
59
+ focusable element focuses it. The focused element matches
60
+ [`:focus`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus) —
61
+ style it; there is no default focus ring beyond your CSS.
62
+
63
+ ```css
64
+ button:focus { border-color: yellow; }
65
+ ```
66
+
67
+ ## Keyboard
68
+
69
+ - `Enter` — activate the focused element (click / toggle / cycle).
70
+ - `Space` — toggle checkbox/radio, advance select.
71
+ - Arrows — cycle selects; move the text cursor in inputs.
72
+ - Printable keys — type into the focused input/textarea.
73
+ - Unhandled keys dispatch `keydown` to the focused element (or the tree
74
+ root) with `{ key, ctrl, shift, meta }`.
75
+ - `Ctrl+C` exits (add `'ctrl+d'` to `run`'s `exitOn` for EOF-style
76
+ exit). `Ctrl+Z` truly suspends: the terminal is restored for the
77
+ shell, and `fg` re-enters modes and repaints with state intact.
78
+ - The [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/)
79
+ is enabled where supported, so combinations the legacy encoding can't
80
+ express (`Ctrl+Enter`, `Shift+Space`, …) arrive with correct
81
+ modifiers. Elsewhere the classic encoding applies.
82
+
83
+ ## Modal dialogs
84
+
85
+ A `<dialog open>` captures input like a browser modal:
86
+ `Tab`/`Shift+Tab` trap inside it, focus is pulled in from outside, and
87
+ `Escape` removes `open` and dispatches a `close` event. Style it with
88
+ `position: absolute` + `z-index` to float above the page.
89
+
90
+ ```svelte
91
+ {#if confirming}
92
+ <dialog open onclose={() => confirming = false}>
93
+ <span>Delete everything?</span>
94
+ <button onclick={confirm}>Yes</button>
95
+ <button onclick={() => confirming = false}>No</button>
96
+ </dialog>
97
+ {/if}
98
+ ```
99
+
100
+ ## Mouse
101
+
102
+ Enabled by default: click (focus + click + default action), wheel
103
+ scrolling of `overflow: auto|scroll` boxes (with scrollbar overlays),
104
+ hover driving `:hover`. Mouse events carry cell coordinates.
105
+
106
+ ## The event model
107
+
108
+ Events dispatch with W3C capture/bubble semantics through the component
109
+ tree; `stopPropagation()` and `preventDefault()` work, and default
110
+ actions (link opening, details toggling, select cycling, label
111
+ activation) respect `preventDefault()`.
112
+
113
+ Payloads ride on `event.data` — `{ value, cursor }` for text input,
114
+ `{ checked, value }` for checkables, `{ open }` for toggle,
115
+ `{ cols, rows }` for region resize. Handlers shared with the browser
116
+ should read both shapes:
117
+
118
+ ```svelte
119
+ <select onchange={(e) => plan = e.data?.value ?? e.target.value}>
120
+ ```