@particle-academy/fancy-term 0.5.0 → 0.6.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 ADDED
@@ -0,0 +1,181 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@particle-academy/fancy-term` are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ > **Pre-1.0:** breaking changes land in MINOR releases. Until 1.0 the minor
8
+ > number is not a compatibility promise — read the entry, not the version.
9
+
10
+ > This file starts here. Earlier releases predate it and were never written up;
11
+ > `git log` is the record for those. It is not backfilled rather than
12
+ > guessed-at, because a changelog that invents its own history is worse than one
13
+ > that admits where it begins.
14
+
15
+ ## [Unreleased]
16
+
17
+ ## 0.6.0 — 2026-10-10
18
+
19
+ ### Added
20
+
21
+ - **`@particle-academy/fancy-term/xterm.css` — load the stylesheet without naming xterm.**
22
+
23
+ ```ts
24
+ import "@particle-academy/fancy-term/xterm.css";
25
+ ```
26
+
27
+ The terminal has never rendered without xterm's stylesheet, so every consumer
28
+ had to write `import "@xterm/xterm/css/xterm.css"` in their own source — which
29
+ made xterm the one third-party package a Fancy-only app still had to name.
30
+
31
+ **Nothing to do.** The old import is identical in effect and still works; this
32
+ is an additional way in, not a replacement. The new subpath is an `@import` of
33
+ xterm's own stylesheet rather than a copy of it, so it resolves to the single
34
+ copy already in your tree and there is no vendored third-party file to drift.
35
+
36
+ ### Changed
37
+
38
+ - **The `@xterm/*` peer ranges are now bounded above** — `@xterm/xterm` moves
39
+ from `>=5.0.0` to `>=5.0.0 <6`, and `@xterm/addon-fit` from `>=0.10.0` to
40
+ `>=0.10.0 <0.11`.
41
+
42
+ **This was live, not theoretical.** `@xterm/xterm@6.0.0` and
43
+ `@xterm/addon-fit@0.11.0` are both `latest` on npm as of today, so the old
44
+ ranges admitted a MAJOR this package has never been built against — and
45
+ nothing reported it, because a resolver quietly picking an untested version
46
+ looks exactly like success. The pairing made it worse rather than safer:
47
+ `addon-fit@0.10.0` declares `peerDependencies: {"@xterm/xterm": "^5.0.0"}` and
48
+ accidentally held the line, while **`0.11.0` declares no peer at all** — so
49
+ `>=0.10.0` plus `>=5.0.0` allowed xterm 6 with addon-fit 0.11, an untested
50
+ combination, silently.
51
+
52
+ **What you must do: almost certainly nothing.** If you are on xterm 5.x and
53
+ addon-fit 0.10.x — what `npm install` has been resolving all along, and what
54
+ this package is built and tested against — the range still admits your
55
+ version and the upgrade is invisible. **If you have explicitly moved to xterm
56
+ 6 or addon-fit 0.11, this release will now fail at install with a peer
57
+ conflict instead of running untested code.** That is the intended behaviour:
58
+ it turns a silent runtime risk into a loud install-time error. Tell us and we
59
+ will qualify 6 properly — widening a range later is safe by construction,
60
+ since it only ever adds candidates.
61
+
62
+ `>=X <2.0` remains correct for a *first-party* sibling, where we cut the
63
+ releases and the upper bound is a promise we keep. On third-party it is a
64
+ promise someone else makes.
65
+
66
+ - **xterm stays a PEER deliberately, and this is the release that writes down
67
+ why.** `<Terminal>` hands the live `XTerm` instance to the consumer through
68
+ `handle.xterm`, `handle.ready` and `onReady(xterm)` — the same reason a React
69
+ component cannot own its copy of React. If this package owned xterm, a
70
+ consumer on a different version would get two copies and addons and
71
+ `instanceof` would operate on the wrong class with no warning. The peer's
72
+ value is not "one copy", it is that a conflict fails loudly at install. The
73
+ reasoning now sits in the README, in `src/index.ts`, in `xterm.css` and in
74
+ `src/packaging.test.ts`, so the next person who proposes tidying it into
75
+ `dependencies` meets the argument first.
76
+
77
+ ### Fixed
78
+
79
+ - **`CHANGELOG.md` is now in the published tarball.** `files` did not whitelist it, so npm never shipped it — and this package puts breaking changes in MINOR releases and tells you in the README to read the entry before taking one. The instruction existed for the author, who has the file, and not for the consumer, who is the only one being instructed. Nothing for you to do; the file simply arrives from this release on.
80
+
81
+ ### Security
82
+
83
+ - `source-map-js` is pinned forward to `^1.2.2` via `overrides`. Versions up to
84
+ 1.2.1 allow an event-loop denial of service through indexed source-map section
85
+ offsets, and it arrives here transitively through the build toolchain.
86
+ **Nothing for a consumer to do, and no runtime change**: an npm package does
87
+ not ship a lockfile, so this governs builds OF this repo, not anything
88
+ installed FROM it. Recorded rather than left silent because the override it
89
+ sits beside — `shell-quote` `^1.9.0`, added for an earlier advisory — was
90
+ carried with no note of why, and had drifted back inside the vulnerable range
91
+ before anyone looked.
92
+
93
+ ## 0.5.1 — 2026-09-29
94
+
95
+ ### Fixed
96
+
97
+ - **`docs/` is real now.** `files` already listed `docs`, and the directory did
98
+ not exist — so every published tarball carried a `files` entry pointing at
99
+ nothing and shipped no reference at all. That is the worse half of this
100
+ defect: a `files` array that names `docs` reads as compliant to anyone
101
+ checking the manifest, so nothing ever looked in the tarball.
102
+
103
+ Adds `docs/Terminal.md` and `docs/ShellSwitcher.md` — the full prop and
104
+ `TerminalOptions` tables, the imperative handle (including `getBuffer()`, the
105
+ Human+ affordance an agent reads instead of scraping the DOM), why the
106
+ clipboard is injectable and what silently breaks in a sandboxed Electron
107
+ renderer without a provider, why OSC 52 defaults to write-only, and how
108
+ pasted images reach the host.
109
+
110
+ No code changed.
111
+
112
+ ## 0.5.0 — 2026-08-07
113
+
114
+ ### Changed
115
+
116
+ - **BREAKING — Node 22 is now declared as the floor.** `engines.node` is `>=22`, where this package previously declared **nothing at all**.
117
+
118
+ Declaring nothing was not the same as supporting old Node: a consumer on 18 installed cleanly and found out at runtime.
119
+
120
+ **What you must do:** on Node 22 or newer, nothing. Note npm only *warns* on an `engines` mismatch while **pnpm fails the install**, so this surfaces differently depending on your package manager. Node 18 is end-of-life and 20 is maintenance-only.
121
+
122
+ - **BREAKING — React 18 is no longer supported.** `peerDependencies.react` / `react-dom` are now `^19.0.0`.
123
+
124
+ **What you must do:** on React 19, nothing. On React 18, stay on the previous release, or upgrade your app to 19 first.
125
+
126
+ React 18 support was a claim nothing tested — every build and test in this package ran against 19, so the 18 half of the old range was never executed. An untested compatibility claim is worse than an absent one, because it reads as support.
127
+
128
+ ### Why
129
+
130
+ These are the kit 0.5 platform floors, applied across every package at once so a consumer never has to resolve a mix. **No API changed, nothing was removed, nothing was renamed** — only what the package requires.
131
+
132
+
133
+ ## 0.4.1 — 2026-07-06
134
+
135
+ ### Fixed
136
+
137
+ - context-menu Copy writes the menu-open selection snapshot, not a click-time re-read
138
+
139
+ ## 0.4.0 — 2026-07-02
140
+
141
+ ### Added
142
+
143
+ - Electron-safe clipboard — injectable provider, OSC 52, copy/paste modes, ready signal (#1)
144
+
145
+ ## 0.3.0 — 2026-06-14
146
+
147
+ ### Added
148
+
149
+ - clipboard (copy/paste + images) + customizable selection context menu
150
+
151
+ ## 0.2.2 — 2026-06-11
152
+
153
+ ### Fixed
154
+
155
+ - omit undefined rows/cols from xterm constructor (was console-erroring)
156
+
157
+ ## 0.2.1 — 2026-06-11
158
+
159
+ ### Fixed
160
+
161
+ - guard fit() via proposeDimensions — no xterm resize(undefined) on unlaid-out container
162
+
163
+ ## 0.2.0 — 2026-06-11
164
+
165
+ ### Changed
166
+
167
+ - **BREAKING** — shell / profile switching (UI + API + session)
168
+
169
+ ## 0.1.0 — 2026-06-10
170
+
171
+ ### Added
172
+
173
+ - fancy-term 0.1.0 — Human+ Terminal (xterm.js wrapper)
174
+
175
+ ### Changed
176
+
177
+ - Replaced an `eslint-disable jsx-a11y/no-autofocus` in `ShellSwitcher` with a
178
+ plain comment explaining why the autofocus is deliberate. `jsx-a11y` has never
179
+ been a dependency of this package, so the directive silenced a rule that did
180
+ not exist — and broke linting the moment ESLint was actually turned on.
181
+ **No action needed**, no behaviour change.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Particle Academy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @particle-academy/fancy-term
2
2
 
3
+ [![Fancified](art/fancified.svg)](https://particle.academy)
4
+
3
5
  **Human+ Terminal for React** — a controlled, themeable `<Terminal>` wrapping
4
6
  [xterm.js](https://xtermjs.org), with hooks and an MCP-bridgeable surface so
5
7
  embedded agents read the buffer, write input, and run commands **without
@@ -16,7 +18,7 @@ Like every Fancy UI component it serves two surfaces at once:
16
18
  Sexy by default via a Fancy dark theme drawn from the react-fancy Tailwind v4
17
19
  tokens.
18
20
 
19
- > **Status:** 0.2.0. `<Terminal>` + `useTerminal` / `useTerminalFit` /
21
+ > **Status:** pre-1.0. `<Terminal>` + `useTerminal` / `useTerminalFit` /
20
22
  > `useTerminalSession` are in place, plus **shell / profile switching** (the
21
23
  > `<ShellSwitcher>` component, controlled `shells` / `activeShell` props, and the
22
24
  > session hook's `switchShell`). The `registerTerminalBridge` MCP bridge
@@ -32,12 +34,29 @@ npm install @particle-academy/fancy-term @xterm/xterm @xterm/addon-fit
32
34
 
33
35
  `react`, `xterm`, and the fit addon are **peer dependencies** — the wrapper itself
34
36
  is zero-runtime-dep (the same posture as `fancy-echarts` over ECharts). Import the
35
- xterm stylesheet once in your app:
37
+ stylesheet once in your app, from here rather than from xterm:
36
38
 
37
39
  ```ts
38
- import "@xterm/xterm/css/xterm.css";
40
+ import "@particle-academy/fancy-term/xterm.css";
39
41
  ```
40
42
 
43
+ Without it the terminal does not render: xterm's character-measurement helper
44
+ has to stay out of layout, and without these rules it does not, so the cell size
45
+ comes out wrong.
46
+
47
+ That subpath re-exports xterm's own stylesheet with `@import`, so it resolves to
48
+ the single copy already in your tree. `import "@xterm/xterm/css/xterm.css"` is
49
+ identical in effect and still works — the subpath exists so a Fancy-only app has
50
+ no reason to name a third-party package in its own source.
51
+
52
+ **Why xterm is a peer and not a dependency**, since it is the obvious thing to
53
+ "tidy": `<Terminal>` hands you the live `XTerm` instance — `handle.xterm`,
54
+ `handle.ready`, `onReady(xterm)` — exactly as a React component hands out React
55
+ elements and therefore cannot own React. If this package owned its own xterm
56
+ copy, a consumer on a different version would get two, and addons and
57
+ `instanceof` would operate on the wrong class with no warning anywhere. The peer
58
+ turns that silent runtime break into a loud install-time error.
59
+
41
60
  ## `<Terminal>`
42
61
 
43
62
  The parent needs a height — the terminal fits its container (like any xterm
@@ -0,0 +1,57 @@
1
+ # ShellSwitcher
2
+
3
+ The shell/profile selector shown above a `<Terminal>`. fancy-term renders the
4
+ control and tracks the choice; **the host owns the list and does the work** —
5
+ reconnecting a PTY to the chosen shell is yours.
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import { ShellSwitcher, BUILTIN_SHELLS, resolveShell } from "@particle-academy/fancy-term";
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Most hosts never render it directly — set `showShellBar` on `<Terminal>`:
16
+
17
+ ```tsx
18
+ <Terminal
19
+ shells={BUILTIN_SHELLS}
20
+ showShellBar
21
+ onShellChange={(id, profile) => session.reconnect(profile)}
22
+ />
23
+ ```
24
+
25
+ Render it yourself when it belongs somewhere else in your chrome:
26
+
27
+ ```tsx
28
+ <ShellSwitcher shells={shells} active={id} onChange={setId} />
29
+ ```
30
+
31
+ ## `ShellProfile`
32
+
33
+ A profile is plain data, so an agent can emit one and a host can store it:
34
+
35
+ | Field | Description |
36
+ |---|---|
37
+ | id | The id used by `activeShell`, `setShell` and `onShellChange`. |
38
+ | label | What the user sees. |
39
+ | … | Whatever else your backend needs to launch it. |
40
+
41
+ `BUILTIN_SHELLS` is a set of sensible presets to spread, not a fixed menu —
42
+ add, remove or replace freely.
43
+
44
+ ## `resolveShell(shells, id)`
45
+
46
+ The pure helper that `setShell`, `<ShellSwitcher>` and the session hook all
47
+ agree on. Returns the matching `ShellProfile`, or `undefined` when the id is not
48
+ in the list — which is why an unknown id is a no-op rather than a crash.
49
+
50
+ ## Controlled vs uncontrolled
51
+
52
+ Pass `activeShell` to control the selection. Omit it and `<Terminal>` tracks it
53
+ internally; `handle.setShell(id)` still works and still fires `onShellChange`.
54
+
55
+ ## See also
56
+
57
+ - [Terminal](./Terminal.md)
@@ -0,0 +1,117 @@
1
+ # Terminal
2
+
3
+ A controlled, themeable terminal over xterm.js. The output buffer is React
4
+ state, so a host can stream command output straight from its own data layer,
5
+ and an agent can read what the human sees.
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import { Terminal } from "@particle-academy/fancy-term";
11
+ ```
12
+
13
+ ## Basic Usage
14
+
15
+ ```tsx
16
+ <Terminal output={log} onData={(d) => pty.write(d)} />
17
+ ```
18
+
19
+ ## Controlled output
20
+
21
+ `output` is diffed against the previous value and only the **appended delta** is
22
+ written, so re-rendering on every chunk is cheap — wire it to a stream and let
23
+ React do the rest. Replacing `output` with a string that does not extend the
24
+ previous one resets the terminal and rewrites it.
25
+
26
+ ## Props
27
+
28
+ Extends `TerminalOptions` (below) and the native `<div>` attributes, minus
29
+ `onInput`, `onResize`, `onPaste`, `contextMenu` and `children`.
30
+
31
+ | Prop | Type | Default | Description |
32
+ |------|------|---------|-------------|
33
+ | output | `string` | — | The controlled buffer. Only the appended delta is written. |
34
+ | shells | `ShellProfile[]` | — | The shells/profiles you offer. Spread `BUILTIN_SHELLS` for presets; the host owns the list. |
35
+ | activeShell | `string` | — | Controlled active shell id. Omit for internal selection. |
36
+ | onShellChange | `(id, profile) => void` | — | Fired when the user or `setShell` switches. |
37
+ | showShellBar | `boolean` | `false` | Render the built-in `<ShellSwitcher>` above the surface. Opt-in so existing layout is unchanged. |
38
+ | contextMenu | `TerminalContextMenuConfig` | `true` | `false` disables; an array replaces the items; `(ctx, defaults) => items` lets you add/reorder. |
39
+
40
+ ### `TerminalOptions`
41
+
42
+ | Option | Type | Default | Description |
43
+ |------|------|---------|-------------|
44
+ | theme | `TerminalTheme` | Fancy dark | xterm colour theme. |
45
+ | rows / cols | `number` | — | Fixed grid. Omit and leave `fit` on to size from the container. |
46
+ | fit | `boolean` | `true` | Auto-fit via the fit addon + `ResizeObserver`. |
47
+ | readOnly | `boolean` | `false` | Block stdin (display-only). |
48
+ | cursorBlink | `boolean` | `true` | |
49
+ | cursorStyle | `CursorStyle` | `"block"` | |
50
+ | fontFamily | `string` | — | Monospace stack. |
51
+ | fontSize | `number` | `13` | |
52
+ | scrollback | `number` | `1000` | |
53
+ | initialOutput | `string` | — | Written once on mount, before any controlled `output`. |
54
+ | onData | `(data: string) => void` | — | Keystrokes and paste data. Wire to your PTY. |
55
+ | onResize | `(size) => void` | — | |
56
+ | clipboard | `ClipboardOption` | `true` | See below. |
57
+ | osc52 | `Osc52Mode` | `"copy"` | See below. |
58
+ | copyPaste | `CopyPasteMode` | — | `"contextmenu"`, `"linux"` (highlight-to-copy, middle-click paste) or `"winmac"`. Ctrl+Shift+C always copies. |
59
+ | onReady | `(xterm: XTerm) => void` | — | Once opened and attached — the imperative twin of `handle.ready`. |
60
+ | onPaste | `(payload) => void \| boolean` | — | See "Pasted images" below. |
61
+
62
+ ## Clipboard is injectable, and that matters in Electron
63
+
64
+ `clipboard` gates the copy chord, the paste interceptor, the context-menu
65
+ copy/paste and OSC 52:
66
+
67
+ - `true` / omitted — backed by `navigator.clipboard`.
68
+ - `false` — disabled. Native text paste still works.
69
+ - a `{ writeText, readText }` **provider** — every copy/paste path routes
70
+ through it.
71
+
72
+ Supply a provider in a sandboxed Electron renderer, where `navigator.clipboard`
73
+ **silently no-ops**, to bridge to the main-process clipboard over IPC. Silently
74
+ is the operative word: without it, copy appears to work and nothing lands.
75
+
76
+ ## OSC 52 defaults to write-only, deliberately
77
+
78
+ Terminal programs (Claude Code, tmux, vim) can set or read the system clipboard
79
+ via `ESC ] 52`. `osc52` is `"copy"` by default — writes only. `"read"` and
80
+ `"both"` also answer read requests, which lets anything running in the terminal
81
+ exfiltrate the clipboard, so they are opt-in. `false` disables it entirely.
82
+
83
+ ## Pasted images
84
+
85
+ `onPaste` receives `{ text, files, images }` on every paste. Plain text still
86
+ pastes natively; this is where a host receives pasted **images**, which a shell
87
+ cannot render — upload them, hand them to an agent, or write a path. Return
88
+ `false` to consume the paste entirely (for example to transform it, then call
89
+ `handle.paste(...)` yourself).
90
+
91
+ ## Imperative handle
92
+
93
+ | Member | Type | Description |
94
+ |---|---|---|
95
+ | xterm | `XTerm \| null` | The instance. Null before mount. |
96
+ | ready | `Promise<XTerm>` | Resolves once opened and measured. `await` it instead of polling; re-armed if the terminal is recreated. |
97
+ | write / writeln | `(data: string) => void` | Raw write; ANSI honoured. |
98
+ | clear / reset | `() => void` | Viewport (keeps scrollback) / full reset. |
99
+ | fit | `() => void` | Re-fit. No-op on a 0-size box or with `fit` off. |
100
+ | focus | `() => void` | |
101
+ | getBuffer | `() => string` | The full buffer as plain text — **what an agent "sees"**. |
102
+ | getSelection | `() => string` | |
103
+ | copySelection | `() => Promise<boolean>` | False when nothing is selected or no clipboard. |
104
+ | paste | `(text?) => Promise<void>` | Reads the system clipboard with no argument. Honours bracketed paste. |
105
+ | selectAll / clearSelection | `() => void` | |
106
+ | setShell | `(id: string) => void` | Resolves the profile and fires `onShellChange`. No-op for an unknown id. |
107
+ | getShell | `() => string \| undefined` | |
108
+
109
+ `getBuffer()` is the Human+ affordance: an agent reads the same text the person
110
+ is looking at, rather than scraping the DOM.
111
+
112
+ ## Headless hooks
113
+
114
+ `useTerminal`, `useTerminalFit` and `useTerminalSession` expose the engine layer
115
+ if you would rather build your own surface. `useTerminalSession` takes a
116
+ `TerminalSessionTransport` and manages connect/reconnect against your backend —
117
+ see [ShellSwitcher](./ShellSwitcher.md) for the shell-selection half.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/fancy-term",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Human+ Terminal for React — a controlled, themeable <Terminal> wrapping xterm.js, with hooks and an MCP-bridgeable surface so embedded agents read the buffer, write input, and run commands without DOM-scraping.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,12 +22,16 @@
22
22
  "types": "./dist/index.d.cts",
23
23
  "default": "./dist/index.cjs"
24
24
  }
25
- }
25
+ },
26
+ "./xterm.css": "./xterm.css",
27
+ "./package.json": "./package.json"
26
28
  },
27
29
  "files": [
28
30
  "dist",
31
+ "xterm.css",
29
32
  "docs",
30
- "README.md"
33
+ "README.md",
34
+ "CHANGELOG.md"
31
35
  ],
32
36
  "scripts": {
33
37
  "build": "tsup",
@@ -47,31 +51,32 @@
47
51
  "fancy"
48
52
  ],
49
53
  "peerDependencies": {
50
- "@xterm/addon-fit": ">=0.10.0",
51
- "@xterm/xterm": ">=5.0.0",
54
+ "@xterm/addon-fit": ">=0.10.0 <0.11",
55
+ "@xterm/xterm": ">=5.0.0 <6",
52
56
  "react": "^19.0.0",
53
57
  "react-dom": "^19.0.0"
54
58
  },
55
59
  "devDependencies": {
56
- "@types/react": "^19.2.0",
57
- "@types/react-dom": "^19.2.0",
60
+ "@types/react": "^19.2.18",
61
+ "@types/react-dom": "^19.2.4",
58
62
  "@xterm/addon-fit": "^0.10.0",
59
63
  "@xterm/xterm": "^5.5.0",
60
64
  "eslint": "^10.8.0",
61
65
  "eslint-plugin-react-hooks": "^7.1.1",
62
- "react": "^19.2.0",
63
- "react-dom": "^19.2.0",
66
+ "react": "^19.2.8",
67
+ "react-dom": "^19.2.8",
64
68
  "tsup": "^8.5.0",
65
69
  "typescript": "^5.8.0",
66
70
  "typescript-eslint": "^8.65.0",
67
- "vitest": "^4.1.8"
71
+ "vitest": "^4.1.11"
68
72
  },
69
73
  "publishConfig": {
70
74
  "access": "public"
71
75
  },
72
76
  "license": "MIT",
73
77
  "overrides": {
74
- "esbuild": "^0.28.1"
78
+ "esbuild": "^0.28.1",
79
+ "source-map-js": "^1.2.2"
75
80
  },
76
81
  "engines": {
77
82
  "node": ">=22"
package/xterm.css ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @particle-academy/fancy-term/xterm.css
3
+ *
4
+ * The stylesheet <Terminal> needs, loadable without naming xterm.
5
+ *
6
+ * import "@particle-academy/fancy-term/xterm.css";
7
+ *
8
+ * xterm.js does not render without its own stylesheet — its character-measurement
9
+ * helper is absolutely positioned, so without these rules it participates in
10
+ * layout and the terminal computes a wrong cell size. Every consumer therefore
11
+ * had to write `import "@xterm/xterm/css/xterm.css"` in their own source, which
12
+ * made xterm the one third-party package a Fancy-only app still had to name.
13
+ *
14
+ * This is an @import, deliberately, NOT a copy of xterm's stylesheet:
15
+ *
16
+ * • It resolves from the CONSUMER's tree, where the peer is installed — so
17
+ * there is still exactly one xterm, which is the whole reason xterm is a
18
+ * peer rather than a dependency (<Terminal> hands the live XTerm instance
19
+ * out through `handle.xterm` / `handle.ready` / `onReady`, so a second copy
20
+ * would silently break addons and `instanceof`).
21
+ * • A vendored copy would drift from the xterm version actually installed,
22
+ * and would make us a redistributor of a third-party file.
23
+ *
24
+ * `src/packaging.test.ts` pins it: with comments stripped, this file must be
25
+ * EXACTLY the one @import below and nothing else. So do not add a rule here —
26
+ * app-level overrides belong in the app, where they can be seen.
27
+ */
28
+ @import "@xterm/xterm/css/xterm.css";