@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.
- package/CHANGELOG.md +425 -0
- package/README.md +42 -29
- package/dist/src/cli/build.d.ts +13 -0
- package/dist/src/cli/build.js +119 -0
- package/dist/src/cli/bundle.d.ts +25 -0
- package/dist/src/cli/bundle.js +61 -0
- package/dist/src/cli/dev.d.ts +10 -0
- package/dist/src/cli/dev.js +152 -0
- package/dist/src/cli/devtools.d.ts +9 -0
- package/dist/src/cli/devtools.js +47 -0
- package/dist/src/cli/init.d.ts +8 -0
- package/dist/src/cli/init.js +153 -0
- package/dist/src/cli/main.d.ts +9 -0
- package/dist/src/cli/main.js +52 -0
- package/dist/src/cli/svt-bin.d.ts +2 -0
- package/dist/src/cli/svt-bin.js +6 -0
- package/dist/src/cli/svt.d.ts +14 -0
- package/dist/src/cli/svt.js +76 -0
- package/dist/src/components/text-buffer.js +8 -5
- package/dist/src/css/animation-runner.d.ts +15 -6
- package/dist/src/css/animation-runner.js +80 -29
- package/dist/src/css/animation.d.ts +12 -0
- package/dist/src/css/animation.js +21 -0
- package/dist/src/css/calc.js +4 -3
- package/dist/src/css/color.d.ts +19 -0
- package/dist/src/css/color.js +371 -62
- package/dist/src/css/compute.d.ts +30 -3
- package/dist/src/css/compute.js +272 -33
- package/dist/src/css/defaults.d.ts +1 -1
- package/dist/src/css/defaults.js +9 -0
- package/dist/src/css/easing.d.ts +9 -0
- package/dist/src/css/easing.js +95 -0
- package/dist/src/css/incremental.d.ts +1 -1
- package/dist/src/css/incremental.js +2 -2
- package/dist/src/css/interpolate.d.ts +13 -0
- package/dist/src/css/interpolate.js +41 -0
- package/dist/src/css/parser.js +59 -3
- package/dist/src/css/pseudo-elements.d.ts +9 -0
- package/dist/src/css/pseudo-elements.js +97 -0
- package/dist/src/css/selector.d.ts +17 -2
- package/dist/src/css/selector.js +128 -13
- package/dist/src/css/specificity.js +17 -6
- package/dist/src/css/values.d.ts +6 -1
- package/dist/src/css/values.js +13 -6
- package/dist/src/debug/context.d.ts +13 -0
- package/dist/src/debug/context.js +11 -0
- package/dist/src/debug/css.d.ts +12 -0
- package/dist/src/debug/css.js +28 -0
- package/dist/src/debug/dom.d.ts +17 -0
- package/dist/src/debug/dom.js +92 -0
- package/dist/src/devtools/DevTools.compiled.js +327 -0
- package/dist/src/devtools/DevTools.css.js +1 -0
- package/dist/src/devtools/client.d.ts +36 -0
- package/dist/src/devtools/client.js +76 -0
- package/dist/src/framelog.d.ts +54 -0
- package/dist/src/framelog.js +99 -0
- package/dist/src/headless.js +12 -4
- package/dist/src/index.d.ts +65 -3
- package/dist/src/index.js +609 -81
- package/dist/src/input/checkable.d.ts +8 -0
- package/dist/src/input/checkable.js +66 -0
- package/dist/src/input/details.d.ts +6 -0
- package/dist/src/input/details.js +34 -0
- package/dist/src/input/focus.d.ts +6 -0
- package/dist/src/input/focus.js +27 -9
- package/dist/src/input/keyboard.d.ts +2 -2
- package/dist/src/input/keyboard.js +32 -5
- package/dist/src/input/label.d.ts +8 -0
- package/dist/src/input/label.js +53 -0
- package/dist/src/input/modal.d.ts +9 -0
- package/dist/src/input/modal.js +28 -0
- package/dist/src/input/mouse.d.ts +2 -2
- package/dist/src/input/mouse.js +15 -2
- package/dist/src/input/select.d.ts +12 -0
- package/dist/src/input/select.js +63 -0
- package/dist/src/input/selection.d.ts +48 -0
- package/dist/src/input/selection.js +150 -0
- package/dist/src/layout/engine.d.ts +2 -0
- package/dist/src/layout/engine.js +1084 -142
- package/dist/src/layout/flex.js +4 -4
- package/dist/src/layout/size.js +3 -2
- package/dist/src/layout/text.d.ts +3 -2
- package/dist/src/layout/text.js +96 -17
- package/dist/src/layout/unicode.d.ts +20 -0
- package/dist/src/layout/unicode.js +121 -0
- package/dist/src/render/animation-clock.d.ts +51 -0
- package/dist/src/render/animation-clock.js +213 -0
- package/dist/src/render/ansi-text.d.ts +26 -0
- package/dist/src/render/ansi-text.js +131 -0
- package/dist/src/render/ansi.d.ts +18 -0
- package/dist/src/render/ansi.js +64 -19
- package/dist/src/render/border.js +166 -17
- package/dist/src/render/buffer.d.ts +1 -0
- package/dist/src/render/buffer.js +5 -2
- package/dist/src/render/color-depth.d.ts +8 -0
- package/dist/src/render/color-depth.js +59 -0
- package/dist/src/render/context.d.ts +1 -0
- package/dist/src/render/context.js +17 -21
- package/dist/src/render/cursor-emit.d.ts +18 -0
- package/dist/src/render/cursor-emit.js +50 -0
- package/dist/src/render/diff.d.ts +12 -0
- package/dist/src/render/diff.js +120 -0
- package/dist/src/render/generation.d.ts +9 -0
- package/dist/src/render/generation.js +14 -0
- package/dist/src/render/graphics-layer.d.ts +27 -0
- package/dist/src/render/graphics-layer.js +86 -0
- package/dist/src/render/image.d.ts +27 -0
- package/dist/src/render/image.js +113 -0
- package/dist/src/render/incremental-paint.d.ts +7 -3
- package/dist/src/render/incremental-paint.js +52 -79
- package/dist/src/render/inline.d.ts +59 -0
- package/dist/src/render/inline.js +219 -0
- package/dist/src/render/kitty-graphics.d.ts +24 -0
- package/dist/src/render/kitty-graphics.js +58 -0
- package/dist/src/render/paint-text.js +68 -22
- package/dist/src/render/paint.d.ts +8 -1
- package/dist/src/render/paint.js +328 -30
- package/dist/src/render/png.d.ts +13 -0
- package/dist/src/render/png.js +145 -0
- package/dist/src/render/scrollbar.d.ts +8 -2
- package/dist/src/render/scrollbar.js +71 -14
- package/dist/src/render/snapshot.js +3 -1
- package/dist/src/renderer/default.d.ts +7 -0
- package/dist/src/renderer/default.js +11 -0
- package/dist/src/renderer/index.d.ts +8 -2
- package/dist/src/renderer/index.js +4 -2
- package/dist/src/renderer/node.d.ts +109 -0
- package/dist/src/renderer/node.js +165 -1
- package/dist/src/terminal/capabilities.d.ts +33 -0
- package/dist/src/terminal/capabilities.js +66 -0
- package/dist/src/terminal/clipboard.d.ts +9 -0
- package/dist/src/terminal/clipboard.js +39 -0
- package/dist/src/terminal/io.d.ts +82 -0
- package/dist/src/terminal/io.js +155 -0
- package/dist/src/terminal/screen.d.ts +3 -10
- package/dist/src/terminal/screen.js +5 -28
- package/dist/src/terminal/stdin-router.d.ts +8 -5
- package/dist/src/terminal/stdin-router.js +22 -11
- package/dist/src/utils/node-map.d.ts +24 -0
- package/dist/src/utils/node-map.js +75 -0
- package/dist/src/vite/config.d.ts +62 -0
- package/dist/src/vite/config.js +191 -0
- package/docs/compatibility.md +67 -0
- package/docs/debug/devtools.md +40 -0
- package/docs/debug/svt.md +50 -0
- package/docs/distribution.md +106 -0
- package/docs/elements.md +120 -0
- package/docs/getting-started.md +177 -0
- package/docs/guide/css.md +187 -0
- package/docs/guide/input.md +143 -0
- package/docs/guide/layout.md +171 -0
- package/docs/guide/theming.md +94 -0
- package/docs/how-it-works.md +115 -0
- package/docs/inline-mode.md +77 -0
- package/docs/layout.md +106 -0
- package/docs/motion.md +91 -0
- package/docs/reference/README.md +65 -0
- package/docs/reference/css/properties/border-corner.md +82 -0
- package/docs/reference/css/properties/border-style.md +168 -0
- package/docs/reference.md +226 -0
- package/docs/selectors.md +80 -0
- package/docs/terminal-css.md +149 -0
- package/docs/terminals.md +83 -0
- 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.
|
package/docs/elements.md
ADDED
|
@@ -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
|
+
`video`, `canvas`, and `iframe` are not rendered (inline images are a
|
|
14
|
+
planned, separate feature).
|
|
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
|
+
```
|