@cplieger/web-terminal-ui 4.5.1 → 4.5.2
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 +15 -10
- package/css/30-tabs.css +46 -34
- package/css/31-switcher.css +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -140,15 +140,15 @@ are importable from `…/features/<name>` (`clipboard`, `context-menu`,
|
|
|
140
140
|
|
|
141
141
|
### Options
|
|
142
142
|
|
|
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 and input
|
|
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. |
|
|
152
152
|
|
|
153
153
|
`createTerminal()` returns a handle: `focus()` re-focuses the terminal input
|
|
154
154
|
(and opens the soft keyboard on touch); `send(bytes)` sends bytes to the active
|
|
@@ -190,6 +190,11 @@ Consumers that ship this UI:
|
|
|
190
190
|
- [`vibekit`](https://github.com/cplieger/vibekit)
|
|
191
191
|
- [`web-terminal-kiro`](https://github.com/cplieger/web-terminal-kiro)
|
|
192
192
|
|
|
193
|
+
## Contributing
|
|
194
|
+
|
|
195
|
+
Issues and PRs are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the
|
|
196
|
+
conventions and how to run the checks locally.
|
|
197
|
+
|
|
193
198
|
## Disclaimer
|
|
194
199
|
|
|
195
200
|
This project is built with care and follows security best practices, but it is intended for personal / self-hosted use. No guarantees of fitness for production environments. Use at your own risk.
|
|
@@ -198,4 +203,4 @@ This project was built with AI-assisted tooling using [Claude](https://claude.co
|
|
|
198
203
|
|
|
199
204
|
## License
|
|
200
205
|
|
|
201
|
-
GPL-3.0-or-later. See
|
|
206
|
+
GPL-3.0-or-later. See [LICENSE](LICENSE).
|
package/css/30-tabs.css
CHANGED
|
@@ -121,6 +121,17 @@
|
|
|
121
121
|
overflow: hidden;
|
|
122
122
|
white-space: nowrap;
|
|
123
123
|
text-overflow: ellipsis;
|
|
124
|
+
/* Center the INK, not the line box: UI fonts carry more ascent than descent
|
|
125
|
+
(Noto Sans at 13px: 14px over / 4px under the baseline), so the flex-
|
|
126
|
+
centered line box leaves the visible text ~1.5px low — reading as
|
|
127
|
+
baseline-aligned with the activity dot's bottom instead of its center.
|
|
128
|
+
Trimming the box to the cap-height/alphabetic band makes flexbox center
|
|
129
|
+
the glyphs themselves, on every platform font. Descender ink still paints
|
|
130
|
+
(verified: ink overflow survives the overflow: hidden ellipsis clip).
|
|
131
|
+
Non-supporting browsers simply keep the untrimmed box. Shared by all
|
|
132
|
+
three chip labels (this one + the two switcher labels in
|
|
133
|
+
31-switcher.css). */
|
|
134
|
+
text-box: trim-both cap alphabetic;
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
/* A little breathing room after the activity dot, before the label. */
|
|
@@ -401,15 +412,14 @@
|
|
|
401
412
|
border: 1.5px solid color-mix(in oklch, var(--text) 45%, transparent);
|
|
402
413
|
}
|
|
403
414
|
|
|
404
|
-
/* Agent working:
|
|
405
|
-
"
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
working from input; the ring separates both from done's bare disc. The old
|
|
415
|
+
/* Agent working: a solid disc with two animated cues (the status-badge
|
|
416
|
+
"processing" idiom — Ant Design's processing badge, the classic CSS
|
|
417
|
+
live-indicator ripple): the disc keeps full opacity and gently GLOWS (its
|
|
418
|
+
colors brighten mid-beat), while a ::after ring RIPPLES outward off the
|
|
419
|
+
disc's edge and dissolves, re-emitted each cycle. No static ring at rest —
|
|
420
|
+
the disc + live ripple ARE the working read; input freezes that exact ping
|
|
421
|
+
at its birth frame (see input below), so motion separates working from
|
|
422
|
+
input, and the ring separates both from done's bare disc. The old
|
|
413
423
|
single-layer pulse faded disc + ring together (opacity on the element),
|
|
414
424
|
which read as a blink and coupled the layers — an element's opacity always
|
|
415
425
|
multiplies onto its pseudos, so true independence requires animating disc
|
|
@@ -420,16 +430,15 @@
|
|
|
420
430
|
position: relative;
|
|
421
431
|
background: var(--status-working);
|
|
422
432
|
border-color: var(--status-working);
|
|
423
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, var(--status-working) 40%, transparent);
|
|
424
433
|
animation: wt-working-glow 1.2s ease-in-out infinite;
|
|
425
434
|
}
|
|
426
435
|
|
|
427
|
-
/* The ripple layer: at scale(1) its 2px border
|
|
428
|
-
9->13px band
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
436
|
+
/* The ripple layer: at scale(1) its 2px border hugs the disc's edge (inset
|
|
437
|
+
-2px, the 9->13px band), then the wave detaches, expanding and fading.
|
|
438
|
+
transform + opacity only, so it composites without repaints. It exists only
|
|
439
|
+
in the working state — no stale layer to pin when a dot changes status.
|
|
440
|
+
(The switcher notification dot never shows working — input/done only — so
|
|
441
|
+
its position: absolute placement never meets this rule's
|
|
433
442
|
position: relative.) */
|
|
434
443
|
:where(.wt-root) .wt-status-dot[data-status="working"]::after {
|
|
435
444
|
content: "";
|
|
@@ -449,18 +458,19 @@
|
|
|
449
458
|
border-color: var(--status-done);
|
|
450
459
|
}
|
|
451
460
|
|
|
452
|
-
/* User action required (permission / approval): working
|
|
453
|
-
solid dot + 2px ring
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
agent's turn is mid-flight but
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
until you act on or switch into
|
|
461
|
+
/* User action required (permission / approval): the working ping frozen at
|
|
462
|
+
its birth frame — solid dot + the same 2px ring at the ripple's 30% birth
|
|
463
|
+
opacity, motionless (--status-input; the default orange is shifted away
|
|
464
|
+
from the old amber so it separates from both default-lime working and green
|
|
465
|
+
done). The stillness is the message: the agent's turn is mid-flight but
|
|
466
|
+
blocked on you. The ring keeps it distinct from done's bare disc without
|
|
467
|
+
leaning on hue; it derives from the same token, so a theme override
|
|
468
|
+
recolors dot + ring together. It persists until you act on or switch into
|
|
469
|
+
the tab. */
|
|
460
470
|
:where(.wt-root) .wt-status-dot[data-status="input"] {
|
|
461
471
|
background: var(--status-input);
|
|
462
472
|
border-color: var(--status-input);
|
|
463
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, var(--status-input)
|
|
473
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--status-input) 30%, transparent);
|
|
464
474
|
}
|
|
465
475
|
|
|
466
476
|
/* Exited: a dim hollow dot, static; distinct from idle by its lower opacity. */
|
|
@@ -490,13 +500,14 @@
|
|
|
490
500
|
}
|
|
491
501
|
}
|
|
492
502
|
|
|
493
|
-
/* The ripple wave: one emission per cycle, launching off the
|
|
494
|
-
dissolving by ~2.1x.
|
|
495
|
-
|
|
503
|
+
/* The ripple wave: one emission per cycle, launching off the disc's edge and
|
|
504
|
+
dissolving by ~2.1x. The 0.3 birth opacity matches the input ring's frozen
|
|
505
|
+
weight, so a live ping and a stuck one read as the same object — subtle
|
|
506
|
+
against the bar now that no static ring doubles it at birth. */
|
|
496
507
|
@keyframes wt-working-ripple {
|
|
497
508
|
0% {
|
|
498
509
|
transform: scale(1);
|
|
499
|
-
opacity: 0.
|
|
510
|
+
opacity: 0.3;
|
|
500
511
|
}
|
|
501
512
|
|
|
502
513
|
100% {
|
|
@@ -509,13 +520,14 @@
|
|
|
509
520
|
@media (prefers-reduced-motion: reduce) {
|
|
510
521
|
:where(.wt-root) .wt-status-dot[data-status="working"] {
|
|
511
522
|
animation: none;
|
|
512
|
-
/* Motion is what separates working from input's frozen
|
|
513
|
-
|
|
514
|
-
input (solid ringed disc) and done (bare disc) by
|
|
515
|
-
(WCAG 1.4.1). The
|
|
523
|
+
/* Motion is what separates working from input's frozen ping; with it
|
|
524
|
+
removed, punch an inner hole (dot -> donut) and add a static ring so
|
|
525
|
+
working differs from input (solid ringed disc) and done (bare disc) by
|
|
526
|
+
shape, not hue alone (WCAG 1.4.1). The ring carries the same 30%
|
|
527
|
+
weight as input's. */
|
|
516
528
|
box-shadow:
|
|
517
529
|
inset 0 0 0 2px var(--bg),
|
|
518
|
-
0 0 0 2px color-mix(in srgb, var(--status-working)
|
|
530
|
+
0 0 0 2px color-mix(in srgb, var(--status-working) 30%, transparent);
|
|
519
531
|
}
|
|
520
532
|
|
|
521
533
|
/* The ripple layer disappears entirely (not just paused mid-wave). */
|
package/css/31-switcher.css
CHANGED
|
@@ -178,6 +178,8 @@
|
|
|
178
178
|
text-overflow: ellipsis;
|
|
179
179
|
white-space: nowrap;
|
|
180
180
|
text-align: left;
|
|
181
|
+
/* Ink-centering trim, same as .wt-tab-label (see 30-tabs.css). */
|
|
182
|
+
text-box: trim-both cap alphabetic;
|
|
181
183
|
}
|
|
182
184
|
|
|
183
185
|
/* Keyboard button: hidden until a keyboardToggle is wired; filled while the key
|
|
@@ -418,6 +420,8 @@
|
|
|
418
420
|
overflow: hidden;
|
|
419
421
|
text-overflow: ellipsis;
|
|
420
422
|
white-space: nowrap;
|
|
423
|
+
/* Ink-centering trim, same as .wt-tab-label (see 30-tabs.css). */
|
|
424
|
+
text-box: trim-both cap alphabetic;
|
|
421
425
|
}
|
|
422
426
|
|
|
423
427
|
/* The close sits flush at the row's right edge (no gap), vertically centered. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.2",
|
|
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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"happy-dom": "20.11.0",
|
|
54
54
|
"html-validate": "11.5.6",
|
|
55
55
|
"knip": "6.27.0",
|
|
56
|
-
"prettier": "3.9.
|
|
56
|
+
"prettier": "3.9.6",
|
|
57
57
|
"stylelint": "17.14.1",
|
|
58
58
|
"stylelint-config-standard": "40.0.0",
|
|
59
59
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|