Fix the stray window scrollbar, the Overview button overflow and show one card per realm

- The main area and the console log are now positioning contexts, so screen-reader-only
  text inside them can no longer extend the whole window (the second scrollbar in the update
  dialog and the window scroll on the Console database tab).
- The client button group on the server card wraps instead of leaving the card.
- With both worlds starting together, each realm gets its own card.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ДмитрийandClaude Sonnet 5.5 committed 2026-10-08 07:04:29 +05:00
1 parent 072a657649
commit f920ff8a59
3 files changed
+39 -5

No files matched your search

+1 -1
View File
@@ -103,7 +103,7 @@ export function ConsolePage({ serverId }: { serverId: string }) {
</Button>
</div>
<div ref={box} className="selectable mt-3 min-h-64 flex-1 overflow-auto rounded-card border border-line bg-black/40 p-3 font-mono text-xs leading-5" role="log" aria-live="off">
<div ref={box} className="selectable relative mt-3 min-h-64 flex-1 overflow-auto rounded-card border border-line bg-black/40 p-3 font-mono text-xs leading-5" role="log" aria-live="off">
{missing ? <p className="text-muted">{missing}</p> : lines.length === 0 ? <p className="text-muted">{t("con.empty")}</p> : lines.map((l, i) => (
<div key={i} className={cn("whitespace-pre-wrap break-all", TONE[l.level])}>
{l.level !== "info" && <span className="sr-only">{l.level}: </span>}
+37 -3
View File
@@ -68,6 +68,19 @@ export function Overview({ server, companions = true, onForget, onOpenUpdates, o
const t = useT();
const human = useHuman();
const [status, setStatus] = useState<StatusView | null>(null);
// With both worlds started together, each realm gets its own card; the first realm owns the database and auth.
const [realms, setRealms] = useState<{ first: string; second: string } | null>(null);
const bothWorlds = !!status?.observed.secondary_world;
useEffect(() => {
if (!bothWorlds) { setRealms(null); return; }
let alive = true;
void api.realmProfiles(server.id).then((r) => {
if (!alive) return;
const name = (m: "coa" | "wildcard") => (m === "coa" ? "Conquest of Azeroth" : "Wildcard");
setRealms({ first: name(r.active), second: name(r.active === "coa" ? "wildcard" : "coa") });
}).catch(() => { if (alive) setRealms(null); });
return () => { alive = false; };
}, [bothWorlds, server.id]);
const [action, setAction] = useState<Action>(null);
const [realmBusy, setRealmBusy] = useState(false);
const [failure, setFailure] = useState<Failure | null>(null);
@@ -220,7 +233,7 @@ export function Overview({ server, companions = true, onForget, onOpenUpdates, o
<RealmPicker serverId={server.id} running={anyUp} disabled={transitioning || upd.applying} onBusy={setRealmBusy} onChanged={() => { onRealmChanged(); void poll(); }} />
<Card className="mt-6 p-7">
<div className="mb-1 text-xs font-medium uppercase tracking-[0.14em] text-muted">{t("overview.server")}</div>
<div className="mb-1 text-xs font-medium uppercase tracking-[0.14em] text-muted">{realms ? `${realms.first} · ${t("overview.server")}` : t("overview.server")}</div>
<div className={cn("flex items-center gap-3 text-3xl font-semibold", tone)} role="status" aria-live="polite">
<span
aria-hidden
@@ -239,7 +252,6 @@ export function Overview({ server, companions = true, onForget, onOpenUpdates, o
<Row label={t("overview.database")} s={mysql} />
<Row label={t("overview.auth")} s={auth} />
<Row label={t("overview.world")} s={world} />
{secondary_world && <Row label={t("realm.secondWorld")} s={secondary_world} />}
</div>
<dl className="mt-4 grid grid-cols-2 gap-4 text-sm">
@@ -301,7 +313,7 @@ export function Overview({ server, companions = true, onForget, onOpenUpdates, o
{t("btn.stopSmall")}
</Button>
)}
<div className="ml-auto flex items-center gap-3">
<div className="ml-auto flex max-w-full flex-wrap items-center justify-end gap-3">
{client && <RealmlistMenu serverId={server.id} />}
{client && jobBusy ? (
<div
@@ -402,6 +414,28 @@ export function Overview({ server, companions = true, onForget, onOpenUpdates, o
)}
</Card>
{secondary_world && (
<Card className="mt-4 p-7">
<div className="mb-1 text-xs font-medium uppercase tracking-[0.14em] text-muted">{realms ? `${realms.second} · ${t("overview.server")}` : t("realm.secondWorld")}</div>
<div
className={cn("flex items-center gap-3 text-3xl font-semibold", secondary_world.state === "running" ? "text-ok" : secondary_world.state === "starting" || transitioning ? "text-warn" : "text-muted")}
role="status"
aria-live="polite"
>
<span aria-hidden className={cn("h-3 w-3 rounded-full", secondary_world.state === "running" ? "bg-ok" : secondary_world.state === "starting" || transitioning ? "bg-warn" : "bg-muted/50")} />
{secondary_world.state === "running" ? t("status.online") : secondary_world.state === "starting" ? t("status.starting") : t("status.offline")}
</div>
<div className="mt-5 divide-y divide-line border-y border-line">
<Row label={t("overview.world")} s={secondary_world} />
</div>
<dl className="mt-4 text-sm">
<dt className="text-muted">{t("overview.uptime")}</dt>
<dd className="mt-0.5 text-lg">{secondary_world.state === "running" ? formatUptime(secondary_world.uptime_secs) : "—"}</dd>
</dl>
<p className="mt-3 text-xs text-muted">{t("realm.bothRunning")}</p>
</Card>
)}
{conflict && !anyUp && (
<Card className="mt-4 border-warn/40 p-4" role="alert">
<p className="font-medium text-warn">{t("overview.portTitle")}</p>
+1 -1
View File
@@ -125,7 +125,7 @@ export function Shell(props: {
</div>
</nav>
<main className="h-full flex-1 overflow-y-auto px-10 py-8">
<main className="relative h-full flex-1 overflow-y-auto px-10 py-8">
{page === "overview" ? (
<Overview key={server.id} server={server} companions={botModule === "companions" && !hiddenPages.includes("bots")} onForget={() => props.onForget(server.id)} onOpenUpdates={() => setPage("settings")} onRealmChanged={reloadModules} onReport={() => setPage("report")} />
) : page === "bots" || page === "server" ? (