Skip to content

Commit 143f94e

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 73f89a5 + ccf914f commit 143f94e

File tree

6 files changed

+116
-49
lines changed

6 files changed

+116
-49
lines changed

src/app/beats/page.tsx

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ import { AlertTriangle } from "lucide-react";
3535
import {
3636
isListBeatsView, parseBeatsView,
3737
} from "@/lib/beats-view";
38-
import {
39-
getTerminalViewportInset,
40-
} from "@/lib/terminal-viewport";
4138
import { useBeatsQuery } from "./use-beats-query";
4239
import { useAgentInfoMap } from "./use-agent-info-map";
4340
import { useBulkActions } from "./use-bulk-actions";
@@ -79,9 +76,6 @@ function useBeatsPageState() {
7976
const { activeRepo, registeredRepos } = useAppStore();
8077
const {
8178
terminals,
82-
panelOpen,
83-
panelMinimized,
84-
panelHeight,
8579
} = useTerminalStore();
8680

8781
const shippingByBeatId = terminals.reduce<
@@ -117,24 +111,14 @@ function useBeatsPageState() {
117111
beats, detailBeatId, detailRepo, isListView,
118112
activeRepo,
119113
});
120-
const listViewportInset =
121-
beatsView === "queues" || beatsView === "active"
122-
? getTerminalViewportInset({
123-
panelOpen,
124-
panelMinimized,
125-
panelHeight,
126-
terminalCount: terminals.length,
127-
})
128-
: "0px";
129-
130114
return {
131115
beatsView, isListView, viewPhase,
132116
isActiveView, activeRepo,
133117
searchQuery, detailBeatId, detailRepo,
134118
beats, isLoading, loadError, isDegradedError,
135119
hasRollingAncestor, showRepoColumn,
136120
agentInfoByBeatId, shippingByBeatId,
137-
listViewportInset, streamingProgress,
121+
streamingProgress,
138122
...bulk, ...actions, ...detail,
139123
};
140124
}
@@ -262,7 +246,6 @@ function BeatsViewBody({
262246
onShipBeat={s.handleShipBeat}
263247
shippingByBeatId={s.shippingByBeatId}
264248
onAbortShipping={s.handleAbortShipping}
265-
listViewportInset={s.listViewportInset}
266249
streamingProgress={
267250
s.streamingProgress
268251
}
@@ -289,7 +272,6 @@ interface BeatsListContentProps {
289272
onAbortShipping: (
290273
beatId: string,
291274
) => Promise<void>;
292-
listViewportInset: string;
293275
streamingProgress: StreamingProgress;
294276
}
295277

@@ -303,7 +285,7 @@ function BeatsListContent(
303285
selectionVersion, searchQuery,
304286
onOpenBeat, onShipBeat,
305287
shippingByBeatId, onAbortShipping,
306-
listViewportInset, streamingProgress,
288+
streamingProgress,
307289
} = props;
308290

309291
const isStreamActive =
@@ -339,10 +321,7 @@ function BeatsListContent(
339321
&& beats.length === 0;
340322

341323
return (
342-
<div
343-
className="overflow-x-auto"
344-
style={{ paddingBottom: listViewportInset }}
345-
>
324+
<div className="overflow-x-auto">
346325
{isDegradedError && (
347326
<DegradedBanner message={loadError} />
348327
)}

src/app/globals.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
:root {
5252
--radius: 0.625rem;
53+
--terminal-viewport-inset: 0px;
5354
--background: oklch(0.988 0.01 316);
5455
--foreground: oklch(0.19 0.02 298);
5556
--card: oklch(0.996 0.008 320);
@@ -128,6 +129,10 @@
128129
}
129130
body {
130131
@apply bg-background text-foreground;
132+
padding-bottom: var(--terminal-viewport-inset);
133+
scroll-padding-bottom: var(
134+
--terminal-viewport-inset
135+
);
131136
}
132137

133138
body[data-scroll-locked] {

src/components/providers.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { useState } from "react";
44
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
55
import { Toaster } from "sonner";
66
import { ClientDiagnosticsRuntime } from "@/components/client-diagnostics-runtime";
7+
import {
8+
TerminalViewportInsetSync,
9+
} from "@/components/terminal-viewport-inset-sync";
710
import { TooltipProvider } from "@/components/ui/tooltip";
811
import { useWindowFocusInvalidation } from "@/hooks/use-window-focus-invalidation";
912
import { initializeDiagnostics } from "@/lib/client-perf";
@@ -28,6 +31,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
2831
return (
2932
<QueryClientProvider client={queryClient}>
3033
<ClientDiagnosticsRuntime />
34+
<TerminalViewportInsetSync />
3135
<GlobalQueryHooks />
3236
<TooltipProvider>
3337
{children}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client";
2+
3+
import { useEffect } from "react";
4+
import {
5+
getTerminalViewportInset,
6+
} from "@/lib/terminal-viewport";
7+
import { useTerminalStore } from "@/stores/terminal-store";
8+
9+
const TERMINAL_VIEWPORT_INSET_VAR =
10+
"--terminal-viewport-inset";
11+
12+
export function TerminalViewportInsetSync() {
13+
const {
14+
panelOpen,
15+
panelMinimized,
16+
panelHeight,
17+
terminals,
18+
} = useTerminalStore();
19+
20+
useEffect(() => {
21+
const inset = getTerminalViewportInset({
22+
panelOpen,
23+
panelMinimized,
24+
panelHeight,
25+
terminalCount: terminals.length,
26+
});
27+
document.body.style.setProperty(
28+
TERMINAL_VIEWPORT_INSET_VAR,
29+
inset,
30+
);
31+
32+
return () => {
33+
document.body.style.removeProperty(
34+
TERMINAL_VIEWPORT_INSET_VAR,
35+
);
36+
};
37+
}, [
38+
panelHeight,
39+
panelMinimized,
40+
panelOpen,
41+
terminals.length,
42+
]);
43+
44+
return null;
45+
}

src/lib/__tests__/beats-page-layout.test.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ const minimizedTerminalBarSource = src(
5252
const terminalViewportSource = src(
5353
"src/lib/terminal-viewport.ts",
5454
);
55+
const providerSource = src(
56+
"src/components/providers.tsx",
57+
);
58+
const insetSyncSource = src(
59+
"src/components/terminal-viewport-inset-sync.tsx",
60+
);
5561

5662
describe("beats page layout: scrolling and hotkeys", () => {
5763
it("allows vertical scrolling in the main wrapper", () => {
@@ -69,24 +75,47 @@ describe("beats page layout: scrolling and hotkeys", () => {
6975
);
7076
});
7177

72-
it("derives Queue/Active list bottom inset from terminal visibility state", () => {
73-
expect(pageSource).toContain(
74-
'beatsView === "queues" || beatsView === "active"',
78+
it("syncs terminal clearance into a shared body inset", () => {
79+
expect(insetSyncSource).toContain(
80+
"panelOpen",
7581
);
76-
expect(pageSource).toContain(
82+
expect(insetSyncSource).toContain(
83+
"panelMinimized",
84+
);
85+
expect(insetSyncSource).toContain(
86+
"panelHeight",
87+
);
88+
expect(insetSyncSource).toContain(
89+
"terminalCount: terminals.length",
90+
);
91+
expect(insetSyncSource).toContain(
7792
"getTerminalViewportInset",
7893
);
79-
expect(pageSource).toContain(
80-
"style={{ paddingBottom: listViewportInset }}",
94+
expect(providerSource).toContain(
95+
"TerminalViewportInsetSync",
96+
);
97+
expect(globalStylesSource).toContain(
98+
"--terminal-viewport-inset: 0px;",
99+
);
100+
expect(globalStylesSource).toContain(
101+
"padding-bottom: var(--terminal-viewport-inset);",
102+
);
103+
expect(globalStylesSource).toContain(
104+
"scroll-padding-bottom: var(",
81105
);
82106
expect(terminalViewportSource).toContain(
83107
"MINIMIZED_TERMINAL_BAR_HEIGHT_PX = 32",
84108
);
85109
expect(minimizedTerminalBarSource).toContain(
86110
"MINIMIZED_TERMINAL_BAR_HEIGHT_PX",
87111
);
112+
expect(pageSource).not.toContain(
113+
"style={{ paddingBottom: listViewportInset }}",
114+
);
88115
});
116+
});
89117

118+
describe("beats page layout: hotkeys and search header", () => {
90119
it("binds Shift+H shortcut help globally for beats screens", () => {
91120
expect(appHeaderHooksSource).toContain(
92121
"useHotkeyHelpHotkey",
@@ -157,7 +186,6 @@ describe("beats page layout: scrolling and hotkeys", () => {
157186
"order-3",
158187
);
159188
});
160-
161189
});
162190

163191
describe("beats page layout: row vertical alignment", () => {

src/lib/__tests__/terminal-scroll-layout-contract.test.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,46 +13,52 @@ function src(rel: string): string {
1313
}
1414

1515
const pageSource = src("src/app/beats/page.tsx");
16-
const minimizedBarSource = src(
17-
"src/components/minimized-terminal-bar.tsx",
16+
const providerSource = src(
17+
"src/components/providers.tsx",
18+
);
19+
const insetSyncSource = src(
20+
"src/components/terminal-viewport-inset-sync.tsx",
1821
);
1922
const terminalViewportSource = src(
2023
"src/lib/terminal-viewport.ts",
2124
);
25+
const globalCssSource = src(
26+
"src/app/globals.css",
27+
);
2228

2329
describe("terminal scroll layout contract", () => {
24-
it("reads the terminal viewport state needed for Queue/Active insets", () => {
25-
expect(pageSource).toContain(
30+
it("syncs terminal viewport state into a shared body inset", () => {
31+
expect(insetSyncSource).toContain(
2632
"panelOpen",
2733
);
28-
expect(pageSource).toContain(
34+
expect(insetSyncSource).toContain(
2935
"panelMinimized",
3036
);
31-
expect(pageSource).toContain(
37+
expect(insetSyncSource).toContain(
3238
"panelHeight",
3339
);
34-
expect(pageSource).toContain(
40+
expect(insetSyncSource).toContain(
3541
"terminalCount: terminals.length",
3642
);
3743
});
3844

39-
it("limits the terminal inset to Queue and Active views", () => {
40-
expect(pageSource).toContain(
41-
'beatsView === "queues" || beatsView === "active"',
42-
);
43-
expect(pageSource).toContain(
44-
': "0px";',
45+
it("registers the terminal viewport inset sync globally", () => {
46+
expect(providerSource).toContain(
47+
"TerminalViewportInsetSync",
4548
);
4649
});
4750

48-
it("keeps minimized bar height and list inset on the same shared constant", () => {
51+
it("applies the shared terminal inset through global layout styles", () => {
4952
expect(terminalViewportSource).toContain(
50-
"MINIMIZED_TERMINAL_BAR_HEIGHT_PX",
53+
"getTerminalViewportInset",
54+
);
55+
expect(globalCssSource).toContain(
56+
"padding-bottom: var(--terminal-viewport-inset);",
5157
);
52-
expect(minimizedBarSource).toContain(
53-
"height: `${MINIMIZED_TERMINAL_BAR_HEIGHT_PX}px`",
58+
expect(globalCssSource).toContain(
59+
"scroll-padding-bottom: var(",
5460
);
55-
expect(pageSource).toContain(
61+
expect(pageSource).not.toContain(
5662
"style={{ paddingBottom: listViewportInset }}",
5763
);
5864
});

0 commit comments

Comments
 (0)