@cplieger/web-terminal-ui 4.5.0 → 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 +59 -40
- 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
|
@@ -59,14 +59,21 @@
|
|
|
59
59
|
display: inline-flex;
|
|
60
60
|
align-items: center;
|
|
61
61
|
gap: var(--sp-1);
|
|
62
|
-
/* Browser-style tabs:
|
|
63
|
-
|
|
64
|
-
(.wt-tab-scroll has overflow-x: auto).
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
/* Browser-style tabs: every tab STARTS at the full 300px, shrinking toward a
|
|
63
|
+
100px floor only once the strip is full; past the floor the list scrolls
|
|
64
|
+
horizontally (.wt-tab-scroll has overflow-x: auto). A definite width (not
|
|
65
|
+
flex-basis: 300px) is load-bearing: the scroller shrink-wraps its content,
|
|
66
|
+
and in intrinsic sizing a non-growable item's flex BASIS caps at its
|
|
67
|
+
content width (min(label, 300px)) — short-labeled tabs then rendered at
|
|
68
|
+
content width and every added tab dragged all widths toward the mean. A
|
|
69
|
+
definite width contributes the full 300px regardless of the label, while
|
|
70
|
+
flex-shrink still compresses it once the bar runs out of room. flex-grow
|
|
71
|
+
is 0, so 300px is also a hard cap — every tab is the same size whatever
|
|
72
|
+
its title; the label truncates instead. box-sizing keeps the width a true
|
|
67
73
|
total. */
|
|
68
74
|
box-sizing: border-box;
|
|
69
|
-
|
|
75
|
+
width: 300px;
|
|
76
|
+
flex: 0 1 auto;
|
|
70
77
|
min-width: 100px;
|
|
71
78
|
padding: var(--sp-2);
|
|
72
79
|
/* A transparent border is reserved on every tab so toggling the inactive
|
|
@@ -114,6 +121,17 @@
|
|
|
114
121
|
overflow: hidden;
|
|
115
122
|
white-space: nowrap;
|
|
116
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;
|
|
117
135
|
}
|
|
118
136
|
|
|
119
137
|
/* A little breathing room after the activity dot, before the label. */
|
|
@@ -394,15 +412,14 @@
|
|
|
394
412
|
border: 1.5px solid color-mix(in oklch, var(--text) 45%, transparent);
|
|
395
413
|
}
|
|
396
414
|
|
|
397
|
-
/* Agent working:
|
|
398
|
-
"
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
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
|
|
406
423
|
single-layer pulse faded disc + ring together (opacity on the element),
|
|
407
424
|
which read as a blink and coupled the layers — an element's opacity always
|
|
408
425
|
multiplies onto its pseudos, so true independence requires animating disc
|
|
@@ -413,16 +430,15 @@
|
|
|
413
430
|
position: relative;
|
|
414
431
|
background: var(--status-working);
|
|
415
432
|
border-color: var(--status-working);
|
|
416
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, var(--status-working) 40%, transparent);
|
|
417
433
|
animation: wt-working-glow 1.2s ease-in-out infinite;
|
|
418
434
|
}
|
|
419
435
|
|
|
420
|
-
/* The ripple layer: at scale(1) its 2px border
|
|
421
|
-
9->13px band
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
|
426
442
|
position: relative.) */
|
|
427
443
|
:where(.wt-root) .wt-status-dot[data-status="working"]::after {
|
|
428
444
|
content: "";
|
|
@@ -442,18 +458,19 @@
|
|
|
442
458
|
border-color: var(--status-done);
|
|
443
459
|
}
|
|
444
460
|
|
|
445
|
-
/* User action required (permission / approval): working
|
|
446
|
-
solid dot + 2px ring
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
agent's turn is mid-flight but
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
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. */
|
|
453
470
|
:where(.wt-root) .wt-status-dot[data-status="input"] {
|
|
454
471
|
background: var(--status-input);
|
|
455
472
|
border-color: var(--status-input);
|
|
456
|
-
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);
|
|
457
474
|
}
|
|
458
475
|
|
|
459
476
|
/* Exited: a dim hollow dot, static; distinct from idle by its lower opacity. */
|
|
@@ -483,13 +500,14 @@
|
|
|
483
500
|
}
|
|
484
501
|
}
|
|
485
502
|
|
|
486
|
-
/* The ripple wave: one emission per cycle, launching off the
|
|
487
|
-
dissolving by ~2.1x.
|
|
488
|
-
|
|
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. */
|
|
489
507
|
@keyframes wt-working-ripple {
|
|
490
508
|
0% {
|
|
491
509
|
transform: scale(1);
|
|
492
|
-
opacity: 0.
|
|
510
|
+
opacity: 0.3;
|
|
493
511
|
}
|
|
494
512
|
|
|
495
513
|
100% {
|
|
@@ -502,13 +520,14 @@
|
|
|
502
520
|
@media (prefers-reduced-motion: reduce) {
|
|
503
521
|
:where(.wt-root) .wt-status-dot[data-status="working"] {
|
|
504
522
|
animation: none;
|
|
505
|
-
/* Motion is what separates working from input's frozen
|
|
506
|
-
|
|
507
|
-
input (solid ringed disc) and done (bare disc) by
|
|
508
|
-
(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. */
|
|
509
528
|
box-shadow:
|
|
510
529
|
inset 0 0 0 2px var(--bg),
|
|
511
|
-
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);
|
|
512
531
|
}
|
|
513
532
|
|
|
514
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",
|