Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fffd2a55f3 | ||
|
|
563a35d2ec | ||
|
|
9dead5652e |
@@ -105,7 +105,7 @@ describe("MultiView", () => {
|
||||
expect(screen.getByText("Tab 1 · 1")).toBeTruthy();
|
||||
const pane = document.querySelector('[data-session-id="s9"]');
|
||||
expect(pane).toBeTruthy();
|
||||
expect(pane!.querySelector(".bg-preview-tint")).toBeTruthy();
|
||||
expect(pane!.classList.contains("is-preview")).toBe(true);
|
||||
expect(screen.getByText("preview")).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
@@ -188,8 +188,9 @@ function Pane({
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"mv-pane flex h-full min-h-0 min-w-0 flex-col border " +
|
||||
(isFocused && !isPreview ? "border-accent" : "border-border-strong")
|
||||
"mv-pane flex h-full min-h-0 min-w-0 flex-col" +
|
||||
(isFocused ? " is-focused" : "") +
|
||||
(isPreview ? " is-preview" : "")
|
||||
}
|
||||
data-leaf-id={leaf.id}
|
||||
data-session-id={leaf.sessionId ?? undefined}
|
||||
@@ -201,10 +202,7 @@ function Pane({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
"mv-pane-header flex items-center justify-between gap-sp-2 border-b border-border-strong px-sp-2 py-sp-1 text-fs-12 " +
|
||||
(isPreview ? "bg-preview-tint text-preview" : "bg-surface")
|
||||
}
|
||||
className="mv-pane-header attach-header flex items-center justify-between gap-sp-2 border-b border-border-strong px-sp-2 py-sp-1 text-fs-12"
|
||||
>
|
||||
{leaf.sessionId ? (
|
||||
<PaneTitle sessionId={leaf.sessionId} session={session} />
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface PaneTitleProps {
|
||||
export function PaneTitle({ sessionId, session, className }: PaneTitleProps) {
|
||||
if (!session) {
|
||||
return (
|
||||
<span className={className ?? "at-title"} title={sessionId}>
|
||||
<span className={className ?? "attach-title"} title={sessionId}>
|
||||
session <span className="sid">{sessionId.slice(0, 8)}</span>
|
||||
</span>
|
||||
);
|
||||
@@ -27,7 +27,7 @@ export function PaneTitle({ sessionId, session, className }: PaneTitleProps) {
|
||||
const icon = tool ? toolIconSvg(tool) : null;
|
||||
|
||||
return (
|
||||
<span className={className ?? "at-title"} title={`${name} · ${session.command} (${sessionId})`}>
|
||||
<span className={className ?? "attach-title"} title={`${name} · ${session.command} (${sessionId})`}>
|
||||
<span className="at-name">{name}</span>
|
||||
{showCommand && (
|
||||
<span className="at-cmd">
|
||||
|
||||
@@ -44,14 +44,17 @@ export interface SessionCardProps {
|
||||
onRenameCancel(): void;
|
||||
}
|
||||
|
||||
/** The fill ladder from docs/SIDEBAR.md "Which row is filled", furthest down
|
||||
* wins. Written as one ordered run so the precedence is the order it reads in. */
|
||||
function rowFill(p: SessionCardProps): string {
|
||||
if (p.active) return "bg-accent-fill";
|
||||
if (p.previewing) return "bg-preview-tint";
|
||||
if (p.open) return "bg-accent/[0.06]";
|
||||
if (p.highlighted) return "bg-preview-tint";
|
||||
return "";
|
||||
/** The fill ladder from docs/SIDEBAR.md "Which row is filled" — the CSS
|
||||
* cascade in styles.css carries the actual fills via `.is-active`, `.is-open`,
|
||||
* `.is-previewing` (all inside `:where()` so source order is the precedence).
|
||||
* We just apply the right class names here. */
|
||||
function rowClasses(p: SessionCardProps): string {
|
||||
const cls: string[] = [];
|
||||
if (p.active) cls.push("is-active");
|
||||
if (p.open) cls.push("is-open");
|
||||
if (p.previewing) cls.push("is-previewing");
|
||||
if (p.highlighted) cls.push("is-highlighted");
|
||||
return cls.join(" ");
|
||||
}
|
||||
|
||||
export function SessionCard(props: SessionCardProps) {
|
||||
@@ -62,7 +65,7 @@ export function SessionCard(props: SessionCardProps) {
|
||||
|
||||
return (
|
||||
<a
|
||||
className={`session-card tree-leaf no-underline ${rowFill(props)} ${
|
||||
className={`session-card tree-leaf no-underline ${rowClasses(props)} ${
|
||||
running ? "" : "opacity-60"
|
||||
}`}
|
||||
href={`#/attach/${encodeURIComponent(s.id)}`}
|
||||
|
||||
@@ -196,12 +196,24 @@ export function useStructuredSession(
|
||||
connect();
|
||||
};
|
||||
|
||||
// Mobile browsers suspend timers and silently kill WebSockets in the
|
||||
// background. Kick a fresh connect when the page becomes visible again.
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState !== "visible" || disposed) return;
|
||||
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) return;
|
||||
cancelTimer();
|
||||
setStatus("reconnecting");
|
||||
connect();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisibility);
|
||||
|
||||
setStatus("connecting");
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
reconnectRef.current = () => {};
|
||||
document.removeEventListener("visibilitychange", onVisibility);
|
||||
cancelTimer();
|
||||
closeSocket();
|
||||
};
|
||||
|
||||
@@ -302,6 +302,24 @@ export const TerminalPane = forwardRef<TerminalPaneHandle, TerminalPaneProps>(
|
||||
ta?.addEventListener("focus", onTaFocus);
|
||||
ta?.addEventListener("blur", onTaBlur);
|
||||
|
||||
// Mobile browsers (Android especially) suspend timers and silently kill
|
||||
// WebSockets when the app goes to background. The backoff timer set by
|
||||
// onclose is frozen too, so recovery stalls until visibility returns. Kick
|
||||
// a fresh connect immediately when the page becomes visible again.
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState !== "visible" || disposed) return;
|
||||
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) return;
|
||||
// Cancel any pending backoff — we're reconnecting right now.
|
||||
if (reconnectTimer !== null) {
|
||||
window.clearTimeout(reconnectTimer);
|
||||
reconnectTimer = null;
|
||||
}
|
||||
reconnectMs = INITIAL_RECONNECT_MS;
|
||||
onStatusChange?.("reconnecting");
|
||||
connect();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisibility);
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
const visual = window.visualViewport ?? null;
|
||||
visual?.addEventListener("resize", onResize);
|
||||
@@ -326,6 +344,7 @@ export const TerminalPane = forwardRef<TerminalPaneHandle, TerminalPaneProps>(
|
||||
reconnectTimer = null;
|
||||
}
|
||||
ro.disconnect();
|
||||
document.removeEventListener("visibilitychange", onVisibility);
|
||||
window.removeEventListener("resize", onResize);
|
||||
visual?.removeEventListener("resize", onResize);
|
||||
ta?.removeEventListener("focus", onTaFocus);
|
||||
|
||||
Reference in New Issue
Block a user