@cplieger/web-terminal-ui 6.1.1 → 6.1.3

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/css/page.css CHANGED
@@ -73,10 +73,15 @@ body {
73
73
  --wt-loading-bg: #000;
74
74
  --wt-loading-bar-inline-size: min(220px, 62vw);
75
75
  --wt-loading-bar-block-size: 5px;
76
- /* Symmetric ease-in-out-cubic, deliberately NOT one of the component easing
77
- tokens: those are all asymmetric (they settle at one end), which reads as a
78
- stutter on a looping indeterminate sweep. */
79
- --wt-loading-sweep-easing: cubic-bezier(0.65, 0, 0.35, 1);
76
+ /* Symmetric, deliberately NOT one of the component easing tokens: those are all
77
+ asymmetric (they settle at one end), which reads as a stutter on a looping
78
+ indeterminate sweep. It is also deliberately NOT an ease-in-out-cubic
79
+ (`cubic-bezier(0.65, 0, 0.35, 1)`, what this was): a curve with zero velocity
80
+ at both ends spends its two slow phases exactly where the sweep is clipped
81
+ off-screen, which measured as 315 ms of blank track at each end of every
82
+ 1.5 s lap. Non-zero end velocity keeps the symmetry and cuts that to 90 ms,
83
+ below the point where the loop reads as a stall. */
84
+ --wt-loading-sweep-easing: cubic-bezier(0.3, 0.25, 0.7, 0.75);
80
85
  --wt-loading-sweep-duration: 1.5s;
81
86
 
82
87
  position: fixed;
@@ -129,6 +134,15 @@ body {
129
134
  content: "";
130
135
  position: absolute;
131
136
  inset-block: 0;
137
+ /* Load-bearing, and the whole reason the sweep used to enter mid-track: with
138
+ both inline offsets `auto` an absolutely positioned box falls back to its
139
+ STATIC position, and .wt-loading sets `text-align: center`, which centres
140
+ that static position inside the 220px track. The keyframes below are written
141
+ against the track's left edge, so every offset was silently shifted right by
142
+ (220 - 92.4) / 2 = 64px: the sweep was already visible at t=0 near the left
143
+ of centre, and it left the track at ~73% of the cycle. Pinning the origin is
144
+ what makes translateX(-100%) mean "one full sweep-width off the left edge". */
145
+ inset-inline-start: 0;
132
146
  inline-size: 42%;
133
147
  border-radius: inherit;
134
148
  background: linear-gradient(
@@ -167,12 +181,17 @@ body {
167
181
  opacity: 0;
168
182
  }
169
183
 
184
+ /* Offsets are percentages of the SWEEP (42% of the track), not of the track, so
185
+ -100% is exactly one sweep-width left of the origin (fully clipped, flush with
186
+ the left edge) and 100% / 0.42 = 238% puts its left edge flush with the right
187
+ edge. Flush at both ends rather than over-travelled: every pixel of travel
188
+ outside the track is dead time on a screen whose only job is to look alive. */
170
189
  @keyframes wt-loading-slide {
171
190
  0% {
172
- transform: translateX(-110%);
191
+ transform: translateX(-100%);
173
192
  }
174
193
  100% {
175
- transform: translateX(250%);
194
+ transform: translateX(238%);
176
195
  }
177
196
  }
178
197
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "6.1.1",
3
+ "version": "6.1.3",
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",
@@ -56,7 +56,7 @@
56
56
  "eslint": "10.8.1",
57
57
  "fast-check": "4.9.0",
58
58
  "happy-dom": "20.11.2",
59
- "html-validate": "11.6.2",
59
+ "html-validate": "11.7.0",
60
60
  "knip": "6.32.2",
61
61
  "prettier": "3.9.6",
62
62
  "stylelint": "17.14.1",
@@ -152,7 +152,13 @@ export interface TabsOptions {
152
152
  *
153
153
  * Not every platform honours it: Safari caches the first icon it fetched and
154
154
  * ignores later changes, and an installed app has no tab icon at all. The
155
- * title count is not gated on this, so those cases lose nothing. */
155
+ * title count is not gated on this, so those cases lose nothing.
156
+ *
157
+ * The links are restored on `pagehide` as well as on teardown, because a
158
+ * browser remembers one icon per URL and renders it for the bookmark, the
159
+ * history row and the new-tab tile. Best-effort there, since the write races
160
+ * the unload. Deliberately not on `freeze`: a frozen background tab is still in
161
+ * the strip rendering its icon, which is the case this exists for. */
156
162
  attentionIcons?: boolean;
157
163
  }
158
164
 
@@ -601,6 +607,39 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
601
607
  }
602
608
  document.addEventListener("visibilitychange", onPageVisible);
603
609
 
610
+ // onPageGone hands the page's own icon back before the page GOES AWAY,
611
+ // which is a different question from the page being hidden and needs a
612
+ // different event. A browser remembers ONE icon per URL and renders it for
613
+ // the bookmark, the history row and the new-tab tile, so a tab closed while
614
+ // a cue was lit leaves a status variant standing in for the app until the
615
+ // page is next loaded.
616
+ //
617
+ // `pagehide`, and deliberately NOT `freeze`. freeze fires for a background
618
+ // tab the browser is conserving resources on (Android after five minutes in
619
+ // the background; desktop for a collapsed tab group and, since Chrome 133, a
620
+ // CPU-heavy tab under Energy Saver), and that tab is STILL in the strip
621
+ // rendering its icon — restoring there would blank the cue in exactly the
622
+ // case the cue exists for. pagehide fires on unload, tab close and bfcache
623
+ // entry, where no strip entry is left to render.
624
+ //
625
+ // Through apply() rather than the icon sink directly, because the sinks are
626
+ // change-gated on the last applied value: writing behind that memo would
627
+ // leave it believing the variant is still up, and a page restored from the
628
+ // bfcache would then never repaint it. Which is what onPageBack is for — a
629
+ // bfcache restore re-runs the fold, so the cue returns for a page that did
630
+ // not actually go away. Best-effort on a real unload, where the icon write
631
+ // races the teardown, and a no-op when nothing was lit.
632
+ function onPageGone(): void {
633
+ attention.apply(NO_ATTENTION);
634
+ }
635
+ function onPageBack(event: PageTransitionEvent): void {
636
+ if (event.persisted) {
637
+ paintAttention();
638
+ }
639
+ }
640
+ window.addEventListener("pagehide", onPageGone);
641
+ window.addEventListener("pageshow", onPageBack);
642
+
604
643
  // paintAttention re-derives the whole attention state from the tab list and
605
644
  // this viewer's acknowledgements, and hands it to the surfaces.
606
645
  //
@@ -4115,6 +4154,8 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
4115
4154
  offMenuKey();
4116
4155
  document.removeEventListener("click", onDocClickMenu);
4117
4156
  document.removeEventListener("visibilitychange", onPageVisible);
4157
+ window.removeEventListener("pagehide", onPageGone);
4158
+ window.removeEventListener("pageshow", onPageBack);
4118
4159
  document.removeEventListener("contextmenu", onDocContextMenu);
4119
4160
  document.removeEventListener("pointerup", onDocTapDismiss, true);
4120
4161
  document.removeEventListener("dragover", onDocTabDrop);