fix(web): make right sidebar header toggle work across mobile, projects, and groups

This commit is contained in:
cynarAI 2026-10-22 12:04:39 +02:00
parent 35b7291660
commit 996af168ef

View File

@ -662,7 +662,7 @@ export function ShellPage() {
);
const inProject = Boolean(activeSpace && !activeSpace.isDefault);
const coordinatorBot: Bot | undefined = useMemo(() => {
if (!inProject || !activeSpace) return undefined;
if ((!inProject && !inGroup) || !activeSpace) return undefined;
const leadId =
snapshot?.leadBotId ??
activeGroup?.leadBotId ??
@ -673,10 +673,12 @@ export function ShellPage() {
bots.find((b) => b.kind === "main" && b.spaceId === activeSpace.id) ??
bots.find((b) => b.spaceId === activeSpace.id);
if (!found) return undefined;
const isLegacyName = found.name === "Chief" || found.name === "Projektkoordinator";
const isLegacyName =
inProject && (found.name === "Chief" || found.name === "Projektkoordinator");
return isLegacyName ? { ...found, name: "Projektmanager" } : found;
}, [
inProject,
inGroup,
activeSpace,
snapshot?.leadBotId,
activeGroup?.leadBotId,
@ -786,6 +788,8 @@ export function ShellPage() {
routeGroupId.current = groupId;
const activeBotId = useRef<string | undefined>(inGroup ? undefined : active?.id);
activeBotId.current = inGroup ? undefined : active?.id;
const coordinatorBotIdRef = useRef<string | undefined>(coordinatorBot?.id);
coordinatorBotIdRef.current = coordinatorBot?.id;
const activeGroupId = useRef<string | undefined>(groupId);
activeGroupId.current = groupId;
const screenRequest = useRef(0);
@ -1053,7 +1057,9 @@ export function ShellPage() {
return loadComputerScreen({
load: () => rpc.computer.screenUrl({ botId: id }),
isCurrent: () =>
request === screenRequest.current && activeBotId.current === id && computerVisible.current,
request === screenRequest.current &&
(activeBotId.current === id || coordinatorBotIdRef.current === id) &&
computerVisible.current,
commit: (screen) => {
setScreenUrl(screen.url);
setComputerError(screen.error);
@ -1480,8 +1486,10 @@ export function ShellPage() {
void refreshThread(active.id).catch(() => undefined);
} else if (isComputerStatusEvent(event)) {
void refreshComputerScreen(active.id).catch(() => undefined);
} else if (event.type === "thread.computer") {
}
if (event.type === "thread.computer") {
agentPointerFeed.publish(event.botId, event.payload, event.createdAt);
}
const screenRun = liveScreenRunId(event);
if (screenRun) {
setScreenRunId(screenRun);
@ -1491,6 +1499,8 @@ export function ShellPage() {
() => setScreenActive(false),
SCREEN_ACTIVE_MS,
);
if (computerRef.current?.state !== "running") {
void refreshComputerScreen(active.id).catch(() => undefined);
}
}
},
@ -1598,6 +1608,9 @@ export function ShellPage() {
// waiting_input: reconcile ask cards if a stale post-send refresh raced SSE.
void refreshGroupThread(groupId).catch(() => undefined);
}
if (event.type === "thread.computer") {
agentPointerFeed.publish(event.botId, event.payload, event.createdAt);
}
},
});
return () => {
@ -1910,9 +1923,10 @@ export function ShellPage() {
const handleRunErrorPresented = useCallback((runId: string) => {
rememberSeenRunErrorId(runId);
}, []);
const groupComputerBotId =
inGroup && activeSnapshot?.leadBotId && bots.some((bot) => bot.id === activeSnapshot.leadBotId)
const groupComputerBotId = inGroup
? (activeSnapshot?.leadBotId && bots.some((bot) => bot.id === activeSnapshot.leadBotId)
? activeSnapshot.leadBotId
: coordinatorBot?.id)
: undefined;
// A bot message without a sender speaks for the conversation's own bot, never as an anonymous one.
const defaultSpeakerId = inGroup ? activeSnapshot?.leadBotId : active?.id;
@ -2031,6 +2045,7 @@ export function ShellPage() {
const workspaceFocusRef = useRef<{ threadId: string; focus: WorkspaceFocus } | null>(null);
const autoPanelRef = useRef(autoPanel);
autoPanelRef.current = autoPanel;
const autoLayoutSplitRef = useRef(false);
const autoCloseTimer = useRef<number | undefined>(undefined);
useEffect(() => () => window.clearTimeout(autoCloseTimer.current), []);
// The pane follows the bot's work on focus edges only, so it never jumps while nothing changed.
@ -2049,7 +2064,7 @@ export function ShellPage() {
if (!wideLayout) return;
const paneState: PaneState = {
open:
panel === "computer"
panel === "computer" || panel === "browser"
? "screen"
: panel === "artifact"
? "artifact"
@ -2066,13 +2081,22 @@ export function ShellPage() {
if (closing === null || autoPanelRef.current !== closing) return;
setPanel(null);
setAutoPanel(null);
if (autoLayoutSplitRef.current) {
autoLayoutSplitRef.current = false;
dualSurface.setLayoutMode("stream");
}
}, AUTO_SCREEN_CLOSE_DELAY_MS);
return;
}
if (next.open === "artifact") setArtifactPane(workspaceFocus.artifact);
if (next.open === "screen") setCanvasTab("browser");
const nextPanel: Panel = next.open === "screen" ? "computer" : next.open;
setPanel(nextPanel);
setAutoPanel(next.auto ? nextPanel : null);
if (dualSurface.layoutMode === "stream") {
autoLayoutSplitRef.current = Boolean(next.auto);
dualSurface.setLayoutMode("split");
}
}, [workspaceFocus.key, activeSnapshot?.threadId, wideLayout]);
const learnScreenPane = useCallback(
(choice: "opened" | "dismissed") => {
@ -2091,6 +2115,7 @@ export function ShellPage() {
if (autoPanel === "computer" && panel === null && focus?.pane === "screen" && !focus.takeover) {
learnScreenPane("dismissed");
}
autoLayoutSplitRef.current = false;
setAutoPanel(null);
}, [panel]);
const openArtifactBeside = useCallback((artifact: WorkspaceArtifact) => {
@ -3886,6 +3911,7 @@ export function ShellPage() {
panel &&
(active ||
activeGroup ||
coordinatorBot ||
panel === "create" ||
panel === "create-group" ||
panel === "browser")
@ -3898,6 +3924,7 @@ export function ShellPage() {
panel &&
(active ||
activeGroup ||
coordinatorBot ||
panel === "create" ||
panel === "create-group" ||
panel === "browser"),
@ -4116,56 +4143,46 @@ export function ShellPage() {
layoutMode={showCanvasOnDesktop ? dualSurface.layoutMode : "stream"}
orientation={dualSurface.orientation}
onSelectMode={handleSelectLayoutMode}
onToggleOrientation={dualSurface.toggleOrientation}
onToggleOrientation={() => {
dualSurface.toggleOrientation();
if (!showCanvasOnDesktop) {
handleSelectLayoutMode("split");
}
}}
disabled={dualSurface.isMobile}
className="hidden md:flex"
/>
{active || groupComputerBotId ? (
{active || activeGroup || inProject || groupComputerBotId ? (
<button
type="button"
title={t`Agent computer`}
aria-label={t`Agent computer`}
onClick={() => {
if (isPanelVisible) {
setPanel(null);
dualSurface.setLayoutMode("stream");
return;
}
if (inProject && !active) {
if (showCanvasOnDesktop) {
setPanel(null);
dualSurface.setLayoutMode("stream");
} else {
dualSurface.setLayoutMode("split");
}
return;
}
// The computer belongs to a bot; from a group, open its lead's computer.
if (!active && groupComputerBotId) {
navigate(`/app/${groupComputerBotId}`);
setCanvasTab("browser");
if (dualSurface.isMobile) {
setCanvasTab("overview");
setPanel("computer");
}
dualSurface.setLayoutMode("split");
return;
}
const isCurrentlyOpen =
(panel === "computer" || panel === "browser") &&
dualSurface.layoutMode !== "stream";
if (isCurrentlyOpen) {
setPanel(null);
dualSurface.setLayoutMode("stream");
} else {
if (transcriptRunning) learnScreenPane("opened");
setCanvasTab("browser");
setCanvasTab(inProject ? "overview" : "browser");
setPanel("computer");
dualSurface.setLayoutMode("split");
if (active) {
// Refresh run/computer so Take control isn't stuck on a stale busyBotName.
void refreshThread(active.id).catch(() => undefined);
}
} else if (groupComputerBotId) {
void refreshComputerScreen(groupComputerBotId).catch(() => undefined);
}
}}
data-active={
(panel === "computer" || panel === "browser" || (inProject && !active)) &&
showCanvasOnDesktop
? ""
: undefined
}
data-active={isPanelVisible ? "" : undefined}
className="app-no-drag rk-touch-target grid size-control-sm min-h-control-sm min-w-control-sm place-items-center rounded-lg hover:bg-accent data-active:bg-accent"
>
<PanelRight strokeWidth={1.7} className="text-foreground/75 size-icon-sm" />