@cplieger/web-terminal-ui 5.4.3 → 5.4.5
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/package.json +1 -1
- package/src/features/tabs/index.ts +57 -19
- package/src/features/tabs/strip.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.5",
|
|
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",
|
|
@@ -233,11 +233,17 @@ async function createSessionHonouringRetry(
|
|
|
233
233
|
// First refusal always speaks; after that only every
|
|
234
234
|
// CREATE_RETRY_REANNOUNCE_MS, so a multi-minute wait is neither silent nor
|
|
235
235
|
// a toast storm.
|
|
236
|
+
//
|
|
237
|
+
// The suffix says the page is WAITING, not recovering from a failure. It
|
|
238
|
+
// used to say "retrying", which read as an error loop when the server had
|
|
239
|
+
// only said "not yet" -- and the first announcement fires before any retry
|
|
240
|
+
// has happened at all, so a user two seconds into a boot was told
|
|
241
|
+
// "retrying" about a request that had been made exactly once.
|
|
236
242
|
if (lastAnnouncedAt === 0 || elapsed - lastAnnouncedAt >= CREATE_RETRY_REANNOUNCE_MS) {
|
|
237
243
|
lastAnnouncedAt = elapsed;
|
|
238
|
-
const
|
|
239
|
-
ctx.toast(`${
|
|
240
|
-
ctx.announce(`${
|
|
244
|
+
const reason = err.serverMessage ?? "Server is not ready yet";
|
|
245
|
+
ctx.toast(`${reason}; waiting…`, 8000);
|
|
246
|
+
ctx.announce(`${reason}; waiting…`);
|
|
241
247
|
}
|
|
242
248
|
// Separate from the throttled pair above, and unthrottled: this writes the
|
|
243
249
|
// reason onto the loading OVERLAY, which is the only surface a user can
|
|
@@ -246,7 +252,7 @@ async function createSessionHonouringRetry(
|
|
|
246
252
|
// stacking notifications, so there is no storm to throttle, and repeating
|
|
247
253
|
// the same string is idempotent. This is what turns the black screen of a
|
|
248
254
|
// twenty-minute tools install into a screen that says why it is waiting.
|
|
249
|
-
ctx.loadingReason(`${err.serverMessage ?? "Server is not ready yet"}
|
|
255
|
+
ctx.loadingReason(`${err.serverMessage ?? "Server is not ready yet"}; waiting…`);
|
|
250
256
|
await new Promise((resolve) => {
|
|
251
257
|
window.setTimeout(resolve, err.retryAfterMs ?? CREATE_RETRY_FALLBACK_MS);
|
|
252
258
|
});
|
|
@@ -738,26 +744,58 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
738
744
|
// name, `list()` reports the real name, and clearing the progress needs no
|
|
739
745
|
// cleanup — the next paint simply stops adding it.
|
|
740
746
|
function relabelAll(): void {
|
|
741
|
-
//
|
|
742
|
-
//
|
|
743
|
-
|
|
747
|
+
// De-duplicate identical labels with a "(k)" suffix, numbered by the session's
|
|
748
|
+
// AGE — never by its position in the strip.
|
|
749
|
+
//
|
|
750
|
+
// The suffix used to be assigned by encounter order while walking tabList, which
|
|
751
|
+
// made it a property of the SLOT: two tabs both called "workspace" always read
|
|
752
|
+
// "workspace" then "workspace (2)" whichever way round they sat, so the label
|
|
753
|
+
// text stayed put while the sessions moved underneath it. Dragging one of them
|
|
754
|
+
// changed nothing on screen, and the reorder — which had worked correctly all
|
|
755
|
+
// along, server included — was invisible. It also meant dragging tab A could
|
|
756
|
+
// renumber tab B, which is the clearest sign the number belonged to the wrong
|
|
757
|
+
// thing.
|
|
758
|
+
//
|
|
759
|
+
// A label answers "which session is this", so it has to be a property of the
|
|
760
|
+
// session: stable while other tabs move, and travelling with its own tab. Age is
|
|
761
|
+
// that key. createdAt (the server's, tie-broken by id) rather than the local
|
|
762
|
+
// `born` counter, so every device and every reload agrees on which "workspace"
|
|
763
|
+
// is (2) — `born` is adoption order, which races.
|
|
764
|
+
//
|
|
765
|
+
// Numbering is recomputed over the LIVE group each time, so it stays contiguous:
|
|
766
|
+
// closing "(2)" of three renumbers the survivor rather than leaving a hole. That
|
|
767
|
+
// is a renumber with a visible cause the user just performed, which is the one
|
|
768
|
+
// kind that reads as sensible.
|
|
769
|
+
const groups = new Map<string, Tab[]>();
|
|
744
770
|
for (const t of tabList) {
|
|
745
771
|
const { text, fallback } = baseLabel(t);
|
|
746
|
-
|
|
747
|
-
|
|
772
|
+
// Fallback labels are not real names: several untitled tabs all read
|
|
773
|
+
// "New tab" with no numbering, which is the pre-existing behaviour.
|
|
774
|
+
if (fallback) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
const group = groups.get(text);
|
|
778
|
+
if (group) {
|
|
779
|
+
group.push(t);
|
|
780
|
+
} else {
|
|
781
|
+
groups.set(text, [t]);
|
|
748
782
|
}
|
|
749
783
|
}
|
|
750
|
-
const
|
|
751
|
-
for (const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (!fallback && (counts.get(text) ?? 0) > 1) {
|
|
755
|
-
const k = (seen.get(text) ?? 0) + 1;
|
|
756
|
-
seen.set(text, k);
|
|
757
|
-
if (k > 1) {
|
|
758
|
-
display = `${text} (${String(k)})`;
|
|
759
|
-
}
|
|
784
|
+
const ordinals = new Map<Tab, number>();
|
|
785
|
+
for (const group of groups.values()) {
|
|
786
|
+
if (group.length < 2) {
|
|
787
|
+
continue; // nothing to disambiguate from, so no suffix at all
|
|
760
788
|
}
|
|
789
|
+
[...group]
|
|
790
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id))
|
|
791
|
+
.forEach((t, i) => {
|
|
792
|
+
ordinals.set(t, i + 1);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
for (const t of tabList) {
|
|
796
|
+
const { text } = baseLabel(t);
|
|
797
|
+
const k = ordinals.get(t);
|
|
798
|
+
const display = k !== undefined && k > 1 ? `${text} (${String(k)})` : text;
|
|
761
799
|
t.display = display;
|
|
762
800
|
t.label.textContent = display;
|
|
763
801
|
// The accessible name carries the state and the percentage as well as
|
|
@@ -31,7 +31,7 @@ import { statusPhrase, statusRevealsDot } from "./model.js";
|
|
|
31
31
|
* pointer announces itself; this only filters the coincidence where one event of a
|
|
32
32
|
* sweep happens to land within REORDER_MOVE_EPS_PX of the previous one (a direction
|
|
33
33
|
* reversal, or a frame where the motion was almost entirely vertical). */
|
|
34
|
-
export const REORDER_STILL_MS =
|
|
34
|
+
export const REORDER_STILL_MS = 50;
|
|
35
35
|
|
|
36
36
|
/** Fallback guard (ms): commit the pending slot this long after the last MOVEMENT, for
|
|
37
37
|
* the case where `dragover` stops arriving altogether and no stationary event ever
|