Fix E2B sandbox timeout comment, frontend stability, and create-flow improvements

- Add TEMPORARY comment on E2B timeoutMs with pointer to rivetkit sandbox
  resilience proposal for when autoPause lands
- Fix React useEffect dependency stability in mock-layout and
  organization-dashboard to prevent infinite re-render loops
- Fix terminal-pane ref handling
- Improve create-flow service and tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nathan Flurry 2026-03-16 15:19:49 -07:00
parent 78cd38d826
commit ee25a6c6a5
6 changed files with 106 additions and 42 deletions

View file

@ -1317,11 +1317,14 @@ export function MockLayout({ organizationId, selectedTaskId, selectedSessionId }
[organizationId],
);
const organizationState = useSubscription(subscriptionManager, "organization", { organizationId });
const organizationRepos = organizationState.data?.repos ?? [];
const taskSummaries = organizationState.data?.taskSummaries ?? [];
const organizationReposData = organizationState.data?.repos;
const taskSummariesData = organizationState.data?.taskSummaries;
const openPullRequestsData = organizationState.data?.openPullRequests;
const organizationRepos = organizationReposData ?? [];
const taskSummaries = taskSummariesData ?? [];
const selectedTaskSummary = useMemo(
() => taskSummaries.find((task) => task.id === selectedTaskId) ?? taskSummaries[0] ?? null,
[selectedTaskId, taskSummaries],
[selectedTaskId, taskSummariesData],
);
const taskState = useSubscription(
subscriptionManager,
@ -1401,9 +1404,9 @@ export function MockLayout({ organizationId, selectedTaskId, selectedSessionId }
summary.id === selectedTaskSummary?.id ? toTaskModel(summary, taskState.data, sessionCache) : toTaskModel(summary),
);
return hydratedTasks.sort((left, right) => right.updatedAtMs - left.updatedAtMs);
}, [selectedTaskSummary, selectedSessionId, sessionState.data, taskState.data, taskSummaries, organizationId]);
const openPullRequests = organizationState.data?.openPullRequests ?? [];
const rawRepositories = useMemo(() => groupRepositories(organizationRepos, tasks, openPullRequests), [tasks, organizationRepos, openPullRequests]);
}, [selectedTaskSummary, selectedSessionId, sessionState.data, taskState.data, taskSummariesData, organizationId]);
const openPullRequests = openPullRequestsData ?? [];
const rawRepositories = useMemo(() => groupRepositories(organizationRepos, tasks, openPullRequests), [tasks, organizationReposData, openPullRequestsData]);
const appSnapshot = useMockAppSnapshot();
const currentUser = activeMockUser(appSnapshot);
const activeOrg = activeMockOrganization(appSnapshot);
@ -1591,6 +1594,7 @@ export function MockLayout({ organizationId, selectedTaskId, selectedSessionId }
}, [activeTask, lastAgentSessionIdByTask, selectedSessionId, syncRouteSession]);
useEffect(() => {
const organizationRepos = organizationReposData ?? [];
if (selectedNewTaskRepoId && organizationRepos.some((repo) => repo.id === selectedNewTaskRepoId)) {
return;
}
@ -1600,7 +1604,7 @@ export function MockLayout({ organizationId, selectedTaskId, selectedSessionId }
if (fallbackRepoId !== selectedNewTaskRepoId) {
setSelectedNewTaskRepoId(fallbackRepoId);
}
}, [activeTask?.repoId, selectedNewTaskRepoId, organizationRepos]);
}, [activeTask?.repoId, selectedNewTaskRepoId, organizationReposData]);
useEffect(() => {
if (!activeTask) {

View file

@ -305,7 +305,8 @@ export function TerminalPane({ organizationId, taskId, isExpanded, onExpand, onC
setProcessTabs([]);
}, [taskId]);
const processes = processesState.data ?? [];
const processesData = processesState.data;
const processes = processesData ?? [];
const openTerminalTab = useCallback((process: SandboxProcessRecord) => {
setProcessTabs((current) => {
@ -361,7 +362,7 @@ export function TerminalPane({ organizationId, taskId, isExpanded, onExpand, onC
const activeProcessTab = activeSessionId ? (processTabsById.get(activeSessionId) ?? null) : null;
const activeTerminalProcess = useMemo(
() => (activeProcessTab ? (processes.find((process) => process.id === activeProcessTab.processId) ?? null) : null),
[activeProcessTab, processes],
[activeProcessTab, processesData],
);
const emptyBodyClassName = css({

View file

@ -335,9 +335,11 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
const activeOrg = appState.data ? currentFoundryOrganization(appState.data) : null;
const organizationState = useSubscription(subscriptionManager, "organization", { organizationId });
const repos = organizationState.data?.repos ?? [];
const rows = organizationState.data?.taskSummaries ?? [];
const selectedSummary = useMemo(() => rows.find((row) => row.id === selectedTaskId) ?? rows[0] ?? null, [rows, selectedTaskId]);
const reposData = organizationState.data?.repos;
const rowsData = organizationState.data?.taskSummaries;
const repos = reposData ?? [];
const rows = rowsData ?? [];
const selectedSummary = useMemo(() => rows.find((row) => row.id === selectedTaskId) ?? rows[0] ?? null, [rowsData, selectedTaskId]);
const taskState = useSubscription(
subscriptionManager,
"task",
@ -363,6 +365,7 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
});
useEffect(() => {
const repos = reposData ?? [];
if (repoOverviewMode && selectedRepoId) {
setCreateRepoId(selectedRepoId);
return;
@ -370,9 +373,11 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
if (!createRepoId && repos.length > 0) {
setCreateRepoId(repos[0]!.id);
}
}, [createRepoId, repoOverviewMode, repos, selectedRepoId]);
}, [createRepoId, repoOverviewMode, reposData, selectedRepoId]);
const repoGroups = useMemo(() => {
const repos = reposData ?? [];
const rows = rowsData ?? [];
const byRepo = new Map<string, typeof rows>();
for (const row of rows) {
const bucket = byRepo.get(row.repoId);
@ -400,7 +405,7 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
}
return a.repoLabel.localeCompare(b.repoLabel);
});
}, [repos, rows]);
}, [reposData, rowsData]);
const selectedForSession = repoOverviewMode ? null : (taskState.data ?? null);
@ -413,6 +418,7 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
}, [selectedForSession]);
useEffect(() => {
const rows = rowsData ?? [];
if (!repoOverviewMode && !selectedTaskId && rows.length > 0) {
void navigate({
to: "/organizations/$organizationId/tasks/$taskId",
@ -424,14 +430,15 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
replace: true,
});
}
}, [navigate, repoOverviewMode, rows, selectedTaskId, organizationId]);
}, [navigate, repoOverviewMode, rowsData, selectedTaskId, organizationId]);
useEffect(() => {
setActiveSessionId(null);
setDraft("");
}, [selectedForSession?.id]);
const sessionRows = selectedForSession?.sessionsSummary ?? [];
const sessionRowsData = selectedForSession?.sessionsSummary;
const sessionRows = sessionRowsData ?? [];
const taskStatus = selectedForSession?.status ?? null;
const taskStatusState = describeTaskState(taskStatus);
const taskStateSummary = `${taskStatusState.title}. ${taskStatusState.detail}`;
@ -450,7 +457,7 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
status: session.status,
})),
}),
[activeSessionId, selectedForSession?.activeSessionId, sessionRows],
[activeSessionId, selectedForSession?.activeSessionId, sessionRowsData],
);
const resolvedSessionId = sessionSelection.sessionId;
const staleSessionId = sessionSelection.staleSessionId;
@ -466,7 +473,7 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
}
: null,
);
const selectedSessionSummary = useMemo(() => sessionRows.find((session) => session.id === resolvedSessionId) ?? null, [resolvedSessionId, sessionRows]);
const selectedSessionSummary = useMemo(() => sessionRows.find((session) => session.id === resolvedSessionId) ?? null, [resolvedSessionId, sessionRowsData]);
const isPendingProvision = selectedSessionSummary?.status === "pending_provision";
const isPendingSessionCreate = selectedSessionSummary?.status === "pending_session_create";
const isSessionError = selectedSessionSummary?.status === "error";
@ -523,7 +530,7 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
activeSandboxId: selectedForSession?.id === task.id ? selectedForSession.activeSandboxId : null,
})),
}),
[repos, rows, selectedForSession, organizationId],
[reposData, rowsData, selectedForSession, organizationId],
);
const startSessionFromTask = async (): Promise<{ id: string; status: "running" | "idle" | "error" }> => {
@ -631,7 +638,7 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
setCreateTaskOpen(true);
};
const repoOptions = useMemo(() => repos.map((repo) => createOption({ id: repo.id, label: repo.label })), [repos]);
const repoOptions = useMemo(() => repos.map((repo) => createOption({ id: repo.id, label: repo.label })), [reposData]);
const selectedRepoOption = repoOptions.find((option) => option.id === createRepoId) ?? null;
const selectedFilterOption = useMemo(
() => createOption(FILTER_OPTIONS.find((option) => option.id === overviewFilter) ?? FILTER_OPTIONS[0]!),
@ -639,7 +646,7 @@ export function OrganizationDashboard({ organizationId, selectedTaskId, selected
);
const sessionOptions = useMemo(
() => sessionRows.map((session) => createOption({ id: session.id, label: `${session.sessionName} (${session.status})` })),
[sessionRows],
[sessionRowsData],
);
const selectedSessionOption = sessionOptions.find((option) => option.id === resolvedSessionId) ?? null;