@cplieger/web-terminal-ui 6.0.2 → 6.1.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 +47 -2
- package/package.json +1 -1
- package/src/kernel/kernel.ts +29 -1
- package/src/kernel/types.ts +38 -0
package/README.md
CHANGED
|
@@ -174,6 +174,7 @@ are importable from `…/features/<name>` (`clipboard`, `context-menu`,
|
|
|
174
174
|
| `persistScrollback` | _(off)_ | Persist each session's scrollback across a page discard, through storage YOU supply. A page that is discarded and reloaded otherwise resumes holding nothing, so it asks the server for everything and refills its whole buffer over the wire — the normal case on iOS, where Safari evicts backgrounded tabs under memory pressure and returning to one re-runs the page. With a snapshot restored, the resume asks only for what was printed while the tab was gone. It helps the FRESH-LOAD case only: a warm reconnect and an in-page tab switch already replay nothing. Off by default because `localStorage` is a shared, origin-wide, quota-limited resource and this library is embedded in applications that keep their own state there — an application decides durability for its own users, a library does not decide it for an embedder. Applies to a single terminal and to every tab alike. See below. |
|
|
175
175
|
| `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. |
|
|
176
176
|
| `onFatalError` | _(built-in recovery)_ | Called with a `TerminalStartupFailure` after a fatal startup failure, in either phase (`feature-setup` or `kernel-init`); behavior below. |
|
|
177
|
+
| `onSessionEnded` | _(none)_ | Called when the active session's process has ended and nothing is retrying — the fact the banner renders as "Session ended". Wire it only if you can act on it: the recovery move is `handle.reattach()`, and whether that helps depends on what your endpoint does on the next connect (see "When a session ends"). Observation only; everything the kernel does about the end happens first and happens regardless, and a handler that throws is logged rather than allowed to take the banner down with it. |
|
|
177
178
|
| `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`, `--status-warning`, `--status-failed`. The library ships neutral defaults. If you retheme the animated trio (`--status-working` / `--status-warning` / `--status-failed`), keep their LIGHTNESS spread: they differ only in hue, so equal-lightness replacements collapse into one another in greyscale and under deuteranopia. |
|
|
178
179
|
|
|
179
180
|
`createTerminal()` returns a handle: `focus()` re-focuses the terminal input
|
|
@@ -181,8 +182,9 @@ are importable from `…/features/<name>` (`clipboard`, `context-menu`,
|
|
|
181
182
|
session through the kernel's sanitizing input funnel (the supported host path
|
|
182
183
|
for a "type this command" affordance); `reset()` drops the local scrollback and
|
|
183
184
|
screen without injecting keystrokes (send a redraw keystroke yourself if you
|
|
184
|
-
want one, for example Ctrl+L);
|
|
185
|
-
|
|
185
|
+
want one, for example Ctrl+L); `reattach()` attaches again to whatever the server
|
|
186
|
+
serves now, for a session that has ended (see "When a session ends"); and
|
|
187
|
+
`destroy()` tears every feature down and releases the kernel.
|
|
186
188
|
|
|
187
189
|
### Persisting scrollback across a discard
|
|
188
190
|
|
|
@@ -387,6 +389,49 @@ the package root and at `@cplieger/web-terminal-ui/startup-copy`, which imports
|
|
|
387
389
|
nothing and touches no DOM so a build script can read it) and substitute the
|
|
388
390
|
strings into your HTML at build time, rather than restating them by hand.
|
|
389
391
|
|
|
392
|
+
### When a session ends
|
|
393
|
+
|
|
394
|
+
A session whose process exits is over: the engine closes with its definitive
|
|
395
|
+
process-exited code, the banner reads "Session ended", and no reconnect is
|
|
396
|
+
attempted. That refusal is deliberate. On a server that hands each session its own
|
|
397
|
+
endpoint, reconnecting could only collect the same close again, which is an
|
|
398
|
+
endless "Reconnecting…" flap over a screen that will never change.
|
|
399
|
+
|
|
400
|
+
Some hosts are the other shape. If your endpoint hands out a NEW session on the
|
|
401
|
+
next connect — one shared PTY the server replaces once it is spent, an endpoint
|
|
402
|
+
that spawns on attach — then the connection the engine declines to make is exactly
|
|
403
|
+
what would produce a working terminal, and you are the only party who knows that.
|
|
404
|
+
So the library gives you the fact and the move, and keeps the policy out of it:
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
const term = createTerminal("#terminal", {
|
|
408
|
+
features: presetTouch,
|
|
409
|
+
wsPath: "/api/shell/ws",
|
|
410
|
+
onSessionEnded: () => {
|
|
411
|
+
// Your endpoint's own restart call goes here if it needs one, then:
|
|
412
|
+
term.reattach();
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
`reattach()` drops the local scrollback and screen, moves the connection state
|
|
418
|
+
off `ended`, and reconnects. The order is the reason it belongs here: the local
|
|
419
|
+
buffer holds the dead session's content and a line index the replacement has
|
|
420
|
+
never reached, so a bare reconnect would resume against the wrong index space, and
|
|
421
|
+
a bare reset would leave "Session ended" standing over a screen it just blanked.
|
|
422
|
+
|
|
423
|
+
It reconnects and nothing more. It starts no process and calls no API of yours, so
|
|
424
|
+
if your server needs to be told to make a new session, tell it first and reattach
|
|
425
|
+
after. Do not call it on a live session: that is a needless full replay, and it
|
|
426
|
+
drops history the server may have evicted since.
|
|
427
|
+
|
|
428
|
+
**Bound your own retries.** A shell that dies as fast as it is spawned will end
|
|
429
|
+
again the moment it is replaced, so a handler that reattaches unconditionally is a
|
|
430
|
+
hot loop. Count consecutive ends, back off, and stop after a few — the banner is
|
|
431
|
+
the honest outcome when a session cannot stay up. The library will not do this for
|
|
432
|
+
you, because how many attempts is worth making, and whether any are, is a fact
|
|
433
|
+
about your server rather than about this terminal.
|
|
434
|
+
|
|
390
435
|
## What ships
|
|
391
436
|
|
|
392
437
|
| Path | Purpose |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "6.0
|
|
3
|
+
"version": "6.1.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": "MPL-2.0",
|
package/src/kernel/kernel.ts
CHANGED
|
@@ -689,7 +689,7 @@ function buildTerminal(
|
|
|
689
689
|
},
|
|
690
690
|
onProcessExit() {
|
|
691
691
|
// The engine's definitive 4001 close: the session's process has exited
|
|
692
|
-
// and the engine will not reconnect it.
|
|
692
|
+
// and the engine will not reconnect it. Three jobs here. markReady()
|
|
693
693
|
// guarantees the page is usable even when the exit lands before any
|
|
694
694
|
// screen frame (attaching to an already-dead session on a server that
|
|
695
695
|
// races the replay) — without it the loading overlay would sit on top of
|
|
@@ -700,6 +700,18 @@ function buildTerminal(
|
|
|
700
700
|
discardUnverifiedRestore({ session: connection.currentSessionId() });
|
|
701
701
|
markReady();
|
|
702
702
|
connState.ended();
|
|
703
|
+
// The host's turn, and LAST: everything above is unconditional, so a
|
|
704
|
+
// handler that throws cannot leave the page without its overlay lowered
|
|
705
|
+
// or its banner shown. The host is told because the kernel's own answer
|
|
706
|
+
// stops here — only the host knows whether its endpoint yields a new
|
|
707
|
+
// session on the next connect, which is what reattach() is for.
|
|
708
|
+
if (opts.onSessionEnded !== undefined) {
|
|
709
|
+
try {
|
|
710
|
+
opts.onSessionEnded();
|
|
711
|
+
} catch (err) {
|
|
712
|
+
console.error("web-terminal-ui: onSessionEnded handler failed", err);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
703
715
|
},
|
|
704
716
|
onWireIncompatible() {
|
|
705
717
|
// Compatibility refusal is terminal for this page instance. Lower the
|
|
@@ -1763,6 +1775,22 @@ function buildTerminal(
|
|
|
1763
1775
|
render.resetScrollback();
|
|
1764
1776
|
render.resetScreen();
|
|
1765
1777
|
},
|
|
1778
|
+
reattach() {
|
|
1779
|
+
if (destroyed) {
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
// The old session's content goes first: it belongs to a process that is
|
|
1783
|
+
// gone, and holding it would also put a `haveThrough` on the resume that
|
|
1784
|
+
// claims lines above anything the replacement has committed.
|
|
1785
|
+
render.resetScrollback();
|
|
1786
|
+
render.resetScreen();
|
|
1787
|
+
// Off `ended`, or the banner would keep saying "Session ended" over a
|
|
1788
|
+
// screen this call just blanked. `reconnecting` carries a grace delay, so a
|
|
1789
|
+
// connect that lands promptly shows no banner at all and a slow one reads
|
|
1790
|
+
// as what it is.
|
|
1791
|
+
connState.reconnecting();
|
|
1792
|
+
connection.reconnectNow();
|
|
1793
|
+
},
|
|
1766
1794
|
destroy() {
|
|
1767
1795
|
if (rootReleased) {
|
|
1768
1796
|
return;
|
package/src/kernel/types.ts
CHANGED
|
@@ -645,6 +645,22 @@ export interface CreateTerminalOptions {
|
|
|
645
645
|
* surface, so a reporting failure cannot leave the page blank. */
|
|
646
646
|
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- an observing handler returns nothing; only a literal `true` claims the recovery surface, so forcing `return undefined` on every observer is worse than the union
|
|
647
647
|
onFatalError?: (failure: TerminalStartupFailure) => boolean | void;
|
|
648
|
+
/** Called when the active session's process has ended and nothing is retrying:
|
|
649
|
+
* the engine's definitive process-exited close, the same fact the connection
|
|
650
|
+
* banner renders as "Session ended".
|
|
651
|
+
*
|
|
652
|
+
* Wired by a host that can do something about it, which means a host that
|
|
653
|
+
* knows its endpoint yields a new session on the next connect (see
|
|
654
|
+
* `TerminalHandle.reattach`). Everything the kernel does about the end — lower
|
|
655
|
+
* the loading overlay, surface the state, discard an unconfirmed restore —
|
|
656
|
+
* happens first and happens regardless, so a host that wires nothing loses
|
|
657
|
+
* nothing, and a callback that throws cannot take the banner down with it (the
|
|
658
|
+
* throw is logged and swallowed).
|
|
659
|
+
*
|
|
660
|
+
* Observation only: the recovery POLICY stays with the host, deliberately.
|
|
661
|
+
* Whether to reattach at all, how many times and how fast depends on what sits
|
|
662
|
+
* behind the endpoint, which this library does not know. */
|
|
663
|
+
onSessionEnded?: () => void;
|
|
648
664
|
/** Theme overrides: CSS custom properties set on the terminal root so a
|
|
649
665
|
* consumer recolors the UI (accent, tab hover/active, the activity-dot
|
|
650
666
|
* palette) without shipping CSS. Keys must be CSS custom-property names
|
|
@@ -679,6 +695,28 @@ export interface TerminalHandle {
|
|
|
679
695
|
* no keystroke — a host that wants a freshly drawn prompt sends one itself
|
|
680
696
|
* (e.g. Ctrl+L via send()). No-op after destroy(). */
|
|
681
697
|
reset(): void;
|
|
698
|
+
/** Attach again to whatever the server serves now, discarding this terminal's
|
|
699
|
+
* view of the session that is gone.
|
|
700
|
+
*
|
|
701
|
+
* For the one state the kernel cannot leave on its own: `ended`. The engine
|
|
702
|
+
* refuses to reconnect a definitively closed session, and it is right to on a
|
|
703
|
+
* per-session server, where reconnecting could only collect the same close
|
|
704
|
+
* again. A host whose endpoint hands out a NEW session on the next connect —
|
|
705
|
+
* one shared PTY that the server replaces once it is spent — is the only party
|
|
706
|
+
* that knows a reconnect is worth making, so it is the party that asks.
|
|
707
|
+
*
|
|
708
|
+
* Three steps, in this order, and none of them is a host's to perform: drop
|
|
709
|
+
* the local scrollback and screen (the old session's content, and a stale
|
|
710
|
+
* absolute-index base whose `haveThrough` would otherwise claim lines the new
|
|
711
|
+
* session has never reached), move the connection state off `ended` so the
|
|
712
|
+
* banner stops contradicting a blank screen, then reconnect.
|
|
713
|
+
*
|
|
714
|
+
* Reconnecting is ALL it does: it starts no process and asks the server for
|
|
715
|
+
* nothing. Whatever makes a new session exist is the host's own call to its
|
|
716
|
+
* own API, made before this one. Do not call it on a live session — that is a
|
|
717
|
+
* needless full replay, and it drops history the server may have evicted since.
|
|
718
|
+
* No-op after destroy(). */
|
|
719
|
+
reattach(): void;
|
|
682
720
|
/** Tear down every feature in reverse order, dispose all subscriptions, and
|
|
683
721
|
* release the kernel's DOM and engine wiring. */
|
|
684
722
|
destroy(): void;
|