Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions apps/app/src/views/thread-detail/SplitThreadArea.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { createStore, Provider as JotaiProvider } from "jotai";
import { useContext, useMemo, useState, type ReactNode } from "react";
import { MemoryRouter, useLocation, useNavigate } from "react-router-dom";
import { QueryClientProvider } from "@tanstack/react-query";
import { QueryClientProvider, useQuery } from "@tanstack/react-query";
import { PERSONAL_PROJECT_ID } from "@bb/domain";
import { TooltipProvider } from "@bb/shared-ui/tooltip";
import type { BbDesktopInfo } from "@bb/desktop-contract";
Expand Down Expand Up @@ -74,6 +74,10 @@ const commandHandlers = vi.hoisted(() => new Map<string, () => boolean>());
const paneContextRenders = vi.hoisted(
() => new Map<string, Array<PaneContextValue | null>>(),
);
const timelineProbe = vi.hoisted(() => ({
active: false,
calls: new Map<string, number>(),
}));
interface ShortcutPresentationFixture {
ariaKeyshortcuts: string;
label: string;
Expand Down Expand Up @@ -291,10 +295,21 @@ vi.mock("./ThreadDetailView", () => ({
ThreadDetailView: ({
projectId = "proj_personal",
threadId = "thr-a",
timelineEnabled = true,
}: {
projectId: string;
threadId: string;
timelineEnabled?: boolean;
}) => {
useQuery({
queryKey: ["timeline-probe", threadId],
queryFn: () => {
const next = (timelineProbe.calls.get(threadId) ?? 0) + 1;
timelineProbe.calls.set(threadId, next);
return next;
},
enabled: timelineProbe.active && timelineEnabled,
});
const pane = useContext(PaneContext);
paneContextRenders.set(threadId, [
...(paneContextRenders.get(threadId) ?? []),
Expand Down Expand Up @@ -705,6 +720,8 @@ beforeEach(() => {
panelGroupLayoutState.layout = [100, 0];
commandHandlers.clear();
paneContextRenders.clear();
timelineProbe.active = false;
timelineProbe.calls.clear();
commandPresentationState.isModifierHeld = false;
commandPresentationState.shortcut = null;
threadStore.set("thr-a", { archivedAt: null, deletedAt: null });
Expand Down Expand Up @@ -859,6 +876,30 @@ describe("SplitThreadArea", () => {
});
});

it("pauses a hidden pane timeline and refreshes it when restored", async () => {
timelineProbe.active = true;
renderSplitArea({
path: threadPath("thr-a"),
layout: twoPaneLayout("pane-1"),
});

await waitFor(() => {
expect(timelineProbe.calls.get("thr-a")).toBe(1);
expect(timelineProbe.calls.get("thr-b")).toBe(1);
});

fireEvent.click(screen.getByTestId("maximize-thr-a"));
await act(async () => {
await queryClient.invalidateQueries({
queryKey: ["timeline-probe", "thr-b"],
});
});
expect(timelineProbe.calls.get("thr-b")).toBe(1);

fireEvent.click(screen.getByTestId("maximize-thr-a"));
await waitFor(() => expect(timelineProbe.calls.get("thr-b")).toBe(2));
});

it("temporarily replaces panel full screen with a clean thread full screen", async () => {
panelFullScreenState.isMainCollapsed = true;
renderSplitArea({
Expand Down Expand Up @@ -2279,8 +2320,8 @@ describe("SplitThreadArea", () => {
);
await waitFor(() =>
expect(
screen.getAllByRole("button", { name: "Close pane" })[0]
?.parentElement?.nextElementSibling,
screen.getAllByRole("button", { name: "Close pane" })[0]?.parentElement
?.nextElementSibling,
).toBeNull(),
);
});
Expand Down
5 changes: 5 additions & 0 deletions apps/app/src/views/thread-detail/SplitThreadArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ function SplitThreadAreaContent({ routeContent }: SplitThreadAreaProps) {
paneId={firstPane.paneId}
isFocused
isSplitPane={false}
timelineEnabled
secondaryPanelRegistry={null}
reservesWindowPanelToggle={false}
onClosePane={
Expand Down Expand Up @@ -818,6 +819,7 @@ function SplitTree(props: SplitTreeProps) {
paneId={node.paneId}
isFocused={isFocused}
isSplitPane
timelineEnabled={!isHiddenByMaximize}
secondaryPanelRegistry={props.secondaryPanelRegistry}
reservesWindowPanelToggle={isMaximized || (isTopRow && isRightEdge)}
onClosePane={props.onClosePane}
Expand Down Expand Up @@ -894,6 +896,7 @@ interface WorkspacePaneContentProps {
paneId: string;
isFocused: boolean;
isSplitPane: boolean;
timelineEnabled: boolean;
secondaryPanelRegistry: PaneSecondaryPanelRegistry | null;
reservesWindowPanelToggle: boolean;
onClosePane: ((paneId: string) => void) | null;
Expand All @@ -912,6 +915,7 @@ const WorkspacePaneContent = memo(function WorkspacePaneContent({
paneId,
isFocused,
isSplitPane,
timelineEnabled,
secondaryPanelRegistry,
reservesWindowPanelToggle,
onClosePane,
Expand Down Expand Up @@ -1018,6 +1022,7 @@ const WorkspacePaneContent = memo(function WorkspacePaneContent({
surface="pane"
projectId={content.projectId}
threadId={content.threadId}
timelineEnabled={timelineEnabled}
/>
</PaneContext.Provider>
);
Expand Down
14 changes: 11 additions & 3 deletions apps/app/src/views/thread-detail/ThreadDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ interface ThreadDetailViewPageProps {

interface ThreadDetailViewPaneProps extends ThreadRoutePathArgs {
surface: "pane";
timelineEnabled: boolean;
}

type ThreadDetailViewProps =
Expand Down Expand Up @@ -501,7 +502,11 @@ function RoutedThreadDetailView({

return (
<DefaultPaneContextProvider onRequestClose={onRequestClose}>
<ThreadDetailViewInternal projectId={projectId} threadId={threadId} />
<ThreadDetailViewInternal
projectId={projectId}
threadId={threadId}
timelineEnabled
/>
</DefaultPaneContextProvider>
);
}
Expand All @@ -513,8 +518,10 @@ export function ThreadDetailView(props: ThreadDetailViewProps) {
return <RoutedThreadDetailView onRequestClose={props.onRequestClose} />;
}

function ThreadDetailViewInternal(props: ThreadRoutePathArgs) {
const { projectId, threadId } = props;
function ThreadDetailViewInternal(
props: ThreadRoutePathArgs & { timelineEnabled: boolean },
) {
const { projectId, threadId, timelineEnabled } = props;
const { isFocused, navigateInPane, onRequestClose, isBoundedPane } =
usePaneContext();
const navigate = useImmediateRouteNavigate();
Expand Down Expand Up @@ -866,6 +873,7 @@ function ThreadDetailViewInternal(props: ThreadRoutePathArgs) {
timelineRows,
} = useThreadTimelineController({
threadId,
enabled: timelineEnabled,
});
const sendMessage = useSendThreadMessage();
const editMessage = useEditThreadMessage();
Expand Down
Loading