@celestia-island/hikari 0.55.141 → 0.55.142
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/components/HkConnectionStatus.tsx +3 -2
- package/src/components/HkStatusBar.test.tsx +82 -29
- package/src/components/HkStatusBar.tsx +52 -38
- package/src/i18n/locales/ar/components.json +0 -1
- package/src/i18n/locales/de/components.json +0 -1
- package/src/i18n/locales/en/components.json +0 -1
- package/src/i18n/locales/es/components.json +0 -1
- package/src/i18n/locales/fr/components.json +0 -1
- package/src/i18n/locales/ja/components.json +0 -1
- package/src/i18n/locales/ko/components.json +0 -1
- package/src/i18n/locales/pt/components.json +0 -1
- package/src/i18n/locales/ru/components.json +0 -1
- package/src/i18n/locales/zh-Hans/components.json +0 -1
- package/src/i18n/locales/zh-Hant/components.json +0 -1
package/package.json
CHANGED
|
@@ -126,8 +126,9 @@ export const HkConnectionStatus = defineComponent({
|
|
|
126
126
|
compact={props.compact}
|
|
127
127
|
>
|
|
128
128
|
{/* Host-appended popover actions (e.g. a manual page
|
|
129
|
-
refresh) ride the
|
|
130
|
-
|
|
129
|
+
refresh) ride the status row's right edge — the only
|
|
130
|
+
recovery button in the popover since the built-in
|
|
131
|
+
labelled retry retired (2026-10-11). */}
|
|
131
132
|
{{ actions: () => slots.actions?.() }}
|
|
132
133
|
</HkStatusBar>
|
|
133
134
|
{bs && (
|
|
@@ -422,7 +422,7 @@ describe("HkStatusBar", () => {
|
|
|
422
422
|
}
|
|
423
423
|
});
|
|
424
424
|
|
|
425
|
-
it("
|
|
425
|
+
it("retires the built-in reconnect button; the hint text carries the retry", async () => {
|
|
426
426
|
const onRetry = vi.fn();
|
|
427
427
|
const container = mountBar({
|
|
428
428
|
version: "1.2.3",
|
|
@@ -436,20 +436,62 @@ describe("HkStatusBar", () => {
|
|
|
436
436
|
.dispatchEvent(new MouseEvent("mouseenter"));
|
|
437
437
|
await nextTick();
|
|
438
438
|
const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel")!;
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
// the
|
|
443
|
-
|
|
444
|
-
expect(
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
expect(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
439
|
+
// 2026-10-11 user direction: the popover must not stack a built-in
|
|
440
|
+
// "Reconnect now" beside the host's own refresh — the light itself
|
|
441
|
+
// retries on click/tap, so the popover keeps ONE recovery action
|
|
442
|
+
// (the host's, via the slot).
|
|
443
|
+
expect(panel.querySelector("[data-status-actions]")).toBeNull();
|
|
444
|
+
expect(panel.textContent).not.toContain("Reconnect now");
|
|
445
|
+
// The fallback hint is back for hosts without a slot action — but it
|
|
446
|
+
// is a REAL control now: the original hint was retired because its
|
|
447
|
+
// clicks went nowhere, so the text carries the retry itself.
|
|
448
|
+
const hint = panel.querySelector<HTMLElement>("[role='button']")!;
|
|
449
|
+
expect(hint.textContent).toContain("Click to retry");
|
|
450
|
+
hint.click();
|
|
451
|
+
expect(onRetry, "hint click retries").toHaveBeenCalledTimes(1);
|
|
452
|
+
// Keyboard parity: the hint is a role=button control, so Enter (and
|
|
453
|
+
// Space) carry the same retry.
|
|
454
|
+
hint.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
|
|
455
|
+
expect(onRetry, "Enter on the hint retries").toHaveBeenCalledTimes(2);
|
|
456
|
+
hint.dispatchEvent(new KeyboardEvent("keydown", { key: " ", bubbles: true }));
|
|
457
|
+
expect(onRetry, "Space on the hint retries").toHaveBeenCalledTimes(2 + 1);
|
|
458
|
+
hint.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
|
|
459
|
+
expect(onRetry, "other keys are inert").toHaveBeenCalledTimes(3);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("hosts the actions cluster on the status row's right side, not a bottom hairline row", async () => {
|
|
463
|
+
const container = document.createElement("div");
|
|
464
|
+
document.body.appendChild(container);
|
|
465
|
+
const app = createApp({
|
|
466
|
+
render: () =>
|
|
467
|
+
h(HkStatusBar, {
|
|
468
|
+
version: "1.2.3",
|
|
469
|
+
connectionStatus: "disconnected",
|
|
470
|
+
connectionInfo: INFO,
|
|
471
|
+
onRetry: () => {},
|
|
472
|
+
}, {
|
|
473
|
+
actions: () => h("button", { class: "host-refresh" }, "Refresh page"),
|
|
474
|
+
}),
|
|
475
|
+
});
|
|
476
|
+
app.mount(container);
|
|
477
|
+
mounts.push({ app, container });
|
|
478
|
+
await nextTick();
|
|
479
|
+
container
|
|
480
|
+
.querySelector<HTMLElement>(".s-status-bar-tag")!
|
|
481
|
+
.dispatchEvent(new MouseEvent("mouseenter"));
|
|
482
|
+
await nextTick();
|
|
483
|
+
const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel")!;
|
|
484
|
+
const clusters = panel.querySelectorAll("[data-status-actions]");
|
|
485
|
+
expect(clusters.length, "exactly one actions cluster").toBe(1);
|
|
486
|
+
// The cluster lives INSIDE the row that carries the status text —
|
|
487
|
+
// status left, actions right, no extra vertical row.
|
|
488
|
+
const statusRow = clusters[0]!.closest("div")!;
|
|
489
|
+
expect(statusRow.textContent).toContain("Disconnected");
|
|
490
|
+
// The old bottom row announced itself with a hairline top border;
|
|
491
|
+
// nothing in the panel may carry it anymore.
|
|
492
|
+
const bordered = [...panel.querySelectorAll<HTMLElement>("*[style*='border']")]
|
|
493
|
+
.filter((el) => el.style.borderTop.includes("solid"));
|
|
494
|
+
expect(bordered, "no hairline actions row remains").toEqual([]);
|
|
453
495
|
});
|
|
454
496
|
|
|
455
497
|
it("keeps the fallback hint when no onRetry is wired", async () => {
|
|
@@ -517,27 +559,38 @@ describe("HkStatusBar", () => {
|
|
|
517
559
|
expect(panel!.querySelector("[data-status-actions]")).toBeNull();
|
|
518
560
|
});
|
|
519
561
|
|
|
520
|
-
it("keeps
|
|
521
|
-
const
|
|
522
|
-
const container =
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
562
|
+
it("keeps host actions available while connection info is still fetching", async () => {
|
|
563
|
+
const onRefresh = vi.fn();
|
|
564
|
+
const container = document.createElement("div");
|
|
565
|
+
document.body.appendChild(container);
|
|
566
|
+
const app = createApp({
|
|
567
|
+
render: () =>
|
|
568
|
+
h(HkStatusBar, {
|
|
569
|
+
version: "1.2.3",
|
|
570
|
+
connectionStatus: "disconnected",
|
|
571
|
+
connectionInfo: null,
|
|
572
|
+
}, {
|
|
573
|
+
actions: () => h("button", { class: "host-refresh", onClick: onRefresh }, "Refresh page"),
|
|
574
|
+
}),
|
|
527
575
|
});
|
|
576
|
+
app.mount(container);
|
|
577
|
+
mounts.push({ app, container });
|
|
528
578
|
await nextTick();
|
|
529
579
|
container
|
|
530
580
|
.querySelector<HTMLElement>(".s-status-bar-tag")!
|
|
531
581
|
.dispatchEvent(new MouseEvent("mouseenter"));
|
|
532
582
|
await nextTick();
|
|
533
583
|
const panel = document.body.querySelector<HTMLElement>(".hk-popover-panel")!;
|
|
534
|
-
// The
|
|
535
|
-
// drops connectionInfo to null is exactly when the
|
|
536
|
-
// most needed.
|
|
537
|
-
const
|
|
538
|
-
expect(
|
|
539
|
-
|
|
540
|
-
expect(
|
|
584
|
+
// The cluster must live OUTSIDE the info/fetching ternary: an outage
|
|
585
|
+
// that drops connectionInfo to null is exactly when the recovery
|
|
586
|
+
// action is most needed. It rides the fetching line's right edge.
|
|
587
|
+
const cluster = panel.querySelector<HTMLElement>("[data-status-actions]");
|
|
588
|
+
expect(cluster, "actions cluster renders without info").toBeTruthy();
|
|
589
|
+
// Same line as the fetching text: label left, actions right.
|
|
590
|
+
expect(cluster!.closest("div")!.textContent).toContain("Fetching connection info");
|
|
591
|
+
const button = cluster!.querySelector<HTMLButtonElement>("button")!;
|
|
592
|
+
button.click();
|
|
593
|
+
expect(onRefresh).toHaveBeenCalledTimes(1);
|
|
541
594
|
});
|
|
542
595
|
|
|
543
596
|
it("no actions row when the slot renders nothing (wrapper forwards an empty slot)", async () => {
|
|
@@ -5,7 +5,6 @@ import { useI18n } from "../i18n/context";
|
|
|
5
5
|
import { HIKARI_FONT_MONO } from "../theme/fontContext";
|
|
6
6
|
|
|
7
7
|
import HPopover from "./HkPopover";
|
|
8
|
-
import HkButton from "./HkButton";
|
|
9
8
|
import type { HkConnectionInfo } from "./HkConnectionInfo";
|
|
10
9
|
import { HkCountdownDigit } from "./HkCountdownDigit";
|
|
11
10
|
import { HkPlaceholderMarquee } from "./HkPlaceholderMarquee";
|
|
@@ -382,22 +381,24 @@ export const HkStatusBar = defineComponent({
|
|
|
382
381
|
: t("hikari::statusBar.disconnected", "Disconnected");
|
|
383
382
|
|
|
384
383
|
const connecting = mode === "reconnecting" || mode === "connecting";
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
// The built-in
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
//
|
|
384
|
+
// Recovery actions (2026-10-11 user direction): the old bottom
|
|
385
|
+
// hairline row stacked a built-in "Reconnect now" button beside the
|
|
386
|
+
// host's own refresh icon — on an outage the popover read as TWO
|
|
387
|
+
// buttons doing one job. The built-in labelled button is RETIRED:
|
|
388
|
+
// the traffic light itself already retries on click/tap, and hosts
|
|
389
|
+
// append their single recovery action (e.g. a manual page refresh)
|
|
390
|
+
// through the `actions` slot. The cluster now rides the FIRST
|
|
391
|
+
// popover row's right side — status text left, actions right — so
|
|
392
|
+
// no extra row of vertical space is spent on it.
|
|
393
|
+
// Gate the cluster on RENDERED content, not on the slot function's
|
|
393
394
|
// existence: HkConnectionStatus forwards `actions` unconditionally,
|
|
394
|
-
// so a function-existence gate would paint an empty
|
|
395
|
-
//
|
|
395
|
+
// so a function-existence gate would paint an empty cluster in the
|
|
396
|
+
// connected state for every host that passed no #actions.
|
|
396
397
|
const actionVnodes = slots.actions?.();
|
|
397
398
|
const hasHostActions = Array.isArray(actionVnodes)
|
|
398
399
|
? actionVnodes.length > 0
|
|
399
400
|
: Boolean(actionVnodes);
|
|
400
|
-
const hasActionsRow =
|
|
401
|
+
const hasActionsRow = hasHostActions;
|
|
401
402
|
|
|
402
403
|
const pv = hkMergeVersionDisplay(props.version, props.panelBuildHash);
|
|
403
404
|
const ev = props.engineVersion;
|
|
@@ -494,6 +495,21 @@ export const HkStatusBar = defineComponent({
|
|
|
494
495
|
{latency} ms
|
|
495
496
|
</span>
|
|
496
497
|
)}
|
|
498
|
+
{hasActionsRow && (
|
|
499
|
+
<span
|
|
500
|
+
data-status-actions
|
|
501
|
+
style={{
|
|
502
|
+
display: "inline-flex",
|
|
503
|
+
alignItems: "center",
|
|
504
|
+
gap: "8px",
|
|
505
|
+
// No latency chip to push against: the cluster
|
|
506
|
+
// still lands on the row's right edge.
|
|
507
|
+
marginLeft: latency !== null ? undefined : "auto",
|
|
508
|
+
}}
|
|
509
|
+
>
|
|
510
|
+
{actionVnodes}
|
|
511
|
+
</span>
|
|
512
|
+
)}
|
|
497
513
|
</div>
|
|
498
514
|
{connecting && attempt > 0 && (
|
|
499
515
|
<div style={{ display: "flex", alignItems: "center", gap: "4px", color: "rgb(var(--color-warning))", fontSize: "0.6875rem", marginBottom: "4px" }}>
|
|
@@ -509,8 +525,21 @@ export const HkStatusBar = defineComponent({
|
|
|
509
525
|
)}
|
|
510
526
|
</div>
|
|
511
527
|
)}
|
|
512
|
-
{mode === "disconnected" && !
|
|
513
|
-
|
|
528
|
+
{mode === "disconnected" && !hasActionsRow && (
|
|
529
|
+
// Fallback for hosts without a slot action — and a REAL
|
|
530
|
+
// control this time: the original hint was retired
|
|
531
|
+
// because its clicks went nowhere (the popover body
|
|
532
|
+
// teleports to <body>), so the text now carries the
|
|
533
|
+
// retry itself, echo guard included.
|
|
534
|
+
<div
|
|
535
|
+
role="button"
|
|
536
|
+
tabindex={0}
|
|
537
|
+
onClick={onRetryButtonClick}
|
|
538
|
+
onKeydown={(e: KeyboardEvent) => {
|
|
539
|
+
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onRetryButtonClick(); }
|
|
540
|
+
}}
|
|
541
|
+
style={{ fontStyle: "italic", fontSize: "0.6875rem", marginBottom: "4px", opacity: 0.7, cursor: "pointer" }}
|
|
542
|
+
>
|
|
514
543
|
{t("hikari::statusBar.clickReconnect", "Click to retry")}
|
|
515
544
|
</div>
|
|
516
545
|
)}
|
|
@@ -565,31 +594,16 @@ export const HkStatusBar = defineComponent({
|
|
|
565
594
|
))}
|
|
566
595
|
</>
|
|
567
596
|
) : (
|
|
568
|
-
<div style={{
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
display: "flex",
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
marginTop: "8px",
|
|
578
|
-
paddingTop: "8px",
|
|
579
|
-
borderTop: "1px solid var(--border-faint, rgb(var(--color-border) / 10%))",
|
|
580
|
-
}}
|
|
581
|
-
>
|
|
582
|
-
{showRetryButton && (
|
|
583
|
-
<HkButton
|
|
584
|
-
size="xs"
|
|
585
|
-
variant="secondary"
|
|
586
|
-
icon="RotateCw"
|
|
587
|
-
onClick={onRetryButtonClick}
|
|
588
|
-
>
|
|
589
|
-
{t("hikari::statusBar.retryNow", "Reconnect now")}
|
|
590
|
-
</HkButton>
|
|
597
|
+
<div style={{ display: "flex", alignItems: "center", gap: "6px", marginBottom: "6px" }}>
|
|
598
|
+
<span style={{ opacity: 0.5 }}>{t("hikari::statusBar.fetching", "Fetching connection info...")}</span>
|
|
599
|
+
{hasActionsRow && (
|
|
600
|
+
// An outage that drops connectionInfo to null is exactly
|
|
601
|
+
// when the recovery action is most needed — the cluster
|
|
602
|
+
// rides the fetching line's right edge.
|
|
603
|
+
<span data-status-actions style={{ display: "inline-flex", alignItems: "center", gap: "8px", marginLeft: "auto" }}>
|
|
604
|
+
{actionVnodes}
|
|
605
|
+
</span>
|
|
591
606
|
)}
|
|
592
|
-
{hasHostActions ? actionVnodes : null}
|
|
593
607
|
</div>
|
|
594
608
|
)}
|
|
595
609
|
</div>
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "غير معروف",
|
|
152
152
|
"hikari::statusBar.backend.warn": "متدهور",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "انقر لإعادة المحاولة",
|
|
154
|
-
"hikari::statusBar.retryNow": "أعد الاتصال الآن",
|
|
155
154
|
"hikari::statusBar.connected": "متصل",
|
|
156
155
|
"hikari::statusBar.connecting": "جارٍ الاتصال...",
|
|
157
156
|
"hikari::statusBar.disconnected": "غير متصل",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "Unbekannt",
|
|
152
152
|
"hikari::statusBar.backend.warn": "Beeinträchtigt",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "Zum Wiederholen klicken",
|
|
154
|
-
"hikari::statusBar.retryNow": "Jetzt neu verbinden",
|
|
155
154
|
"hikari::statusBar.connected": "Verbunden",
|
|
156
155
|
"hikari::statusBar.connecting": "Verbinde...",
|
|
157
156
|
"hikari::statusBar.disconnected": "Getrennt",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "Unknown",
|
|
152
152
|
"hikari::statusBar.backend.warn": "Degraded",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "Click to retry",
|
|
154
|
-
"hikari::statusBar.retryNow": "Reconnect now",
|
|
155
154
|
"hikari::statusBar.connected": "Connected",
|
|
156
155
|
"hikari::statusBar.connecting": "Connecting...",
|
|
157
156
|
"hikari::statusBar.disconnected": "Disconnected",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "Desconocido",
|
|
152
152
|
"hikari::statusBar.backend.warn": "Degradado",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "Haga clic para reintentar",
|
|
154
|
-
"hikari::statusBar.retryNow": "Reconectar ahora",
|
|
155
154
|
"hikari::statusBar.connected": "Conectado",
|
|
156
155
|
"hikari::statusBar.connecting": "Conectando...",
|
|
157
156
|
"hikari::statusBar.disconnected": "Desconectado",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "Inconnu",
|
|
152
152
|
"hikari::statusBar.backend.warn": "Dégradé",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "Cliquez pour réessayer",
|
|
154
|
-
"hikari::statusBar.retryNow": "Reconnecter maintenant",
|
|
155
154
|
"hikari::statusBar.connected": "Connecté",
|
|
156
155
|
"hikari::statusBar.connecting": "Connexion...",
|
|
157
156
|
"hikari::statusBar.disconnected": "Déconnecté",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "不明",
|
|
152
152
|
"hikari::statusBar.backend.warn": "低下",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "クリックして再接続",
|
|
154
|
-
"hikari::statusBar.retryNow": "今すぐ再接続",
|
|
155
154
|
"hikari::statusBar.connected": "接続済み",
|
|
156
155
|
"hikari::statusBar.connecting": "接続中...",
|
|
157
156
|
"hikari::statusBar.disconnected": "切断",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "알 수 없음",
|
|
152
152
|
"hikari::statusBar.backend.warn": "저하",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "클릭하여 재연결",
|
|
154
|
-
"hikari::statusBar.retryNow": "지금 재연결",
|
|
155
154
|
"hikari::statusBar.connected": "연결됨",
|
|
156
155
|
"hikari::statusBar.connecting": "연결 중...",
|
|
157
156
|
"hikari::statusBar.disconnected": "연결 끊김",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "Desconhecido",
|
|
152
152
|
"hikari::statusBar.backend.warn": "Degradado",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "Clique para tentar novamente",
|
|
154
|
-
"hikari::statusBar.retryNow": "Reconectar agora",
|
|
155
154
|
"hikari::statusBar.connected": "Conectado",
|
|
156
155
|
"hikari::statusBar.connecting": "Conectando...",
|
|
157
156
|
"hikari::statusBar.disconnected": "Desconectado",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "Неизвестно",
|
|
152
152
|
"hikari::statusBar.backend.warn": "Деградация",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "Нажмите, чтобы повторить",
|
|
154
|
-
"hikari::statusBar.retryNow": "Переподключиться сейчас",
|
|
155
154
|
"hikari::statusBar.connected": "Подключено",
|
|
156
155
|
"hikari::statusBar.connecting": "Подключение...",
|
|
157
156
|
"hikari::statusBar.disconnected": "Отключено",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "未知",
|
|
152
152
|
"hikari::statusBar.backend.warn": "降级",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "点击以尝试重连",
|
|
154
|
-
"hikari::statusBar.retryNow": "立即重连",
|
|
155
154
|
"hikari::statusBar.connected": "已连接",
|
|
156
155
|
"hikari::statusBar.connecting": "连接中...",
|
|
157
156
|
"hikari::statusBar.disconnected": "断开",
|
|
@@ -151,7 +151,6 @@
|
|
|
151
151
|
"hikari::statusBar.backend.unknown": "未知",
|
|
152
152
|
"hikari::statusBar.backend.warn": "降級",
|
|
153
153
|
"hikari::statusBar.clickReconnect": "點擊以嘗試重連",
|
|
154
|
-
"hikari::statusBar.retryNow": "立即重連",
|
|
155
154
|
"hikari::statusBar.connected": "已連線",
|
|
156
155
|
"hikari::statusBar.connecting": "連線中...",
|
|
157
156
|
"hikari::statusBar.disconnected": "斷開",
|