@cplieger/web-terminal-ui 8.6.0-dev.7 → 8.6.0-dev.9
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 +1 -1
- package/css/00-tokens.css +13 -18
- package/css/01-scope.css +2 -2
- package/css/02-terminal.css +19 -40
- package/css/10-primitives.css +1 -1
- package/css/30-tabs.css +24 -43
- package/css/31-switcher.css +3 -3
- package/css/MANIFEST +3 -3
- package/css/MANIFEST.touch +1 -1
- package/css/page.css +25 -66
- package/package.json +2 -2
- package/scaffold/index.html +1 -1
- package/src/features/tabs/index.ts +19 -27
- package/src/features/tabs/model.ts +31 -31
- package/src/features/tabs/strip.ts +1 -2
- package/src/predict.ts +1 -1
- package/src/presets/agent-tabbed.ts +6 -8
- package/src/presets/tabbed.ts +10 -18
- package/src/presets.ts +8 -7
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ web-terminal-ui is built for a shell or a coding agent served by web-terminal-en
|
|
|
14
14
|
|
|
15
15
|
- One `createTerminal()` call builds the UI inside an element you provide. Four presets cover a single, a touch and two tabbed layouts.
|
|
16
16
|
- On a touchscreen it adds a key bar with Tab, Esc, arrows, Enter and a sticky Ctrl, a long-press paste menu and iOS soft-keyboard handling.
|
|
17
|
-
- Tabs show activity dots from the program's `OSC 9;4` progress
|
|
17
|
+
- Tabs show activity dots from the program's `OSC 7501` status, `OSC 9;4` progress and classified `OSC 9` notifications, and raise browser notifications. Two tabs can sit side by side. A full-page terminal gives each tab and each split its own page address, and names the browser tab after the active tab, followed by the page's own title: `fix build · Web Terminal`.
|
|
18
18
|
- Native text selection, IME input and dictation work, and predictive echo hides latency.
|
|
19
19
|
|
|
20
20
|
Consider [xterm.js](https://github.com/xtermjs/xterm.js) if you want a terminal component for any PTY backend, such as node-pty. It has an optional GPU-accelerated renderer and a set of addons, and VS Code uses it. To build a UI of your own on web-terminal-engine, depend on the engine alone.
|
package/css/00-tokens.css
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
/* ===== Themeable tokens (the consumer-configurable "template") =====
|
|
27
27
|
These default to the neutral built-in look; a consumer recolors the UI by
|
|
28
28
|
overriding them via createTerminal's `theme` option (which sets them on the
|
|
29
|
-
terminal root) or its own CSS.
|
|
30
|
-
|
|
29
|
+
terminal root) or its own CSS. Everything above is the fixed design palette;
|
|
30
|
+
everything here is the public theme API. */
|
|
31
31
|
--accent: oklch(72% 0.15 250deg); /* light blue; themed per consumer */
|
|
32
32
|
--tab-bg: oklch(30% 0 0deg / 90%); /* inactive-tab chip (translucent) */
|
|
33
33
|
--tab-hover-bg: var(--text-active); /* pressed controls / mobile switcher hovers */
|
|
@@ -61,10 +61,8 @@
|
|
|
61
61
|
accent-derived fill (--tab-active-bg) rather than on the bar. Measured
|
|
62
62
|
(sRGB, WCAG 1.4.3 formula, each layer composited over the page's #000):
|
|
63
63
|
8.0:1 against the bar, but only 2.6:1 against the active-tab fill -- under
|
|
64
|
-
the 3:1 non-text minimum.
|
|
65
|
-
|
|
66
|
-
figure back as its baseline and built a finding on it, so treat the number
|
|
67
|
-
here as load-bearing and re-measure rather than adjust it by eye.
|
|
64
|
+
the 3:1 non-text minimum. Treat the number as load-bearing and re-measure
|
|
65
|
+
rather than adjust it by eye.
|
|
68
66
|
|
|
69
67
|
Why it is nevertheless not a 1.4.1 failure, and why the fill is not simply
|
|
70
68
|
darkened: hue never carries state on its own here -- working is a travelling
|
|
@@ -82,11 +80,12 @@
|
|
|
82
80
|
--status-working: #52a9fe; /* agent working (waves): the same blue as the default --accent */
|
|
83
81
|
--status-done: #22c55e; /* turn finished: solid green */
|
|
84
82
|
--status-input: #fb923c; /* action required: solid orange + ring */
|
|
85
|
-
/*
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
/* The warning and failed statuses (STATUS_WARNING and STATUS_FAILED in
|
|
84
|
+
features/tabs/model.ts name their sources). Both render as the SAME object
|
|
85
|
+
as working -- identical glow + travelling wave, one shared rule and one
|
|
86
|
+
shared pair of keyframes (30-tabs.css) -- differing ONLY in hue, because
|
|
87
|
+
OSC 9;4 models one progress indicator with several states rather than three
|
|
88
|
+
unrelated dots. Neither wears a ring: the frozen hard ring is
|
|
90
89
|
--status-input's exclusive meaning ("blocked, waiting on you"), and neither
|
|
91
90
|
warning nor error asks the user to act.
|
|
92
91
|
LIGHTNESS SEPARATION IS LOAD-BEARING, NOT DECORATIVE. Three states that
|
|
@@ -102,8 +101,8 @@
|
|
|
102
101
|
indistinguishable to a colour-blind user, whatever their hues. warning is
|
|
103
102
|
YELLOW, deliberately not amber -- amber would collide with --status-input's
|
|
104
103
|
orange (L 75.8%), which sits between working and warning. */
|
|
105
|
-
--status-warning: #facc15; /*
|
|
106
|
-
--status-failed: #dc2626; /*
|
|
104
|
+
--status-warning: #facc15; /* warning: yellow disc, working's wave */
|
|
105
|
+
--status-failed: #dc2626; /* a reported error: red disc, working's wave; crashed reuses it solid and static */
|
|
107
106
|
--radius: 0.75rem;
|
|
108
107
|
--radius-inner: 0.5rem;
|
|
109
108
|
/* Small square icon buttons (e.g. the 24px tab-close x): at that size the
|
|
@@ -173,11 +172,7 @@
|
|
|
173
172
|
activity mark's breath (30-tabs.css). A token rather than three literals
|
|
174
173
|
because the three are phase-locked BY sharing it: the ripple launches off the
|
|
175
174
|
disc as the glow peaks, and the mark beside them reports on the same clock, so
|
|
176
|
-
retuning one alone desynchronises cues a reader sees side by side.
|
|
177
|
-
comment saying "change both or neither" until 2026-09, which is the same rule
|
|
178
|
-
with nothing enforcing it.
|
|
179
|
-
Shared with marotte's `--dot-beat-dur` (01-tokens.css) for the reason the
|
|
180
|
-
--status-* inks are: one signal across the two apps, beat included. */
|
|
175
|
+
retuning one alone desynchronises cues a reader sees side by side. */
|
|
181
176
|
--dot-beat-dur: 1.6s;
|
|
182
177
|
--dur-standard: 0.2s;
|
|
183
178
|
--dur-enter: 0.25s;
|
package/css/01-scope.css
CHANGED
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
color-scheme: dark;
|
|
45
45
|
/* Design-system scrollbar COLOR on every scroller in the subtree (the
|
|
46
46
|
terminal, the tab scroller, the expanded switcher list): border-grey
|
|
47
|
-
thumb, no track
|
|
48
|
-
|
|
47
|
+
thumb, no track. scrollbar-color INHERITS, so the boundary is the one
|
|
48
|
+
place to set it. Where it is not
|
|
49
49
|
supported the color-scheme above still renders the UA fallback dark.
|
|
50
50
|
Overlay-scrollbar platforms are unaffected. */
|
|
51
51
|
scrollbar-color: var(--border) transparent;
|
package/css/02-terminal.css
CHANGED
|
@@ -68,16 +68,11 @@
|
|
|
68
68
|
"calt" 0;
|
|
69
69
|
font-kerning: none;
|
|
70
70
|
|
|
71
|
-
/*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
span Ink draws inside our rows, which would visually stick at
|
|
77
|
-
its old column on space/arrow key presses until a content-
|
|
78
|
-
changing keystroke forced a repaint. Without `contain`, every
|
|
79
|
-
row update participates in normal paint flow and the cursor
|
|
80
|
-
cell tracks the column on every flush. */
|
|
71
|
+
/* No `contain: layout style` here: iOS Safari (WebKit) can drop paint
|
|
72
|
+
invalidations for inline children of a layout-contained element that
|
|
73
|
+
replaceChildren() updates, so a program's inverse-styled cursor span
|
|
74
|
+
would stick at its old column on space or arrow keys until a
|
|
75
|
+
content-changing keystroke forced a repaint. */
|
|
81
76
|
}
|
|
82
77
|
|
|
83
78
|
:where(.wt-root) .term-row:empty::after {
|
|
@@ -213,17 +208,13 @@
|
|
|
213
208
|
display: block;
|
|
214
209
|
}
|
|
215
210
|
|
|
216
|
-
/* Clickable URLs. OSC 8
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
keeps the link discoverable at rest.
|
|
224
|
-
Auto-detected bare URLs (.term-autolink) are tightly scoped to the URL text and
|
|
225
|
-
never bleed, so they keep a subtle persistent underline. (The engine also only
|
|
226
|
-
anchors runs with real link text, so the underline hugs the text either way.) */
|
|
211
|
+
/* Clickable URLs. An OSC 8 link (.term-link) underlines on HOVER only, as in
|
|
212
|
+
iTerm2, VTE and kitty: an application can hold one link open across a table
|
|
213
|
+
cell's padding, borders and the empty column beside it, so a persistent
|
|
214
|
+
underline would bleed across the cell and, on wrap, the whole row. The app's
|
|
215
|
+
own text colour keeps it discoverable at rest. A detected bare URL
|
|
216
|
+
(.term-autolink) spans only the URL text, so it keeps a subtle persistent
|
|
217
|
+
underline. */
|
|
227
218
|
:where(.wt-root) .term-link {
|
|
228
219
|
color: inherit;
|
|
229
220
|
text-decoration: none;
|
|
@@ -238,25 +229,13 @@
|
|
|
238
229
|
text-decoration: underline solid;
|
|
239
230
|
}
|
|
240
231
|
|
|
241
|
-
/* Where
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
The bleed risk above is what kept this off, and it is narrower than it reads:
|
|
249
|
-
the engine refuses to anchor a run with no real link text, so the padding, the
|
|
250
|
-
box-drawing borders and the indent cells a wrapped link leaks onto the next row
|
|
251
|
-
stay unanchored and unmarked (verified against a captured OSC 8 link that wraps
|
|
252
|
-
mid-URL: the three leaked indent cells carry the href on the wire and get no
|
|
253
|
-
anchor). What remains is an app leaving one link open across genuine text in an
|
|
254
|
-
adjacent column, which would be underlined too. On a device that cannot hover,
|
|
255
|
-
an occasional over-underline beats a link with no affordance.
|
|
256
|
-
|
|
257
|
-
`hover: none` is the pointing device's capability, not a viewport width, so a
|
|
258
|
-
narrow window on a desktop keeps hover styling and a tablet with a trackpad is
|
|
259
|
-
treated as hoverable. */
|
|
232
|
+
/* Where the primary pointing device cannot hover, a hover-only underline gives
|
|
233
|
+
a touch interaction no persistent affordance: a TUI that emits OSC 8 for
|
|
234
|
+
every link it renders (a program may enable hyperlinks for the iTerm2
|
|
235
|
+
identity the engine advertises) fills a phone session with tappable,
|
|
236
|
+
invisible links. The bleed risk above stays small because the engine anchors
|
|
237
|
+
no run without real link text. `hover: none` describes the primary pointing
|
|
238
|
+
device, not viewport width, so a narrow desktop window keeps hover styling. */
|
|
260
239
|
@media (hover: none) {
|
|
261
240
|
:where(.wt-root) .term-link {
|
|
262
241
|
text-decoration: underline dotted;
|
package/css/10-primitives.css
CHANGED
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
real <button>: tabs, +, keyboard, switcher rows, menu items, the FAB).
|
|
79
79
|
:focus-visible only — a tap/click never shows the ring (the UA heuristic),
|
|
80
80
|
so the touch-first surfaces stay clean while Tab-key navigation gets the
|
|
81
|
-
|
|
81
|
+
accent ring (2px accent, 2px out).
|
|
82
82
|
Follows each control's border-radius; themeable via --accent like the "+"
|
|
83
83
|
glyphs. The terminal's own hidden textarea is not a button and keeps its
|
|
84
84
|
outline: none (02-terminal.css). */
|
package/css/30-tabs.css
CHANGED
|
@@ -107,8 +107,8 @@
|
|
|
107
107
|
width: 300px;
|
|
108
108
|
flex: 0 1 auto;
|
|
109
109
|
min-width: 100px;
|
|
110
|
-
/* Positioning context for the chip's own overlay: the
|
|
111
|
-
|
|
110
|
+
/* Positioning context for the chip's own overlay: the determinate progress
|
|
111
|
+
bar sits on its bottom edge (see .wt-progress-bar). Nothing else
|
|
112
112
|
inside a chip is absolutely positioned, and the strip's two root-level
|
|
113
113
|
overlays (.wt-tab-ghost, .wt-tab-menu) are children of .wt-root, not of a
|
|
114
114
|
chip, so this cannot capture them. */
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
|
|
189
189
|
/* The active tab reads clearly above the inactive chips: a stronger fill plus an
|
|
190
190
|
accent-coloured edge (inactive chips carry a faint white --border; the active
|
|
191
|
-
one gets the accent equivalent). Themeable via --accent
|
|
191
|
+
one gets the accent equivalent). Themeable via --accent. */
|
|
192
192
|
:where(.wt-root) .wt-tab-active {
|
|
193
193
|
background: var(--tab-active-bg);
|
|
194
194
|
border-color: var(--tab-active-border);
|
|
@@ -659,25 +659,14 @@
|
|
|
659
659
|
bottom: calc(var(--wt-tabbar-h, 44px) + var(--safe-bottom, 0px));
|
|
660
660
|
}
|
|
661
661
|
|
|
662
|
-
/* ===== Activity dots
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
warning
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
Revealed PER TAB: the dot is hidden until its own session reports activity
|
|
672
|
-
(OSC 9;4 progress, or a classified OSC 9 notification), at which point the
|
|
673
|
-
tabs feature adds .wt-reports to that dot (paintStatusDot). A program that
|
|
674
|
-
emits no OSC 9 signal (a plain bash/sh) has no activity concept, so its tabs
|
|
675
|
-
stay clean, label-only; a progress-reporting agent (kiro-cli, Claude Code)
|
|
676
|
-
lights up. An agent-shell consumer can PRESUME the report instead (tabs'
|
|
677
|
-
presumeReports option, on in presetAgentTabbed): .wt-reports is then set
|
|
678
|
-
from tab creation, so the idle dot shows immediately rather than popping in
|
|
679
|
-
after the agent's first signal. The element stays in the DOM either way (JS
|
|
680
|
-
sets data-status regardless), so revealing it is a pure CSS switch. */
|
|
662
|
+
/* ===== Activity dots: one .wt-status-dot vocabulary for the strip, the
|
|
663
|
+
switcher and the overview rows. Eight built-in states told apart without
|
|
664
|
+
colour (WCAG 1.4.1, by shape and motion): three animated progress hues
|
|
665
|
+
(working, warning, failed) spread across lightness, a ringed disc (input), two
|
|
666
|
+
static discs (done, crashed) and two hollow ones (idle, exited); any other
|
|
667
|
+
status keeps the default hollow dot below. A dot shows once its session
|
|
668
|
+
reports activity (.wt-reports, set by paintStatusDot) or from creation under
|
|
669
|
+
tabs' presumeReports option; data-status is set either way. */
|
|
681
670
|
:where(.wt-root) .wt-status-dot {
|
|
682
671
|
display: none;
|
|
683
672
|
width: 9px;
|
|
@@ -699,12 +688,10 @@
|
|
|
699
688
|
animation: none;
|
|
700
689
|
}
|
|
701
690
|
|
|
702
|
-
/* ===== Determinate progress bar (OSC 9;4
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
by all three chip sites (desktop strip chip, mobile active row, expanded list
|
|
707
|
-
row) because it ships in the one chipContent builder.
|
|
691
|
+
/* ===== Determinate progress bar (the session's percentage: OSC 7501 progress, else OSC 9;4) =====
|
|
692
|
+
One 2px line on the chip's bottom edge, shared by all three chip sites
|
|
693
|
+
(desktop strip chip, mobile active row, expanded list row) because it ships
|
|
694
|
+
in the one chipContent builder.
|
|
708
695
|
|
|
709
696
|
The ACCENT colour, not a per-state tint: the bar answers "how far", the dot
|
|
710
697
|
answers "in what state", and tinting the bar per state would say the same
|
|
@@ -757,8 +744,8 @@
|
|
|
757
744
|
the element), which read as a blink and coupled the layers — an element's
|
|
758
745
|
opacity always multiplies onto its pseudos, so true independence requires
|
|
759
746
|
animating disc COLORS, not element opacity. Server-side this is driven by the
|
|
760
|
-
|
|
761
|
-
the prompt.
|
|
747
|
+
program's own status reports, so it tracks agent activity, not the user
|
|
748
|
+
typing at the prompt.
|
|
762
749
|
|
|
763
750
|
WORKING, WARNING AND FAILED SHARE THIS RULE, AND BOTH KEYFRAMES, VERBATIM.
|
|
764
751
|
OSC 9;4 models ONE progress indicator with several states (1/3 active,
|
|
@@ -876,8 +863,8 @@
|
|
|
876
863
|
}
|
|
877
864
|
|
|
878
865
|
/* Agent done / ready: solid disc (--status-done), no motion. The default green
|
|
879
|
-
|
|
880
|
-
|
|
866
|
+
(#22c55e) sits at almost exactly working's default lightness now that
|
|
867
|
+
working is the accent blue (both ~72% OKLab L), so
|
|
881
868
|
the pair separates by hue plus the wave's motion rather than by lightness —
|
|
882
869
|
and under prefers-reduced-motion by shape, since working becomes a donut. */
|
|
883
870
|
:where(.wt-root) .wt-status-dot[data-status="done"] {
|
|
@@ -885,17 +872,11 @@
|
|
|
885
872
|
border-color: var(--status-done);
|
|
886
873
|
}
|
|
887
874
|
|
|
888
|
-
/* User action required
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
outline rather than working's soft travelling wave. (Before the wave landed,
|
|
894
|
-
working's ripple was a 2px ring too and this state was literally that ping
|
|
895
|
-
frozen at its birth frame; the two now differ by edge quality as well as
|
|
896
|
-
motion, which reads more distinctly at 9px.) The ring derives from the same
|
|
897
|
-
token as the dot, so a theme override recolors both together. It persists
|
|
898
|
-
until you act on or switch into the tab. */
|
|
875
|
+
/* User action required: a solid dot inside a static, hard-edged 2px ring
|
|
876
|
+
(--status-input). Stillness and the hard edge separate it from working's
|
|
877
|
+
soft travelling wave. The ring derives from the dot's token, so a theme
|
|
878
|
+
override recolors both. It persists until you act on or switch into the
|
|
879
|
+
tab. */
|
|
899
880
|
:where(.wt-root) .wt-status-dot[data-status="input"] {
|
|
900
881
|
background: var(--status-input);
|
|
901
882
|
border-color: var(--status-input);
|
package/css/31-switcher.css
CHANGED
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
/* Right padding reserves room for the close (x) overlaid inside the row. */
|
|
141
141
|
padding: 0 var(--touch-target) 0 var(--sp-2);
|
|
142
142
|
/* The bar row always represents the active tab, so it wears the active-tab
|
|
143
|
-
fill/foreground the desktop strip uses (themeable
|
|
143
|
+
fill/foreground the desktop strip uses (themeable). */
|
|
144
144
|
background: var(--tab-active-bg);
|
|
145
145
|
color: var(--tab-active-fg);
|
|
146
146
|
border: 1px solid var(--tab-active-border);
|
|
@@ -360,7 +360,7 @@
|
|
|
360
360
|
process crashed or reported an error. Those four only (CueStatus) — the rule
|
|
361
361
|
the user set is "only states that want the user, like a notification", so the
|
|
362
362
|
ongoing progress states (working / warning) never raise it. `failed` DOES,
|
|
363
|
-
because
|
|
363
|
+
because a reported error is a result the program parked rather than a phase in
|
|
364
364
|
progress; the override below is what keeps it looking like a notification here.
|
|
365
365
|
Reuses the exact .wt-status-dot rules (30-tabs.css, --status-* tokens) so it
|
|
366
366
|
matches the per-tab dots. Hidden until the tabs feature sets data-status;
|
|
@@ -410,7 +410,7 @@
|
|
|
410
410
|
|
|
411
411
|
/* Mobile "+": the primary action, carried in the accent colour so it reads as
|
|
412
412
|
permanent and prominent. Hover fill is gated on any-hover (see the note in
|
|
413
|
-
10-primitives.css): on a bare touchscreen any-hover is none, so the
|
|
413
|
+
10-primitives.css): on a bare touchscreen any-hover is none, so the accent
|
|
414
414
|
cannot linger after a tap and the press feedback comes from :active; a trackpad
|
|
415
415
|
or Pencil reports hover and gets the wash, which a (hover: hover) gate denied it.
|
|
416
416
|
*/
|
package/css/MANIFEST
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# web-terminal-ui CSS — the REFERENCE FULL-PAGE bundle.
|
|
2
|
-
# What
|
|
3
|
-
#
|
|
4
|
-
#
|
|
2
|
+
# What a full-page host serves (as scaffold/index.html does): the page kit
|
|
3
|
+
# (page.css — html/body reset + @font-face; full-page hosts ONLY) plus the
|
|
4
|
+
# complete tabbed component set. An EMBEDDING
|
|
5
5
|
# consumer must NOT serve this file's bundle — it concatenates the per-preset
|
|
6
6
|
# manifest matching its composition instead (MANIFEST.single / MANIFEST.touch /
|
|
7
7
|
# MANIFEST.tabbed), which contains no page-level rules.
|
package/css/MANIFEST.touch
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Component bundle for presetTouch() compositions (single + the mobile key
|
|
2
2
|
# toolbar). Library styles only — root-scoped, no page-level rules; the host
|
|
3
|
-
# page owns its own reset/fonts (
|
|
3
|
+
# page owns its own reset/fonts (an embedded panel serves exactly this).
|
|
4
4
|
# Files concatenate in declared order; the order IS the cascade.
|
|
5
5
|
00-tokens.css
|
|
6
6
|
01-scope.css
|
package/css/page.css
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* ===== Page kit (full-page hosts ONLY — never ship this to an embedder) =====
|
|
2
2
|
The page-level half of the full-page terminal product: what the HOST PAGE
|
|
3
|
-
owns when the terminal IS the page (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
owns when the terminal IS the page (as in scaffold/index.html). The component
|
|
4
|
+
bundle (00-10) never touches the host document; everything here is
|
|
5
|
+
deliberately page-scoped. An embedding consumer serves a per-preset manifest
|
|
6
|
+
instead and owns its own page.
|
|
7
7
|
Concatenated FIRST by the reference MANIFEST. */
|
|
8
8
|
|
|
9
9
|
*,
|
|
@@ -39,30 +39,13 @@ body {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/* ===== The pre-JS loading overlay =====
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Why it can live in a stylesheet at all, since the overlay's whole job is to be
|
|
50
|
-
on screen before the app is: a full-page host loads this bundle through a plain
|
|
51
|
-
render-blocking <link rel="stylesheet"> in <head>, so NOTHING paints until it
|
|
52
|
-
arrives. An inline copy buys no earlier paint. What an inline copy does buy is
|
|
53
|
-
the case where this stylesheet FAILS to load, which a consumer's own bootstrap
|
|
54
|
-
watchdog reports — so a consumer keeps a deliberately minimal inline fallback
|
|
55
|
-
for that failure surface only, not a second copy of everything below.
|
|
56
|
-
|
|
57
|
-
Consumer overrides work the same way the component tokens do, except simpler:
|
|
58
|
-
the overlay is the consumer's OWN element, so it sets the tokens on #loading in
|
|
59
|
-
its own CSS and needs no JS. web-terminal-kiro already did exactly this with
|
|
60
|
-
--accent before any of this was tokenised.
|
|
61
|
-
|
|
62
|
-
Selected by CLASS, never by the consumer's `id`: the library owns .wt-loading*
|
|
63
|
-
and must not reach into a host document's own identifiers (the same
|
|
64
|
-
no-element-id-contract rule the kernel follows for the terminal subtree), and
|
|
65
|
-
an unnamespaced `.bar` in a host page would collide with anything. */
|
|
42
|
+
The one definition, so a full-page consumer links it rather than carrying a
|
|
43
|
+
drifting inline copy. A full-page host loads this bundle render-blocking in
|
|
44
|
+
<head>, so an inline copy paints no earlier; a consumer keeps only a minimal
|
|
45
|
+
inline fallback for a failed stylesheet load. The overlay is the consumer's
|
|
46
|
+
own element, so it overrides by setting the tokens on it. Selected by CLASS,
|
|
47
|
+
never by id: the library owns .wt-loading* and does not reach into the host
|
|
48
|
+
document's identifiers, and an unnamespaced .bar would collide. */
|
|
66
49
|
.wt-loading {
|
|
67
50
|
/* The overlay's accent: the bar and the status text derive from it, so one
|
|
68
51
|
override recolors the whole screen. Defaults to the component --accent
|
|
@@ -225,44 +208,12 @@ body {
|
|
|
225
208
|
}
|
|
226
209
|
|
|
227
210
|
/* ===== Fonts =====
|
|
228
|
-
The bundled
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
embedder does not serve these files and never inherits these URLs, so it
|
|
235
|
-
either ships its own @font-face or lets both tokens fall back to the platform
|
|
236
|
-
monospace stack.
|
|
237
|
-
|
|
238
|
-
No face carries ascent-override or descent-override: the terminal's cell
|
|
239
|
-
background is the run padding in 02-terminal.css, never these metrics.
|
|
240
|
-
|
|
241
|
-
The tiling overlay, "Web Terminal Glyphs", is the second family below, and it
|
|
242
|
-
is ONE file declared under FOUR descriptor sets. The released face is a single
|
|
243
|
-
upright 400 weight, so a lone rule would leave the engine free to synthesise a
|
|
244
|
-
stand-in for a bold or italic run. What the four sets (400/700 x normal/italic)
|
|
245
|
-
against the same URL foreclose is the EMBOLDENED stand-in, in Blink and in
|
|
246
|
-
Gecko both: a bold run resolves a real face where the same asset under one rule
|
|
247
|
-
is thickened. They do NOT foreclose the OBLIQUE one in Blink, which honours the
|
|
248
|
-
italic descriptor for face SELECTION and slants the run anyway; the only thing
|
|
249
|
-
keeping an italic box-drawing corner upright there is font-synthesis: none on
|
|
250
|
-
.term (02-terminal.css). Both mechanisms are load-bearing and neither is the
|
|
251
|
-
whole guarantee. All four rules point at one file, so the bytes download once.
|
|
252
|
-
|
|
253
|
-
No overlay face declares unicode-range, and that absence is deliberate: the
|
|
254
|
-
font's own cmap IS its range (1,098 tiling codepoints, no letters, no digits,
|
|
255
|
-
no space), so a letter falls through to Monaspace by ordinary family fallback,
|
|
256
|
-
and a range here would only narrow that fallback to a list somebody has to
|
|
257
|
-
maintain against the next release of the font.
|
|
258
|
-
|
|
259
|
-
The overlay is listed FIRST in --font-mono and never in --font-ui
|
|
260
|
-
(00-tokens.css owns why). A full-page host must serve
|
|
261
|
-
WebTerminalGlyphs.woff2 beside the Monaspace faces, along with that font's
|
|
262
|
-
LICENSE and NOTICE: it is a separate Apache-2.0 work, and §4 binds whoever
|
|
263
|
-
ships the bytes. This package ships none, it only names the URL — so a host
|
|
264
|
-
that has not caught up errors the face out and renders exactly as it did
|
|
265
|
-
before, with the tiling ranges coming from Monaspace and the system font. */
|
|
211
|
+
The bundled faces, served by the full-page host at /vendor/fonts
|
|
212
|
+
(docs/host-page.md owns the files and their licences). They back BOTH
|
|
213
|
+
--font-mono and --font-ui: chip-label ink centring is a per-font constant
|
|
214
|
+
(--label-ink-shift, 00-tokens.css), so the chrome cannot take a system stack
|
|
215
|
+
that resolves differently per engine. No face sets ascent- or
|
|
216
|
+
descent-override: the cell background is the run padding in 02-terminal.css. */
|
|
266
217
|
@font-face {
|
|
267
218
|
font-family: "Monaspace Neon NF";
|
|
268
219
|
src: url("/vendor/fonts/MonaspaceNeonNF-Regular.woff2") format("woff2");
|
|
@@ -295,6 +246,14 @@ body {
|
|
|
295
246
|
font-display: block;
|
|
296
247
|
}
|
|
297
248
|
|
|
249
|
+
/* "Web Terminal Glyphs" is ONE upright 400 file under FOUR descriptor sets, so
|
|
250
|
+
a bold run resolves a real face instead of an emboldened stand-in (Blink and
|
|
251
|
+
Gecko). Blink still slants an italic run; font-synthesis: none on .term
|
|
252
|
+
(02-terminal.css) keeps it upright, and both are needed. The bytes download
|
|
253
|
+
once. No unicode-range: the font's cmap is its range (tiling codepoints
|
|
254
|
+
only), so letters fall through to Monaspace by family fallback, and a list
|
|
255
|
+
here would need upkeep against each font release. 00-tokens.css owns why it
|
|
256
|
+
is first in --font-mono and absent from --font-ui. */
|
|
298
257
|
@font-face {
|
|
299
258
|
font-family: "Web Terminal Glyphs";
|
|
300
259
|
src: url("/vendor/fonts/WebTerminalGlyphs.woff2") format("woff2");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "8.6.0-dev.
|
|
3
|
+
"version": "8.6.0-dev.9",
|
|
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",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"fast-check": "4.10.2",
|
|
64
64
|
"html-validate": "11.16.2",
|
|
65
65
|
"knip": "6.41.0",
|
|
66
|
-
"playwright": "1.64.
|
|
66
|
+
"playwright": "1.64.1",
|
|
67
67
|
"prettier": "3.9.10",
|
|
68
68
|
"stylelint": "17.16.0",
|
|
69
69
|
"stylelint-config-standard": "40.0.0",
|
package/scaffold/index.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
arrives with /style.css above, which is render-blocking, so nothing paints
|
|
17
17
|
before it either way. A consumer that also wants a failure surface for the
|
|
18
18
|
case where /style.css itself fails to load adds its own minimal inline block
|
|
19
|
-
for that
|
|
19
|
+
for that; a bare host does not need one. -->
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
22
22
|
<!-- createTerminal() builds the entire terminal UI inside this single root
|
|
@@ -173,12 +173,11 @@ export interface TabsOptions {
|
|
|
173
173
|
|
|
174
174
|
/** The layout read, the session list and session creation can be TEMPORARILY
|
|
175
175
|
* refused: a host answers 503 with `Retry-After` and a body message while it
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* is re-told periodically. */
|
|
176
|
+
* starts up or restores its sessions, a window that can run twenty minutes.
|
|
177
|
+
* Only 503 retries, on the server's own schedule and repeating its own
|
|
178
|
+
* explanation. The bound is ELAPSED TIME, not an attempt count, which at
|
|
179
|
+
* `Retry-After: 5` would give up in a minute; every iteration sleeps at least
|
|
180
|
+
* the hint, so this is never a hot loop, and the user is re-told periodically. */
|
|
182
181
|
const CREATE_RETRY_MAX_TOTAL_MS = 1200000;
|
|
183
182
|
const CREATE_RETRY_FALLBACK_MS = 5000;
|
|
184
183
|
const CREATE_RETRY_REANNOUNCE_MS = 60000;
|
|
@@ -331,14 +330,13 @@ class SettleCreateFailed extends Error {
|
|
|
331
330
|
}
|
|
332
331
|
|
|
333
332
|
/** Read the layout, then list, then (when nothing live is listed) create one
|
|
334
|
-
* session, starting the turn again after any 503, so a
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
* reason. */
|
|
333
|
+
* session, starting the turn again after any 503, so a create refused
|
|
334
|
+
* mid-restore never adds a tab beside the restored ones. A non-503 layout read
|
|
335
|
+
* proves the host was not restoring when it was served, so a list issued after
|
|
336
|
+
* it sees the whole restored set. Past `CREATE_RETRY_MAX_TOTAL_MS` a refused
|
|
337
|
+
* layout read or list returns the last good list, or rethrows the 503; a create
|
|
338
|
+
* that fails for good, a spent 503 included, throws `SettleCreateFailed`
|
|
339
|
+
* carrying the list, an abort its reason. */
|
|
342
340
|
async function settleSessionSet(
|
|
343
341
|
api: SessionAPIClient,
|
|
344
342
|
ctx: TerminalContext,
|
|
@@ -1008,9 +1006,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
1008
1006
|
pageTitle.release();
|
|
1009
1007
|
});
|
|
1010
1008
|
}
|
|
1011
|
-
//
|
|
1012
|
-
//
|
|
1013
|
-
// PRESS-time frame, so a move a hold lifted across still ends the drag.
|
|
1009
|
+
// A live drag's viewport is judged against the PRESS-time frame, so a move a
|
|
1010
|
+
// hold lifted across still ends the drag.
|
|
1014
1011
|
let draggingEl: HTMLElement | null = null;
|
|
1015
1012
|
// The chips currently carrying an inline displacement from the commit slide.
|
|
1016
1013
|
// A set plus one settle function, so whatever a slide wrote can always be handed
|
|
@@ -1070,9 +1067,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
1070
1067
|
const prefersReduce = (): boolean =>
|
|
1071
1068
|
win.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1072
1069
|
|
|
1073
|
-
// Recomputes every display label with de-duplication.
|
|
1074
|
-
// is applied at render time and stored nowhere, so de-duplication and the
|
|
1075
|
-
// rename field see real names and clearing it needs no cleanup.
|
|
1070
|
+
// Recomputes every display label with de-duplication.
|
|
1076
1071
|
function relabelAll(): void {
|
|
1077
1072
|
// The "(k)" suffix is numbered by the session's AGE, never by its slot:
|
|
1078
1073
|
// numbered by encounter order, the labels stayed put while the sessions
|
|
@@ -1142,11 +1137,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
1142
1137
|
return foldedCueStatus(statusOf(t), t.activity);
|
|
1143
1138
|
}
|
|
1144
1139
|
|
|
1145
|
-
/** shownProgress is a tab's percentage as it may currently be DISPLAYED
|
|
1146
|
-
*
|
|
1147
|
-
* one the progress channel owns. Those are the only two ways a percentage
|
|
1148
|
-
* stops showing — the program's own OSC 9;4;0 (an explicit -1) and a status
|
|
1149
|
-
* from another channel; see renderedProgress for why there is no third. */
|
|
1140
|
+
/** shownProgress is a tab's percentage as it may currently be DISPLAYED;
|
|
1141
|
+
* renderedProgress owns when that is. */
|
|
1150
1142
|
function shownProgress(t: Tab): number {
|
|
1151
1143
|
return renderedProgress(statusOf(t), t.progress);
|
|
1152
1144
|
}
|
|
@@ -3346,8 +3338,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
3346
3338
|
t.seenSeq = seen;
|
|
3347
3339
|
closeReadSurfaces(t);
|
|
3348
3340
|
}
|
|
3349
|
-
// A program that
|
|
3350
|
-
// permission request for the next user gesture.
|
|
3341
|
+
// A program that reports activity may also post an OSC 9 notification:
|
|
3342
|
+
// arm the permission request for the next user gesture.
|
|
3351
3343
|
if (reports) {
|
|
3352
3344
|
ctx.shell.notifications.arm();
|
|
3353
3345
|
}
|
|
@@ -19,7 +19,9 @@ export type StatusRecord = SessionInfo & {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
// The status vocabulary is module-private so a status's MEANING has one home:
|
|
22
|
-
// every consumer asks a predicate below instead of comparing strings.
|
|
22
|
+
// every consumer asks a predicate below instead of comparing strings. The
|
|
23
|
+
// protocol states named below are the built-in sources; a classified OSC 9
|
|
24
|
+
// notification may set any status.
|
|
23
25
|
//
|
|
24
26
|
// An exited session is viewable history that can never produce output again, so
|
|
25
27
|
// session selection prefers live sessions everywhere.
|
|
@@ -30,11 +32,14 @@ const STATUS_EXITED = "exited";
|
|
|
30
32
|
* restart never paints as a failure. Terminal, like "exited". */
|
|
31
33
|
const STATUS_CRASHED = "crashed";
|
|
32
34
|
|
|
33
|
-
/**
|
|
34
|
-
*
|
|
35
|
+
/** An error the program reported: OSC 7501 state `error`, or OSC 9;4 progress
|
|
36
|
+
* state 2 (iTerm2 semantics). A STATE that persists until the program reports
|
|
37
|
+
* another, or the process dies. */
|
|
35
38
|
const STATUS_FAILED = "failed";
|
|
36
39
|
|
|
37
|
-
/** OSC 9;4 progress state 4, the warning state (iTerm2 semantics).
|
|
40
|
+
/** OSC 9;4 progress state 4, the warning state (iTerm2 semantics). OSC 9;4
|
|
41
|
+
* cannot set it once the session has sent an OSC 7501 report, until a
|
|
42
|
+
* terminal reset (RIS): OSC 7501 outranks OSC 9;4 until a RIS forgets it. */
|
|
38
43
|
const STATUS_WARNING = "warning";
|
|
39
44
|
|
|
40
45
|
/** Whether the session's process is GONE, whichever way it went: reloading onto
|
|
@@ -51,8 +56,9 @@ export function statusRevealsDot(status: string): boolean {
|
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
/** The human wording for a status, for BOTH the dot's tooltip and the tab's
|
|
54
|
-
* accessible name, so they cannot disagree.
|
|
55
|
-
*
|
|
59
|
+
* accessible name, so they cannot disagree. A status outside the built-in
|
|
60
|
+
* vocabulary (a host classifier's, or a newer server's) falls back to the raw
|
|
61
|
+
* value rather than being hidden. */
|
|
56
62
|
export function statusPhrase(status: string): string {
|
|
57
63
|
switch (status) {
|
|
58
64
|
case "working":
|
|
@@ -94,22 +100,16 @@ export function normalizeProgress(value: unknown): number {
|
|
|
94
100
|
return Math.min(100, Math.round(value));
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
/** The percentage a status may SHOW. Two things clear it: the program's own
|
|
106
|
-
* OSC 9;4;0 (arriving as PROGRESS_ABSENT) and a status the channel does not own
|
|
107
|
-
* (kiro-cli parks state 4 at its context-usage percentage when idle, which once
|
|
108
|
-
* painted a done dot beside a 72% bar). Deliberately no third clear: 100% is a
|
|
109
|
-
* STATE that persists, and a timeout would assert a change the program never
|
|
110
|
-
* reported. */
|
|
103
|
+
/** The percentage a status may SHOW. Under any live status it is the server's
|
|
104
|
+
* value, because the status and the percentage can come from different
|
|
105
|
+
* channels: OSC 7501 or a notification can set the status while OSC 9;4
|
|
106
|
+
* supplies the percentage. Two things clear it: the server's PROGRESS_ABSENT
|
|
107
|
+
* (the program cleared or never reported a percentage, e.g. OSC 9;4;0) and an
|
|
108
|
+
* ended process, whose percentage nothing will ever clear. No timeout clears
|
|
109
|
+
* it: 100% is a STATE that persists, and a timeout would assert a change the
|
|
110
|
+
* program never reported. */
|
|
111
111
|
export function renderedProgress(status: string, progress: number): number {
|
|
112
|
-
return
|
|
112
|
+
return isEndedStatus(status) ? PROGRESS_ABSENT : progress;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// The secondary-activity vocabulary: a host-reported background activity that
|
|
@@ -221,8 +221,8 @@ export function cueIconName(status: CueStatus): "input" | "done" | "alert" {
|
|
|
221
221
|
|
|
222
222
|
/** The cue statuses meaning the turn is OVER WITHOUT INCIDENT, the only ones a
|
|
223
223
|
* live background task may blank. `done` is the whole set: every other cue must
|
|
224
|
-
* reach the viewer AT ONCE, and `failed`
|
|
225
|
-
* turn
|
|
224
|
+
* reach the viewer AT ONCE, and `failed` is an error the program reported,
|
|
225
|
+
* not a clean end of the turn. */
|
|
226
226
|
const SETTLED_CUES: ReadonlySet<string> = new Set<CueStatus>(["done"]);
|
|
227
227
|
|
|
228
228
|
/** The status the CUE surfaces see: the session's own, unless a settled turn's
|
|
@@ -380,10 +380,9 @@ export interface Tab {
|
|
|
380
380
|
/** The chip's determinate progress bar (the 2px line on its bottom edge).
|
|
381
381
|
* Present on every chip, hidden until a percentage exists. */
|
|
382
382
|
progressEl: HTMLElement;
|
|
383
|
-
/** The last
|
|
383
|
+
/** The last percentage the server reported for this session, or
|
|
384
384
|
* PROGRESS_ABSENT. Held raw: whether it is currently SHOWN is a render-time
|
|
385
|
-
* question (renderedProgress)
|
|
386
|
-
* any title field. */
|
|
385
|
+
* question (renderedProgress). */
|
|
387
386
|
progress: number;
|
|
388
387
|
/** The chip's secondary activity mark (the rounded-square ring beside the
|
|
389
388
|
* status dot). Present on every chip, and costing no layout until a state
|
|
@@ -403,9 +402,10 @@ export interface Tab {
|
|
|
403
402
|
* offset away, and it stopped meaning the same line once the session produced
|
|
404
403
|
* output while backgrounded. In memory only. */
|
|
405
404
|
view: ViewMemory | null;
|
|
406
|
-
/** Sticky: true once this session emitted a genuine activity signal (OSC
|
|
407
|
-
*
|
|
408
|
-
*
|
|
405
|
+
/** Sticky: true once this session emitted a genuine activity signal (OSC 7501
|
|
406
|
+
* status, OSC 9;4 progress or a classified OSC 9 notification). Its activity
|
|
407
|
+
* dot is shown only while true; a session that never reports activity (a
|
|
408
|
+
* plain shell) keeps a clean, dot-less tab. Fed from the server's
|
|
409
409
|
* reportsActivity via applyStatus. */
|
|
410
410
|
reports: boolean;
|
|
411
411
|
/** The user's pinned name (the server's `pinnedTitle` wire field). Outranks
|
|
@@ -565,8 +565,8 @@ const SESSION_API_TIMEOUT_MS = 15000;
|
|
|
565
565
|
|
|
566
566
|
/** A non-2xx response from the session API, carrying what the server said. A
|
|
567
567
|
* host may TEMPORARILY refuse session creation (a 503 with `Retry-After` and a
|
|
568
|
-
* body message while it
|
|
569
|
-
*
|
|
568
|
+
* body message while it starts up), and a caller that only saw an `Error`
|
|
569
|
+
* read that as a broken page. */
|
|
570
570
|
export class SessionAPIError extends Error {
|
|
571
571
|
/** HTTP status, so a caller can branch on 503 (retry) vs 429 vs 5xx. */
|
|
572
572
|
readonly status: number;
|
|
@@ -14,7 +14,6 @@ import { activityPhrase, normalizeActivity, statusPhrase, statusRevealsDot } fro
|
|
|
14
14
|
// motion is spelled in — index.ts owns the mechanism, strip.ts owns the values,
|
|
15
15
|
// the same split switcher.ts keeps for the mobile swipe.
|
|
16
16
|
|
|
17
|
-
// Twin of marotte static-src/tabs-drag.ts's constants; change both or neither.
|
|
18
17
|
/** How long (ms) the pointer must have been un-moved before a position report at an
|
|
19
18
|
* unchanged position (a `dragover`, or the pointer path's re-report on this tick) is
|
|
20
19
|
* believed as "stopped". Small, because such a report is positive evidence of rest;
|
|
@@ -236,7 +235,7 @@ export function paintStatusDot(el: HTMLElement, status: string, reports: boolean
|
|
|
236
235
|
* a tooltip worded by activityPhrase. No state REMOVES the attribute, which is
|
|
237
236
|
* what collapses the mark's footprint to zero. The reveal is the mark's OWN
|
|
238
237
|
* state, not the status dot's reportsActivity gate: a background run is a fact
|
|
239
|
-
* independent of whether the program ever
|
|
238
|
+
* independent of whether the program ever reported status. */
|
|
240
239
|
export function paintActivityMark(el: HTMLElement, state: string, count: number): void {
|
|
241
240
|
const phrase = activityPhrase(state, count);
|
|
242
241
|
if (phrase === "") {
|
package/src/predict.ts
CHANGED
|
@@ -86,7 +86,7 @@ export function createPredictor(): Predictor {
|
|
|
86
86
|
if (predCol > 0) {
|
|
87
87
|
predCol--;
|
|
88
88
|
} else if (predRow > 0) {
|
|
89
|
-
//
|
|
89
|
+
// A line editor wraps a Backspace at col 0 to the previous row.
|
|
90
90
|
predRow--;
|
|
91
91
|
predCol = cols - 1;
|
|
92
92
|
}
|
|
@@ -10,14 +10,12 @@ import { type TabbedPresetOptions, buildTabbed } from "./tabbed.js";
|
|
|
10
10
|
/** Tabbed UI for an agent shell. Same features as presetTabbed, tuned for
|
|
11
11
|
* sessions that ARE agents:
|
|
12
12
|
* - `presumeReports`: every session's program is an agent that WILL report
|
|
13
|
-
* OSC 9;4 progress
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* plain shell never grows
|
|
18
|
-
*
|
|
19
|
-
* status classifier that maps OSC 9 notifications to done/needs-input. Tab
|
|
20
|
-
* names are server-side too but are not agent-specific — see buildTabbed. */
|
|
13
|
+
* activity (OSC 7501 status, OSC 9;4 progress or a classified OSC 9
|
|
14
|
+
* notification), so the idle activity dot
|
|
15
|
+
* shows from tab creation instead of popping in when the agent first
|
|
16
|
+
* reports; the server's sticky reportsActivity flag then merely confirms.
|
|
17
|
+
* (presetTabbed keeps the evidence-driven reveal: a plain shell never grows
|
|
18
|
+
* a meaningless dot.) */
|
|
21
19
|
export function presetAgentTabbed(opts: TabbedPresetOptions = {}): TerminalFeature<unknown>[] {
|
|
22
20
|
return buildTabbed(true, opts);
|
|
23
21
|
}
|
package/src/presets/tabbed.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* presetTabbed (and the shared tabbed composition builder): the full
|
|
3
3
|
* multi-session UI. Requires a server that speaks the session API
|
|
4
|
-
* (`/api/sessions`, `/ws?session=`, and the status SSE `/api/sessions/events`)
|
|
5
|
-
* such as `web-terminal-server` or `web-terminal-kiro`.
|
|
4
|
+
* (`/api/sessions`, `/ws?session=`, and the status SSE `/api/sessions/events`).
|
|
6
5
|
*
|
|
7
6
|
* @module
|
|
8
7
|
*/
|
|
@@ -26,22 +25,15 @@ export interface TabbedPresetOptions {
|
|
|
26
25
|
attentionIcons?: boolean;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
/** buildTabbed composes the tabbed UI: the single-pane touch features,
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* creation
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* which now differ only in presumed activity reporting (presumeReports).
|
|
39
|
-
*
|
|
40
|
-
* Titles are not a preset concern. The ENGINE resolves a session's name — its
|
|
41
|
-
* pinned name, a title its host pushed, the program's OSC window title, or its
|
|
42
|
-
* own foreground-process/cwd inference — and both presets render what it reports. A
|
|
43
|
-
* browser that re-derived any of that could only disagree with the server and
|
|
44
|
-
* with every other client attached to the same session. */
|
|
28
|
+
/** buildTabbed composes the tabbed UI: the single-pane touch features, a mobile
|
|
29
|
+
* toolbar opened from the tab bar's keyboard button, the activity monitor (the
|
|
30
|
+
* status SSE), tabs wired to both, and animations. The monitor is always
|
|
31
|
+
* included: a tab's dot shows only once its session reports activity (OSC
|
|
32
|
+
* 7501 status, OSC 9;4 progress or a classified OSC 9 notification), or from
|
|
33
|
+
* creation under presumeReports.
|
|
34
|
+
* The toolbar and monitor precede tabs, which reads their APIs via ctx.use.
|
|
35
|
+
* Titles are the engine's: a browser that re-derived one would disagree with
|
|
36
|
+
* the server and every other client. Shared with presetAgentTabbed. */
|
|
45
37
|
export function buildTabbed(
|
|
46
38
|
agentShell: boolean,
|
|
47
39
|
opts: TabbedPresetOptions = {},
|
package/src/presets.ts
CHANGED
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
* The tabbed presets (presetTabbed / presetAgentTabbed) share the same feature
|
|
12
12
|
* set (both include the activity monitor, which drives the per-tab activity
|
|
13
13
|
* dot). They differ in one flag, `presumeReports`: presetAgentTabbed presumes
|
|
14
|
-
* every session's program is an agent that WILL report OSC
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* every session's program is an agent that WILL report activity (OSC 7501
|
|
15
|
+
* status, OSC 9;4 progress or a classified OSC 9 notification), so the idle dot
|
|
16
|
+
* shows from tab creation instead of appearing seconds later on the first
|
|
17
|
+
* report, while presetTabbed keeps the evidence-driven reveal so a plain shell
|
|
18
|
+
* never grows a meaningless dot.
|
|
19
|
+
* Session NAMES are not a preset concern: the engine resolves them, including a
|
|
20
|
+
* title its host pushes. Which status a session holds is the server's: the UI
|
|
21
|
+
* renders the status it is sent.
|
|
21
22
|
*
|
|
22
23
|
* @module
|
|
23
24
|
*/
|