@cplieger/web-terminal-ui 4.5.2 → 4.7.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/README.md CHANGED
@@ -8,15 +8,14 @@
8
8
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/cplieger/web-terminal-ui/badge)](https://scorecard.dev/viewer/?uri=github.com/cplieger/web-terminal-ui)
9
9
 
10
10
  The reference touch-first browser UI for
11
- [`@cplieger/web-terminal-engine`](https://github.com/cplieger/web-terminal-engine) — the part
12
- that turns the engine's render/scroll/connection/keyboard modules into a
13
- usable terminal on a phone as well as a desktop.
11
+ [`@cplieger/web-terminal-engine`](https://github.com/cplieger/web-terminal-engine).
12
+ It turns the engine's render/scroll/connection/keyboard modules into a usable
13
+ terminal on a phone as well as a desktop.
14
14
 
15
15
  One `createTerminal(root, { features })` call builds the entire terminal UI
16
16
  inside a single container element you provide. A small always-present kernel
17
- (the display output, the hidden keyboard textarea, IME, engine wiring, the
18
- sanitizing input funnel, connection-state, and named layout regions) composes
19
- with opt-in feature modules that own everything above the raw terminal:
17
+ composes with opt-in feature modules that own everything above the raw
18
+ terminal:
20
19
 
21
20
  - a **display-only** terminal output (native text selection survives redraws)
22
21
  and a hidden `<textarea>` that owns the keyboard and IME (the kernel)
@@ -44,7 +43,7 @@ depend on the engine directly and skip this package.
44
43
  npm install @cplieger/web-terminal-ui @cplieger/web-terminal-engine
45
44
  ```
46
45
 
47
- `@cplieger/web-terminal-engine` is a peer dependency — the UI is built on the
46
+ `@cplieger/web-terminal-engine` is a peer dependency: the UI is built on the
48
47
  engine, so the consumer pins the engine version explicitly. Pairing compatibility
49
48
  is governed by the engine's
50
49
  [directional wire contract](https://github.com/cplieger/web-terminal-engine#wire-protocol),
@@ -57,14 +56,14 @@ Serve a CSS bundle matching how you embed the terminal, plus a minimal HTML
57
56
  page that has one empty container element, then call
58
57
  `createTerminal(root, { features })` from your entry module.
59
58
 
60
- **Full-page host** (the terminal IS the page — `web-terminal-server`,
59
+ **Full-page host** (the terminal IS the page, as in `web-terminal-server` and
61
60
  `web-terminal-kiro`): concatenate `css/MANIFEST` into the `style.css` your page
62
61
  links. That reference bundle is `css/page.css` (the page kit: `html/body`
63
62
  reset + the terminal web font's `@font-face`, expecting the font files at
64
63
  `/vendor/fonts/`) plus the complete component set.
65
64
 
66
- **Embedder** (the terminal lives inside your app's layout — a panel, a pane):
67
- concatenate the per-preset manifest matching your composition instead —
65
+ **Embedder** (the terminal lives inside your app's layout, as a panel or pane):
66
+ concatenate the per-preset manifest matching your composition instead:
68
67
  `css/MANIFEST.single`, `css/MANIFEST.touch`, or `css/MANIFEST.tabbed`. These
69
68
  contain ONLY root-scoped component styles: no page reset, no fonts, no
70
69
  document-level rules, nothing to quarantine. Pass `layout: "container"` so the
@@ -99,7 +98,7 @@ entry modules beside it:
99
98
  ```
100
99
 
101
100
  `createTerminal(root, opts?)` builds the entire terminal subtree (the kernel
102
- plus every feature's chrome) inside `root` itself — there is no element-id
101
+ plus every feature's chrome) inside `root` itself. There is no element-id
103
102
  contract for the host page to reproduce, and every style and CSS custom
104
103
  property is scoped to the `wt-root` class it stamps on your element (removed
105
104
  again by `destroy()`). Call it exactly once; the engine's
@@ -109,46 +108,43 @@ to copy and adapt.
109
108
 
110
109
  Four presets are provided; each is a plain feature-array factory, so you can
111
110
  spread and edit it. Import the barrel (`@cplieger/web-terminal-ui/presets`) for
112
- convenience, or a per-preset entry module for the minimal delivered import
113
- graph — `…/presets/single`, `…/presets/touch`, `…/presets/tabbed`,
114
- `…/presets/agent-tabbed` (the barrel statically reaches every feature; the
115
- touch entry, for example, never imports the tabs module). Individual features
111
+ convenience, or a per-preset entry module (`…/presets/single`, `…/presets/touch`,
112
+ `…/presets/tabbed`, `…/presets/agent-tabbed`) for the minimal delivered import
113
+ graph; the barrel statically reaches every feature, while the touch entry, for
114
+ example, never imports the tabs module. Individual features
116
115
  are importable from `…/features/<name>` (`clipboard`, `context-menu`,
117
116
  `scroll-to-bottom`, `predictive-echo`, `connection-banner`, `mobile-toolbar`,
118
117
  `tabs`, `activity-monitor`, `animations`) for hand-picked compositions:
119
118
 
120
- - `presetSingle()` — single-pane desktop UI (context menu, clipboard,
119
+ - `presetSingle()`: single-pane desktop UI (context menu, clipboard,
121
120
  scroll-to-bottom, predictive echo, connection banner).
122
- - `presetTouch()` — `presetSingle()` plus the mobile key toolbar.
123
- - `presetTabbed()` — the generic tabbed UI: `presetTouch()` plus tabs, the
124
- activity monitor, and animations. Each tab's title is OSC-first — it follows
121
+ - `presetTouch()`: `presetSingle()` plus the mobile key toolbar.
122
+ - `presetTabbed()`: the generic tabbed UI, `presetTouch()` plus tabs, the
123
+ activity monitor, and animations. Each tab's title is OSC-first: it follows
125
124
  the process window title (OSC 0/2) when the program sets one and keeps it
126
125
  updated, otherwise the last command submitted. The per-tab activity dot
127
126
  reveals itself only when a session reports OSC 9;4 progress, so a plain shell
128
127
  keeps clean, label-only tabs. Requires a server that speaks the session API
129
128
  (`/api/sessions` and `/ws?session=`), such as `web-terminal-server`.
130
- - `presetAgentTabbed()` — the same feature set as `presetTabbed()` (activity
131
- monitor included), tuned for an agent shell such as `web-terminal-kiro`: with
132
- `preferInputTitle`, each tab's label follows the latest submitted line
133
- (persisted server-side and recovered on reload) and the program's non-empty
134
- but useless OSC 0/2 title is ignored; and with `presumeReports`, the idle
135
- activity dot shows from tab creation instead of waiting for the session's
136
- first OSC 9;4 signal — every session is an agent, so there is nothing to
137
- prove. Its status dots (idle / working / done / needs-input) come from the
138
- same activity monitor, driven by the server's status SSE and its OSC-9
139
- classifier.
129
+ - `presetAgentTabbed()`: the same feature set as `presetTabbed()`, tuned for an
130
+ agent shell such as `web-terminal-kiro`. With `preferInputTitle`, each tab's
131
+ label follows the latest submitted line (persisted server-side and recovered
132
+ on reload) and the program's OSC 0/2 title is ignored. With `presumeReports`,
133
+ the idle activity dot shows from tab creation instead of waiting for the
134
+ session's first OSC 9;4 signal. Its status dots (idle / working / done /
135
+ needs-input) come from the same activity monitor.
140
136
 
141
137
  ### Options
142
138
 
143
- | Option | Default | Purpose |
144
- | -------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
145
- | `features` | _(none — bare kernel)_ | The feature list. Omitted or empty builds only the terminal (no chrome). Use a preset from `./presets` or a hand-picked array. |
146
- | `layout` | `"viewport"` | How the terminal claims space. `"viewport"`: the root becomes a fixed full-viewport box (the full-page product). `"container"`: the root fills your container element, which becomes the styling and positioning boundary (the embedded case). |
147
- | `wsPath` | `"/ws"` | WebSocket endpoint path the engine connects to. |
148
- | `fontReady` | `'14px "MonaspiceNe NFM"'` | CSS font shorthand awaited before the first resize, so the server is sized against the real web font's cell metrics rather than a fallback. |
149
- | `loading` | _(none)_ | A pre-JS loading overlay element (kept in your served HTML so it paints before this module loads); it is faded out and removed once the first frame renders. |
150
- | `onFatalError` | _(built-in recovery)_ | Called after a fatal feature setup failure has stopped the connection, released the terminal runtime, and cleared the root. It receives `{ phase, feature, cause }`. Return `true` only after rendering replacement recovery UI into the root; otherwise the kernel shows its Reload page surface. |
151
- | `theme` | _(none)_ | Theme overrides (CSS custom properties on the terminal root): `--accent`, `--tab-bg`, `--tab-hover-bg`, `--tab-active-bg`, `--tab-active-fg`, `--tab-active-border`, plus the activity-dot palette `--status-working`, `--status-done`, `--status-input` (the working ripple and the input ring derive from their own tokens). The library ships neutral defaults. |
139
+ | Option | Default | Purpose |
140
+ | -------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
141
+ | `features` | _(none; bare kernel)_ | The feature list. Omitted or empty builds only the terminal (no chrome). Use a preset from `./presets` or a hand-picked array. |
142
+ | `layout` | `"viewport"` | How the terminal claims space. `"viewport"`: the root becomes a fixed full-viewport box (the full-page product). `"container"`: the root fills your container element, which becomes the styling and positioning boundary (the embedded case). |
143
+ | `wsPath` | `"/ws"` | WebSocket endpoint path the engine connects to. |
144
+ | `fontReady` | `'14px "MonaspiceNe NFM"'` | CSS font shorthand awaited before the first resize, so the server is sized against the real web font's cell metrics rather than a fallback. |
145
+ | `loading` | _(none)_ | A pre-JS loading overlay element (kept in your served HTML so it paints before this module loads); it is faded out and removed once the first frame renders. |
146
+ | `onFatalError` | _(built-in recovery)_ | Called with a `TerminalStartupFailure` after a fatal startup failure, in either phase (`feature-setup` or `kernel-init`); behavior below. |
147
+ | `theme` | _(none)_ | Theme overrides (CSS custom properties on the terminal root): `--accent`, `--tab-bg`, `--tab-hover-bg`, `--tab-active-bg`, `--tab-active-fg`, `--tab-active-border`, plus the activity-dot palette `--status-working`, `--status-done`, `--status-input`. The library ships neutral defaults. |
152
148
 
153
149
  `createTerminal()` returns a handle: `focus()` re-focuses the terminal input
154
150
  (and opens the soft keyboard on touch); `send(bytes)` sends bytes to the active
@@ -158,11 +154,19 @@ screen without injecting keystrokes (send a redraw keystroke yourself if you
158
154
  want one, for example Ctrl+L); and `destroy()` tears every feature down and
159
155
  releases the kernel.
160
156
 
161
- If a feature's setup throws or rejects, the kernel stops the connection, tears
162
- down every completed feature and core listener, clears the broken subtree, and
163
- shows a reload surface. The surface is modal when the terminal owns the
164
- viewport and non-modal when it fills an embedded container. `onFatalError`
165
- receives the failure after cleanup; return `true` only when the host has rendered
157
+ Startup can fail in two phases, and both end at the same recovery surface. If a
158
+ feature's setup throws or rejects (`phase: "feature-setup"`), the kernel stops the
159
+ connection, tears down every completed feature and core listener, clears the
160
+ broken subtree, and shows a reload surface. If `createTerminal` itself throws
161
+ (`phase: "kernel-init"` — an invalid feature list, a DOM invariant), it shows the
162
+ same surface, lowers your loading overlay so the surface is visible, and then
163
+ rethrows, so a caller with its own error handling still sees the error. Either
164
+ way the surface is modal when the terminal owns the viewport and non-modal when
165
+ it fills an embedded container.
166
+
167
+ `onFatalError` receives the failure after cleanup. Discriminate on `phase`:
168
+ `feature-setup` names the offending `feature`, `kernel-init` does not, because
169
+ feature composition never began. Return `true` only when the host has rendered
166
170
  replacement recovery UI into the terminal root.
167
171
 
168
172
  ## What ships
@@ -178,10 +182,10 @@ replacement recovery UI into the terminal root.
178
182
 
179
183
  The web-terminal family:
180
184
 
181
- - [`web-terminal-engine`](https://github.com/cplieger/web-terminal-engine) — the
185
+ - [`web-terminal-engine`](https://github.com/cplieger/web-terminal-engine): the
182
186
  Go session engine + TypeScript browser renderer this UI is built on (peer
183
187
  dependency).
184
- - [`web-terminal-server`](https://github.com/cplieger/web-terminal-server) — a
188
+ - [`web-terminal-server`](https://github.com/cplieger/web-terminal-server): a
185
189
  ready-to-run container that serves this UI over HTTP + WebSocket for any
186
190
  command.
187
191
 
@@ -24,10 +24,15 @@
24
24
  scrollbar-gutter: stable;
25
25
  /* The scroll controller (vterm scroll.ts) is the single owner of
26
26
  scrollTop: it pins to the bottom only while the user is following,
27
- and does nothing while they are scrolled up reading. New output
28
- always appends at the tail (the absolute-index model never inserts
29
- above the viewport), so native scroll anchoring is safe to leave on
30
- and keeps the read position steady on the rare insert-above. */
27
+ and does nothing while they are scrolled up reading.
28
+ Content ABOVE the reading position DOES change: rows are evicted
29
+ from the top of history at the retention cap, and the trim marker
30
+ is inserted there. Native scroll anchoring compensates for that on
31
+ Chrome and Firefox, so it stays enabled here — but WebKit has never
32
+ implemented it, which is why the renderer also corrects the drift
33
+ itself (render.ts captureReadAnchor / restoreReadAnchor). That
34
+ correction measures residual drift, so it is a no-op wherever this
35
+ declaration already did the job. */
31
36
  overflow-anchor: auto;
32
37
  /* .term is the scroll container, so the touch-action intersection for a
33
38
  touch on the output stops here (the html/body pan-x pan-y does not reach
package/css/30-tabs.css CHANGED
@@ -86,6 +86,12 @@
86
86
  cursor: pointer;
87
87
  white-space: nowrap;
88
88
  user-select: none;
89
+ /* Kills the 300ms double-tap-to-zoom delay, which is what makes a double-TAP
90
+ usable as the touch rename entry: on iPadOS a long-press on this chip starts
91
+ a reorder drag (it is draggable), so the context menu is unreachable by touch
92
+ and the double-tap is the only path in. Also removes the tap delay for the
93
+ ordinary switch tap. */
94
+ touch-action: manipulation;
89
95
  }
90
96
 
91
97
  /* Inactive tabs read as outlined chips with a subtle dark fill (so adjacent tabs
@@ -104,6 +110,16 @@
104
110
  color: var(--tab-active-fg);
105
111
  }
106
112
 
113
+ /* Press (mousedown / touch-hold): one rung past the hover lift — same recipe,
114
+ deeper mix — so hover -> press reads as a single dimension. After the hover
115
+ rule so it wins while both apply. Scoped like hover: clicking the active tab
116
+ selects nothing (it is already active), so it presses like the static surface
117
+ it is. */
118
+ :where(.wt-root) .wt-tab:not(.wt-tab-active):active {
119
+ background: color-mix(in oklch, var(--tab-bg), var(--text) 25%);
120
+ color: var(--tab-active-fg);
121
+ }
122
+
107
123
  /* The active tab reads clearly above the inactive chips: a stronger fill plus an
108
124
  accent-coloured edge (inactive chips carry a faint white --border; the active
109
125
  one gets the accent equivalent). Themeable via --accent (web-terminal-kiro -> purple). */
@@ -166,6 +182,13 @@
166
182
  color: var(--text);
167
183
  }
168
184
 
185
+ /* Press: deepen the hover box (--text-active is a 15% --text wash; press doubles
186
+ it to 30%) so mousedown visibly registers before the tab disappears. */
187
+ :where(.wt-root) .wt-tab-close:active {
188
+ background: color-mix(in oklch, var(--text) 30%, transparent);
189
+ color: var(--text);
190
+ }
191
+
169
192
  /* On an inactive tab the close appears only on hover, kept in the layout via
170
193
  visibility so the tab width never jumps; on the active tab it is always shown. */
171
194
  :where(.wt-root) .wt-tab:not(.wt-tab-active) .wt-tab-close {
@@ -221,6 +244,13 @@
221
244
  background: var(--tab-active-bg);
222
245
  }
223
246
 
247
+ /* Press: brighten the hover fill with the active-tab fg (the
248
+ .wt-switcher-current press recipe — a lighter press over an already-filled
249
+ control), keeping the accent glyph. */
250
+ :where(.wt-root) .wt-tab-new:active {
251
+ background: color-mix(in oklch, var(--tab-active-bg), var(--tab-active-fg) 12%);
252
+ }
253
+
224
254
  /* The +/x icons are inline SVG so they center exactly in their flex-centered
225
255
  button and stay symmetric regardless of the UI font (fixes the off-center
226
256
  glyph and the lopsided "+"). The + is a touch larger and bolder than the x. */
@@ -270,6 +300,20 @@
270
300
  margin-left: auto;
271
301
  }
272
302
 
303
+ /* Hover wash, matching every other bar control (.wt-tab-new, the switcher's
304
+ buttons) — without it this was the one control in the chrome that gave no
305
+ pointer feedback at all. Gated to real hover pointers because the button only
306
+ SHOWS on a coarse pointer (the iPad strip): a plain touchscreen has no
307
+ pointer-leave, so :hover would stick after a tap and the wash would linger;
308
+ the feedback there is :active, from .wt-btn. Before .wt-active / .wt-armed so
309
+ those toggled fills win on a hovered button (equal specificity, source order
310
+ decides). */
311
+ @media (hover: hover) {
312
+ :where(.wt-root) .wt-tab-kb:hover {
313
+ background: var(--tab-hover-bg);
314
+ }
315
+ }
316
+
273
317
  /* Grid open: filled so the toggled state reads at a glance (mirrors the
274
318
  switcher keyboard button). */
275
319
  :where(.wt-root) .wt-tab-kb.wt-active {
@@ -298,6 +342,66 @@
298
342
  opacity: 0.5;
299
343
  }
300
344
 
345
+ /* ===== Inline rename (tabs feature) =====
346
+ The edit surface is the LABEL's own box, so the width the user types into is
347
+ the width the label will have. The field takes the label's flex slot exactly;
348
+ the label is [hidden] and the close button is hidden for the duration (a 44px
349
+ destructive target next to a live text field is a mis-click waiting to happen,
350
+ and the freed width goes to the field). */
351
+ :where(.wt-root) .wt-tab-rename {
352
+ flex: 1 1 auto;
353
+ min-width: 0;
354
+ background: transparent;
355
+ border: none;
356
+ padding: 0;
357
+ color: var(--tab-active-fg);
358
+ font: inherit;
359
+ /* .wt-tab sets user-select: none, and WebKit CASCADES that into descendants
360
+ even though the property is non-inherited per spec (the same deviation the
361
+ touch model documents for .term-output). Without this, caret placement and
362
+ text selection break inside the field in Safari — i.e. on the iPad. */
363
+ user-select: text;
364
+ /* The chip is cursor: pointer; a text field must not inherit that. */
365
+ cursor: text;
366
+ }
367
+
368
+ /* The family focus ring is scoped to button:focus-visible in 10-primitives.css,
369
+ so the field needs its own. It is focused programmatically the instant it
370
+ appears, so :focus (not :focus-visible) is correct here — the ring IS the
371
+ affordance that says "type here". */
372
+ :where(.wt-root) .wt-tab-rename:focus {
373
+ outline: 2px solid var(--accent);
374
+ outline-offset: 1px;
375
+ }
376
+
377
+ /* While editing, hide the close button: a 44px destructive target adjacent to a
378
+ live text field is a mis-click waiting to happen, and the freed width goes to
379
+ the field.
380
+
381
+ The chip is NOT widened. An earlier draft gave the edited chip its full 300px
382
+ so a crowded strip (tabs shrink toward a 100px floor) would not offer a 100px
383
+ edit box — but nothing in this design system transitions `width` on a tab, so
384
+ that was an instant ~200px shove of every neighbouring tab plus a scroll jump,
385
+ at the exact moment the user is trying to read what they are typing. A cramped
386
+ field on a crowded strip is the lesser problem. */
387
+ :where(.wt-root) .wt-tab-editing .wt-tab-close {
388
+ display: none;
389
+ }
390
+
391
+ /* The drag preview clone (setDragGhost): a full-opacity copy of the chip parked
392
+ under .wt-root — OUTSIDE the bar's backdrop-filter, which is what stops WebKit
393
+ from rendering the automatic drag image (webkit.org/b/22787) — and laid exactly
394
+ over the real chip via inline left/top/width/height. It exists for less than a
395
+ frame (the browser snapshots it, then a rAF removes it), so it must not be
396
+ hidden (an invisible element makes no snapshot) and must not intercept the
397
+ pointer. z-index clears the bar so the copy, not the dimmed original, is what
398
+ would show if it ever painted. */
399
+ :where(.wt-root) .wt-tab-ghost {
400
+ position: absolute;
401
+ z-index: 16;
402
+ pointer-events: none;
403
+ }
404
+
301
405
  /* Tab context menu (tabs feature, desktop right-click): Close / Close others /
302
406
  Close to the right / Close to the left / Close all. Replaces the old bar
303
407
  "Close all" button. Mirrors .wt-ctx-menu (a fixed surface popover, clamped
@@ -340,11 +444,27 @@
340
444
  background: var(--text-active);
341
445
  }
342
446
 
447
+ /* Press: deepen the hover wash to 30% --text (the .wt-tab-close press rung) so
448
+ the click registers before the menu closes. */
449
+ :where(.wt-root) .wt-tab-menu button:enabled:active {
450
+ background: color-mix(in oklch, var(--text) 30%, transparent);
451
+ }
452
+
343
453
  :where(.wt-root) .wt-tab-menu button:disabled {
344
454
  color: color-mix(in oklch, var(--text) 35%, transparent);
345
455
  cursor: default;
346
456
  }
347
457
 
458
+ /* Group separator: the naming pair (Rename / Use automatic name) sits above the
459
+ move and close groups, so a hairline keeps the non-destructive actions visually
460
+ apart from the destructive ones. The menu is otherwise a flat flex column of
461
+ buttons with no separator vocabulary. */
462
+ :where(.wt-root) .wt-tab-menu-sep {
463
+ height: 1px;
464
+ margin: var(--sp-1) 0;
465
+ background: var(--border);
466
+ }
467
+
348
468
  /* Pull the terminal surface up off the docked BOTTOM tab strip so it does not
349
469
  overlap the last rows (a mirror of the old top offset). The tabs feature
350
470
  marks the surface (.wt-with-tabbar) and keeps the measured strip height in
@@ -208,6 +208,15 @@
208
208
  overflow: hidden;
209
209
  }
210
210
 
211
+ /* Hover wash like the switch and "+" buttons beside it; gated to real hover
212
+ pointers for the same reason they are (a sticky :hover after a tap). Before
213
+ .wt-active / .wt-armed so those fills win on a hovered button. */
214
+ @media (hover: hover) {
215
+ :where(.wt-root) .wt-switcher-kb:hover {
216
+ background: var(--tab-hover-bg);
217
+ }
218
+ }
219
+
211
220
  :where(.wt-root) .wt-switcher-kb[hidden] {
212
221
  display: none;
213
222
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "4.5.2",
3
+ "version": "4.7.0",
4
4
  "type": "module",
5
5
  "description": "Reference touch-first browser UI for @cplieger/web-terminal-engine: a composable kernel + feature modules (tabs, activity monitor, mobile toolbar, context menu, IME, predictive echo, viewport handling) and CSS. One createTerminal() call.",
6
6
  "license": "GPL-3.0-or-later",
@@ -38,7 +38,10 @@
38
38
  "verify": "bash scripts/verify.sh"
39
39
  },
40
40
  "peerDependencies": {
41
- "@cplieger/web-terminal-engine": "^3.0.0"
41
+ "@cplieger/web-terminal-engine": "^3.2.0"
42
+ },
43
+ "overrides": {
44
+ "qs": "^6.15.2"
42
45
  },
43
46
  "devDependencies": {
44
47
  "@eslint/js": "10.0.1",
@@ -50,9 +53,9 @@
50
53
  "@vitest/coverage-v8": "4.1.10",
51
54
  "eslint": "10.7.0",
52
55
  "fast-check": "4.9.0",
53
- "happy-dom": "20.11.0",
56
+ "happy-dom": "20.11.1",
54
57
  "html-validate": "11.5.6",
55
- "knip": "6.27.0",
58
+ "knip": "6.29.0",
56
59
  "prettier": "3.9.6",
57
60
  "stylelint": "17.14.1",
58
61
  "stylelint-config-standard": "40.0.0",