fix: terminal shift-left bug and session sidebar naming/filtering

- Guard ResizeObserver in terminal against internal xterm DOM changes
  by tracking last width/height and only calling fit() on real resize
- Remove padding from .terminal-container and conflicting .xterm height
  override that caused measurement mismatches with xterm-addon-fit
- Filter live session sidebar to active statuses only (running, building,
  pending) instead of showing all sessions including stopped ones
- Add display name fallback across sidebar, sessions page, and instance
  list to prevent blank names when display_name is empty

Quality gates: tsc (pass), eslint (pass)
This commit is contained in:
Developer
2026-06-02 14:03:47 +00:00
parent 5a8eca814d
commit d894cd9723
6 changed files with 35 additions and 16 deletions
+5
View File
@@ -48,3 +48,8 @@ apps/web/dist/
# OS # OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Local runtime state
.atl/
.pi/
swap-pane
+9 -4
View File
@@ -16,8 +16,11 @@ const NAV_ITEMS: { to: string; label: string; icon: IconName }[] = [
{ to: "/settings", label: "Settings", icon: "settings" } { to: "/settings", label: "Settings", icon: "settings" }
]; ];
const ACTIVE_STATUSES = ["running", "building", "pending"];
const SessionItem = ({ session }: { session: Session }) => { const SessionItem = ({ session }: { session: Session }) => {
const isRunning = session.status === "running"; const isRunning = session.status === "running";
const displayName = session.display_name || session.tool_type_name || "Unnamed Session";
return ( return (
<a <a
@@ -25,11 +28,11 @@ const SessionItem = ({ session }: { session: Session }) => {
target={session.url ? "_blank" : undefined} target={session.url ? "_blank" : undefined}
rel={session.url ? "noopener noreferrer" : undefined} rel={session.url ? "noopener noreferrer" : undefined}
className="nav-item session-item" className="nav-item session-item"
title={`${session.display_name} (${session.status})`} title={`${displayName} (${session.status})`}
> >
<span className={`session-status ${isRunning ? "running" : ""}`} /> <span className={`session-status ${isRunning ? "running" : ""}`} />
<Icon name={session.tool_icon as IconName} size="sm" /> <Icon name={session.tool_icon as IconName} size="sm" />
<span className="session-name">{session.display_name}</span> <span className="session-name">{displayName}</span>
</a> </a>
); );
}; };
@@ -101,11 +104,13 @@ export const AppShell = () => {
); );
})} })}
{sessions.length > 0 && ( {sessions.filter((s) => ACTIVE_STATUSES.includes(s.status)).length > 0 && (
<> <>
<div className="nav-divider" /> <div className="nav-divider" />
<div className="nav-section-title">Live sessions</div> <div className="nav-section-title">Live sessions</div>
{sessions.map((session) => ( {sessions
.filter((s) => ACTIVE_STATUSES.includes(s.status))
.map((session) => (
<SessionItem key={session.id} session={session} /> <SessionItem key={session.id} session={session} />
))} ))}
</> </>
+1 -1
View File
@@ -194,7 +194,7 @@ export const InstanceList = ({ projectId, repoId, toolTypes }: InstanceListProps
{instances.map((instance) => ( {instances.map((instance) => (
<div key={instance.id} className="instance-card"> <div key={instance.id} className="instance-card">
<div className="instance-info"> <div className="instance-info">
<div className="instance-name">{instance.display_name}</div> <div className="instance-name">{instance.display_name || instance.tool_type_name || "Unnamed Instance"}</div>
<div className="instance-meta"> <div className="instance-meta">
<span <span
className="status-dot" className="status-dot"
+15 -1
View File
@@ -178,12 +178,26 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
// Resize observer for container-level resize detection // Resize observer for container-level resize detection
let resizeTimeout: ReturnType<typeof setTimeout> | null = null; let resizeTimeout: ReturnType<typeof setTimeout> | null = null;
const resizeObserver = new ResizeObserver(() => { let lastWidth = 0;
let lastHeight = 0;
const resizeObserver = new ResizeObserver((entries) => {
if (resizeTimeout) { if (resizeTimeout) {
clearTimeout(resizeTimeout); clearTimeout(resizeTimeout);
} }
const entry = entries[0];
if (!entry) return;
const { width, height } = entry.contentRect;
resizeTimeout = setTimeout(() => { resizeTimeout = setTimeout(() => {
resizeTimeout = null; resizeTimeout = null;
// Guard against internal xterm DOM changes that don't affect container size
if (
Math.abs(width - lastWidth) < 1 &&
Math.abs(height - lastHeight) < 1
) {
return;
}
lastWidth = width;
lastHeight = height;
const prevCols = term.cols; const prevCols = term.cols;
const prevRows = term.rows; const prevRows = term.rows;
fitAddon.fit(); fitAddon.fit();
+3 -3
View File
@@ -266,7 +266,7 @@ export const SessionsPage = () => {
<h2>Last Session</h2> <h2>Last Session</h2>
<div className="card last-session-card"> <div className="card last-session-card">
<div className="last-session-info"> <div className="last-session-info">
<h3>{lastSession.display_name}</h3> <h3>{lastSession.display_name || lastSession.tool_type_name || "Unnamed Session"}</h3>
<p className="muted"> <p className="muted">
{lastSession.tool_type_name} · {lastSession.project_name} · {lastSession.repository_name} {lastSession.tool_type_name} · {lastSession.project_name} · {lastSession.repository_name}
</p> </p>
@@ -316,7 +316,7 @@ export const SessionsPage = () => {
{activeSessions.map((session) => ( {activeSessions.map((session) => (
<div className="card session-card" key={session.id}> <div className="card session-card" key={session.id}>
<div className="session-info"> <div className="session-info">
<h4>{session.display_name}</h4> <h4>{session.display_name || session.tool_type_name || "Unnamed Session"}</h4>
<p className="muted"> <p className="muted">
{session.tool_type_name} · {session.project_name} {session.tool_type_name} · {session.project_name}
</p> </p>
@@ -445,7 +445,7 @@ export const SessionsPage = () => {
{recentSessions.map((session) => ( {recentSessions.map((session) => (
<div className="recent-session-item" key={session.id}> <div className="recent-session-item" key={session.id}>
<div className="recent-session-info"> <div className="recent-session-info">
<span className="recent-session-name">{session.display_name}</span> <span className="recent-session-name">{session.display_name || session.tool_type_name || "Unnamed Session"}</span>
<span className="muted"> <span className="muted">
{session.tool_type_name} · {session.project_name} {session.tool_type_name} · {session.project_name}
</span> </span>
-5
View File
@@ -2748,11 +2748,6 @@ a.nav-item,
.terminal-container { .terminal-container {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
padding: 0.25rem;
}
.terminal-container .xterm {
height: 100%;
} }
.terminal-container .xterm-viewport { .terminal-container .xterm-viewport {