Revert idle "claude foreground" filter — back to legacy 5s notify
Reverts in one combined commit: -9931a92(inline pane_id + watch list into bash script) -6772b8d(pivot per-distro → per-pane via TILETOPIA_PANE_ID env) -f51033a(original per-distro idle filter) End-to-end probe never worked correctly against the real running app even after fixing the wsl.exe-drops-positional-args bug. Probe script ran fine in isolation but kept returning false-negative when called through tiletopia's wsl.exe spawn. Rather than keep iterating, back out cleanly — pane behaviour is now the original "go idle after 5s of silence regardless of what's running." memory.md session log notes the lessons for a future retry: don't ship per-distro again (CLAUDE.md explicitly says multi-claude-per-distro is the primary use case); prove the probe end-to-end before wiring into the idle effect (a "Test probe" button in MCP panel would have caught this in minutes). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9931a92c5f
commit
50fbd0e531
7 changed files with 27 additions and 486 deletions
13
src/ipc.ts
13
src/ipc.ts
|
|
@ -39,19 +39,6 @@ export interface SshHost {
|
|||
|
||||
export const listDistros = (): Promise<string[]> => invoke("list_distros");
|
||||
|
||||
/** Ask the backend whether any built-in "watched" process (currently just
|
||||
* `claude`) is running in THIS specific pane (not just somewhere in the
|
||||
* distro). Per-pane detection works via a `TILETOPIA_PANE_ID` env marker
|
||||
* injected at spawn — see src-tauri/src/probe.rs. Cached per (distro,
|
||||
* pane_id) for ~3s. Probe failures resolve to `false` (don't suppress) —
|
||||
* better to occasionally over-notify than permanently silence. Only
|
||||
* meaningful for WSL panes; PS + SSH should skip this. */
|
||||
export const isWatchProcessRunning = (
|
||||
distro: string,
|
||||
paneId: number,
|
||||
): Promise<boolean> =>
|
||||
invoke("is_watch_process_running", { distro, paneId });
|
||||
|
||||
export const spawnPane = (args: {
|
||||
spec: SpawnSpec;
|
||||
cols: number;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
import { type LeafNode, resolveFontSize, type LeafShellSpec } from "./tree";
|
||||
import { useOrchestration } from "./orchestration";
|
||||
import XtermPane from "../../components/XtermPane";
|
||||
import { isWatchProcessRunning, type SpawnSpec } from "../../ipc";
|
||||
import type { SpawnSpec } from "../../ipc";
|
||||
import "./LeafPane.css";
|
||||
|
||||
const IDLE_THRESHOLD_MS = 5000;
|
||||
|
|
@ -116,29 +116,8 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
|||
// ---- idle detection ----------------------------------------------------
|
||||
// Local boolean for the red border + status text on this pane; reported
|
||||
// up to App via orch.reportLeafIdle for the titlebar's "N idle" badge.
|
||||
//
|
||||
// Filter: for WSL panes, before flagging idle we probe the backend to see
|
||||
// if any "watched" process (currently just `claude`) is running in THIS
|
||||
// pane specifically — per-pane, not per-distro. Per-pane is essential for
|
||||
// tiletopia's primary use case (multiple claude sessions across panes in
|
||||
// the same distro). The backend matches by reading `TILETOPIA_PANE_ID`
|
||||
// out of each candidate process's `/proc/<pid>/environ` (the env var is
|
||||
// injected at spawn time; see src-tauri/src/pty.rs WSLENV setup).
|
||||
//
|
||||
// PowerShell + SSH skip the probe and fall through to legacy behaviour
|
||||
// (PS has no portable `ps`; SSH processes live on a remote box).
|
||||
const lastDataTimeRef = useRef(Date.now());
|
||||
const [isIdle, setIsIdle] = useState(false);
|
||||
const isWslPane = leaf.shellKind === "wsl";
|
||||
// Captures the distro name into the interval callback. Empty string when
|
||||
// the leaf doesn't have one yet — the probe returns "not running" for
|
||||
// empty input so the pane goes idle normally.
|
||||
const wslDistro = isWslPane ? (leaf.distro ?? "") : "";
|
||||
// Backend pane id (PaneId, the u64 used inside Rust). Set by the
|
||||
// XtermPane onSpawn callback; null until the spawn round-trip completes.
|
||||
// Idle ticks before that point pass 0 — won't match any real pane's
|
||||
// TILETOPIA_PANE_ID env, so the probe returns false (no suppression).
|
||||
const paneIdRef = useRef<number | null>(null);
|
||||
const onDataReceived = useCallback(() => {
|
||||
lastDataTimeRef.current = Date.now();
|
||||
setIsIdle((cur) => {
|
||||
|
|
@ -147,83 +126,17 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
|||
});
|
||||
}, [orch.reportLeafIdle, leaf.id]);
|
||||
useEffect(() => {
|
||||
// Guard against late-resolving probes after unmount or another tick
|
||||
// already shipping a fresher answer.
|
||||
let cancelled = false;
|
||||
let inFlight = false;
|
||||
|
||||
const tick = () => {
|
||||
const id = window.setInterval(() => {
|
||||
const dt = Date.now() - lastDataTimeRef.current;
|
||||
const nowIdle = dt >= IDLE_THRESHOLD_MS;
|
||||
|
||||
// Transitioning out of idle is unconditional — fresh output beats
|
||||
// any probe answer.
|
||||
if (!nowIdle) {
|
||||
setIsIdle((cur) => {
|
||||
if (!cur) return cur;
|
||||
orch.reportLeafIdle(leaf.id, false);
|
||||
return false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Transitioning into idle. Non-WSL panes: report immediately (legacy
|
||||
// behaviour). WSL panes: gate on the probe; suppress if a watched
|
||||
// process is running in the distro.
|
||||
if (!isWslPane) {
|
||||
setIsIdle((cur) => {
|
||||
if (cur) return cur;
|
||||
orch.reportLeafIdle(leaf.id, true);
|
||||
return true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// WSL path. Don't stack probes — one in flight per pane at a time.
|
||||
if (inFlight) return;
|
||||
inFlight = true;
|
||||
const paneIdForProbe = paneIdRef.current ?? 0;
|
||||
void isWatchProcessRunning(wslDistro, paneIdForProbe)
|
||||
.then((suppress) => {
|
||||
if (cancelled) return;
|
||||
// If output arrived while the probe was in flight, the next tick
|
||||
// (or onDataReceived) will reconcile; don't flip-flop here.
|
||||
if (Date.now() - lastDataTimeRef.current < IDLE_THRESHOLD_MS) return;
|
||||
if (suppress) {
|
||||
// claude (or another watched proc) is running in THIS pane —
|
||||
// treat the silence as expected; stay out of the idle set.
|
||||
setIsIdle((cur) => {
|
||||
if (!cur) return cur;
|
||||
orch.reportLeafIdle(leaf.id, false);
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
setIsIdle((cur) => {
|
||||
if (cur) return cur;
|
||||
orch.reportLeafIdle(leaf.id, true);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
// Probe IPC errored — don't flip idle either way; next tick retries.
|
||||
// The Rust side now also fails-safe to "not running" so the pane
|
||||
// will flag idle eventually if the probe stays broken.
|
||||
if (cancelled) return;
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug("idle probe failed", e);
|
||||
})
|
||||
.finally(() => {
|
||||
inFlight = false;
|
||||
});
|
||||
};
|
||||
|
||||
const id = window.setInterval(tick, 1000);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearInterval(id);
|
||||
};
|
||||
}, [leaf.id, orch.reportLeafIdle, isWslPane, wslDistro]);
|
||||
setIsIdle((cur) => {
|
||||
if (cur === nowIdle) return cur;
|
||||
orch.reportLeafIdle(leaf.id, nowIdle);
|
||||
return nowIdle;
|
||||
});
|
||||
}, 1000);
|
||||
return () => clearInterval(id);
|
||||
}, [leaf.id, orch.reportLeafIdle]);
|
||||
// Clear from the app-level idle set when this pane unmounts.
|
||||
useEffect(() => {
|
||||
return () => orch.reportLeafIdle(leaf.id, false);
|
||||
|
|
@ -250,7 +163,6 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
|||
|
||||
const onPaneSpawned = useCallback(
|
||||
(paneId: number) => {
|
||||
paneIdRef.current = paneId;
|
||||
orch.registerPaneId(leaf.id, paneId);
|
||||
},
|
||||
[orch.registerPaneId, leaf.id],
|
||||
|
|
@ -260,10 +172,7 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
|||
// which broke broadcast routing (peers found, but their paneIds
|
||||
// had been silently removed from the map).
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
paneIdRef.current = null;
|
||||
orch.registerPaneId(leaf.id, null);
|
||||
};
|
||||
return () => orch.registerPaneId(leaf.id, null);
|
||||
}, [orch.registerPaneId, leaf.id]);
|
||||
|
||||
const onXtermFocus = useCallback(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue